Repository: junit-team/junit-lambda Branch: main Commit: 2e5966b07a02 Files: 2298 Total size: 8.5 MB Directory structure: gitextract_rqeyty5f/ ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ ├── config.yml │ │ ├── feature_request.md │ │ └── task.md │ ├── PULL_REQUEST_TEMPLATE.md │ ├── actions/ │ │ ├── main-build/ │ │ │ └── action.yml │ │ ├── maven-central-user-token/ │ │ │ └── action.yml │ │ ├── run-gradle/ │ │ │ └── action.yml │ │ └── setup-test-jdk/ │ │ └── action.yml │ ├── codecov.yml │ ├── dco.yml │ ├── renovate.json5 │ ├── scripts/ │ │ ├── checkBuildReproducibility.sh │ │ ├── close-github-milestone.js │ │ ├── create-github-release.js │ │ └── waitForUrl.sh │ ├── workflows/ │ │ ├── close-inactive-issues.yml │ │ ├── codeql.yml │ │ ├── cross-version.yml │ │ ├── deploy-docs.yml │ │ ├── gradle-dependency-submission.yml │ │ ├── label-opened-issues.yml │ │ ├── label-pull-request.yml │ │ ├── lock-resolved-issues.yml │ │ ├── main.yml │ │ ├── ossf-scorecard.yml │ │ ├── release.yml │ │ ├── reproducible-build.yml │ │ ├── sanitize-closed-issues.yml │ │ └── zizmor-analysis.yml │ └── zizmor.yml ├── .gitignore ├── .idea/ │ └── vcs.xml ├── .jitpack.yml ├── CONTRIBUTING.md ├── KEYS ├── LICENSE.md ├── NOTICE.md ├── README.md ├── RELEASING.md ├── SECURITY.md ├── build.gradle.kts ├── documentation/ │ ├── .tool-versions │ ├── README.md │ ├── antora-playbook.yml │ ├── antora.yml │ ├── documentation.gradle.kts │ ├── modules/ │ │ └── ROOT/ │ │ ├── extra-site-nav.adoc │ │ ├── images/ │ │ │ └── _source/ │ │ │ ├── extensions_BrokenLifecycleMethodConfigDemo.txt │ │ │ ├── extensions_DatabaseTestsDemo.txt │ │ │ └── extensions_lifecycle.docx │ │ ├── nav.adoc │ │ ├── pages/ │ │ │ ├── advanced-topics/ │ │ │ │ ├── engines.adoc │ │ │ │ ├── junit-platform-reporting.adoc │ │ │ │ ├── junit-platform-suite-engine.adoc │ │ │ │ ├── launcher-api.adoc │ │ │ │ └── testkit.adoc │ │ │ ├── api-evolution.adoc │ │ │ ├── appendix.adoc │ │ │ ├── extensions/ │ │ │ │ ├── conditional-test-execution.adoc │ │ │ │ ├── exception-handling.adoc │ │ │ │ ├── intercepting-invocations.adoc │ │ │ │ ├── keeping-state-in-extensions.adoc │ │ │ │ ├── overview.adoc │ │ │ │ ├── parameter-resolution.adoc │ │ │ │ ├── pre-interrupt-callback.adoc │ │ │ │ ├── providing-invocation-contexts-for-class-templates.adoc │ │ │ │ ├── providing-invocation-contexts-for-test-templates.adoc │ │ │ │ ├── registering-extensions.adoc │ │ │ │ ├── relative-execution-order-of-user-code-and-extensions.adoc │ │ │ │ ├── supported-utilities-in-extensions.adoc │ │ │ │ ├── test-instance-factories.adoc │ │ │ │ ├── test-instance-post-processing.adoc │ │ │ │ ├── test-instance-pre-construct-callback.adoc │ │ │ │ ├── test-instance-pre-destroy-callback.adoc │ │ │ │ ├── test-lifecycle-callbacks.adoc │ │ │ │ └── test-result-processing.adoc │ │ │ ├── migrating-from-junit4.adoc │ │ │ ├── overview.adoc │ │ │ ├── release-notes.adoc │ │ │ ├── running-tests/ │ │ │ │ ├── build-support.adoc │ │ │ │ ├── capturing-standard-output-error.adoc │ │ │ │ ├── configuration-parameters.adoc │ │ │ │ ├── console-launcher.adoc │ │ │ │ ├── discovery-issues.adoc │ │ │ │ ├── discovery-selectors.adoc │ │ │ │ ├── ide-support.adoc │ │ │ │ ├── intro.adoc │ │ │ │ ├── source-launcher.adoc │ │ │ │ ├── stack-trace-pruning.adoc │ │ │ │ ├── tags.adoc │ │ │ │ └── using-listeners-and-interceptors.adoc │ │ │ └── writing-tests/ │ │ │ ├── annotations.adoc │ │ │ ├── assertions.adoc │ │ │ ├── assumptions.adoc │ │ │ ├── built-in-extensions.adoc │ │ │ ├── class-templates.adoc │ │ │ ├── conditional-test-execution.adoc │ │ │ ├── definitions.adoc │ │ │ ├── dependency-injection-for-constructors-and-methods.adoc │ │ │ ├── disabling-tests.adoc │ │ │ ├── display-names.adoc │ │ │ ├── dynamic-tests.adoc │ │ │ ├── exception-handling.adoc │ │ │ ├── intro.adoc │ │ │ ├── nested-tests.adoc │ │ │ ├── parallel-execution.adoc │ │ │ ├── parameterized-classes-and-tests.adoc │ │ │ ├── repeated-tests.adoc │ │ │ ├── tagging-and-filtering.adoc │ │ │ ├── test-classes-and-methods.adoc │ │ │ ├── test-execution-order.adoc │ │ │ ├── test-instance-lifecycle.adoc │ │ │ ├── test-interfaces-and-default-methods.adoc │ │ │ ├── test-templates.adoc │ │ │ └── timeouts.adoc │ │ └── partials/ │ │ └── release-notes/ │ │ ├── release-notes-6.0.0.adoc │ │ ├── release-notes-6.0.1.adoc │ │ ├── release-notes-6.0.2.adoc │ │ ├── release-notes-6.0.3.adoc │ │ ├── release-notes-6.1.0-M1.adoc │ │ ├── release-notes-6.1.0-RC1.adoc │ │ ├── release-notes-6.1.0.adoc │ │ └── release-notes-TEMPLATE.adoc │ ├── package.json │ └── src/ │ ├── javadoc/ │ │ ├── junit-overview.html │ │ └── junit-stylesheet.css │ ├── main/ │ │ └── java/ │ │ └── example/ │ │ ├── domain/ │ │ │ ├── Person.java │ │ │ └── package-info.java │ │ ├── registration/ │ │ │ ├── WebClient.java │ │ │ ├── WebResponse.java │ │ │ ├── WebServerExtension.java │ │ │ └── package-info.java │ │ └── util/ │ │ ├── Calculator.java │ │ ├── ListWriter.java │ │ ├── StringUtils.java │ │ └── package-info.java │ ├── plantuml/ │ │ ├── component-diagram.puml │ │ ├── junit-platform-suite-engine-diagram.puml │ │ ├── junit-platform-suite-engine-duplicate-test-execution-diagram.puml │ │ └── launcher-api-diagram.puml │ ├── test/ │ │ ├── java/ │ │ │ ├── example/ │ │ │ │ ├── AssertJAssertionsDemo.java │ │ │ │ ├── AssertionsDemo.java │ │ │ │ ├── AssumptionsDemo.java │ │ │ │ ├── AutoCloseDemo.java │ │ │ │ ├── BeforeAndAfterSuiteDemo.java │ │ │ │ ├── ClassTemplateDemo.java │ │ │ │ ├── ConditionalTestExecutionDemo.java │ │ │ │ ├── CustomLauncherInterceptor.java │ │ │ │ ├── CustomTestEngine.java │ │ │ │ ├── DefaultLocaleTimezoneExtensionDemo.java │ │ │ │ ├── DisabledClassDemo.java │ │ │ │ ├── DisabledTestsDemo.java │ │ │ │ ├── DisplayNameDemo.java │ │ │ │ ├── DisplayNameGeneratorDemo.java │ │ │ │ ├── DocumentationTestSuite.java │ │ │ │ ├── DynamicTestsDemo.java │ │ │ │ ├── DynamicTestsNamedDemo.java │ │ │ │ ├── ExampleTestCase.java │ │ │ │ ├── ExplicitExecutionModeDemo.java │ │ │ │ ├── ExternalCustomConditionDemo.java │ │ │ │ ├── ExternalFieldSourceDemo.java │ │ │ │ ├── ExternalMethodSourceDemo.java │ │ │ │ ├── Fast.java │ │ │ │ ├── FastTest.java │ │ │ │ ├── FirstCustomEngine.java │ │ │ │ ├── HttpServerDemo.java │ │ │ │ ├── IgnoredTestsDemo.java │ │ │ │ ├── JUnit4Tests.java │ │ │ │ ├── MethodSourceParameterResolutionDemo.java │ │ │ │ ├── MyFirstJUnitJupiterRecordTests.java │ │ │ │ ├── MyFirstJUnitJupiterTests.java │ │ │ │ ├── MyRandomParametersTest.java │ │ │ │ ├── OrderedNestedTestClassesDemo.java │ │ │ │ ├── OrderedTestsDemo.java │ │ │ │ ├── ParameterizedClassDemo.java │ │ │ │ ├── ParameterizedLifecycleDemo.java │ │ │ │ ├── ParameterizedMigrationDemo.java │ │ │ │ ├── ParameterizedRecordDemo.java │ │ │ │ ├── ParameterizedTestDemo.java │ │ │ │ ├── PollingTimeoutDemo.java │ │ │ │ ├── RepeatedTestsDemo.java │ │ │ │ ├── SecondCustomEngine.java │ │ │ │ ├── SlowTests.java │ │ │ │ ├── StandardTests.java │ │ │ │ ├── SuiteDemo.java │ │ │ │ ├── SystemPropertyExtensionDemo.java │ │ │ │ ├── TaggingDemo.java │ │ │ │ ├── TempDirectoryDemo.java │ │ │ │ ├── TestInfoDemo.java │ │ │ │ ├── TestReporterDemo.java │ │ │ │ ├── TestTemplateDemo.java │ │ │ │ ├── TestingAStackDemo.java │ │ │ │ ├── TimeoutDemo.java │ │ │ │ ├── UsingTheLauncherDemo.java │ │ │ │ ├── UsingTheLauncherForDiscoveryDemo.java │ │ │ │ ├── callbacks/ │ │ │ │ │ ├── AbstractDatabaseTests.java │ │ │ │ │ ├── BrokenLifecycleMethodConfigDemo.java │ │ │ │ │ ├── DatabaseTestsDemo.java │ │ │ │ │ ├── Extension1.java │ │ │ │ │ ├── Extension2.java │ │ │ │ │ ├── Logger.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── defaultmethods/ │ │ │ │ │ ├── ComparableContract.java │ │ │ │ │ ├── EqualsContract.java │ │ │ │ │ ├── StringTests.java │ │ │ │ │ ├── Testable.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── exception/ │ │ │ │ │ ├── AssertDoesNotThrowExceptionDemo.java │ │ │ │ │ ├── ExceptionAssertionDemo.java │ │ │ │ │ ├── ExceptionAssertionExactDemo.java │ │ │ │ │ ├── FailedAssertionDemo.java │ │ │ │ │ ├── IgnoreIOExceptionExtension.java │ │ │ │ │ ├── IgnoreIOExceptionTests.java │ │ │ │ │ ├── MultipleHandlersTestCase.java │ │ │ │ │ ├── RecordStateOnErrorExtension.java │ │ │ │ │ ├── UncaughtExceptionHandlingDemo.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── extensions/ │ │ │ │ │ ├── HttpServerExtension.java │ │ │ │ │ ├── HttpServerResource.java │ │ │ │ │ ├── ParameterResolverConflictDemo.java │ │ │ │ │ ├── ParameterResolverCustomAnnotationDemo.java │ │ │ │ │ ├── ParameterResolverCustomTypeDemo.java │ │ │ │ │ ├── ParameterResolverNoConflictDemo.java │ │ │ │ │ ├── Random.java │ │ │ │ │ ├── RandomNumberDemo.java │ │ │ │ │ ├── RandomNumberExtension.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── interceptor/ │ │ │ │ │ ├── SwingEdtInterceptor.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ ├── registration/ │ │ │ │ │ ├── DocumentationDemo.java │ │ │ │ │ └── WebServerDemo.java │ │ │ │ ├── session/ │ │ │ │ │ ├── CloseableHttpServer.java │ │ │ │ │ ├── GlobalSetupTeardownListener.java │ │ │ │ │ ├── HttpTests.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── sharedresources/ │ │ │ │ │ ├── ChildrenSharedResourcesDemo.java │ │ │ │ │ ├── DynamicSharedResourcesDemo.java │ │ │ │ │ ├── SharedResourceDemo.java │ │ │ │ │ ├── StaticSharedResourcesDemo.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── testinterface/ │ │ │ │ │ ├── TestInterfaceDemo.java │ │ │ │ │ ├── TestInterfaceDynamicTestsDemo.java │ │ │ │ │ ├── TestLifecycleLogger.java │ │ │ │ │ ├── TimeExecutionLogger.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── testkit/ │ │ │ │ │ ├── EngineTestKitAllEventsDemo.java │ │ │ │ │ ├── EngineTestKitDiscoveryDemo.java │ │ │ │ │ ├── EngineTestKitFailedMethodDemo.java │ │ │ │ │ ├── EngineTestKitSkippedMethodDemo.java │ │ │ │ │ ├── EngineTestKitStatisticsDemo.java │ │ │ │ │ └── package-info.java │ │ │ │ └── timing/ │ │ │ │ ├── TimingExtension.java │ │ │ │ ├── TimingExtensionTests.java │ │ │ │ └── package-info.java │ │ │ └── extensions/ │ │ │ ├── DisabledOnOpenJ9.java │ │ │ ├── ExpectToFail.java │ │ │ └── package-info.java │ │ ├── kotlin/ │ │ │ └── example/ │ │ │ ├── FibonacciCalculator.kt │ │ │ ├── KotlinAssertionsDemo.kt │ │ │ ├── KotlinCoroutinesDemo.kt │ │ │ ├── KotlinCoroutinesRunTestDemo.kt │ │ │ ├── kotlin/ │ │ │ │ ├── AssumptionsDemo.kt │ │ │ │ ├── AutoCloseDemo.kt │ │ │ │ ├── ConditionalTestExecutionDemo.kt │ │ │ │ ├── DefaultLocaleTimezoneExtensionDemo.kt │ │ │ │ ├── DisplayNameDemo.kt │ │ │ │ ├── DisplayNameGeneratorDemo.kt │ │ │ │ ├── DynamicTestsDemo.kt │ │ │ │ ├── DynamicTestsNamedDemo.kt │ │ │ │ ├── ExternalCustomConditionDemo.kt │ │ │ │ ├── Fast.kt │ │ │ │ ├── FastTest.kt │ │ │ │ ├── HttpServerDemo.kt │ │ │ │ ├── MyFirstJUnitJupiterTests.kt │ │ │ │ ├── MyRandomParametersTest.kt │ │ │ │ ├── ParameterizedClassDemo.kt │ │ │ │ ├── PollingTimeoutDemo.kt │ │ │ │ ├── RepeatedTestsDemo.kt │ │ │ │ ├── StandardTests.kt │ │ │ │ ├── SystemPropertyExtensionDemo.kt │ │ │ │ ├── TempDirectoryDemo.kt │ │ │ │ ├── TestInfoDemo.kt │ │ │ │ ├── TestReporterDemo.kt │ │ │ │ ├── TestingAStackDemo.kt │ │ │ │ ├── TimeoutDemo.kt │ │ │ │ ├── defaultmethods/ │ │ │ │ │ ├── ComparableContract.kt │ │ │ │ │ ├── EqualsContract.kt │ │ │ │ │ ├── StringTests.kt │ │ │ │ │ └── Testable.kt │ │ │ │ ├── exception/ │ │ │ │ │ ├── AssertDoesNotThrowExceptionDemo.kt │ │ │ │ │ ├── ExceptionAssertionDemo.kt │ │ │ │ │ ├── ExceptionAssertionExactDemo.kt │ │ │ │ │ ├── FailedAssertionDemo.kt │ │ │ │ │ └── UncaughtExceptionHandlingDemo.kt │ │ │ │ ├── extensions/ │ │ │ │ │ ├── HttpServerExtension.kt │ │ │ │ │ ├── HttpServerResource.kt │ │ │ │ │ ├── ParameterResolverConflictDemo.kt │ │ │ │ │ ├── ParameterResolverCustomAnnotationDemo.kt │ │ │ │ │ ├── ParameterResolverCustomTypeDemo.kt │ │ │ │ │ ├── ParameterResolverNoConflictDemo.kt │ │ │ │ │ ├── Random.kt │ │ │ │ │ ├── RandomNumberDemo.kt │ │ │ │ │ └── RandomNumberExtension.kt │ │ │ │ ├── registration/ │ │ │ │ │ ├── DocumentationDemo.kt │ │ │ │ │ └── DocumentationExtension.kt │ │ │ │ ├── testinterface/ │ │ │ │ │ ├── TestInterfaceDemo.kt │ │ │ │ │ ├── TestInterfaceDynamicTestsDemo.kt │ │ │ │ │ ├── TestLifecycleLogger.kt │ │ │ │ │ └── TimeExecutionLogger.kt │ │ │ │ └── timing/ │ │ │ │ ├── TimingExtension.kt │ │ │ │ └── TimingExtensionTests.kt │ │ │ └── registration/ │ │ │ └── KotlinWebServerDemo.kt │ │ └── resources/ │ │ ├── META-INF/ │ │ │ └── services/ │ │ │ └── org.junit.platform.launcher.LauncherSessionListener │ │ ├── junit-platform.properties │ │ ├── log4j2-test.xml │ │ └── two-column.csv │ └── tools/ │ └── java/ │ └── org/ │ └── junit/ │ └── api/ │ └── tools/ │ ├── AbstractApiReportWriter.java │ ├── ApiReport.java │ ├── ApiReportGenerator.java │ ├── ApiReportWriter.java │ ├── AsciidocApiReportWriter.java │ ├── Declaration.java │ ├── HtmlApiReportWriter.java │ ├── MarkdownApiReportWriter.java │ └── package-info.java ├── gradle/ │ ├── base/ │ │ ├── code-generator-model/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle.kts │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── kotlin/ │ │ │ │ └── junitbuild/ │ │ │ │ └── generator/ │ │ │ │ └── model/ │ │ │ │ └── JRE.kt │ │ │ └── resources/ │ │ │ └── jre.yaml │ │ ├── dsl-extensions/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle.kts │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── kotlin/ │ │ │ └── junitbuild/ │ │ │ └── extensions/ │ │ │ ├── DependencyExtensions.kt │ │ │ ├── ProjectExtensions.kt │ │ │ ├── StringExtensions.kt │ │ │ ├── TaskExtensions.kt │ │ │ ├── VersionExtensions.kt │ │ │ └── junitbuild.dsl-extensions.gradle.kts │ │ ├── gradle.properties │ │ └── settings.gradle.kts │ ├── config/ │ │ ├── checkstyle/ │ │ │ ├── checkstyleMain.xml │ │ │ ├── checkstyleNohttp.xml │ │ │ ├── checkstyleTest.xml │ │ │ └── suppressions.xml │ │ ├── eclipse/ │ │ │ ├── junit-eclipse-formatter-settings.xml │ │ │ └── junit-eclipse.importorder │ │ ├── roseau/ │ │ │ └── config.yaml │ │ └── spotless/ │ │ └── eclipse-public-license-2.0.java │ ├── gradle-daemon-jvm.properties │ ├── libs.versions.toml │ ├── plugins/ │ │ ├── antora/ │ │ │ ├── build.gradle.kts │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── kotlin/ │ │ │ ├── junitbuild/ │ │ │ │ └── antora/ │ │ │ │ └── AntoraConfiguration.kt │ │ │ └── junitbuild.antora-conventions.gradle.kts │ │ ├── backward-compatibility/ │ │ │ ├── build.gradle.kts │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── kotlin/ │ │ │ ├── junitbuild/ │ │ │ │ └── compatibility/ │ │ │ │ ├── BackwardCompatibilityChecksExtension.kt │ │ │ │ └── roseau/ │ │ │ │ └── RoseauDiff.kt │ │ │ └── junitbuild.backward-compatibility.gradle.kts │ │ ├── build-parameters/ │ │ │ └── build.gradle.kts │ │ ├── code-generator/ │ │ │ ├── build.gradle.kts │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── kotlin/ │ │ │ ├── junitbuild/ │ │ │ │ └── generator/ │ │ │ │ └── GenerateJreRelatedSourceCode.kt │ │ │ └── junitbuild.code-generator.gradle.kts │ │ ├── common/ │ │ │ ├── build.gradle.kts │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── kotlin/ │ │ │ ├── JavaLibraryExtension.kt │ │ │ ├── License.kt │ │ │ ├── junitbuild/ │ │ │ │ ├── deps/ │ │ │ │ │ └── ByteBuddyAlignmentRule.kt │ │ │ │ ├── eclipse/ │ │ │ │ │ └── EclipseConventionsExtension.kt │ │ │ │ ├── exec/ │ │ │ │ │ ├── CaptureJavaExecOutput.kt │ │ │ │ │ ├── ClasspathSystemPropertyProvider.kt │ │ │ │ │ ├── GenerateStandaloneConsoleLauncherShadowedArtifactsFile.kt │ │ │ │ │ └── RunConsoleLauncher.kt │ │ │ │ ├── graalvm/ │ │ │ │ │ └── NativeImagePropertiesExtension.kt │ │ │ │ ├── java/ │ │ │ │ │ ├── UpdateJarAction.kt │ │ │ │ │ └── WriteArtifactsFile.kt │ │ │ │ └── javadoc/ │ │ │ │ ├── JavadocConventionsExtension.kt │ │ │ │ ├── JavadocValuesOption.kt │ │ │ │ ├── ModuleSpecificJavadocFileOption.kt │ │ │ │ └── VersionNumber.kt │ │ │ ├── junitbuild.base-conventions.gradle.kts │ │ │ ├── junitbuild.build-metadata.gradle.kts │ │ │ ├── junitbuild.checkstyle-conventions.gradle.kts │ │ │ ├── junitbuild.checkstyle-nohttp.gradle.kts │ │ │ ├── junitbuild.eclipse-conventions.gradle.kts │ │ │ ├── junitbuild.jacoco-aggregation-conventions.gradle.kts │ │ │ ├── junitbuild.jacoco-conventions.gradle.kts │ │ │ ├── junitbuild.jacoco-java-conventions.gradle.kts │ │ │ ├── junitbuild.java-aggregator-conventions.gradle.kts │ │ │ ├── junitbuild.java-errorprone-conventions.gradle.kts │ │ │ ├── junitbuild.java-library-conventions.gradle.kts │ │ │ ├── junitbuild.java-toolchain-conventions.gradle.kts │ │ │ ├── junitbuild.javadoc-conventions.gradle.kts │ │ │ ├── junitbuild.jmh-conventions.gradle.kts │ │ │ ├── junitbuild.junit4-compatibility.gradle.kts │ │ │ ├── junitbuild.kotlin-library-conventions.gradle.kts │ │ │ ├── junitbuild.osgi-conventions.gradle.kts │ │ │ ├── junitbuild.publishing-conventions.gradle.kts │ │ │ ├── junitbuild.settings-conventions.settings.gradle.kts │ │ │ ├── junitbuild.shadow-conventions.gradle.kts │ │ │ ├── junitbuild.spotless-conventions.gradle.kts │ │ │ └── junitbuild.testing-conventions.gradle.kts │ │ ├── publishing/ │ │ │ ├── build.gradle.kts │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── kotlin/ │ │ │ ├── junitbuild/ │ │ │ │ └── release/ │ │ │ │ └── VerifyBinaryArtifactsAreIdentical.kt │ │ │ ├── junitbuild.maven-central-publishing.settings.gradle.kts │ │ │ └── junitbuild.temp-maven-repo.gradle.kts │ │ └── settings.gradle.kts │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── junit-bom/ │ ├── README.md │ └── junit-bom.gradle.kts ├── junit-jupiter/ │ ├── junit-jupiter.gradle.kts │ └── src/ │ └── main/ │ └── java/ │ └── module-info.java ├── junit-jupiter-api/ │ ├── junit-jupiter-api.gradle.kts │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ ├── module-info.java │ │ │ └── org/ │ │ │ └── junit/ │ │ │ └── jupiter/ │ │ │ └── api/ │ │ │ ├── AfterAll.java │ │ │ ├── AfterEach.java │ │ │ ├── AssertAll.java │ │ │ ├── AssertArrayEquals.java │ │ │ ├── AssertDoesNotThrow.java │ │ │ ├── AssertEquals.java │ │ │ ├── AssertFalse.java │ │ │ ├── AssertInstanceOf.java │ │ │ ├── AssertIterableEquals.java │ │ │ ├── AssertLinesMatch.java │ │ │ ├── AssertNotEquals.java │ │ │ ├── AssertNotNull.java │ │ │ ├── AssertNotSame.java │ │ │ ├── AssertNull.java │ │ │ ├── AssertSame.java │ │ │ ├── AssertThrows.java │ │ │ ├── AssertThrowsExactly.java │ │ │ ├── AssertTimeout.java │ │ │ ├── AssertTimeoutPreemptively.java │ │ │ ├── AssertTrue.java │ │ │ ├── AssertionFailureBuilder.java │ │ │ ├── AssertionUtils.java │ │ │ ├── Assertions.java │ │ │ ├── Assumptions.java │ │ │ ├── AutoClose.java │ │ │ ├── BeforeAll.java │ │ │ ├── BeforeEach.java │ │ │ ├── ClassDescriptor.java │ │ │ ├── ClassOrderer.java │ │ │ ├── ClassOrdererContext.java │ │ │ ├── ClassTemplate.java │ │ │ ├── Constants.java │ │ │ ├── Disabled.java │ │ │ ├── DisplayName.java │ │ │ ├── DisplayNameGeneration.java │ │ │ ├── DisplayNameGenerator.java │ │ │ ├── DynamicContainer.java │ │ │ ├── DynamicNode.java │ │ │ ├── DynamicTest.java │ │ │ ├── IndicativeSentencesGeneration.java │ │ │ ├── MediaType.java │ │ │ ├── MethodDescriptor.java │ │ │ ├── MethodOrderer.java │ │ │ ├── MethodOrdererContext.java │ │ │ ├── Named.java │ │ │ ├── NamedExecutable.java │ │ │ ├── Nested.java │ │ │ ├── Order.java │ │ │ ├── RandomOrdererUtils.java │ │ │ ├── RepeatedTest.java │ │ │ ├── RepetitionInfo.java │ │ │ ├── Tag.java │ │ │ ├── Tags.java │ │ │ ├── Test.java │ │ │ ├── TestClassOrder.java │ │ │ ├── TestFactory.java │ │ │ ├── TestInfo.java │ │ │ ├── TestInstance.java │ │ │ ├── TestMethodOrder.java │ │ │ ├── TestReporter.java │ │ │ ├── TestTemplate.java │ │ │ ├── Timeout.java │ │ │ ├── condition/ │ │ │ │ ├── AbstractJreCondition.java │ │ │ │ ├── AbstractJreRangeCondition.java │ │ │ │ ├── AbstractOsBasedExecutionCondition.java │ │ │ │ ├── AbstractRepeatableAnnotationCondition.java │ │ │ │ ├── BooleanExecutionCondition.java │ │ │ │ ├── DisabledForJreRange.java │ │ │ │ ├── DisabledForJreRangeCondition.java │ │ │ │ ├── DisabledIf.java │ │ │ │ ├── DisabledIfCondition.java │ │ │ │ ├── DisabledIfEnvironmentVariable.java │ │ │ │ ├── DisabledIfEnvironmentVariableCondition.java │ │ │ │ ├── DisabledIfEnvironmentVariables.java │ │ │ │ ├── DisabledIfSystemProperties.java │ │ │ │ ├── DisabledIfSystemProperty.java │ │ │ │ ├── DisabledIfSystemPropertyCondition.java │ │ │ │ ├── DisabledInNativeImage.java │ │ │ │ ├── DisabledOnJre.java │ │ │ │ ├── DisabledOnJreCondition.java │ │ │ │ ├── DisabledOnOs.java │ │ │ │ ├── DisabledOnOsCondition.java │ │ │ │ ├── EnabledForJreRange.java │ │ │ │ ├── EnabledForJreRangeCondition.java │ │ │ │ ├── EnabledIf.java │ │ │ │ ├── EnabledIfCondition.java │ │ │ │ ├── EnabledIfEnvironmentVariable.java │ │ │ │ ├── EnabledIfEnvironmentVariableCondition.java │ │ │ │ ├── EnabledIfEnvironmentVariables.java │ │ │ │ ├── EnabledIfSystemProperties.java │ │ │ │ ├── EnabledIfSystemProperty.java │ │ │ │ ├── EnabledIfSystemPropertyCondition.java │ │ │ │ ├── EnabledInNativeImage.java │ │ │ │ ├── EnabledOnJre.java │ │ │ │ ├── EnabledOnJreCondition.java │ │ │ │ ├── EnabledOnOs.java │ │ │ │ ├── EnabledOnOsCondition.java │ │ │ │ ├── MethodBasedCondition.java │ │ │ │ ├── OS.java │ │ │ │ └── package-info.java │ │ │ ├── extension/ │ │ │ │ ├── AfterAllCallback.java │ │ │ │ ├── AfterClassTemplateInvocationCallback.java │ │ │ │ ├── AfterEachCallback.java │ │ │ │ ├── AfterTestExecutionCallback.java │ │ │ │ ├── AnnotatedElementContext.java │ │ │ │ ├── BeforeAllCallback.java │ │ │ │ ├── BeforeClassTemplateInvocationCallback.java │ │ │ │ ├── BeforeEachCallback.java │ │ │ │ ├── BeforeTestExecutionCallback.java │ │ │ │ ├── ClassTemplateInvocationContext.java │ │ │ │ ├── ClassTemplateInvocationContextProvider.java │ │ │ │ ├── ClassTemplateInvocationLifecycleMethod.java │ │ │ │ ├── ConditionEvaluationResult.java │ │ │ │ ├── DynamicTestInvocationContext.java │ │ │ │ ├── ExecutableInvoker.java │ │ │ │ ├── ExecutionCondition.java │ │ │ │ ├── ExtendWith.java │ │ │ │ ├── Extension.java │ │ │ │ ├── ExtensionConfigurationException.java │ │ │ │ ├── ExtensionContext.java │ │ │ │ ├── ExtensionContextException.java │ │ │ │ ├── Extensions.java │ │ │ │ ├── InvocationInterceptor.java │ │ │ │ ├── LifecycleMethodExecutionExceptionHandler.java │ │ │ │ ├── MediaType.java │ │ │ │ ├── ParameterContext.java │ │ │ │ ├── ParameterResolutionException.java │ │ │ │ ├── ParameterResolver.java │ │ │ │ ├── PreInterruptCallback.java │ │ │ │ ├── PreInterruptContext.java │ │ │ │ ├── ReflectiveInvocationContext.java │ │ │ │ ├── RegisterExtension.java │ │ │ │ ├── TemplateInvocationValidationException.java │ │ │ │ ├── TestExecutionExceptionHandler.java │ │ │ │ ├── TestInstanceFactory.java │ │ │ │ ├── TestInstanceFactoryContext.java │ │ │ │ ├── TestInstancePostProcessor.java │ │ │ │ ├── TestInstancePreConstructCallback.java │ │ │ │ ├── TestInstancePreDestroyCallback.java │ │ │ │ ├── TestInstances.java │ │ │ │ ├── TestInstantiationAwareExtension.java │ │ │ │ ├── TestInstantiationException.java │ │ │ │ ├── TestTemplateInvocationContext.java │ │ │ │ ├── TestTemplateInvocationContextProvider.java │ │ │ │ ├── TestWatcher.java │ │ │ │ ├── package-info.java │ │ │ │ └── support/ │ │ │ │ ├── TypeBasedParameterResolver.java │ │ │ │ └── package-info.java │ │ │ ├── function/ │ │ │ │ ├── Executable.java │ │ │ │ ├── ThrowingConsumer.java │ │ │ │ ├── ThrowingSupplier.java │ │ │ │ └── package-info.java │ │ │ ├── io/ │ │ │ │ ├── CleanupMode.java │ │ │ │ ├── DefaultDeletionResult.java │ │ │ │ ├── TempDir.java │ │ │ │ ├── TempDirDeletionStrategy.java │ │ │ │ ├── TempDirFactory.java │ │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ ├── parallel/ │ │ │ │ ├── Execution.java │ │ │ │ ├── ExecutionMode.java │ │ │ │ ├── Isolated.java │ │ │ │ ├── ResourceAccessMode.java │ │ │ │ ├── ResourceLock.java │ │ │ │ ├── ResourceLockTarget.java │ │ │ │ ├── ResourceLocks.java │ │ │ │ ├── ResourceLocksProvider.java │ │ │ │ ├── Resources.java │ │ │ │ └── package-info.java │ │ │ ├── timeout/ │ │ │ │ ├── PreemptiveTimeoutUtils.java │ │ │ │ └── package-info.java │ │ │ └── util/ │ │ │ ├── ClearSystemProperty.java │ │ │ ├── DefaultLocale.java │ │ │ ├── DefaultLocaleExtension.java │ │ │ ├── DefaultTimeZone.java │ │ │ ├── DefaultTimeZoneExtension.java │ │ │ ├── JupiterLocaleUtils.java │ │ │ ├── JupiterPropertyUtils.java │ │ │ ├── LocaleProvider.java │ │ │ ├── NullLocaleProvider.java │ │ │ ├── NullTimeZoneProvider.java │ │ │ ├── ReadsDefaultLocale.java │ │ │ ├── ReadsDefaultTimeZone.java │ │ │ ├── ReadsSystemProperty.java │ │ │ ├── RestoreSystemProperties.java │ │ │ ├── SetSystemProperty.java │ │ │ ├── SystemPropertiesExtension.java │ │ │ ├── TimeZoneProvider.java │ │ │ ├── WritesDefaultLocale.java │ │ │ ├── WritesDefaultTimeZone.java │ │ │ ├── WritesSystemProperty.java │ │ │ └── package-info.java │ │ └── kotlin/ │ │ └── org/ │ │ └── junit/ │ │ └── jupiter/ │ │ └── api/ │ │ └── Assertions.kt │ ├── templates/ │ │ └── resources/ │ │ ├── main/ │ │ │ └── org/ │ │ │ └── junit/ │ │ │ └── jupiter/ │ │ │ └── api/ │ │ │ └── condition/ │ │ │ └── JRE.java.jte │ │ └── testFixtures/ │ │ └── org/ │ │ └── junit/ │ │ └── jupiter/ │ │ └── api/ │ │ └── condition/ │ │ └── JavaVersionPredicates.java.jte │ ├── test/ │ │ └── README.md │ └── testFixtures/ │ └── java/ │ └── org/ │ └── junit/ │ └── jupiter/ │ └── api/ │ ├── AssertionTestUtils.java │ ├── EqualsAndHashCodeAssertions.java │ ├── TemporaryClasspathExecutor.java │ ├── extension/ │ │ ├── DisabledInEclipse.java │ │ ├── DisabledOnOpenJ9.java │ │ └── ExtensionContextParameterResolver.java │ ├── fixtures/ │ │ └── TrackLogRecords.java │ └── io/ │ └── FailingTempDirDeletionStrategy.java ├── junit-jupiter-engine/ │ ├── junit-jupiter-engine.gradle.kts │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ ├── module-info.java │ │ │ └── org/ │ │ │ └── junit/ │ │ │ └── jupiter/ │ │ │ └── engine/ │ │ │ ├── Constants.java │ │ │ ├── JupiterTestEngine.java │ │ │ ├── config/ │ │ │ │ ├── CachingJupiterConfiguration.java │ │ │ │ ├── ConfigurationParameterConverter.java │ │ │ │ ├── DefaultJupiterConfiguration.java │ │ │ │ ├── EnumConfigurationParameterConverter.java │ │ │ │ ├── FilteringConfigurationParameterConverter.java │ │ │ │ ├── InstantiatingConfigurationParameterConverter.java │ │ │ │ ├── JupiterConfiguration.java │ │ │ │ └── package-info.java │ │ │ ├── descriptor/ │ │ │ │ ├── AbstractExtensionContext.java │ │ │ │ ├── CallbackSupport.java │ │ │ │ ├── ClassBasedTestDescriptor.java │ │ │ │ ├── ClassExtensionContext.java │ │ │ │ ├── ClassTemplateInvocationExtensionContext.java │ │ │ │ ├── ClassTemplateInvocationTestDescriptor.java │ │ │ │ ├── ClassTemplateTestDescriptor.java │ │ │ │ ├── ClassTestDescriptor.java │ │ │ │ ├── DefaultDynamicTestInvocationContext.java │ │ │ │ ├── DefaultTestInstanceFactoryContext.java │ │ │ │ ├── DisplayNameUtils.java │ │ │ │ ├── DynamicContainerTestDescriptor.java │ │ │ │ ├── DynamicDescendantFilter.java │ │ │ │ ├── DynamicExtensionContext.java │ │ │ │ ├── DynamicNodeTestDescriptor.java │ │ │ │ ├── DynamicTestTestDescriptor.java │ │ │ │ ├── ExclusiveResourceCollector.java │ │ │ │ ├── ExtensionUtils.java │ │ │ │ ├── Filterable.java │ │ │ │ ├── JupiterEngineDescriptor.java │ │ │ │ ├── JupiterEngineExtensionContext.java │ │ │ │ ├── JupiterTestDescriptor.java │ │ │ │ ├── LifecycleMethodUtils.java │ │ │ │ ├── MethodBasedTestDescriptor.java │ │ │ │ ├── MethodExtensionContext.java │ │ │ │ ├── MethodSourceSupport.java │ │ │ │ ├── NestedClassTestDescriptor.java │ │ │ │ ├── ResourceLockAware.java │ │ │ │ ├── TemplateExecutor.java │ │ │ │ ├── TestClassAware.java │ │ │ │ ├── TestFactoryTestDescriptor.java │ │ │ │ ├── TestInstanceLifecycleUtils.java │ │ │ │ ├── TestMethodTestDescriptor.java │ │ │ │ ├── TestTemplateExtensionContext.java │ │ │ │ ├── TestTemplateInvocationTestDescriptor.java │ │ │ │ ├── TestTemplateTestDescriptor.java │ │ │ │ ├── UniqueIdPrefixTransformer.java │ │ │ │ ├── Validatable.java │ │ │ │ └── package-info.java │ │ │ ├── discovery/ │ │ │ │ ├── AbstractAnnotatedDescriptorWrapper.java │ │ │ │ ├── AbstractOrderingVisitor.java │ │ │ │ ├── ClassOrderingVisitor.java │ │ │ │ ├── ClassSelectorResolver.java │ │ │ │ ├── DeclaredMethodSelector.java │ │ │ │ ├── DefaultClassDescriptor.java │ │ │ │ ├── DefaultClassOrdererContext.java │ │ │ │ ├── DefaultMethodDescriptor.java │ │ │ │ ├── DefaultMethodOrdererContext.java │ │ │ │ ├── DiscoverySelectorResolver.java │ │ │ │ ├── MethodOrderingVisitor.java │ │ │ │ ├── MethodSegmentResolver.java │ │ │ │ ├── MethodSelectorResolver.java │ │ │ │ ├── package-info.java │ │ │ │ └── predicates/ │ │ │ │ ├── IsTestFactoryMethod.java │ │ │ │ ├── IsTestMethod.java │ │ │ │ ├── IsTestTemplateMethod.java │ │ │ │ ├── IsTestableMethod.java │ │ │ │ ├── TestClassPredicates.java │ │ │ │ └── package-info.java │ │ │ ├── execution/ │ │ │ │ ├── AfterEachMethodAdapter.java │ │ │ │ ├── BeforeEachMethodAdapter.java │ │ │ │ ├── ConditionEvaluationException.java │ │ │ │ ├── ConditionEvaluator.java │ │ │ │ ├── ConstructorInvocation.java │ │ │ │ ├── DefaultExecutableInvoker.java │ │ │ │ ├── DefaultParameterContext.java │ │ │ │ ├── DefaultTestInstances.java │ │ │ │ ├── ExtensionContextSupplier.java │ │ │ │ ├── InterceptingExecutableInvoker.java │ │ │ │ ├── InvocationInterceptorChain.java │ │ │ │ ├── JupiterEngineExecutionContext.java │ │ │ │ ├── LauncherStoreFacade.java │ │ │ │ ├── MethodInvocation.java │ │ │ │ ├── NamespaceAwareStore.java │ │ │ │ ├── ParameterResolutionUtils.java │ │ │ │ ├── TestInstancesProvider.java │ │ │ │ └── package-info.java │ │ │ ├── extension/ │ │ │ │ ├── AutoCloseExtension.java │ │ │ │ ├── DefaultPreInterruptContext.java │ │ │ │ ├── DefaultRepetitionInfo.java │ │ │ │ ├── DefaultTestReporter.java │ │ │ │ ├── DisabledCondition.java │ │ │ │ ├── ExtensionContextInternal.java │ │ │ │ ├── ExtensionRegistrar.java │ │ │ │ ├── ExtensionRegistry.java │ │ │ │ ├── MutableExtensionRegistry.java │ │ │ │ ├── PreInterruptCallbackInvocation.java │ │ │ │ ├── PreInterruptCallbackInvocationFactory.java │ │ │ │ ├── PreInterruptThreadDumpPrinter.java │ │ │ │ ├── RepeatedTestDisplayNameFormatter.java │ │ │ │ ├── RepeatedTestExtension.java │ │ │ │ ├── RepeatedTestInvocationContext.java │ │ │ │ ├── RepetitionExtension.java │ │ │ │ ├── SameThreadTimeoutInvocation.java │ │ │ │ ├── SeparateThreadTimeoutInvocation.java │ │ │ │ ├── TempDirectory.java │ │ │ │ ├── TestInfoParameterResolver.java │ │ │ │ ├── TestReporterParameterResolver.java │ │ │ │ ├── TimeoutConfiguration.java │ │ │ │ ├── TimeoutDuration.java │ │ │ │ ├── TimeoutDurationParser.java │ │ │ │ ├── TimeoutExceptionFactory.java │ │ │ │ ├── TimeoutExtension.java │ │ │ │ ├── TimeoutInvocationFactory.java │ │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ └── support/ │ │ │ ├── JupiterThrowableCollectorFactory.java │ │ │ ├── MethodReflectionUtils.java │ │ │ ├── OpenTest4JAndJUnit4AwareThrowableCollector.java │ │ │ └── package-info.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── services/ │ │ └── org.junit.platform.engine.TestEngine │ ├── test/ │ │ └── README.md │ └── testFixtures/ │ └── java/ │ └── org/ │ └── junit/ │ └── jupiter/ │ └── engine/ │ └── discovery/ │ └── JupiterUniqueIdBuilder.java ├── junit-jupiter-migrationsupport/ │ ├── README.md │ ├── junit-jupiter-migrationsupport.gradle.kts │ └── src/ │ ├── main/ │ │ └── java/ │ │ ├── module-info.java │ │ └── org/ │ │ └── junit/ │ │ └── jupiter/ │ │ └── migrationsupport/ │ │ ├── EnableJUnit4MigrationSupport.java │ │ ├── conditions/ │ │ │ ├── IgnoreCondition.java │ │ │ └── package-info.java │ │ ├── package-info.java │ │ └── rules/ │ │ ├── EnableRuleMigrationSupport.java │ │ ├── ExpectedExceptionSupport.java │ │ ├── ExternalResourceSupport.java │ │ ├── TestRuleSupport.java │ │ ├── VerifierSupport.java │ │ ├── adapter/ │ │ │ ├── AbstractTestRuleAdapter.java │ │ │ ├── ExpectedExceptionAdapter.java │ │ │ ├── ExternalResourceAdapter.java │ │ │ ├── GenericBeforeAndAfterAdvice.java │ │ │ ├── VerifierAdapter.java │ │ │ └── package-info.java │ │ ├── member/ │ │ │ ├── AbstractTestRuleAnnotatedMember.java │ │ │ ├── TestRuleAnnotatedField.java │ │ │ ├── TestRuleAnnotatedMember.java │ │ │ ├── TestRuleAnnotatedMethod.java │ │ │ └── package-info.java │ │ └── package-info.java │ └── test/ │ └── README.md ├── junit-jupiter-params/ │ ├── junit-jupiter-params.gradle.kts │ └── src/ │ ├── jmh/ │ │ └── java/ │ │ └── org/ │ │ └── junit/ │ │ └── jupiter/ │ │ └── params/ │ │ └── ParameterizedInvocationNameFormatterBenchmarks.java │ ├── main/ │ │ ├── java/ │ │ │ ├── module-info.java │ │ │ └── org/ │ │ │ └── junit/ │ │ │ └── jupiter/ │ │ │ └── params/ │ │ │ ├── AbstractParameterizedClassInvocationLifecycleMethodInvoker.java │ │ │ ├── AfterParameterizedClassInvocation.java │ │ │ ├── AfterParameterizedClassInvocationMethodInvoker.java │ │ │ ├── ArgumentCountValidationMode.java │ │ │ ├── ArgumentCountValidator.java │ │ │ ├── ArgumentSetLifecycleMethod.java │ │ │ ├── BeforeClassTemplateInvocationFieldInjector.java │ │ │ ├── BeforeParameterizedClassInvocation.java │ │ │ ├── BeforeParameterizedClassInvocationMethodInvoker.java │ │ │ ├── ClassTemplateConstructorParameterResolver.java │ │ │ ├── DefaultParameterInfo.java │ │ │ ├── EvaluatedArgumentSet.java │ │ │ ├── InstancePostProcessingClassTemplateFieldInjector.java │ │ │ ├── Parameter.java │ │ │ ├── ParameterInfo.java │ │ │ ├── ParameterizedClass.java │ │ │ ├── ParameterizedClassContext.java │ │ │ ├── ParameterizedClassExtension.java │ │ │ ├── ParameterizedClassInvocationContext.java │ │ │ ├── ParameterizedDeclarationContext.java │ │ │ ├── ParameterizedInvocationConstants.java │ │ │ ├── ParameterizedInvocationContext.java │ │ │ ├── ParameterizedInvocationContextProvider.java │ │ │ ├── ParameterizedInvocationNameFormatter.java │ │ │ ├── ParameterizedInvocationParameterResolver.java │ │ │ ├── ParameterizedTest.java │ │ │ ├── ParameterizedTestContext.java │ │ │ ├── ParameterizedTestExtension.java │ │ │ ├── ParameterizedTestInvocationContext.java │ │ │ ├── ParameterizedTestMethodParameterResolver.java │ │ │ ├── ParameterizedTestSpiInstantiator.java │ │ │ ├── QuoteUtils.java │ │ │ ├── ResolutionCache.java │ │ │ ├── ResolverFacade.java │ │ │ ├── aggregator/ │ │ │ │ ├── AggregateWith.java │ │ │ │ ├── ArgumentAccessException.java │ │ │ │ ├── ArgumentsAccessor.java │ │ │ │ ├── ArgumentsAggregationException.java │ │ │ │ ├── ArgumentsAggregator.java │ │ │ │ ├── DefaultArgumentsAccessor.java │ │ │ │ ├── SimpleArgumentsAggregator.java │ │ │ │ └── package-info.java │ │ │ ├── converter/ │ │ │ │ ├── AnnotationBasedArgumentConverter.java │ │ │ │ ├── ArgumentConversionException.java │ │ │ │ ├── ArgumentConverter.java │ │ │ │ ├── ConvertWith.java │ │ │ │ ├── DefaultArgumentConverter.java │ │ │ │ ├── JavaTimeArgumentConverter.java │ │ │ │ ├── JavaTimeConversionPattern.java │ │ │ │ ├── SimpleArgumentConverter.java │ │ │ │ ├── TypedArgumentConverter.java │ │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ ├── provider/ │ │ │ │ ├── AnnotationBasedArgumentsProvider.java │ │ │ │ ├── Arguments.java │ │ │ │ ├── ArgumentsProvider.java │ │ │ │ ├── ArgumentsSource.java │ │ │ │ ├── ArgumentsSources.java │ │ │ │ ├── ArgumentsUtils.java │ │ │ │ ├── CsvArgumentsProvider.java │ │ │ │ ├── CsvFileArgumentsProvider.java │ │ │ │ ├── CsvFileSource.java │ │ │ │ ├── CsvFileSources.java │ │ │ │ ├── CsvParsingException.java │ │ │ │ ├── CsvReaderFactory.java │ │ │ │ ├── CsvSource.java │ │ │ │ ├── CsvSources.java │ │ │ │ ├── EmptyArgumentsProvider.java │ │ │ │ ├── EmptySource.java │ │ │ │ ├── EnumArgumentsProvider.java │ │ │ │ ├── EnumSource.java │ │ │ │ ├── EnumSources.java │ │ │ │ ├── FieldArgumentsProvider.java │ │ │ │ ├── FieldSource.java │ │ │ │ ├── FieldSources.java │ │ │ │ ├── MethodArgumentsProvider.java │ │ │ │ ├── MethodSource.java │ │ │ │ ├── MethodSources.java │ │ │ │ ├── NullAndEmptySource.java │ │ │ │ ├── NullArgumentsProvider.java │ │ │ │ ├── NullEnum.java │ │ │ │ ├── NullSource.java │ │ │ │ ├── ValueArgumentsProvider.java │ │ │ │ ├── ValueSource.java │ │ │ │ ├── ValueSources.java │ │ │ │ └── package-info.java │ │ │ └── support/ │ │ │ ├── AnnotationConsumer.java │ │ │ ├── AnnotationConsumerInitializer.java │ │ │ ├── FieldContext.java │ │ │ ├── ParameterDeclaration.java │ │ │ ├── ParameterDeclarations.java │ │ │ ├── ParameterInfo.java │ │ │ ├── ParameterNameAndArgument.java │ │ │ └── package-info.java │ │ └── kotlin/ │ │ └── org/ │ │ └── junit/ │ │ └── jupiter/ │ │ └── params/ │ │ └── aggregator/ │ │ └── ArgumentsAccessor.kt │ ├── test/ │ │ └── README.md │ └── testFixtures/ │ └── java/ │ └── org/ │ └── junit/ │ └── jupiter/ │ └── params/ │ └── provider/ │ └── RecordArguments.java ├── junit-platform-commons/ │ ├── junit-platform-commons.gradle.kts │ └── src/ │ ├── main/ │ │ └── java/ │ │ ├── module-info.java │ │ └── org/ │ │ └── junit/ │ │ └── platform/ │ │ └── commons/ │ │ ├── JUnitException.java │ │ ├── PreconditionViolationException.java │ │ ├── annotation/ │ │ │ ├── Contract.java │ │ │ ├── Testable.java │ │ │ └── package-info.java │ │ ├── function/ │ │ │ ├── Try.java │ │ │ └── package-info.java │ │ ├── io/ │ │ │ ├── DefaultResource.java │ │ │ ├── Resource.java │ │ │ ├── ResourceFilter.java │ │ │ └── package-info.java │ │ ├── logging/ │ │ │ ├── LogRecordListener.java │ │ │ ├── Logger.java │ │ │ ├── LoggerFactory.java │ │ │ └── package-info.java │ │ ├── package-info.java │ │ ├── support/ │ │ │ ├── AnnotationSupport.java │ │ │ ├── ClassSupport.java │ │ │ ├── DefaultResource.java │ │ │ ├── HierarchyTraversalMode.java │ │ │ ├── ModifierSupport.java │ │ │ ├── ReflectionSupport.java │ │ │ ├── Resource.java │ │ │ ├── ResourceSupport.java │ │ │ ├── SearchOption.java │ │ │ ├── conversion/ │ │ │ │ ├── ConversionException.java │ │ │ │ ├── ConversionSupport.java │ │ │ │ ├── FallbackStringToObjectConverter.java │ │ │ │ ├── StringToBooleanConverter.java │ │ │ │ ├── StringToCharacterConverter.java │ │ │ │ ├── StringToClassConverter.java │ │ │ │ ├── StringToCommonJavaTypesConverter.java │ │ │ │ ├── StringToEnumConverter.java │ │ │ │ ├── StringToJavaTimeConverter.java │ │ │ │ ├── StringToNumberConverter.java │ │ │ │ ├── StringToObjectConverter.java │ │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ └── scanning/ │ │ │ ├── ClassFilter.java │ │ │ ├── ClasspathScanner.java │ │ │ └── package-info.java │ │ └── util/ │ │ ├── AnnotationUtils.java │ │ ├── ClassLoaderUtils.java │ │ ├── ClassNamePatternFilterUtils.java │ │ ├── ClassUtils.java │ │ ├── ClasspathFileVisitor.java │ │ ├── ClasspathScannerLoader.java │ │ ├── CloseablePath.java │ │ ├── CollectionUtils.java │ │ ├── DefaultClasspathScanner.java │ │ ├── ExceptionUtils.java │ │ ├── FunctionUtils.java │ │ ├── KotlinFunctionUtils.java │ │ ├── KotlinReflectionUtils.java │ │ ├── LruCache.java │ │ ├── ModuleUtils.java │ │ ├── PackageUtils.java │ │ ├── Preconditions.java │ │ ├── ReflectionUtils.java │ │ ├── ResourceUtils.java │ │ ├── RuntimeUtils.java │ │ ├── SearchPathUtils.java │ │ ├── ServiceLoaderUtils.java │ │ ├── StringUtils.java │ │ ├── ToStringBuilder.java │ │ ├── UnrecoverableExceptions.java │ │ └── package-info.java │ ├── test/ │ │ └── README.md │ └── testFixtures/ │ └── java/ │ └── org/ │ └── junit/ │ └── platform/ │ └── commons/ │ └── test/ │ ├── ConcurrencyTestingUtils.java │ ├── IdeUtils.java │ ├── PreconditionAssertions.java │ ├── TestClassLoader.java │ └── package-info.java ├── junit-platform-console/ │ ├── LICENSE-picocli.md │ ├── junit-platform-console.gradle.kts │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ ├── module-info.java │ │ │ └── org/ │ │ │ └── junit/ │ │ │ └── platform/ │ │ │ └── console/ │ │ │ ├── ConsoleLauncher.java │ │ │ ├── ConsoleLauncherToolProvider.java │ │ │ ├── command/ │ │ │ │ ├── BaseCommand.java │ │ │ │ ├── CommandFacade.java │ │ │ │ ├── CommandResult.java │ │ │ │ ├── ConsoleTestExecutor.java │ │ │ │ ├── CustomClassLoaderCloseStrategy.java │ │ │ │ ├── CustomContextClassLoaderExecutor.java │ │ │ │ ├── DiscoverTestsCommand.java │ │ │ │ ├── DiscoveryRequestCreator.java │ │ │ │ ├── ExecuteTestsCommand.java │ │ │ │ ├── ExitCode.java │ │ │ │ ├── FailFastListener.java │ │ │ │ ├── ListTestEnginesCommand.java │ │ │ │ ├── MainCommand.java │ │ │ │ ├── ManifestVersionProvider.java │ │ │ │ ├── OutputStreamConfig.java │ │ │ │ ├── StandardStreamsHandler.java │ │ │ │ └── package-info.java │ │ │ ├── options/ │ │ │ │ ├── AnsiColorOptionMixin.java │ │ │ │ ├── ClasspathEntriesConverter.java │ │ │ │ ├── ConsoleUtils.java │ │ │ │ ├── Details.java │ │ │ │ ├── SelectorConverter.java │ │ │ │ ├── TestConsoleOutputOptions.java │ │ │ │ ├── TestConsoleOutputOptionsMixin.java │ │ │ │ ├── TestDiscoveryOptions.java │ │ │ │ ├── TestDiscoveryOptionsMixin.java │ │ │ │ └── package-info.java │ │ │ ├── output/ │ │ │ │ ├── ColorPalette.java │ │ │ │ ├── DetailsPrintingListener.java │ │ │ │ ├── FlatPrintingListener.java │ │ │ │ ├── Style.java │ │ │ │ ├── TestFeedPrintingListener.java │ │ │ │ ├── Theme.java │ │ │ │ ├── TreeNode.java │ │ │ │ ├── TreePrinter.java │ │ │ │ ├── TreePrintingListener.java │ │ │ │ ├── VerboseTreePrintingListener.java │ │ │ │ └── package-info.java │ │ │ └── package-info.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── services/ │ │ └── java.util.spi.ToolProvider │ └── test/ │ └── README.md ├── junit-platform-console-standalone/ │ └── junit-platform-console-standalone.gradle.kts ├── junit-platform-engine/ │ ├── junit-platform-engine.gradle.kts │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ ├── module-info.java │ │ │ └── org/ │ │ │ └── junit/ │ │ │ └── platform/ │ │ │ └── engine/ │ │ │ ├── CancellationToken.java │ │ │ ├── CompositeFilter.java │ │ │ ├── CompositeTestDescriptorVisitor.java │ │ │ ├── ConfigurationParameters.java │ │ │ ├── DefaultDiscoveryIssue.java │ │ │ ├── DisabledCancellationToken.java │ │ │ ├── DiscoveryFilter.java │ │ │ ├── DiscoveryIssue.java │ │ │ ├── DiscoverySelector.java │ │ │ ├── DiscoverySelectorIdentifier.java │ │ │ ├── EngineDiscoveryListener.java │ │ │ ├── EngineDiscoveryRequest.java │ │ │ ├── EngineExecutionListener.java │ │ │ ├── ExecutionRequest.java │ │ │ ├── Filter.java │ │ │ ├── FilterResult.java │ │ │ ├── OutputDirectoryCreator.java │ │ │ ├── RegularCancellationToken.java │ │ │ ├── SelectorResolutionResult.java │ │ │ ├── TestDescriptor.java │ │ │ ├── TestEngine.java │ │ │ ├── TestExecutionResult.java │ │ │ ├── TestSource.java │ │ │ ├── TestTag.java │ │ │ ├── UniqueId.java │ │ │ ├── UniqueIdFormat.java │ │ │ ├── discovery/ │ │ │ │ ├── AbstractClassNameFilter.java │ │ │ │ ├── ClassNameFilter.java │ │ │ │ ├── ClassSelector.java │ │ │ │ ├── ClasspathResourceSelector.java │ │ │ │ ├── ClasspathRootSelector.java │ │ │ │ ├── DirectorySelector.java │ │ │ │ ├── DiscoverySelectorIdentifierParser.java │ │ │ │ ├── DiscoverySelectorIdentifierParsers.java │ │ │ │ ├── DiscoverySelectors.java │ │ │ │ ├── ExcludeClassNameFilter.java │ │ │ │ ├── ExcludePackageNameFilter.java │ │ │ │ ├── FilePosition.java │ │ │ │ ├── FileSelector.java │ │ │ │ ├── IncludeClassNameFilter.java │ │ │ │ ├── IncludePackageNameFilter.java │ │ │ │ ├── IterationSelector.java │ │ │ │ ├── MethodSelector.java │ │ │ │ ├── ModuleSelector.java │ │ │ │ ├── NestedClassSelector.java │ │ │ │ ├── NestedMethodSelector.java │ │ │ │ ├── PackageNameFilter.java │ │ │ │ ├── PackageSelector.java │ │ │ │ ├── UniqueIdSelector.java │ │ │ │ ├── UriSelector.java │ │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ ├── reporting/ │ │ │ │ ├── FileEntry.java │ │ │ │ ├── OutputDirectoryProvider.java │ │ │ │ ├── OutputDirectoryProviderAdapter.java │ │ │ │ ├── ReportEntry.java │ │ │ │ └── package-info.java │ │ │ └── support/ │ │ │ ├── config/ │ │ │ │ ├── PrefixedConfigurationParameters.java │ │ │ │ └── package-info.java │ │ │ ├── descriptor/ │ │ │ │ ├── AbstractTestDescriptor.java │ │ │ │ ├── ClassSource.java │ │ │ │ ├── ClasspathResourceSource.java │ │ │ │ ├── CompositeTestSource.java │ │ │ │ ├── DefaultUriSource.java │ │ │ │ ├── DirectorySource.java │ │ │ │ ├── EngineDescriptor.java │ │ │ │ ├── FilePosition.java │ │ │ │ ├── FileSource.java │ │ │ │ ├── FileSystemSource.java │ │ │ │ ├── MethodSource.java │ │ │ │ ├── PackageSource.java │ │ │ │ ├── UriSource.java │ │ │ │ └── package-info.java │ │ │ ├── discovery/ │ │ │ │ ├── ClassContainerSelectorResolver.java │ │ │ │ ├── DiscoveryIssueReporter.java │ │ │ │ ├── EngineDiscoveryRequestResolution.java │ │ │ │ ├── EngineDiscoveryRequestResolver.java │ │ │ │ ├── ResourceContainerSelectorResolver.java │ │ │ │ ├── ResourceUtils.java │ │ │ │ ├── SelectorResolver.java │ │ │ │ └── package-info.java │ │ │ ├── hierarchical/ │ │ │ │ ├── BlockingAwareFuture.java │ │ │ │ ├── CompositeLock.java │ │ │ │ ├── DefaultParallelExecutionConfiguration.java │ │ │ │ ├── DefaultParallelExecutionConfigurationStrategy.java │ │ │ │ ├── DelegatingFuture.java │ │ │ │ ├── EngineExecutionContext.java │ │ │ │ ├── ExclusiveResource.java │ │ │ │ ├── ForkJoinPoolHierarchicalTestExecutorService.java │ │ │ │ ├── HierarchicalTestEngine.java │ │ │ │ ├── HierarchicalTestExecutor.java │ │ │ │ ├── HierarchicalTestExecutorService.java │ │ │ │ ├── LockManager.java │ │ │ │ ├── Node.java │ │ │ │ ├── NodeExecutionAdvisor.java │ │ │ │ ├── NodeTestTask.java │ │ │ │ ├── NodeTestTaskContext.java │ │ │ │ ├── NodeTreeWalker.java │ │ │ │ ├── NodeUtils.java │ │ │ │ ├── NopLock.java │ │ │ │ ├── OpenTest4JAwareThrowableCollector.java │ │ │ │ ├── ParallelExecutionConfiguration.java │ │ │ │ ├── ParallelExecutionConfigurationStrategy.java │ │ │ │ ├── ParallelHierarchicalTestExecutorServiceFactory.java │ │ │ │ ├── ResourceLock.java │ │ │ │ ├── SameThreadHierarchicalTestExecutorService.java │ │ │ │ ├── SingleLock.java │ │ │ │ ├── ThrowableCollector.java │ │ │ │ ├── WorkerThreadPoolHierarchicalTestExecutorService.java │ │ │ │ └── package-info.java │ │ │ └── store/ │ │ │ ├── Namespace.java │ │ │ ├── NamespacedHierarchicalStore.java │ │ │ ├── NamespacedHierarchicalStoreException.java │ │ │ └── package-info.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── services/ │ │ └── org.junit.platform.engine.discovery.DiscoverySelectorIdentifierParser │ ├── test/ │ │ └── README.md │ └── testFixtures/ │ └── java/ │ └── org/ │ └── junit/ │ └── platform/ │ ├── engine/ │ │ └── support/ │ │ └── hierarchical/ │ │ ├── DemoEngineExecutionContext.java │ │ ├── DemoHierarchicalContainerDescriptor.java │ │ ├── DemoHierarchicalEngineDescriptor.java │ │ ├── DemoHierarchicalTestDescriptor.java │ │ └── DemoHierarchicalTestEngine.java │ └── fakes/ │ ├── FaultyTestEngines.java │ ├── TestDescriptorStub.java │ ├── TestEngineSpy.java │ ├── TestEngineStub.java │ └── package-info.java ├── junit-platform-launcher/ │ ├── junit-platform-launcher.gradle.kts │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ ├── module-info.java │ │ │ └── org/ │ │ │ └── junit/ │ │ │ └── platform/ │ │ │ └── launcher/ │ │ │ ├── AbstractMethodFilter.java │ │ │ ├── EngineDiscoveryResult.java │ │ │ ├── EngineFilter.java │ │ │ ├── ExcludeMethodFilter.java │ │ │ ├── IncludeMethodFilter.java │ │ │ ├── Launcher.java │ │ │ ├── LauncherConstants.java │ │ │ ├── LauncherDiscoveryListener.java │ │ │ ├── LauncherDiscoveryRequest.java │ │ │ ├── LauncherExecutionRequest.java │ │ │ ├── LauncherInterceptor.java │ │ │ ├── LauncherSession.java │ │ │ ├── LauncherSessionListener.java │ │ │ ├── MethodFilter.java │ │ │ ├── PostDiscoveryFilter.java │ │ │ ├── TagFilter.java │ │ │ ├── TestExecutionListener.java │ │ │ ├── TestIdentifier.java │ │ │ ├── TestPlan.java │ │ │ ├── core/ │ │ │ │ ├── ClasspathAlignmentChecker.java │ │ │ │ ├── ClasspathAlignmentCheckingLauncherInterceptor.java │ │ │ │ ├── CompositeEngineExecutionListener.java │ │ │ │ ├── CompositeTestExecutionListener.java │ │ │ │ ├── DefaultDiscoveryRequest.java │ │ │ │ ├── DefaultLauncher.java │ │ │ │ ├── DefaultLauncherConfig.java │ │ │ │ ├── DefaultLauncherExecutionRequest.java │ │ │ │ ├── DefaultLauncherSession.java │ │ │ │ ├── DelegatingEngineExecutionListener.java │ │ │ │ ├── DelegatingLauncher.java │ │ │ │ ├── DelegatingLauncherDiscoveryListener.java │ │ │ │ ├── DelegatingLauncherDiscoveryRequest.java │ │ │ │ ├── DiscoveryIssueCollector.java │ │ │ │ ├── DiscoveryIssueException.java │ │ │ │ ├── DiscoveryIssueNotifier.java │ │ │ │ ├── DiscoveryIssueReportingDiscoveryListener.java │ │ │ │ ├── EngineDiscoveryOrchestrator.java │ │ │ │ ├── EngineDiscoveryResultValidator.java │ │ │ │ ├── EngineExecutionOrchestrator.java │ │ │ │ ├── EngineFilterer.java │ │ │ │ ├── EngineIdValidator.java │ │ │ │ ├── ExecutionListenerAdapter.java │ │ │ │ ├── HierarchicalOutputDirectoryCreator.java │ │ │ │ ├── InterceptingLauncher.java │ │ │ │ ├── InternalTestPlan.java │ │ │ │ ├── IterationOrder.java │ │ │ │ ├── LauncherConfig.java │ │ │ │ ├── LauncherConfigurationParameters.java │ │ │ │ ├── LauncherDiscoveryRequestBuilder.java │ │ │ │ ├── LauncherDiscoveryResult.java │ │ │ │ ├── LauncherExecutionRequestBuilder.java │ │ │ │ ├── LauncherFactory.java │ │ │ │ ├── LauncherListenerRegistry.java │ │ │ │ ├── LauncherPhase.java │ │ │ │ ├── ListenerRegistry.java │ │ │ │ ├── MemoryCleanupListener.java │ │ │ │ ├── OutcomeDelayingEngineExecutionListener.java │ │ │ │ ├── ServiceLoaderRegistry.java │ │ │ │ ├── ServiceLoaderTestEngineRegistry.java │ │ │ │ ├── SessionPerRequestLauncher.java │ │ │ │ ├── StackTracePruningEngineExecutionListener.java │ │ │ │ ├── StreamInterceptingTestExecutionListener.java │ │ │ │ ├── StreamInterceptor.java │ │ │ │ ├── TestEngineFormatter.java │ │ │ │ └── package-info.java │ │ │ ├── jfr/ │ │ │ │ ├── FlightRecordingDiscoveryListener.java │ │ │ │ ├── FlightRecordingExecutionListener.java │ │ │ │ ├── JfrUtils.java │ │ │ │ ├── UniqueId.java │ │ │ │ └── package-info.java │ │ │ ├── listeners/ │ │ │ │ ├── LoggingListener.java │ │ │ │ ├── MutableTestExecutionSummary.java │ │ │ │ ├── OutputDir.java │ │ │ │ ├── SummaryGeneratingListener.java │ │ │ │ ├── TestExecutionSummary.java │ │ │ │ ├── UniqueIdTrackingListener.java │ │ │ │ ├── discovery/ │ │ │ │ │ ├── AbortOnFailureLauncherDiscoveryListener.java │ │ │ │ │ ├── CompositeLauncherDiscoveryListener.java │ │ │ │ │ ├── LauncherDiscoveryListeners.java │ │ │ │ │ ├── LoggingLauncherDiscoveryListener.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ └── session/ │ │ │ │ ├── CompositeLauncherSessionListener.java │ │ │ │ ├── LauncherSessionListeners.java │ │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ └── tagexpression/ │ │ │ ├── DequeStack.java │ │ │ ├── Operator.java │ │ │ ├── Operators.java │ │ │ ├── ParseResult.java │ │ │ ├── ParseResults.java │ │ │ ├── ParseStatus.java │ │ │ ├── Parser.java │ │ │ ├── ShuntingYard.java │ │ │ ├── Stack.java │ │ │ ├── TagExpression.java │ │ │ ├── TagExpressions.java │ │ │ ├── Token.java │ │ │ ├── TokenWith.java │ │ │ ├── Tokenizer.java │ │ │ └── package-info.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── services/ │ │ └── org.junit.platform.launcher.TestExecutionListener │ ├── test/ │ │ └── README.md │ └── testFixtures/ │ └── java/ │ └── org/ │ └── junit/ │ └── platform/ │ └── launcher/ │ └── core/ │ ├── ConfigurationParametersFactoryForTests.java │ ├── LauncherFactoryForTestingPurposesOnly.java │ ├── NamespacedHierarchicalStoreProviders.java │ └── OutputDirectoryCreators.java ├── junit-platform-reporting/ │ ├── LICENSE-open-test-reporting.md │ ├── junit-platform-reporting.gradle.kts │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ ├── module-info.java │ │ │ └── org/ │ │ │ └── junit/ │ │ │ └── platform/ │ │ │ └── reporting/ │ │ │ ├── legacy/ │ │ │ │ ├── LegacyReportingUtils.java │ │ │ │ ├── package-info.java │ │ │ │ └── xml/ │ │ │ │ ├── LegacyXmlReportGeneratingListener.java │ │ │ │ ├── XmlReportData.java │ │ │ │ ├── XmlReportWriter.java │ │ │ │ └── package-info.java │ │ │ ├── open/ │ │ │ │ └── xml/ │ │ │ │ ├── GitInfoCollector.java │ │ │ │ ├── JUnitContributor.java │ │ │ │ ├── JUnitFactory.java │ │ │ │ ├── LegacyReportingName.java │ │ │ │ ├── OpenTestReportGeneratingListener.java │ │ │ │ ├── Type.java │ │ │ │ ├── UniqueId.java │ │ │ │ └── package-info.java │ │ │ └── package-info.java │ │ └── resources/ │ │ ├── META-INF/ │ │ │ └── services/ │ │ │ ├── org.junit.platform.launcher.TestExecutionListener │ │ │ └── org.opentest4j.reporting.tooling.spi.htmlreport.Contributor │ │ └── org/ │ │ └── junit/ │ │ └── platform/ │ │ └── reporting/ │ │ └── open/ │ │ └── xml/ │ │ ├── catalog.xml │ │ └── junit.xsd │ ├── test/ │ │ └── README.md │ └── testFixtures/ │ └── java/ │ └── org/ │ └── junit/ │ └── platform/ │ └── reporting/ │ └── testutil/ │ └── FileUtils.java ├── junit-platform-suite/ │ ├── junit-platform-suite.gradle.kts │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── module-info.java │ └── test/ │ └── README.md ├── junit-platform-suite-api/ │ ├── junit-platform-suite-api.gradle.kts │ └── src/ │ ├── main/ │ │ └── java/ │ │ ├── module-info.java │ │ └── org/ │ │ └── junit/ │ │ └── platform/ │ │ └── suite/ │ │ └── api/ │ │ ├── AfterSuite.java │ │ ├── BeforeSuite.java │ │ ├── ConfigurationParameter.java │ │ ├── ConfigurationParameters.java │ │ ├── ConfigurationParametersResource.java │ │ ├── ConfigurationParametersResources.java │ │ ├── DisableParentConfigurationParameters.java │ │ ├── ExcludeClassNamePatterns.java │ │ ├── ExcludeEngines.java │ │ ├── ExcludePackages.java │ │ ├── ExcludeTags.java │ │ ├── IncludeClassNamePatterns.java │ │ ├── IncludeEngines.java │ │ ├── IncludePackages.java │ │ ├── IncludeTags.java │ │ ├── Select.java │ │ ├── SelectClasses.java │ │ ├── SelectClasspathResource.java │ │ ├── SelectClasspathResources.java │ │ ├── SelectDirectories.java │ │ ├── SelectFile.java │ │ ├── SelectFiles.java │ │ ├── SelectMethod.java │ │ ├── SelectMethods.java │ │ ├── SelectModules.java │ │ ├── SelectPackages.java │ │ ├── SelectUris.java │ │ ├── Selects.java │ │ ├── Suite.java │ │ ├── SuiteDisplayName.java │ │ └── package-info.java │ └── test/ │ └── README.md ├── junit-platform-suite-engine/ │ ├── junit-platform-suite-engine.gradle.kts │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ ├── module-info.java │ │ │ └── org/ │ │ │ └── junit/ │ │ │ └── platform/ │ │ │ └── suite/ │ │ │ └── engine/ │ │ │ ├── AdditionalDiscoverySelectors.java │ │ │ ├── ClassSelectorResolver.java │ │ │ ├── DiscoverySelectorResolver.java │ │ │ ├── IsSuiteClass.java │ │ │ ├── LifecycleMethodUtils.java │ │ │ ├── NoTestsDiscoveredException.java │ │ │ ├── SuiteEngineDescriptor.java │ │ │ ├── SuiteLauncher.java │ │ │ ├── SuiteLauncherDiscoveryRequestBuilder.java │ │ │ ├── SuiteTestDescriptor.java │ │ │ ├── SuiteTestEngine.java │ │ │ └── package-info.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── services/ │ │ └── org.junit.platform.engine.TestEngine │ └── test/ │ └── README.md ├── junit-platform-testkit/ │ ├── LICENSE.md │ ├── junit-platform-testkit.gradle.kts │ └── src/ │ ├── main/ │ │ └── java/ │ │ ├── module-info.java │ │ └── org/ │ │ └── junit/ │ │ └── platform/ │ │ └── testkit/ │ │ ├── engine/ │ │ │ ├── Assertions.java │ │ │ ├── EngineDiscoveryResults.java │ │ │ ├── EngineExecutionResults.java │ │ │ ├── EngineTestKit.java │ │ │ ├── Event.java │ │ │ ├── EventConditions.java │ │ │ ├── EventStatistics.java │ │ │ ├── EventType.java │ │ │ ├── Events.java │ │ │ ├── Execution.java │ │ │ ├── ExecutionRecorder.java │ │ │ ├── Executions.java │ │ │ ├── TerminationInfo.java │ │ │ ├── TestExecutionResultConditions.java │ │ │ └── package-info.java │ │ └── package-info.java │ └── test/ │ └── README.md ├── junit-start/ │ ├── junit-start.gradle.kts │ └── src/ │ └── main/ │ └── java/ │ ├── module-info.java │ └── org/ │ └── junit/ │ └── start/ │ ├── JUnit.java │ └── package-info.java ├── junit-vintage-engine/ │ ├── junit-vintage-engine.gradle.kts │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ ├── module-info.java │ │ │ └── org/ │ │ │ └── junit/ │ │ │ └── vintage/ │ │ │ └── engine/ │ │ │ ├── Constants.java │ │ │ ├── JUnit4VersionCheck.java │ │ │ ├── VintageTestEngine.java │ │ │ ├── descriptor/ │ │ │ │ ├── DescriptionUtils.java │ │ │ │ ├── OrFilter.java │ │ │ │ ├── RunnerDecorator.java │ │ │ │ ├── RunnerRequest.java │ │ │ │ ├── RunnerTestDescriptor.java │ │ │ │ ├── TestSourceProvider.java │ │ │ │ ├── VintageEngineDescriptor.java │ │ │ │ ├── VintageTestDescriptor.java │ │ │ │ └── package-info.java │ │ │ ├── discovery/ │ │ │ │ ├── ClassSelectorResolver.java │ │ │ │ ├── DefensiveAllDefaultPossibilitiesBuilder.java │ │ │ │ ├── FilterableIgnoringRunnerDecorator.java │ │ │ │ ├── IgnoringRunnerDecorator.java │ │ │ │ ├── IsPotentialJUnit4TestClass.java │ │ │ │ ├── IsPotentialJUnit4TestMethod.java │ │ │ │ ├── MethodSelectorResolver.java │ │ │ │ ├── RunnerTestDescriptorPostProcessor.java │ │ │ │ ├── UniqueIdFilter.java │ │ │ │ ├── VintageDiscoverer.java │ │ │ │ └── package-info.java │ │ │ ├── execution/ │ │ │ │ ├── CancellationTokenAwareRunNotifier.java │ │ │ │ ├── EventType.java │ │ │ │ ├── RunListenerAdapter.java │ │ │ │ ├── RunnerExecutor.java │ │ │ │ ├── TestRun.java │ │ │ │ ├── VintageExecutor.java │ │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ └── support/ │ │ │ ├── UniqueIdReader.java │ │ │ ├── UniqueIdStringifier.java │ │ │ └── package-info.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── services/ │ │ └── org.junit.platform.engine.TestEngine │ ├── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── junit/ │ │ │ └── vintage/ │ │ │ └── engine/ │ │ │ ├── JUnit4ParameterizedTests.java │ │ │ ├── JUnit4VersionCheckTests.java │ │ │ ├── VintageLauncherIntegrationTests.java │ │ │ ├── VintageTestEngineBasicTests.java │ │ │ ├── VintageTestEngineDiscoveryTests.java │ │ │ ├── VintageTestEngineExecutionTests.java │ │ │ ├── VintageTestEngineTestSuite.java │ │ │ ├── VintageUniqueIdBuilder.java │ │ │ ├── descriptor/ │ │ │ │ ├── DescriptionUtilsTests.java │ │ │ │ ├── OrFilterTests.java │ │ │ │ ├── TestSourceProviderTests.java │ │ │ │ └── VintageTestDescriptorTests.java │ │ │ ├── discovery/ │ │ │ │ ├── IsPotentialJUnit4TestClassTests.java │ │ │ │ ├── RunnerTestDescriptorPostProcessorTests.java │ │ │ │ └── VintageDiscovererTests.java │ │ │ ├── execution/ │ │ │ │ ├── ParallelExecutionIntegrationTests.java │ │ │ │ └── TestRunTests.java │ │ │ └── support/ │ │ │ ├── UniqueIdReaderTests.java │ │ │ └── UniqueIdStringifierTests.java │ │ └── resources/ │ │ ├── junit-platform.properties │ │ ├── log4j2-test.xml │ │ └── vintage-testjar.jar │ └── testFixtures/ │ ├── groovy/ │ │ └── org/ │ │ └── junit/ │ │ └── vintage/ │ │ └── engine/ │ │ └── samples/ │ │ └── spock/ │ │ └── SpockTestCaseWithUnrolledAndRegularFeatureMethods.groovy │ └── java/ │ └── org/ │ └── junit/ │ ├── platform/ │ │ └── runner/ │ │ └── JUnitPlatform.java │ └── vintage/ │ └── engine/ │ └── samples/ │ ├── PlainOldJavaClassWithoutAnyTestsTestCase.java │ ├── junit3/ │ │ ├── AbstractJUnit3TestCase.java │ │ ├── IgnoredJUnit3TestCase.java │ │ ├── JUnit3ParallelSuiteWithSubsuites.java │ │ ├── JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails.java │ │ ├── JUnit3SuiteWithSubsuites.java │ │ ├── JUnit4SuiteWithIgnoredJUnit3TestCase.java │ │ └── PlainJUnit3TestCaseWithSingleTestWhichFails.java │ └── junit4/ │ ├── AbstractJUnit4TestCase.java │ ├── AbstractJunit4TestCaseWithConstructorParameter.java │ ├── CancellingTestCase.java │ ├── Categories.java │ ├── CompletelyDynamicTestCase.java │ ├── ConcreteJUnit4TestCase.java │ ├── ConfigurableRunner.java │ ├── DynamicRunner.java │ ├── EmptyIgnoredTestCase.java │ ├── EnclosedJUnit4TestCase.java │ ├── EnclosedWithParameterizedChildrenJUnit4TestCase.java │ ├── ExceptionThrowingRunner.java │ ├── IgnoredJUnit4TestCase.java │ ├── IgnoredJUnit4TestCaseWithNotFilterableRunner.java │ ├── IgnoredParameterizedTestCase.java │ ├── JUnit4ParallelClassesTestCase.java │ ├── JUnit4ParallelMethodsTestCase.java │ ├── JUnit4ParameterizedTestCase.java │ ├── JUnit4SuiteOfSuiteWithFilterableChildRunner.java │ ├── JUnit4SuiteOfSuiteWithIgnoredJUnit4TestCase.java │ ├── JUnit4SuiteOfSuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.java │ ├── JUnit4SuiteOfSuiteWithJUnit4TestCaseWithErrorInBeforeClass.java │ ├── JUnit4SuiteWithExceptionThrowingRunner.java │ ├── JUnit4SuiteWithIgnoredJUnit4TestCase.java │ ├── JUnit4SuiteWithJUnit3SuiteWithSingleTestCase.java │ ├── JUnit4SuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.java │ ├── JUnit4SuiteWithJUnit4TestCaseWithErrorInBeforeClass.java │ ├── JUnit4SuiteWithJUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.java │ ├── JUnit4SuiteWithJUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.java │ ├── JUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored.java │ ├── JUnit4SuiteWithTwoTestCases.java │ ├── JUnit4TestCaseWithAssumptionFailureInBeforeClass.java │ ├── JUnit4TestCaseWithDistinguishableOverloadedMethod.java │ ├── JUnit4TestCaseWithErrorCollectorStoringMultipleFailures.java │ ├── JUnit4TestCaseWithErrorInAfterClass.java │ ├── JUnit4TestCaseWithErrorInBeforeClass.java │ ├── JUnit4TestCaseWithExceptionThrowingRunner.java │ ├── JUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.java │ ├── JUnit4TestCaseWithIndistinguishableOverloadedMethod.java │ ├── JUnit4TestCaseWithNotFilterableRunner.java │ ├── JUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.java │ ├── JUnit4TestCaseWithRunnerWithDuplicateChangingChildDescriptions.java │ ├── Label.java │ ├── MalformedJUnit4TestCase.java │ ├── NotFilterableRunner.java │ ├── ParameterizedTestCase.java │ ├── ParameterizedTimingTestCase.java │ ├── ParameterizedWithAfterParamFailureTestCase.java │ ├── ParameterizedWithBeforeParamFailureTestCase.java │ ├── PlainJUnit4TestCaseWithFiveTestMethods.java │ ├── PlainJUnit4TestCaseWithLifecycleMethods.java │ ├── PlainJUnit4TestCaseWithSingleInheritedTestWhichFails.java │ ├── PlainJUnit4TestCaseWithSingleTestWhichFails.java │ ├── PlainJUnit4TestCaseWithSingleTestWhichIsIgnored.java │ ├── PlainJUnit4TestCaseWithTwoTestMethods.java │ ├── RunnerThatOnlyReportsFailures.java │ ├── RunnerWithCustomUniqueIdsAndDisplayNames.java │ ├── SingleFailingTheoryTestCase.java │ ├── TestCaseRunWithJUnitPlatformRunner.java │ └── package-info.java ├── jupiter-tests/ │ ├── jupiter-tests.gradle.kts │ └── src/ │ ├── templates/ │ │ └── resources/ │ │ └── test/ │ │ └── org/ │ │ └── junit/ │ │ └── jupiter/ │ │ └── api/ │ │ └── condition/ │ │ ├── DisabledOnJreConditionTests.java.jte │ │ ├── DisabledOnJreIntegrationTests.java.jte │ │ ├── EnabledOnJreConditionTests.java.jte │ │ └── EnabledOnJreIntegrationTests.java.jte │ └── test/ │ ├── groovy/ │ │ └── org/ │ │ └── junit/ │ │ └── jupiter/ │ │ └── api/ │ │ ├── GroovyAssertEqualsTests.groovy │ │ ├── GroovyAssertNotEqualsTests.groovy │ │ └── PrimitiveAndWrapperTypeHelpers.groovy │ ├── java/ │ │ ├── DefaultPackageTestCase.java │ │ ├── example/ │ │ │ └── B_TestCase.java │ │ └── org/ │ │ └── junit/ │ │ └── jupiter/ │ │ ├── JupiterTestSuite.java │ │ ├── api/ │ │ │ ├── AssertAllAssertionsTests.java │ │ │ ├── AssertArrayEqualsAssertionsTests.java │ │ │ ├── AssertDoesNotThrowAssertionsTests.java │ │ │ ├── AssertEqualsAssertionsTests.java │ │ │ ├── AssertFalseAssertionsTests.java │ │ │ ├── AssertInstanceOfAssertionsTests.java │ │ │ ├── AssertIterableEqualsAssertionsTests.java │ │ │ ├── AssertLinesMatchAssertionsTests.java │ │ │ ├── AssertNotEqualsAssertionsTests.java │ │ │ ├── AssertNotNullAssertionsTests.java │ │ │ ├── AssertNotSameAssertionsTests.java │ │ │ ├── AssertNullAssertionsTests.java │ │ │ ├── AssertSameAssertionsTests.java │ │ │ ├── AssertThrowsAssertionsTests.java │ │ │ ├── AssertThrowsExactlyAssertionsTests.java │ │ │ ├── AssertTimeoutAssertionsTests.java │ │ │ ├── AssertTimeoutPreemptivelyAssertionsTests.java │ │ │ ├── AssertTrueAssertionsTests.java │ │ │ ├── AssertionFailureBuilderTest.java │ │ │ ├── AssumptionsTests.java │ │ │ ├── ConstantTests.java │ │ │ ├── DisplayNameGenerationInheritanceTestCase.java │ │ │ ├── DisplayNameGenerationTests.java │ │ │ ├── DynamicContainerTests.java │ │ │ ├── DynamicTestTests.java │ │ │ ├── EnigmaThrowable.java │ │ │ ├── FailAssertionsTests.java │ │ │ ├── IndicativeSentencesGenerationInheritanceTestCase.java │ │ │ ├── IndicativeSentencesNestedTestCase.java │ │ │ ├── IndicativeSentencesOnSubClassScenarioOneTestCase.java │ │ │ ├── IndicativeSentencesOnSubClassTestCase.java │ │ │ ├── IndicativeSentencesRuntimeEnclosingTypeScenarioOneTestCase.java │ │ │ ├── IndicativeSentencesRuntimeEnclosingTypeScenarioTwoTestCase.java │ │ │ ├── IndicativeSentencesRuntimeEnclosingTypeTestCase.java │ │ │ ├── IndicativeSentencesTopLevelTestCase.java │ │ │ ├── IterableFactory.java │ │ │ ├── MediaTypeTests.java │ │ │ ├── RandomlyOrderedTests.java │ │ │ ├── condition/ │ │ │ │ ├── AbstractExecutionConditionTests.java │ │ │ │ ├── ConditionEvaluationResultTests.java │ │ │ │ ├── DisabledForJreRangeConditionTests.java │ │ │ │ ├── DisabledForJreRangeIntegrationTests.java │ │ │ │ ├── DisabledIfConditionClassLoaderTests.java │ │ │ │ ├── DisabledIfConditionTests.java │ │ │ │ ├── DisabledIfEnvironmentVariableConditionTests.java │ │ │ │ ├── DisabledIfEnvironmentVariableIntegrationTests.java │ │ │ │ ├── DisabledIfIntegrationTests.java │ │ │ │ ├── DisabledIfSystemPropertyConditionTests.java │ │ │ │ ├── DisabledIfSystemPropertyIntegrationTests.java │ │ │ │ ├── DisabledOnOsConditionTests.java │ │ │ │ ├── DisabledOnOsIntegrationTests.java │ │ │ │ ├── EnabledForJreRangeConditionTests.java │ │ │ │ ├── EnabledForJreRangeIntegrationTests.java │ │ │ │ ├── EnabledIfConditionClassLoaderTests.java │ │ │ │ ├── EnabledIfConditionTests.java │ │ │ │ ├── EnabledIfEnvironmentVariableConditionTests.java │ │ │ │ ├── EnabledIfEnvironmentVariableIntegrationTests.java │ │ │ │ ├── EnabledIfIntegrationTests.java │ │ │ │ ├── EnabledIfSystemPropertyConditionTests.java │ │ │ │ ├── EnabledIfSystemPropertyIntegrationTests.java │ │ │ │ ├── EnabledOnOsConditionTests.java │ │ │ │ ├── EnabledOnOsIntegrationTests.java │ │ │ │ ├── JRETests.java │ │ │ │ ├── OSTests.java │ │ │ │ ├── StaticConditionMethods.java │ │ │ │ └── TestDoubleJRETests.java │ │ │ ├── extension/ │ │ │ │ ├── CloseableResourceIntegrationTests.java │ │ │ │ ├── DeprecatedMediaTypeTests.java │ │ │ │ ├── ExecutableInvokerIntegrationTests.java │ │ │ │ ├── ExtensionComposabilityTests.java │ │ │ │ ├── Heavyweight.java │ │ │ │ ├── HeavyweightTests.java │ │ │ │ ├── KitchenSinkExtension.java │ │ │ │ ├── MediaTypeInteroperabilityTests.java │ │ │ │ └── support/ │ │ │ │ └── TypeBasedParameterResolverTests.java │ │ │ ├── io/ │ │ │ │ └── TempDirDeletionStrategyTests.java │ │ │ ├── parallel/ │ │ │ │ ├── LockTests.java │ │ │ │ ├── ResourceLockAnnotationTests.java │ │ │ │ └── ResourceLocksProviderTests.java │ │ │ ├── subpackage/ │ │ │ │ ├── SubclassedAssertionsTests.java │ │ │ │ └── SubclassedAssumptionsTests.java │ │ │ ├── timeout/ │ │ │ │ └── PreemptiveTimeoutUtilsTest.java │ │ │ └── util/ │ │ │ ├── DefaultLocaleTests.java │ │ │ ├── DefaultTimeZoneTests.java │ │ │ ├── JupiterPropertyUtilsTests.java │ │ │ └── SystemPropertiesExtensionTests.java │ │ ├── engine/ │ │ │ ├── AbstractJupiterTestEngineTests.java │ │ │ ├── AtypicalJvmMethodNameTests.java │ │ │ ├── BeforeAllAndAfterAllComposedAnnotationTests.java │ │ │ ├── BeforeEachAndAfterEachComposedAnnotationTests.java │ │ │ ├── ClassTemplateInvocationTests.java │ │ │ ├── DefaultExecutionModeTests.java │ │ │ ├── DefaultMethodTests.java │ │ │ ├── DisabledTests.java │ │ │ ├── DynamicNodeGenerationTests.java │ │ │ ├── ExceptionHandlingTests.java │ │ │ ├── ExecutionCancellationTests.java │ │ │ ├── FailedAssumptionsTests.java │ │ │ ├── InvalidLifecycleMethodConfigurationTests.java │ │ │ ├── JupiterTestEngineTests.java │ │ │ ├── LifecycleMethodOverridingTests.java │ │ │ ├── MultipleTestableAnnotationsTests.java │ │ │ ├── NestedTestClassesTests.java │ │ │ ├── NestedWithInheritanceTests.java │ │ │ ├── NestedWithSeparateInheritanceTests.java │ │ │ ├── OverloadedTestMethodTests.java │ │ │ ├── RecordTests.java │ │ │ ├── ReportingTests.java │ │ │ ├── SealedClassTests.java │ │ │ ├── StandardTestClassTests.java │ │ │ ├── StaticNestedBeforeAllAndAfterAllMethodsTests.java │ │ │ ├── StaticNestedTestCase.java │ │ │ ├── TestClassInheritanceTests.java │ │ │ ├── TestInstanceLifecycleConfigurationTests.java │ │ │ ├── TestInstanceLifecycleKotlinTests.java │ │ │ ├── TestInstanceLifecycleTests.java │ │ │ ├── TestMethodOverridingTests.java │ │ │ ├── TestTemplateInvocationTests.java │ │ │ ├── TopLevelComposedNested.java │ │ │ ├── TopLevelNestedTestCase.java │ │ │ ├── bridge/ │ │ │ │ ├── AbstractNonGenericTests.java │ │ │ │ ├── AbstractNumberTests.java │ │ │ │ ├── BridgeMethodTests.java │ │ │ │ ├── ChildWithBridgeMethods.java │ │ │ │ ├── ChildWithoutBridgeMethods.java │ │ │ │ ├── NumberResolver.java │ │ │ │ ├── NumberTestGroup.java │ │ │ │ └── PackagePrivateParent.java │ │ │ ├── config/ │ │ │ │ ├── CachingJupiterConfigurationTests.java │ │ │ │ ├── DefaultJupiterConfigurationTests.java │ │ │ │ └── InstantiatingConfigurationParameterConverterTests.java │ │ │ ├── descriptor/ │ │ │ │ ├── CustomDisplayNameGenerator.java │ │ │ │ ├── DisplayNameUtilsTests.java │ │ │ │ ├── ExtensionContextTests.java │ │ │ │ ├── ExtensionsUtilsTests.java │ │ │ │ ├── JupiterTestDescriptorTests.java │ │ │ │ ├── LauncherStoreFacadeTest.java │ │ │ │ ├── LifecycleMethodUtilsTests.java │ │ │ │ ├── NamespaceTests.java │ │ │ │ ├── ResourceAutoClosingTests.java │ │ │ │ ├── TestFactoryTestDescriptorTests.java │ │ │ │ ├── TestInstanceLifecycleUtilsTests.java │ │ │ │ ├── TestTemplateInvocationTestDescriptorTests.java │ │ │ │ ├── TestTemplateTestDescriptorTests.java │ │ │ │ └── subpackage/ │ │ │ │ ├── Class1WithTestCases.java │ │ │ │ ├── Class2WithTestCases.java │ │ │ │ ├── ClassWithStaticInnerTestCases.java │ │ │ │ └── ClassWithoutTestCases.java │ │ │ ├── discovery/ │ │ │ │ ├── DiscoverySelectorResolverTests.java │ │ │ │ ├── DiscoveryTests.java │ │ │ │ └── predicates/ │ │ │ │ ├── IsTestFactoryMethodTests.java │ │ │ │ ├── IsTestMethodTests.java │ │ │ │ ├── IsTestTemplateMethodTests.java │ │ │ │ └── TestClassPredicatesTests.java │ │ │ ├── execution/ │ │ │ │ ├── AbstractExecutableInvokerTests.java │ │ │ │ ├── DefaultExecutableInvokerTests.java │ │ │ │ ├── DefaultTestInstancesTests.java │ │ │ │ ├── DynamicTestIntegrationTests.java │ │ │ │ ├── ExtensionContextStoreConcurrencyTests.java │ │ │ │ ├── ExtensionContextStoreTests.java │ │ │ │ ├── InterceptingExecutableInvokerTests.java │ │ │ │ ├── JupiterEngineExecutionContextTests.java │ │ │ │ ├── ParameterResolutionUtilsTests.java │ │ │ │ ├── UniqueIdParsingForArrayParameterIntegrationTests.java │ │ │ │ └── injection/ │ │ │ │ └── sample/ │ │ │ │ ├── CustomAnnotation.java │ │ │ │ ├── CustomAnnotationParameterResolver.java │ │ │ │ ├── CustomType.java │ │ │ │ ├── CustomTypeParameterResolver.java │ │ │ │ ├── DoubleParameterResolver.java │ │ │ │ ├── LongParameterResolver.java │ │ │ │ ├── MapOfListsTypeBasedParameterResolver.java │ │ │ │ ├── MapOfStringsParameterResolver.java │ │ │ │ ├── NullIntegerParameterResolver.java │ │ │ │ ├── NumberParameterResolver.java │ │ │ │ ├── PrimitiveArrayParameterResolver.java │ │ │ │ └── PrimitiveIntegerParameterResolver.java │ │ │ ├── extension/ │ │ │ │ ├── AutoCloseTests.java │ │ │ │ ├── BeforeAndAfterAllTests.java │ │ │ │ ├── BeforeAndAfterEachTests.java │ │ │ │ ├── BeforeAndAfterTestExecutionCallbackTests.java │ │ │ │ ├── CloseablePathTests.java │ │ │ │ ├── ConfigLoaderExtension.java │ │ │ │ ├── DefaultTestReporterTests.java │ │ │ │ ├── EnigmaException.java │ │ │ │ ├── EventuallyInterruptibleInvocation.java │ │ │ │ ├── ExecutionConditionTests.java │ │ │ │ ├── ExtensionContextExecutionTests.java │ │ │ │ ├── ExtensionRegistrationViaParametersAndFieldsTests.java │ │ │ │ ├── ExtensionRegistryTests.java │ │ │ │ ├── InvocationInterceptorTests.java │ │ │ │ ├── LifecycleMethodExecutionExceptionHandlerTests.java │ │ │ │ ├── OrderedClassTests.java │ │ │ │ ├── OrderedMethodTests.java │ │ │ │ ├── OrderedProgrammaticExtensionRegistrationTests.java │ │ │ │ ├── ParameterResolverTests.java │ │ │ │ ├── PreInterruptCallbackTests.java │ │ │ │ ├── ProgrammaticExtensionRegistrationTests.java │ │ │ │ ├── RepeatedTestTests.java │ │ │ │ ├── SameThreadTimeoutInvocationTests.java │ │ │ │ ├── SeparateThreadTimeoutInvocationTests.java │ │ │ │ ├── ServiceLoaderExtension.java │ │ │ │ ├── TempDirectoryCleanupTests.java │ │ │ │ ├── TempDirectoryMetaAnnotationTests.java │ │ │ │ ├── TempDirectoryPreconditionTests.java │ │ │ │ ├── TempDirectoryTests.java │ │ │ │ ├── TestExecutionExceptionHandlerTests.java │ │ │ │ ├── TestInfoParameterResolverTests.java │ │ │ │ ├── TestInstanceFactoryTests.java │ │ │ │ ├── TestInstancePostProcessorAndPreDestroyCallbackTests.java │ │ │ │ ├── TestInstancePostProcessorTests.java │ │ │ │ ├── TestInstancePreConstructCallbackTests.java │ │ │ │ ├── TestInstancePreDestroyCallbackTests.java │ │ │ │ ├── TestInstancePreDestroyCallbackUtilityMethodTests.java │ │ │ │ ├── TestReporterParameterResolverTests.java │ │ │ │ ├── TestWatcherTests.java │ │ │ │ ├── TimeoutConfigurationTests.java │ │ │ │ ├── TimeoutDurationParserTests.java │ │ │ │ ├── TimeoutDurationTests.java │ │ │ │ ├── TimeoutExceptionFactoryTests.java │ │ │ │ ├── TimeoutExtensionTests.java │ │ │ │ ├── TimeoutInvocationFactoryTests.java │ │ │ │ └── sub/ │ │ │ │ ├── AlwaysDisabledCondition.java │ │ │ │ ├── AnotherAlwaysDisabledCondition.java │ │ │ │ └── SystemPropertyCondition.java │ │ │ ├── subpackage/ │ │ │ │ └── SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase.java │ │ │ └── support/ │ │ │ └── OpenTest4JAndJUnit4AwareThrowableCollectorTests.java │ │ ├── migrationsupport/ │ │ │ ├── JupiterMigrationSupportTestSuite.java │ │ │ ├── conditions/ │ │ │ │ ├── IgnoreAnnotationIntegrationTests.java │ │ │ │ └── IgnoreConditionTests.java │ │ │ └── rules/ │ │ │ ├── AbstractTestRuleAdapterTests.java │ │ │ ├── EnableRuleMigrationSupportWithBothRuleTypesTests.java │ │ │ ├── ExpectedExceptionSupportTests.java │ │ │ ├── ExternalResourceSupportForDifferentDeclaredReturnTypesRulesTests.java │ │ │ ├── ExternalResourceSupportForMixedMethodAndFieldRulesTests.java │ │ │ ├── ExternalResourceSupportForMultipleFieldRulesTests.java │ │ │ ├── ExternalResourceSupportForMultipleMethodRulesTests.java │ │ │ ├── ExternalResourceSupportForTemporaryFolderFieldTests.java │ │ │ ├── ExternalResourceSupportWithInheritanceTests.java │ │ │ ├── ExternalResourceWithoutAdapterTests.java │ │ │ ├── FailAfterAllHelper.java │ │ │ ├── LauncherBasedEnableRuleMigrationSupportTests.java │ │ │ ├── VerifierSupportForMixedMethodAndFieldRulesTests.java │ │ │ ├── WrongExtendWithForVerifierFieldTests.java │ │ │ └── WrongExtendWithForVerifierMethodTests.java │ │ └── params/ │ │ ├── ParameterInfoIntegrationTests.java │ │ ├── ParameterizedClassIntegrationTests.java │ │ ├── ParameterizedInvocationNameFormatterTests.java │ │ ├── ParameterizedTestContextTests.java │ │ ├── ParameterizedTestExtensionTests.java │ │ ├── ParameterizedTestIntegrationTests.java │ │ ├── ParameterizedTestSuite.java │ │ ├── aggregator/ │ │ │ ├── AggregatorIntegrationTests.java │ │ │ └── DefaultArgumentsAccessorTests.java │ │ ├── converter/ │ │ │ ├── DefaultArgumentConverterTests.java │ │ │ ├── JavaTimeArgumentConverterTests.java │ │ │ └── TypedArgumentConverterTests.java │ │ ├── provider/ │ │ │ ├── AnnotationBasedArgumentsProviderTests.java │ │ │ ├── ArgumentsTests.java │ │ │ ├── CsvArgumentsProviderTests.java │ │ │ ├── CsvFileArgumentsProviderTests.java │ │ │ ├── EnumArgumentsProviderTests.java │ │ │ ├── EnumSourceTests.java │ │ │ ├── FieldArgumentsProviderTests.java │ │ │ ├── MethodArgumentsProviderTests.java │ │ │ ├── MockCsvAnnotationBuilder.java │ │ │ └── ValueArgumentsProviderTests.java │ │ └── support/ │ │ ├── AnnotationConsumerInitializerTests.java │ │ └── DeprecatedParameterInfoIntegrationTests.java │ ├── kotlin/ │ │ └── org/ │ │ └── junit/ │ │ └── jupiter/ │ │ ├── api/ │ │ │ └── kotlin/ │ │ │ ├── GenericInlineValueClassTests.kt │ │ │ ├── KotlinAssertTimeoutAssertionsTests.kt │ │ │ ├── KotlinAssertionsTests.kt │ │ │ ├── KotlinDynamicTests.kt │ │ │ ├── KotlinFailAssertionsTests.kt │ │ │ ├── KotlinSuspendFunctionsTests.kt │ │ │ └── PrimitiveWrapperInlineValueClassTests.kt │ │ ├── engine/ │ │ │ └── kotlin/ │ │ │ ├── ArbitraryNamingKotlinTestCase.kt │ │ │ ├── InstancePerClassKotlinTestCase.kt │ │ │ ├── InstancePerMethodKotlinTestCase.kt │ │ │ ├── KotlinDefaultImplsTestCase.kt │ │ │ ├── KotlinInterfaceImplementationTestCase.kt │ │ │ └── KotlinInterfaceTestCase.kt │ │ └── params/ │ │ ├── ParameterizedClassKotlinIntegrationTests.kt │ │ ├── ParameterizedInvocationNameFormatterIntegrationTests.kt │ │ ├── ParameterizedTestKotlinSequenceIntegrationTests.kt │ │ ├── aggregator/ │ │ │ ├── ArgumentsAccessorKotlinTests.kt │ │ │ └── DisplayNameTests.kt │ │ └── converter/ │ │ └── TypedArgumentConverterKotlinTests.kt │ └── resources/ │ ├── META-INF/ │ │ └── services/ │ │ └── org.junit.jupiter.api.extension.Extension │ ├── junit-platform.properties │ ├── jupiter-testjar.jar │ ├── log4j2-test.xml │ └── org/ │ └── junit/ │ └── jupiter/ │ └── params/ │ ├── provider/ │ │ ├── broken.csv │ │ ├── default-max-chars.csv │ │ ├── exceeds-default-max-chars.csv │ │ ├── leading-trailing-spaces.csv │ │ └── single-column.csv │ ├── two-column-with-headers.csv │ └── two-column.csv ├── platform-tests/ │ ├── platform-tests.gradle.kts │ └── src/ │ ├── jmh/ │ │ └── java/ │ │ └── org/ │ │ └── junit/ │ │ └── jupiter/ │ │ └── jmh/ │ │ └── AssertionBenchmarks.java │ ├── processStarter/ │ │ └── java/ │ │ └── org/ │ │ └── junit/ │ │ └── platform/ │ │ └── tests/ │ │ └── process/ │ │ ├── OutputFiles.java │ │ ├── ProcessResult.java │ │ ├── ProcessStarter.java │ │ ├── WatchedOutput.java │ │ ├── WatchedProcess.java │ │ └── package-info.java │ └── test/ │ ├── java/ │ │ ├── DefaultPackageTestCase.java │ │ └── org/ │ │ └── junit/ │ │ └── platform/ │ │ ├── JUnitPlatformTestSuite.java │ │ ├── StackTracePruningTests.java │ │ ├── TestEngineTests.java │ │ ├── commons/ │ │ │ ├── annotation/ │ │ │ │ └── TestableAnnotationTests.java │ │ │ ├── function/ │ │ │ │ └── TryTests.java │ │ │ ├── support/ │ │ │ │ ├── AnnotationSupportTests.java │ │ │ │ ├── ClassSupportTests.java │ │ │ │ ├── ModifierSupportTests.java │ │ │ │ ├── ReflectionSupportTests.java │ │ │ │ ├── ResourceInteroperabilityTests.java │ │ │ │ ├── ResourceSupportTests.java │ │ │ │ └── conversion/ │ │ │ │ ├── ConversionSupportTests.java │ │ │ │ └── FallbackStringToObjectConverterTests.java │ │ │ └── util/ │ │ │ ├── AnnotationUtilsTests.java │ │ │ ├── ClassLoaderUtilsTests.java │ │ │ ├── ClassNamePatternFilterUtilsTests.java │ │ │ ├── ClassUtilsTests.java │ │ │ ├── CloseablePathTests.java │ │ │ ├── CollectionUtilsTests.java │ │ │ ├── DefaultClasspathScannerTests.java │ │ │ ├── ExceptionUtilsTests.java │ │ │ ├── FunctionUtilsTests.java │ │ │ ├── LruCacheTests.java │ │ │ ├── PackageUtilsTests.java │ │ │ ├── PreconditionsTests.java │ │ │ ├── ReflectionUtilsTests.java │ │ │ ├── ReflectionUtilsWithGenericTypeHierarchiesTests.java │ │ │ ├── RuntimeUtilsTests.java │ │ │ ├── SerializationUtils.java │ │ │ ├── StringUtilsTests.java │ │ │ ├── ToStringBuilderTests.java │ │ │ ├── classes/ │ │ │ │ ├── AExecutionConditionClass.java │ │ │ │ ├── ATestExecutionListenerClass.java │ │ │ │ ├── AVanillaEmpty.java │ │ │ │ ├── BExecutionConditionClass.java │ │ │ │ ├── BTestExecutionListenerClass.java │ │ │ │ ├── BVanillaEmpty.java │ │ │ │ └── CustomType.java │ │ │ └── pkg1/ │ │ │ ├── ClassLevelDir.java │ │ │ ├── InstanceLevelDir.java │ │ │ ├── SuperclassWithStaticPackagePrivateBeforeMethod.java │ │ │ ├── SuperclassWithStaticPackagePrivateTempDirField.java │ │ │ └── subpkg/ │ │ │ ├── SubclassWithNonStaticPackagePrivateBeforeMethod.java │ │ │ └── SubclassWithNonStaticPackagePrivateTempDirField.java │ │ ├── console/ │ │ │ ├── ConsoleDetailsTests.java │ │ │ ├── ConsoleLauncherIntegrationTests.java │ │ │ ├── ConsoleLauncherTests.java │ │ │ ├── ConsoleLauncherWrapper.java │ │ │ ├── ConsoleLauncherWrapperResult.java │ │ │ ├── command/ │ │ │ │ ├── CommandLineOptionsParsingTests.java │ │ │ │ ├── ConsoleTestExecutorTests.java │ │ │ │ ├── ConsoleUtilsTests.java │ │ │ │ ├── CustomContextClassLoaderExecutorTests.java │ │ │ │ ├── DiscoveryRequestCreatorTests.java │ │ │ │ ├── ExecuteTestsCommandTests.java │ │ │ │ ├── StdStreamTestCase.java │ │ │ │ └── ThemeTests.java │ │ │ ├── output/ │ │ │ │ ├── ColorPaletteTests.java │ │ │ │ ├── FlatPrintingListenerTests.java │ │ │ │ ├── TestFeedPrintingListenerTests.java │ │ │ │ ├── TreeNodeTests.java │ │ │ │ ├── TreePrinterTests.java │ │ │ │ └── VerboseTreePrintingListenerTests.java │ │ │ └── subpackage/ │ │ │ ├── ContainerForInnerTest.java │ │ │ ├── ContainerForInnerTests.java │ │ │ ├── ContainerForStaticNestedTest.java │ │ │ ├── ContainerForStaticNestedTests.java │ │ │ ├── FailingTestCase.java │ │ │ ├── SecondTest.java │ │ │ ├── Test.java │ │ │ ├── Test1.java │ │ │ ├── Tests.java │ │ │ └── ThirdTests.java │ │ ├── engine/ │ │ │ ├── CompositeTestDescriptorVisitorTests.java │ │ │ ├── DiscoveryIssueTests.java │ │ │ ├── FilterCompositionTests.java │ │ │ ├── TestDescriptorTests.java │ │ │ ├── TestTagTests.java │ │ │ ├── UniqueIdFormatTests.java │ │ │ ├── UniqueIdTests.java │ │ │ ├── discovery/ │ │ │ │ ├── ClassNameFilterTests.java │ │ │ │ ├── ClassSelectorTests.java │ │ │ │ ├── ClasspathResourceSelectorTests.java │ │ │ │ ├── ClasspathRootSelectorTests.java │ │ │ │ ├── DirectorySelectorTests.java │ │ │ │ ├── DiscoverySelectorsTests.java │ │ │ │ ├── FilePositionTests.java │ │ │ │ ├── FileSelectorTests.java │ │ │ │ ├── IterationSelectorTests.java │ │ │ │ ├── MethodSelectorTests.java │ │ │ │ ├── ModuleSelectorTests.java │ │ │ │ ├── NestedClassSelectorTests.java │ │ │ │ ├── NestedMethodSelectorTests.java │ │ │ │ ├── PackageNameFilterTests.java │ │ │ │ ├── PackageSelectorTests.java │ │ │ │ ├── UniqueIdSelectorTests.java │ │ │ │ └── UriSelectorTests.java │ │ │ └── support/ │ │ │ ├── config/ │ │ │ │ └── PrefixedConfigurationParametersTests.java │ │ │ ├── descriptor/ │ │ │ │ ├── AbstractTestDescriptorTests.java │ │ │ │ ├── AbstractTestSourceTests.java │ │ │ │ ├── ClassSourceTests.java │ │ │ │ ├── ClasspathResourceSourceTests.java │ │ │ │ ├── CompositeTestSourceTests.java │ │ │ │ ├── DefaultUriSourceTests.java │ │ │ │ ├── DemoClassTestDescriptor.java │ │ │ │ ├── DemoMethodTestDescriptor.java │ │ │ │ ├── FilePositionTests.java │ │ │ │ ├── FileSystemSourceTests.java │ │ │ │ ├── MethodSourceTests.java │ │ │ │ ├── PackageSourceTests.java │ │ │ │ ├── TestDescriptorOrderChildrenTests.java │ │ │ │ └── TestDescriptorTests.java │ │ │ ├── discovery/ │ │ │ │ ├── EngineDiscoveryRequestResolverTests.java │ │ │ │ └── ResourceContainerSelectorResolverTest.java │ │ │ ├── hierarchical/ │ │ │ │ ├── CompositeLockTests.java │ │ │ │ ├── DefaultParallelExecutionConfigurationStrategyTests.java │ │ │ │ ├── ForkJoinDeadLockTests.java │ │ │ │ ├── ForkJoinPoolHierarchicalTestExecutorServiceTests.java │ │ │ │ ├── HierarchicalTestExecutorTests.java │ │ │ │ ├── LockManagerTests.java │ │ │ │ ├── MemoryLeakTests.java │ │ │ │ ├── NodeTreeWalkerIntegrationTests.java │ │ │ │ ├── ParallelExecutionIntegrationTests.java │ │ │ │ ├── ResourceLockSupport.java │ │ │ │ ├── ResourceLockTests.java │ │ │ │ ├── SameThreadExecutionIntegrationTests.java │ │ │ │ ├── SingleLockTests.java │ │ │ │ ├── ThrowableCollectorTests.java │ │ │ │ ├── WorkerLeaseManagerTests.java │ │ │ │ └── WorkerThreadPoolHierarchicalTestExecutorServiceTests.java │ │ │ └── store/ │ │ │ ├── NamespaceTests.java │ │ │ └── NamespacedHierarchicalStoreTests.java │ │ ├── launcher/ │ │ │ ├── DiscoveryFilterStub.java │ │ │ ├── FilterStub.java │ │ │ ├── InterceptedTestEngine.java │ │ │ ├── InterceptorInjectedLauncherSessionListener.java │ │ │ ├── MethodFilterTests.java │ │ │ ├── PostDiscoveryFilterStub.java │ │ │ ├── TagFilterTests.java │ │ │ ├── TagIntegrationTests.java │ │ │ ├── TestIdentifierTests.java │ │ │ ├── TestLauncherDiscoveryListener.java │ │ │ ├── TestLauncherInterceptor1.java │ │ │ ├── TestLauncherInterceptor2.java │ │ │ ├── TestLauncherSessionListener.java │ │ │ ├── TestPlanTests.java │ │ │ ├── TestPostDiscoveryTagFilter.java │ │ │ ├── core/ │ │ │ │ ├── ClasspathAlignmentCheckerTests.java │ │ │ │ ├── CompositeEngineExecutionListenerTests.java │ │ │ │ ├── CompositeTestExecutionListenerTests.java │ │ │ │ ├── DefaultLauncherEngineFilterTests.java │ │ │ │ ├── DefaultLauncherTests.java │ │ │ │ ├── DiscoveryIssueCollectorTests.java │ │ │ │ ├── DiscoveryIssueReportingDiscoveryListenerTests.java │ │ │ │ ├── EngineDiscoveryResultValidatorTests.java │ │ │ │ ├── ExecutionListenerAdapterTests.java │ │ │ │ ├── HierarchicalOutputDirectoryCreatorTests.java │ │ │ │ ├── InternalTestPlanTests.java │ │ │ │ ├── LauncherConfigTests.java │ │ │ │ ├── LauncherConfigurationParametersTests.java │ │ │ │ ├── LauncherDiscoveryRequestBuilderTests.java │ │ │ │ ├── LauncherDiscoveryResultTests.java │ │ │ │ ├── LauncherFactoryTests.java │ │ │ │ ├── LauncherPreconditionTests.java │ │ │ │ ├── LauncherSessionTests.java │ │ │ │ ├── ListenerRegistryTests.java │ │ │ │ ├── StoreSharingTests.java │ │ │ │ ├── StreamInterceptingTestExecutionListenerIntegrationTests.java │ │ │ │ └── StreamInterceptorTests.java │ │ │ ├── jfr/ │ │ │ │ ├── FlightRecordingDiscoveryListenerIntegrationTests.java │ │ │ │ └── FlightRecordingExecutionListenerIntegrationTests.java │ │ │ ├── listeners/ │ │ │ │ ├── AnotherUnusedTestExecutionListener.java │ │ │ │ ├── LoggingListenerTests.java │ │ │ │ ├── NoopTestExecutionListener.java │ │ │ │ ├── OutputDirTests.java │ │ │ │ ├── SummaryGenerationTests.java │ │ │ │ ├── UniqueIdTrackingListenerIntegrationTests.java │ │ │ │ ├── UnusedTestExecutionListener.java │ │ │ │ ├── discovery/ │ │ │ │ │ ├── AbortOnFailureLauncherDiscoveryListenerTests.java │ │ │ │ │ ├── CompositeLauncherDiscoveryListenerTests.java │ │ │ │ │ └── LoggingLauncherDiscoveryListenerTests.java │ │ │ │ └── session/ │ │ │ │ └── CompositeLauncherSessionListenerTests.java │ │ │ └── tagexpression/ │ │ │ ├── ParserErrorTests.java │ │ │ ├── ParserTests.java │ │ │ ├── TagExpressionsTests.java │ │ │ ├── TokenTests.java │ │ │ └── TokenizerTests.java │ │ ├── reporting/ │ │ │ ├── legacy/ │ │ │ │ └── xml/ │ │ │ │ ├── IncrementingClock.java │ │ │ │ ├── LegacyXmlReportGeneratingListenerTests.java │ │ │ │ ├── XmlReportAssertions.java │ │ │ │ ├── XmlReportDataTests.java │ │ │ │ └── XmlReportWriterTests.java │ │ │ └── open/ │ │ │ └── xml/ │ │ │ ├── JUnitContributorTests.java │ │ │ └── OpenTestReportGeneratingListenerTests.java │ │ ├── suite/ │ │ │ └── engine/ │ │ │ ├── BeforeAndAfterSuiteTests.java │ │ │ ├── SuiteEngineTests.java │ │ │ ├── SuiteLauncherDiscoveryRequestBuilderTests.java │ │ │ ├── SuiteTestDescriptorTests.java │ │ │ ├── error/ │ │ │ │ ├── ErrorSelector.java │ │ │ │ ├── ErrorSelectorIdentifierParser.java │ │ │ │ ├── SelectorProcessingErrorCausingEngine.java │ │ │ │ └── package-info.java │ │ │ ├── testcases/ │ │ │ │ ├── ConfigurationSensitiveTestCase.java │ │ │ │ ├── DynamicTestsTestCase.java │ │ │ │ ├── EmptyDynamicTestsTestCase.java │ │ │ │ ├── EmptyTestTestCase.java │ │ │ │ ├── ErroneousTestCase.java │ │ │ │ ├── JUnit4TestsTestCase.java │ │ │ │ ├── MultipleTestsTestCase.java │ │ │ │ ├── SingleTestTestCase.java │ │ │ │ ├── StatefulTestCase.java │ │ │ │ └── TaggedTestTestCase.java │ │ │ └── testsuites/ │ │ │ ├── AbstractSuite.java │ │ │ ├── BlankSuiteDisplayNameSuite.java │ │ │ ├── ConfigurationSuite.java │ │ │ ├── CyclicSuite.java │ │ │ ├── DynamicSuite.java │ │ │ ├── EmptyCyclicSuite.java │ │ │ ├── EmptyDynamicTestSuite.java │ │ │ ├── EmptyDynamicTestWithFailIfNoTestFalseSuite.java │ │ │ ├── EmptyTestCaseSuite.java │ │ │ ├── EmptyTestCaseWithFailIfNoTestFalseSuite.java │ │ │ ├── ErroneousTestSuite.java │ │ │ ├── InheritedSuite.java │ │ │ ├── LifecycleMethodsSuites.java │ │ │ ├── MultiEngineSuite.java │ │ │ ├── MultipleSuite.java │ │ │ ├── NestedSuite.java │ │ │ ├── SelectByIdentifierSuite.java │ │ │ ├── SelectClassesSuite.java │ │ │ ├── SelectMethodsSuite.java │ │ │ ├── SelectorProcessingErrorTestSuite.java │ │ │ ├── SuiteDisplayNameSuite.java │ │ │ ├── SuiteSuite.java │ │ │ ├── SuiteWithErroneousTestSuite.java │ │ │ ├── ThreePartCyclicSuite.java │ │ │ └── WhitespaceSuiteDisplayNameSuite.java │ │ └── testkit/ │ │ └── engine/ │ │ ├── EngineDiscoveryResultsIntegrationTests.java │ │ ├── EngineTestKitTests.java │ │ ├── EventsTests.java │ │ ├── ExecutionsIntegrationTests.java │ │ ├── NestedContainerEventConditionTests.java │ │ └── TestExecutionResultConditionsTests.java │ ├── kotlin/ │ │ └── org/ │ │ └── junit/ │ │ └── platform/ │ │ └── commons/ │ │ └── util/ │ │ └── KotlinReflectionUtilsTests.kt │ └── resources/ │ ├── META-INF/ │ │ └── services/ │ │ └── org.junit.platform.engine.discovery.DiscoverySelectorIdentifierParser │ ├── config-test-override.properties │ ├── config-test.properties │ ├── console/ │ │ └── details/ │ │ ├── basic/ │ │ │ ├── Basic-changeDisplayName-flat-ascii.out.txt │ │ │ ├── Basic-changeDisplayName-flat-unicode.out.txt │ │ │ ├── Basic-changeDisplayName-none-ascii.out.txt │ │ │ ├── Basic-changeDisplayName-none-unicode.out.txt │ │ │ ├── Basic-changeDisplayName-summary-ascii.out.txt │ │ │ ├── Basic-changeDisplayName-summary-unicode.out.txt │ │ │ ├── Basic-changeDisplayName-testfeed-ascii.out.txt │ │ │ ├── Basic-changeDisplayName-testfeed-unicode.out.txt │ │ │ ├── Basic-changeDisplayName-tree-ascii.out.txt │ │ │ ├── Basic-changeDisplayName-tree-unicode.out.txt │ │ │ ├── Basic-changeDisplayName-verbose-ascii.out.txt │ │ │ ├── Basic-changeDisplayName-verbose-unicode.out.txt │ │ │ ├── Basic-empty-flat-ascii.out.txt │ │ │ ├── Basic-empty-flat-unicode.out.txt │ │ │ ├── Basic-empty-none-ascii.out.txt │ │ │ ├── Basic-empty-none-unicode.out.txt │ │ │ ├── Basic-empty-summary-ascii.out.txt │ │ │ ├── Basic-empty-summary-unicode.out.txt │ │ │ ├── Basic-empty-testfeed-ascii.out.txt │ │ │ ├── Basic-empty-testfeed-unicode.out.txt │ │ │ ├── Basic-empty-tree-ascii.out.txt │ │ │ ├── Basic-empty-tree-unicode.out.txt │ │ │ ├── Basic-empty-verbose-ascii.out.txt │ │ │ └── Basic-empty-verbose-unicode.out.txt │ │ ├── fail/ │ │ │ ├── Fail-failWithMultiLineMessage-flat-ascii.out.txt │ │ │ ├── Fail-failWithMultiLineMessage-flat-unicode.out.txt │ │ │ ├── Fail-failWithMultiLineMessage-none-ascii.out.txt │ │ │ ├── Fail-failWithMultiLineMessage-none-unicode.out.txt │ │ │ ├── Fail-failWithMultiLineMessage-summary-ascii.out.txt │ │ │ ├── Fail-failWithMultiLineMessage-summary-unicode.out.txt │ │ │ ├── Fail-failWithMultiLineMessage-testfeed-ascii.out.txt │ │ │ ├── Fail-failWithMultiLineMessage-testfeed-unicode.out.txt │ │ │ ├── Fail-failWithMultiLineMessage-tree-ascii.out.txt │ │ │ ├── Fail-failWithMultiLineMessage-tree-unicode.out.txt │ │ │ ├── Fail-failWithMultiLineMessage-verbose-ascii.out.txt │ │ │ ├── Fail-failWithMultiLineMessage-verbose-unicode.out.txt │ │ │ ├── Fail-failWithSingleLineMessage-flat-ascii.out.txt │ │ │ ├── Fail-failWithSingleLineMessage-flat-unicode.out.txt │ │ │ ├── Fail-failWithSingleLineMessage-none-ascii.out.txt │ │ │ ├── Fail-failWithSingleLineMessage-none-unicode.out.txt │ │ │ ├── Fail-failWithSingleLineMessage-summary-ascii.out.txt │ │ │ ├── Fail-failWithSingleLineMessage-summary-unicode.out.txt │ │ │ ├── Fail-failWithSingleLineMessage-testfeed-ascii.out.txt │ │ │ ├── Fail-failWithSingleLineMessage-testfeed-unicode.out.txt │ │ │ ├── Fail-failWithSingleLineMessage-tree-ascii.out.txt │ │ │ ├── Fail-failWithSingleLineMessage-tree-unicode.out.txt │ │ │ ├── Fail-failWithSingleLineMessage-verbose-ascii.out.txt │ │ │ └── Fail-failWithSingleLineMessage-verbose-unicode.out.txt │ │ ├── report/ │ │ │ ├── Report-reportMultiEntriesWithMultiMappings-flat-ascii.out.txt │ │ │ ├── Report-reportMultiEntriesWithMultiMappings-flat-unicode.out.txt │ │ │ ├── Report-reportMultiEntriesWithMultiMappings-none-ascii.out.txt │ │ │ ├── Report-reportMultiEntriesWithMultiMappings-none-unicode.out.txt │ │ │ ├── Report-reportMultiEntriesWithMultiMappings-summary-ascii.out.txt │ │ │ ├── Report-reportMultiEntriesWithMultiMappings-summary-unicode.out.txt │ │ │ ├── Report-reportMultiEntriesWithMultiMappings-testfeed-ascii.out.txt │ │ │ ├── Report-reportMultiEntriesWithMultiMappings-testfeed-unicode.out.txt │ │ │ ├── Report-reportMultiEntriesWithMultiMappings-tree-ascii.out.txt │ │ │ ├── Report-reportMultiEntriesWithMultiMappings-tree-unicode.out.txt │ │ │ ├── Report-reportMultiEntriesWithMultiMappings-verbose-ascii.out.txt │ │ │ ├── Report-reportMultiEntriesWithMultiMappings-verbose-unicode.out.txt │ │ │ ├── Report-reportMultiEntriesWithSingleMapping-flat-ascii.out.txt │ │ │ ├── Report-reportMultiEntriesWithSingleMapping-flat-unicode.out.txt │ │ │ ├── Report-reportMultiEntriesWithSingleMapping-none-ascii.out.txt │ │ │ ├── Report-reportMultiEntriesWithSingleMapping-none-unicode.out.txt │ │ │ ├── Report-reportMultiEntriesWithSingleMapping-summary-ascii.out.txt │ │ │ ├── Report-reportMultiEntriesWithSingleMapping-summary-unicode.out.txt │ │ │ ├── Report-reportMultiEntriesWithSingleMapping-testfeed-ascii.out.txt │ │ │ ├── Report-reportMultiEntriesWithSingleMapping-testfeed-unicode.out.txt │ │ │ ├── Report-reportMultiEntriesWithSingleMapping-tree-ascii.out.txt │ │ │ ├── Report-reportMultiEntriesWithSingleMapping-tree-unicode.out.txt │ │ │ ├── Report-reportMultiEntriesWithSingleMapping-verbose-ascii.out.txt │ │ │ ├── Report-reportMultiEntriesWithSingleMapping-verbose-unicode.out.txt │ │ │ ├── Report-reportMultipleMessages-flat-ascii.out.txt │ │ │ ├── Report-reportMultipleMessages-flat-unicode.out.txt │ │ │ ├── Report-reportMultipleMessages-none-ascii.out.txt │ │ │ ├── Report-reportMultipleMessages-none-unicode.out.txt │ │ │ ├── Report-reportMultipleMessages-summary-ascii.out.txt │ │ │ ├── Report-reportMultipleMessages-summary-unicode.out.txt │ │ │ ├── Report-reportMultipleMessages-testfeed-ascii.out.txt │ │ │ ├── Report-reportMultipleMessages-testfeed-unicode.out.txt │ │ │ ├── Report-reportMultipleMessages-tree-ascii.out.txt │ │ │ ├── Report-reportMultipleMessages-tree-unicode.out.txt │ │ │ ├── Report-reportMultipleMessages-verbose-ascii.out.txt │ │ │ ├── Report-reportMultipleMessages-verbose-unicode.out.txt │ │ │ ├── Report-reportSingleEntryWithSingleMapping-flat-ascii.out.txt │ │ │ ├── Report-reportSingleEntryWithSingleMapping-flat-unicode.out.txt │ │ │ ├── Report-reportSingleEntryWithSingleMapping-none-ascii.out.txt │ │ │ ├── Report-reportSingleEntryWithSingleMapping-none-unicode.out.txt │ │ │ ├── Report-reportSingleEntryWithSingleMapping-summary-ascii.out.txt │ │ │ ├── Report-reportSingleEntryWithSingleMapping-summary-unicode.out.txt │ │ │ ├── Report-reportSingleEntryWithSingleMapping-testfeed-ascii.out.txt │ │ │ ├── Report-reportSingleEntryWithSingleMapping-testfeed-unicode.out.txt │ │ │ ├── Report-reportSingleEntryWithSingleMapping-tree-ascii.out.txt │ │ │ ├── Report-reportSingleEntryWithSingleMapping-tree-unicode.out.txt │ │ │ ├── Report-reportSingleEntryWithSingleMapping-verbose-ascii.out.txt │ │ │ ├── Report-reportSingleEntryWithSingleMapping-verbose-unicode.out.txt │ │ │ ├── Report-reportSingleMessage-flat-ascii.out.txt │ │ │ ├── Report-reportSingleMessage-flat-unicode.out.txt │ │ │ ├── Report-reportSingleMessage-none-ascii.out.txt │ │ │ ├── Report-reportSingleMessage-none-unicode.out.txt │ │ │ ├── Report-reportSingleMessage-summary-ascii.out.txt │ │ │ ├── Report-reportSingleMessage-summary-unicode.out.txt │ │ │ ├── Report-reportSingleMessage-testfeed-ascii.out.txt │ │ │ ├── Report-reportSingleMessage-testfeed-unicode.out.txt │ │ │ ├── Report-reportSingleMessage-tree-ascii.out.txt │ │ │ ├── Report-reportSingleMessage-tree-unicode.out.txt │ │ │ ├── Report-reportSingleMessage-verbose-ascii.out.txt │ │ │ └── Report-reportSingleMessage-verbose-unicode.out.txt │ │ └── skip/ │ │ ├── Skip-skipWithMultiLineMessage-flat-ascii.out.txt │ │ ├── Skip-skipWithMultiLineMessage-flat-unicode.out.txt │ │ ├── Skip-skipWithMultiLineMessage-none-ascii.out.txt │ │ ├── Skip-skipWithMultiLineMessage-none-unicode.out.txt │ │ ├── Skip-skipWithMultiLineMessage-summary-ascii.out.txt │ │ ├── Skip-skipWithMultiLineMessage-summary-unicode.out.txt │ │ ├── Skip-skipWithMultiLineMessage-testfeed-ascii.out.txt │ │ ├── Skip-skipWithMultiLineMessage-testfeed-unicode.out.txt │ │ ├── Skip-skipWithMultiLineMessage-tree-ascii.out.txt │ │ ├── Skip-skipWithMultiLineMessage-tree-unicode.out.txt │ │ ├── Skip-skipWithMultiLineMessage-verbose-ascii.out.txt │ │ ├── Skip-skipWithMultiLineMessage-verbose-unicode.out.txt │ │ ├── Skip-skipWithSingleLineReason-flat-ascii.out.txt │ │ ├── Skip-skipWithSingleLineReason-flat-unicode.out.txt │ │ ├── Skip-skipWithSingleLineReason-none-ascii.out.txt │ │ ├── Skip-skipWithSingleLineReason-none-unicode.out.txt │ │ ├── Skip-skipWithSingleLineReason-summary-ascii.out.txt │ │ ├── Skip-skipWithSingleLineReason-summary-unicode.out.txt │ │ ├── Skip-skipWithSingleLineReason-testfeed-ascii.out.txt │ │ ├── Skip-skipWithSingleLineReason-testfeed-unicode.out.txt │ │ ├── Skip-skipWithSingleLineReason-tree-ascii.out.txt │ │ ├── Skip-skipWithSingleLineReason-tree-unicode.out.txt │ │ ├── Skip-skipWithSingleLineReason-verbose-ascii.out.txt │ │ └── Skip-skipWithSingleLineReason-verbose-unicode.out.txt │ ├── default-package.resource │ ├── do_not_delete_me.txt │ ├── error-engine/ │ │ └── META-INF/ │ │ └── services/ │ │ └── org.junit.platform.engine.TestEngine │ ├── folder with spaces/ │ │ └── jar test with spaces.jar │ ├── gh-1436-invalid-nested-class-file.jar │ ├── intercepted-testservices/ │ │ └── META-INF/ │ │ └── services/ │ │ ├── org.junit.platform.engine.TestEngine │ │ └── org.junit.platform.launcher.LauncherSessionListener │ ├── jartest-shadowed.jar │ ├── jartest.jar │ ├── jenkins-junit.xsd │ ├── junit-platform.properties │ ├── log4j2-test.xml │ ├── modules-2500/ │ │ ├── foo/ │ │ │ ├── Foo.java │ │ │ └── module-info.java │ │ └── foo.bar/ │ │ ├── FooBar.java │ │ └── module-info.java │ ├── org/ │ │ └── junit/ │ │ └── platform/ │ │ └── commons/ │ │ ├── example.resource │ │ └── other-example.resource │ ├── test-junit-platform.properties │ └── testservices/ │ └── META-INF/ │ └── services/ │ ├── org.junit.platform.launcher.LauncherDiscoveryListener │ ├── org.junit.platform.launcher.LauncherInterceptor │ ├── org.junit.platform.launcher.LauncherSessionListener │ ├── org.junit.platform.launcher.PostDiscoveryFilter │ └── org.junit.platform.launcher.TestExecutionListener ├── platform-tooling-support-tests/ │ ├── platform-tooling-support-tests.gradle.kts │ ├── projects/ │ │ ├── graalvm-starter/ │ │ │ ├── build.gradle.kts │ │ │ ├── settings.gradle.kts │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ └── java/ │ │ │ │ └── com/ │ │ │ │ └── example/ │ │ │ │ └── project/ │ │ │ │ └── Calculator.java │ │ │ └── test/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── example/ │ │ │ │ └── project/ │ │ │ │ ├── CalculatorParameterizedClassTests.java │ │ │ │ ├── CalculatorTests.java │ │ │ │ ├── ClassLevelAnnotationTests.java │ │ │ │ ├── GraalvmSuite.java │ │ │ │ └── VintageTests.java │ │ │ └── resources/ │ │ │ └── junit-platform.properties │ │ ├── gradle-kotlin-extensions/ │ │ │ ├── build.gradle.kts │ │ │ ├── gradle.properties │ │ │ ├── settings.gradle.kts │ │ │ └── src/ │ │ │ └── test/ │ │ │ └── kotlin/ │ │ │ └── com/ │ │ │ └── example/ │ │ │ └── project/ │ │ │ └── ExtensionFunctionsTests.kt │ │ ├── gradle-missing-engine/ │ │ │ ├── build.gradle.kts │ │ │ ├── gradle.properties │ │ │ ├── settings.gradle.kts │ │ │ └── src/ │ │ │ └── test/ │ │ │ └── java/ │ │ │ └── FooTests.java │ │ ├── jar-describe-module/ │ │ │ ├── junit-jupiter-api.expected.txt │ │ │ ├── junit-jupiter-engine.expected.txt │ │ │ ├── junit-jupiter-migrationsupport.expected.txt │ │ │ ├── junit-jupiter-params.expected.txt │ │ │ ├── junit-jupiter.expected.txt │ │ │ ├── junit-platform-commons.expected.txt │ │ │ ├── junit-platform-console.expected.txt │ │ │ ├── junit-platform-engine.expected.txt │ │ │ ├── junit-platform-launcher.expected.txt │ │ │ ├── junit-platform-reporting.expected.txt │ │ │ ├── junit-platform-suite-api.expected.txt │ │ │ ├── junit-platform-suite-engine.expected.txt │ │ │ ├── junit-platform-suite.expected.txt │ │ │ ├── junit-platform-testkit.expected.txt │ │ │ ├── junit-start.expected.txt │ │ │ └── junit-vintage-engine.expected.txt │ │ ├── junit-start/ │ │ │ ├── compact/ │ │ │ │ ├── JUnitRun.java │ │ │ │ └── JUnitRunClass.java │ │ │ └── modular/ │ │ │ ├── module-info.java │ │ │ └── p/ │ │ │ ├── JUnitRunModule.java │ │ │ └── MultiplicationTests.java │ │ ├── jupiter-starter/ │ │ │ ├── build.gradle.kts │ │ │ ├── build.xml │ │ │ ├── gradle.properties │ │ │ ├── pom.xml │ │ │ ├── settings.gradle.kts │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ └── java/ │ │ │ │ └── com/ │ │ │ │ └── example/ │ │ │ │ └── project/ │ │ │ │ └── Calculator.java │ │ │ └── test/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── example/ │ │ │ │ └── project/ │ │ │ │ ├── CalculatorParameterizedClassTests.java │ │ │ │ └── CalculatorTests.java │ │ │ └── resources/ │ │ │ └── junit-platform.properties │ │ ├── kotlin-coroutines/ │ │ │ ├── build.gradle.kts │ │ │ ├── settings.gradle.kts │ │ │ └── src/ │ │ │ └── test/ │ │ │ └── kotlin/ │ │ │ └── com/ │ │ │ └── example/ │ │ │ └── project/ │ │ │ └── SuspendFunctionTests.kt │ │ ├── maven-surefire-compatibility/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── test/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── example/ │ │ │ └── project/ │ │ │ └── DummyTests.java │ │ ├── memory-cleanup/ │ │ │ └── src/ │ │ │ └── OneMillionTests.java │ │ ├── reflection-tests/ │ │ │ ├── build.gradle.kts │ │ │ ├── gradle.properties │ │ │ ├── settings.gradle.kts │ │ │ └── src/ │ │ │ └── test/ │ │ │ └── java/ │ │ │ └── ReflectionTestCase.java │ │ ├── standalone/ │ │ │ ├── expected-err.txt │ │ │ ├── expected-out.txt │ │ │ ├── logging.properties │ │ │ └── src/ │ │ │ ├── other/ │ │ │ │ └── OtherwiseNotReferencedClass.java │ │ │ └── standalone/ │ │ │ ├── JupiterIntegration.java │ │ │ ├── JupiterParamsIntegration.java │ │ │ ├── SuiteIntegration.java │ │ │ └── VintageIntegration.java │ │ └── vintage/ │ │ ├── build.gradle.kts │ │ ├── gradle.properties │ │ ├── pom.xml │ │ ├── settings.gradle.kts │ │ └── src/ │ │ └── test/ │ │ └── java/ │ │ ├── DefaultPackageTest.java │ │ └── com/ │ │ └── example/ │ │ └── vintage/ │ │ └── VintageTest.java │ └── src/ │ ├── archUnit/ │ │ └── java/ │ │ └── platform/ │ │ └── tooling/ │ │ └── support/ │ │ └── tests/ │ │ └── ArchUnitTests.java │ ├── main/ │ │ └── java/ │ │ └── platform/ │ │ └── tooling/ │ │ └── support/ │ │ ├── Helper.java │ │ ├── MavenRepo.java │ │ ├── ProcessStarters.java │ │ ├── ThirdPartyJars.java │ │ └── package-info.java │ └── test/ │ ├── java/ │ │ └── platform/ │ │ └── tooling/ │ │ └── support/ │ │ ├── HelperTests.java │ │ └── tests/ │ │ ├── AntStarterTests.java │ │ ├── FilePrefix.java │ │ ├── GraalVmStarterTests.java │ │ ├── GradleKotlinExtensionsTests.java │ │ ├── GradleMissingEngineTests.java │ │ ├── GradleModuleFileTests.java │ │ ├── GradleStarterTests.java │ │ ├── JUnitStartTests.java │ │ ├── JarContainsManifestFirstTests.java │ │ ├── JarDescribeModuleTests.java │ │ ├── KotlinCoroutinesTests.java │ │ ├── LocalMavenRepo.java │ │ ├── ManagedResource.java │ │ ├── ManifestTests.java │ │ ├── MavenEnvVars.java │ │ ├── MavenPomFileTests.java │ │ ├── MavenRepoProxy.java │ │ ├── MavenStarterTests.java │ │ ├── MavenSurefireCompatibilityTests.java │ │ ├── MemoryCleanupTests.java │ │ ├── ModularCompilationTests.java │ │ ├── ModularUserGuideTests.java │ │ ├── OutputAttachingExtension.java │ │ ├── Projects.java │ │ ├── ReflectionCompatibilityTests.java │ │ ├── StandaloneTests.java │ │ ├── ToolProviderTests.java │ │ ├── UnalignedClasspathTests.java │ │ ├── VintageGradleIntegrationTests.java │ │ ├── VintageMavenIntegrationTests.java │ │ └── XmlAssertions.java │ └── resources/ │ ├── junit-platform.properties │ ├── log4j2-test.xml │ └── platform/ │ └── tooling/ │ └── support/ │ └── tests/ │ ├── AntStarterTests_snapshots/ │ │ └── open-test-report.xml.snapshot │ ├── GradleStarterTests_snapshots/ │ │ └── open-test-report.xml.snapshot │ └── MavenStarterTests_snapshots/ │ └── open-test-report.xml.snapshot └── settings.gradle.kts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*] charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.bat] end_of_line = crlf [*.md] # Trailing whitespace is important in Markdown (they distinguish a new line from a new paragraph) trim_trailing_whitespace = false [*.{kt,kts}] ij_kotlin_allow_trailing_comma = false ij_kotlin_allow_trailing_comma_on_call_site = false [*.kts] indent_style = tab ================================================ FILE: .gitattributes ================================================ * text eol=lf *.bat text eol=crlf *.png binary *.key binary *.jar binary *.ttf binary release-notes-* merge=union ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Report a bug or regression about: Create a report to help us improve type: Bug labels: ["type: bug"] --- ## Steps to reproduce ## Context - Used versions (Jupiter/Vintage/Platform): - Build Tool/IDE: ## Deliverables - [ ] ... ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Ask a question url: https://github.com/junit-team/junit-framework/discussions/categories/q-a about: Please ask and answer questions here - name: Ask a question (Stack Overflow) url: https://stackoverflow.com/questions/tagged/junit5 about: Alternatively, ask questions here ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Request a feature or enhancement about: Suggest an idea for this project type: Feature labels: ["type: new feature"] --- ## Deliverables - [ ] ... ================================================ FILE: .github/ISSUE_TEMPLATE/task.md ================================================ --- name: Create a task about: Create a task for a specific piece of work type: Task labels: ["type: task"] --- ## Deliverables - [ ] ... ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ --- I hereby agree to the terms of the [JUnit Contributor License Agreement](https://github.com/junit-team/junit-framework/blob/002a0052926ddee57cf90580fa49bc37e5a72427/CONTRIBUTING.md#junit-contributor-license-agreement). --- ### Definition of Done - [ ] There are no TODOs left in the code - [ ] Method [preconditions](https://docs.junit.org/snapshot/api/org.junit.platform.commons/org/junit/platform/commons/util/Preconditions.html) are checked and documented in the method's Javadoc - [ ] [Coding conventions](https://github.com/junit-team/junit-framework/blob/HEAD/CONTRIBUTING.md#coding-conventions) (e.g. for logging) have been followed - [ ] Change is covered by [automated tests](https://github.com/junit-team/junit-framework/blob/HEAD/CONTRIBUTING.md#tests) including corner cases, errors, and exception handling - [ ] Public API has [Javadoc](https://github.com/junit-team/junit-framework/blob/HEAD/CONTRIBUTING.md#javadoc) and [`@API` annotations](https://apiguardian-team.github.io/apiguardian/docs/current/api/org/apiguardian/api/API.html) - [ ] Change is documented in the [User Guide](https://docs.junit.org/snapshot/) and [Release Notes](https://docs.junit.org/snapshot/release-notes.html) ================================================ FILE: .github/actions/main-build/action.yml ================================================ name: Main build description: Sets up JDKs and runs Gradle inputs: arguments: required: true description: Gradle arguments default: :platform-tooling-support-tests:test build eclipse --no-configuration-cache # Disable configuration cache due to https://github.com/diffplug/spotless/issues/2318 encryptionKey: required: true description: Gradle cache encryption key runs: using: "composite" steps: - uses: ./.github/actions/setup-test-jdk - uses: ./.github/actions/run-gradle with: arguments: ${{ inputs.arguments }} encryptionKey: ${{ inputs.encryptionKey }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 if: ${{ always() }} with: name: Open Test Reports (${{ github.job }}) path: '**/build/reports/open-test-report.html' ================================================ FILE: .github/actions/maven-central-user-token/action.yml ================================================ name: Prepare Maven Central user token description: Compute the Maven Central user token from username and password inputs: username: required: true description: Maven Central username password: required: true description: Maven Central password runs: using: "composite" steps: - shell: bash run: | # zizmor: ignore[github-env] USER_TOKEN=$(printf "${USERNAME}:${PASSWORD}" | base64) echo "::add-mask::$USER_TOKEN" echo "MAVEN_CENTRAL_USER_TOKEN=$USER_TOKEN" >> $GITHUB_ENV env: USERNAME: ${{ inputs.username }} PASSWORD: ${{ inputs.password }} ================================================ FILE: .github/actions/run-gradle/action.yml ================================================ name: Run Gradle description: Sets up Gradle JDKs and runs Gradle inputs: arguments: required: true description: Gradle arguments default: build broken-by-issue: required: false description: Expect the build to fail due the referenced GitHub issue default: "" encryptionKey: required: true description: Gradle cache encryption key runs: using: "composite" steps: - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 id: setup-gradle-jdk with: distribution: temurin java-version: 25 check-latest: true - uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0 with: cache-encryption-key: ${{ inputs.encryptionKey }} - uses: testlens-app/setup-testlens@d96a555133c275a00949d2cc77b70fe9a4242ebf # v1.9.2 with: write-log-files: true - shell: bash env: JAVA_HOME: ${{ steps.setup-gradle-jdk.outputs.path }} run: | # zizmor: ignore[template-injection] ./gradlew \ -Dorg.gradle.java.installations.auto-download=false \ -Pjunit.develocity.buildCache.pushEnabled=${{ github.repository == 'junit-team/junit-framework' }} \ -Pjunit.develocity.predictiveTestSelection.enabled=true \ -Pjunit.develocity.predictiveTestSelection.selectRemainingTests=${{ github.event_name != 'pull_request' }} \ "-Dscan.value.GitHub job=${{ github.job }}" \ javaToolchains \ ${{ inputs.arguments }} || error_code=$? if [ -n "${{ inputs.broken-by-issue }}" ]; then if [ -n "$error_code" ]; then # we expected the build to fail, it failed => pass exit 0 else # we expected the build to fail, it didn't => fail RED='\033[0;31m' RESET='\033[0m' # No Color echo "${RED}Build was expected to fail, but it passed. Was ${{ inputs.broken-by-issue }} resolved?${RESET}" exit 1 fi fi # exit with the original error code if set if [ -n "$error_code" ]; then exit "$error_code" fi - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 if: ${{ always() }} with: name: TestLens logs (${{ github.job }} - ${{ job.check_run_id }}) path: '**/build/**/testlens-logs/**' if-no-files-found: ignore ================================================ FILE: .github/actions/setup-test-jdk/action.yml ================================================ name: Set up Test JDK description: Sets up the JDK required to run platform-tooling-support-tests inputs: distribution: required: true description: 'The JDK distribution to use' default: 'temurin' runs: using: "composite" steps: - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: ${{ inputs.distribution }} java-version: 17 check-latest: true - shell: bash run: echo "JDK17=$JAVA_HOME" >> $GITHUB_ENV # zizmor: ignore[github-env] ================================================ FILE: .github/codecov.yml ================================================ comment: false coverage: status: project: default: threshold: "1" informational: true patch: off codecov: max_report_age: off ================================================ FILE: .github/dco.yml ================================================ require: members: false ================================================ FILE: .github/renovate.json5 ================================================ { $schema: 'https://docs.renovatebot.com/renovate-schema.json', extends: [ 'github>junit-team/renovate-config', ], baseBranchPatterns: [ 'main', 'docs-site' ], packageRules: [ { matchCurrentValue: '/^2\\./', allowedVersions: '(,3.0)', matchPackageNames: [ 'org.codehaus.groovy:{/,}**', ], }, { matchCurrentValue: '/^1\\./', allowedVersions: '/^1\\..*-groovy-2\\.*/', matchPackageNames: [ 'org.spockframework:{/,}**', ], }, { allowedVersions: '!/-SNAPSHOT$/', matchPackageNames: [ 'org.opentest4j.reporting:{/,}**', ], }, { matchManagers: 'npm', matchPackageNames: [ '@antora/assembler', '@antora/html-single-extension', '@antora/pdf-extension', ], "groupName": "@antora/assembler", }, ], } ================================================ FILE: .github/scripts/checkBuildReproducibility.sh ================================================ #!/bin/bash -e rm -f checksums-1.txt checksums-2.txt SOURCE_DATE_EPOCH=$(date +%s) export SOURCE_DATE_EPOCH function calculate_checksums() { OUTPUT=$1 ./gradlew \ --configuration-cache \ --no-build-cache \ -Dorg.gradle.java.installations.auto-download=false \ -Dscan.tag.Reproducibility \ clean \ assemble find . -name '*.jar' \ | grep '/build/libs/' \ | grep --invert-match 'javadoc' \ | sort \ | xargs sha512sum > "${OUTPUT}" } calculate_checksums checksums-1.txt calculate_checksums checksums-2.txt diff checksums-1.txt checksums-2.txt ================================================ FILE: .github/scripts/close-github-milestone.js ================================================ module.exports = async ({ github, context }) => { const releaseVersion = process.env.RELEASE_VERSION; const query = ` query ($owner: String!, $repo: String!, $title: String!) { repository(owner: $owner, name: $repo) { milestones(first: 100, query: $title) { nodes { title number openIssueCount } } } } `; const {repository} = await github.graphql(query, { owner: context.repo.owner, repo: context.repo.repo, title: releaseVersion }); const [milestone] = repository.milestones.nodes.filter(it => it.title === releaseVersion); if (!milestone) { throw new Error(`Milestone "${releaseVersion}" not found`); } if (milestone.openIssueCount > 0) { throw new Error(`Milestone "${releaseVersion}" has ${milestone.openIssueCount} open issue(s)`); } const requestBody = { owner: context.repo.owner, repo: context.repo.repo, milestone_number: milestone.number, state: 'closed', due_on: new Date().toISOString() }; console.log(requestBody); await github.rest.issues.updateMilestone(requestBody); }; ================================================ FILE: .github/scripts/create-github-release.js ================================================ module.exports = async ({ github, context }) => { const releaseVersion = process.env.RELEASE_VERSION; const requestBody = { owner: context.repo.owner, repo: context.repo.repo, tag_name: `r${releaseVersion}`, name: `JUnit ${releaseVersion}`, generate_release_notes: true, body: `JUnit ${releaseVersion} = Platform ${releaseVersion} + Jupiter ${releaseVersion} + Vintage ${releaseVersion}\n\nSee [Release Notes](https://docs.junit.org/${releaseVersion}/release-notes.html).`, prerelease: releaseVersion.includes("-"), }; console.log(requestBody); await github.rest.repos.createRelease(requestBody); }; ================================================ FILE: .github/scripts/waitForUrl.sh ================================================ #!/usr/bin/env bash URL=$1 printf 'Waiting for %s' "$URL" until curl --output /dev/null --silent --location --head --fail "$URL"; do printf '.' sleep 5 done echo ' OK' ================================================ FILE: .github/workflows/close-inactive-issues.yml ================================================ name: Close inactive issues and PRs on: schedule: - cron: "30 1 * * *" workflow_dispatch: permissions: {} jobs: close-issues: runs-on: ubuntu-latest permissions: issues: write pull-requests: write steps: - uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0 with: only-labels: "status: waiting-for-feedback" days-before-stale: 14 days-before-close: 21 stale-issue-label: "status: stale" stale-pr-label: "status: stale" stale-issue-message: > If you would like us to be able to process this issue, please provide the requested information. If the information is not provided within the next 3 weeks, we will be unable to proceed and this issue will be closed. close-issue-message: > Closing due to lack of requested feedback. If you would like to proceed with your contribution, please provide the requested information and we will re-open this issue. stale-pr-message: > If you would like us to be able to process this pull request, please provide the requested information or make the requested changes. If the information is not provided or the requested changes are not made within the next 3 weeks, we will be unable to proceed and this pull request will be closed. close-pr-message: > Closing due to lack of requested feedback. If you would like to proceed with your contribution, please provide the requested information or make the requested changes, and we will re-open this pull request. ================================================ FILE: .github/workflows/codeql.yml ================================================ name: "CodeQL" on: push: branches: - main - 'releases/**' pull_request: # The branches below must be a subset of the branches above branches: - main - 'releases/**' schedule: - cron: '0 19 * * 3' concurrency: # Cancels in-progress runs only for pull requests group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true permissions: {} env: DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} jobs: analyze: name: Analyze (${{ matrix.language }}) runs-on: ubuntu-latest permissions: security-events: write strategy: fail-fast: false matrix: include: - language: actions build-mode: none - language: java-kotlin build-mode: manual - language: javascript build-mode: none steps: - name: Check out repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Initialize CodeQL uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} tools: linked - name: Build if: matrix.build-mode == 'manual' uses: ./.github/actions/run-gradle with: encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }} arguments: >- --no-build-cache -Dscan.tag.CodeQL classes - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4 with: category: "/language:${{matrix.language}}" ================================================ FILE: .github/workflows/cross-version.yml ================================================ name: Cross-Version on: schedule: - cron: '0 0 * * 6' # Every Saturday at 00:00 UTC push: branches: - main - 'releases/**' pull_request: branches: - '**' concurrency: # Cancels in-progress runs only for pull requests group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true permissions: {} env: DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} jobs: openjdk: strategy: fail-fast: false matrix: jdk: - version: 26 type: ga - version: 27 type: ea - version: 26 type: ea release: leyden - version: 27 type: ea release: valhalla name: "OpenJDK ${{ matrix.jdk.version }} (${{ matrix.jdk.release || matrix.jdk.type }})" runs-on: ubuntu-latest steps: - name: Check out repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 persist-credentials: false - name: Set up Test JDK uses: ./.github/actions/setup-test-jdk - name: "Set up JDK ${{ matrix.jdk.version }} (${{ matrix.jdk.release || 'ea' }})" if: matrix.jdk.type == 'ea' uses: oracle-actions/setup-java@fff43251af9936a0e6a4d5d0946e14f1680e9b6b # v1.5.0 with: website: jdk.java.net release: ${{ matrix.jdk.release || matrix.jdk.version }} version: latest - name: "Set up JDK ${{ matrix.jdk.version }} (${{ matrix.jdk.distribution || 'temurin' }})" if: matrix.jdk.type == 'ga' uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: ${{ matrix.jdk.distribution || 'temurin' }} java-version: ${{ matrix.jdk.version }} check-latest: true - name: 'Prepare JDK${{ matrix.jdk.version }} env var' shell: bash run: echo "JDK${{ matrix.jdk.version }}=$JAVA_HOME" >> $GITHUB_ENV - name: Build uses: ./.github/actions/run-gradle with: encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }} # Disable configuration cache due to https://github.com/diffplug/spotless/issues/2318 arguments: >- -Ptesting.enableJaCoCo=false -PjavaToolchain.version=${{ matrix.jdk.version }} -Dscan.tag.JDK_${{ matrix.jdk.version }} build --no-configuration-cache broken-by-issue: ${{ matrix.jdk.broken-by-issue }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 if: ${{ always() }} with: name: Open Test Reports (${{ github.job }} ${{ matrix.jdk.version }} (${{ matrix.jdk.release || matrix.jdk.type }})) path: '**/build/reports/open-test-report.html' openj9: strategy: fail-fast: false matrix: jdk: [ 25 ] name: "OpenJ9 ${{ matrix.jdk }}" runs-on: ubuntu-latest steps: - name: Check out repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 persist-credentials: false - name: Set up Test JDK uses: ./.github/actions/setup-test-jdk with: distribution: semeru - name: 'Set up JDK ${{ matrix.jdk }}' uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: semeru java-version: ${{ matrix.jdk }} check-latest: true - name: 'Prepare JDK${{ matrix.jdk }} env var' shell: bash run: echo "JDK${{ matrix.jdk }}=$JAVA_HOME" >> $GITHUB_ENV - name: Build uses: ./.github/actions/run-gradle with: encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }} # Disable configuration cache due to https://github.com/diffplug/spotless/issues/2318 arguments: >- -Ptesting.enableJaCoCo=false -PjavaToolchain.version=${{ matrix.jdk }} -PjavaToolchain.implementation=j9 -Dscan.tag.JDK_${{ matrix.jdk }} -Dscan.tag.OpenJ9 build --no-configuration-cache - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 if: ${{ always() }} with: name: Open Test Reports (${{ github.job }}) path: '**/build/reports/open-test-report.html' ================================================ FILE: .github/workflows/deploy-docs.yml ================================================ name: Deploy Documentation on: [ workflow_call, workflow_dispatch ] permissions: {} jobs: deploy_documentation: name: Deploy Documentation runs-on: ubuntu-slim permissions: actions: write steps: - name: Trigger documentation site deployment uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: max_attempts: 10 retry_wait_seconds: 5 timeout_seconds: 20 command: gh workflow run deploy-docs.yml --repo junit-team/junit-framework --ref docs-site ================================================ FILE: .github/workflows/gradle-dependency-submission.yml ================================================ name: Gradle Dependency Submission on: push: branches: - main permissions: {} env: DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} jobs: dependency-submission: if: github.repository == 'junit-team/junit-framework' runs-on: ubuntu-latest permissions: contents: write steps: - name: Check out repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 persist-credentials: false - name: Setup Java uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: temurin java-version: 25 check-latest: true - name: Generate and submit dependency graph id: dependency-submission uses: gradle/actions/dependency-submission@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0 continue-on-error: true - name: Generate and submit dependency graph (retry) if: steps.dependency-submission.outcome == 'failure' uses: gradle/actions/dependency-submission@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0 ================================================ FILE: .github/workflows/label-opened-issues.yml ================================================ name: Add label to opened issues on: issues: types: - opened permissions: {} jobs: label_issues: runs-on: ubuntu-latest permissions: issues: write steps: - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const issue = await github.rest.issues.get({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, }); const originalLabels = issue.data.labels.map(l => l.name); const statusLabels = originalLabels.filter(l => l.startsWith("status: ")); if (statusLabels.length === 0 && !originalLabels.includes("up-for-grabs")) { github.rest.issues.addLabels({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, labels: ["status: new"] }) } ================================================ FILE: .github/workflows/label-pull-request.yml ================================================ name: Copy labels from linked issues to PR on: pull_request_target: types: [opened, reopened] # zizmor: ignore[dangerous-triggers] permissions: {} jobs: copy_labels: name: Copy labels runs-on: ubuntu-latest permissions: pull-requests: write steps: - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const query = ` query($owner: String!, $repo: String!, $pr: Int!) { repository(owner: $owner, name: $repo) { pullRequest(number: $pr) { closingIssuesReferences(first: 10) { nodes { labels(first: 100) { nodes { name } } } } } } } `; const {repository} = await github.graphql(query, { owner: context.repo.owner, repo: context.repo.repo, pr: context.issue.number }); let labels = Array.from(new Set(repository.pullRequest.closingIssuesReferences.nodes .flatMap((node) => node.labels.nodes.map((label) => label.name)))) .filter((label) => !label.startsWith("status:") && label !== "up-for-grabs"); if (labels.length > 0) { console.log(`Adding labels to PR: ${labels}`); await github.rest.issues.addLabels({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, labels: labels }); } ================================================ FILE: .github/workflows/lock-resolved-issues.yml ================================================ name: Lock resolved issues on: schedule: - cron: "39 1 * * 0" workflow_dispatch: permissions: {} concurrency: group: lock-resolved-issues jobs: action: runs-on: ubuntu-latest permissions: issues: write pull-requests: write discussions: write steps: - uses: dessant/lock-threads@7266a7ce5c1df01b1c6db85bf8cd86c737dadbe7 # v6.0.0 with: process-only: issues log-output: true ================================================ FILE: .github/workflows/main.yml ================================================ name: CI on: push: branches: - main - 'releases/**' tags-ignore: - '**' pull_request: branches: - '**' concurrency: # Cancels in-progress runs only for pull requests group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true permissions: {} env: DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} jobs: Linux: runs-on: ubuntu-latest steps: - name: Check out repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 persist-credentials: false - name: Install GraalVM uses: graalvm/setup-graalvm@bef4b0e916c7dd079bf60fb95d49139f67e32c5f # v1.5.3 with: distribution: graalvm version: latest java-version: 21 - name: Build uses: ./.github/actions/main-build with: encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }} # Disable configuration cache due to https://github.com/diffplug/spotless/issues/2318 arguments: >- :platform-tooling-support-tests:test build jacocoRootReport --no-configuration-cache - name: Upload to Codecov.io uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 with: token: ${{ secrets.CODECOV_TOKEN }} Windows: runs-on: windows-latest steps: - name: Check out repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 persist-credentials: false - name: Build uses: ./.github/actions/main-build with: encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }} macOS: runs-on: macos-latest steps: - name: Check out repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 persist-credentials: false - name: Build uses: ./.github/actions/main-build with: encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }} publish_artifacts: name: Publish Snapshot Artifacts needs: macOS runs-on: ubuntu-latest permissions: attestations: write # required for build provenance attestation id-token: write # required for build provenance attestation if: github.event_name == 'push' && github.repository == 'junit-team/junit-framework' && (startsWith(github.ref, 'refs/heads/releases/') || github.ref == 'refs/heads/main') steps: - name: Check out repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 persist-credentials: false - name: Publish uses: ./.github/actions/run-gradle env: ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} with: encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }} arguments: >- publishAggregationToCentralSnapshots prepareGitHubAttestation -x check - name: Generate build provenance attestations uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 with: subject-path: documentation/build/attestation/*.jar build_documentation: name: Build Documentation needs: macOS runs-on: ubuntu-latest steps: - name: Check out repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 persist-credentials: false - name: Install Graphviz run: | sudo apt-get update sudo apt-get install graphviz - name: Install Node.js uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version-file: documentation/.tool-versions - name: Build Documentation uses: ./.github/actions/run-gradle with: encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }} arguments: >- antora -Pantora.downloadNode=false -Dscan.tag.Documentation deploy_documentation: name: Deploy Documentation needs: build_documentation if: github.event_name == 'push' && github.repository == 'junit-team/junit-framework' && github.ref == 'refs/heads/main' uses: ./.github/workflows/deploy-docs.yml permissions: actions: write ================================================ FILE: .github/workflows/ossf-scorecard.yml ================================================ name: OpenSSF Scorecard supply-chain security analysis on: branch_protection_rule: schedule: - cron: '31 20 * * 6' push: branches: [ "main" ] permissions: {} jobs: analysis: name: Scorecard analysis runs-on: ubuntu-latest permissions: # Needed to upload the results to code-scanning dashboard. security-events: write # Needed to publish results and get a badge (see publish_results below). id-token: write steps: - name: "Checkout code" uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: "Run analysis" uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 with: results_file: results.sarif results_format: sarif # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: # - you want to enable the Branch-Protection check on a *public* repository, or # - you are installing Scorecard on a *private* repository # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional. # repo_token: ${{ secrets.SCORECARD_TOKEN }} # Public repositories: # - Publish results to OpenSSF REST API for easy access by consumers # - Allows the repository to include the Scorecard badge. # - See https://github.com/ossf/scorecard-action#publishing-results. # For private repositories: # - `publish_results` will always be set to `false`, regardless # of the value entered here. publish_results: true # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: SARIF file path: results.sarif retention-days: 5 # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4 with: sarif_file: results.sarif ================================================ FILE: .github/workflows/release.yml ================================================ name: Release on: workflow_dispatch: inputs: releaseVersion: description: Version to be released (e.g. "5.12.0-M1") required: true deploymentId: description: ID of the Maven Central Publish Portal deployment required: true dryRun: type: boolean description: Enable dry-run mode required: false default: false permissions: {} env: DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} STAGING_REPO_URL: https://central.sonatype.com/api/v1/publisher/deployment/${{ inputs.deploymentId }}/download RELEASE_TAG: r${{ inputs.releaseVersion }} RELEASE_VERSION: ${{ inputs.releaseVersion }} jobs: verify_reproducibility: name: Verify reproducibility runs-on: ubuntu-latest permissions: attestations: write # required for build provenance attestation id-token: write # required for build provenance attestation steps: - name: Check out repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 ref: "refs/tags/${{ env.RELEASE_TAG }}" persist-credentials: false - name: Prepare Maven Central user token uses: ./.github/actions/maven-central-user-token with: username: ${{ secrets.MAVEN_CENTRAL_USERNAME }} password: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} - name: Download reference JAR from staging repository id: referenceJar run: | curl --silent --fail --location --output /tmp/reference.jar \ --header "Authorization: Bearer $MAVEN_CENTRAL_USER_TOKEN" \ "${STAGING_REPO_URL}/org/junit/jupiter/junit-jupiter-api/${RELEASE_VERSION}/junit-jupiter-api-${RELEASE_VERSION}.jar" sudo apt-get update && sudo apt-get install --yes jc unzip -c /tmp/reference.jar META-INF/MANIFEST.MF | jc --jar-manifest | jq '.[0]' > /tmp/manifest.json echo "createdBy=$(jq --raw-output .Created_By /tmp/manifest.json)" >> "$GITHUB_OUTPUT" echo "buildTimestamp=$(jq --raw-output .Build_Date /tmp/manifest.json) $(jq --raw-output .Build_Time /tmp/manifest.json)" >> "$GITHUB_OUTPUT" - name: Verify artifacts uses: ./.github/actions/run-gradle with: encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }} arguments: >- --rerun-tasks -Pmanifest.buildTimestamp="${{ steps.referenceJar.outputs.buildTimestamp }}" -Pmanifest.createdBy="${{ steps.referenceJar.outputs.createdBy }}" :verifyArtifactsInStagingRepositoryAreReproducible --remote-repo-url=${{ env.STAGING_REPO_URL }} - name: Generate build provenance attestations if: ${{ inputs.dryRun == false }} uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 with: subject-path: build/repo/**/*.jar verify_consumability: name: Verify consumability runs-on: ubuntu-latest steps: - name: Check out repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 ref: "refs/tags/${{ env.RELEASE_TAG }}" path: junit-framework persist-credentials: false - name: Check out examples repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: ${{ github.repository_owner }}/junit-examples token: ${{ secrets.JUNIT_BUILDS_GITHUB_TOKEN_EXAMPLES_REPO }} fetch-depth: 1 path: junit-examples persist-credentials: false - name: Set up JDK uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: java-version: 25 distribution: temurin - uses: sbt/setup-sbt@93e926cbdb4a428e41b4ef754124ec82925ffdc2 # v1.1.23 - name: Update JUnit dependencies in examples run: java src/Updater.java ${RELEASE_VERSION} working-directory: junit-examples - name: Prepare Maven Central user token uses: ./junit-framework/.github/actions/maven-central-user-token with: username: ${{ secrets.MAVEN_CENTRAL_USERNAME }} password: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} - name: Inject staging repository URL run: java src/StagingRepoInjector.java ${STAGING_REPO_URL} working-directory: junit-examples - name: Build examples run: java src/Builder.java --exclude=junit-jupiter-starter-bazel,junit-jupiter-starter-sbt,junit-source-launcher working-directory: junit-examples env: MAVEN_ARGS: --settings ${{ github.workspace }}/junit-examples/src/central-staging-maven-settings.xml --activate-profiles central-staging close_github_milestone: name: Close GitHub milestone runs-on: ubuntu-latest permissions: issues: write steps: - name: Check out repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 persist-credentials: false - name: Close GitHub milestone if: ${{ inputs.dryRun == false }} uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: result-encoding: string script: | const closeGithubMilestone = require('./.github/scripts/close-github-milestone.js'); closeGithubMilestone({ github, context }); publish_deployment: name: Publish to Maven Central needs: [ verify_reproducibility, verify_consumability, close_github_milestone ] runs-on: ubuntu-latest steps: - name: Check out repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 ref: "refs/tags/${{ env.RELEASE_TAG }}" persist-credentials: false - name: Release staging repository if: ${{ inputs.dryRun == false }} uses: ./.github/actions/run-gradle env: ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} with: encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }} arguments: nmcpPublishDeployment -PnmcpDeploymentId=${{ inputs.deploymentId }} publish_documentation: name: Publish documentation needs: publish_deployment runs-on: ubuntu-latest permissions: actions: write steps: - name: Check out repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 persist-credentials: false - name: Trigger deployment env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: gh workflow run deploy-docs.yml --repo junit-team/junit-framework --ref docs-site - name: Install Poppler (for pdfinfo) run: | sudo apt-get update sudo apt-get install --yes poppler-utils - name: Wait for deployment if: ${{ inputs.dryRun == false }} timeout-minutes: 30 run: ./.github/scripts/waitForUrl.sh "${URL}" env: URL: https://docs.junit.org/${{ inputs.releaseVersion }}/ - name: Verify integrity of PDF version of User Guide if: inputs.dryRun == false && !contains(inputs.releaseVersion, '-') run: | curl --silent --fail --location --output /tmp/junit-user-guide.pdf "${PDF_URL}" pdfinfo /tmp/junit-user-guide.pdf env: PDF_URL: https://docs.junit.org/${{ inputs.releaseVersion }}/_exports/junit-user-guide-${{ inputs.releaseVersion }}.pdf update_examples: name: Update examples needs: publish_deployment runs-on: ubuntu-latest steps: - name: Check out examples repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: ${{ github.repository_owner }}/junit-examples token: ${{ secrets.JUNIT_BUILDS_GITHUB_TOKEN_EXAMPLES_REPO }} persist-credentials: true - name: Set up JDK uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: java-version: 25 distribution: temurin - uses: sbt/setup-sbt@93e926cbdb4a428e41b4ef754124ec82925ffdc2 # v1.1.23 - name: Update JUnit dependencies in examples run: java src/Updater.java ${RELEASE_VERSION} - name: Build examples if: ${{ inputs.dryRun == false }} run: java src/Builder.java - name: Create release branch run: | git config user.name "JUnit Team" git config user.email "team@junit.org" git switch -c "${RELEASE_TAG}" git status git commit -a -m "Use ${RELEASE_VERSION}" - name: Push release branch if: ${{ inputs.dryRun == false }} run: | git push origin "${RELEASE_TAG}" - name: Update main branch (only for GA releases) if: ${{ inputs.dryRun == false && !contains(inputs.releaseVersion, '-') }} run: | git fetch origin main git switch main git merge --ff-only "${RELEASE_TAG}" git push origin main create_github_release: name: Create GitHub release if: ${{ inputs.dryRun == false }} needs: [ publish_documentation, update_examples ] runs-on: ubuntu-latest permissions: contents: write steps: - name: Check out repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 persist-credentials: false - name: Create GitHub release uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const createGithubRelease = require('./.github/scripts/create-github-release.js'); createGithubRelease({ github, context }); ================================================ FILE: .github/workflows/reproducible-build.yml ================================================ name: Reproducible build on: push: branches: - main - 'releases/**' pull_request: branches: - '**' concurrency: # Cancels in-progress runs only for pull requests group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true permissions: {} env: DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} jobs: check_build_reproducibility: name: 'Check build reproducibility' runs-on: ubuntu-latest steps: - name: Check out repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 persist-credentials: false - name: Restore Gradle cache and display toolchains uses: ./.github/actions/run-gradle with: encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }} # Disable configuration cache due to https://github.com/diffplug/spotless/issues/2318 arguments: >- --quiet --no-configuration-cache - name: Build and compare checksums shell: bash run: ./.github/scripts/checkBuildReproducibility.sh ================================================ FILE: .github/workflows/sanitize-closed-issues.yml ================================================ name: Sanitizes assigned labels and milestone on closed issues on: issues: types: - closed permissions: {} jobs: label_issues: runs-on: ubuntu-latest permissions: issues: write steps: - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const issue = await github.rest.issues.get({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, }); const originalLabels = issue.data.labels.map(l => l.name); const newLabels = originalLabels.filter(l => l !== "status: in progress" && l !== "status: new" && l !== "status: waiting-for-feedback" && l !== "status: waiting-for-interest"); if (newLabels.length !== originalLabels.length) { await github.rest.issues.update({ issue_number: issue.data.number, owner: context.repo.owner, repo: context.repo.repo, labels: newLabels, }); } if (issue.data.state_reason === "not_planned" || issue.data.state_reason === "duplicate") { if (issue.data.milestone) { await github.rest.issues.update({ issue_number: issue.data.number, owner: context.repo.owner, repo: context.repo.repo, milestone: null, }); } const statusLabels = newLabels.filter(l => l.startsWith("status: ")); if (statusLabels.length === 0) { if (issue.data.state_reason === "not_planned") { await github.rest.issues.createComment({ issue_number: issue.data.number, owner: context.repo.owner, repo: context.repo.repo, body: "Please assign a status label to this issue.", }); await github.rest.issues.update({ issue_number: issue.data.number, owner: context.repo.owner, repo: context.repo.repo, state: "open", }); } else { newLabels.push("status: duplicate"); await github.rest.issues.update({ issue_number: issue.data.number, owner: context.repo.owner, repo: context.repo.repo, labels: newLabels, }); } } } else { if (!(newLabels.includes("type: task") || newLabels.includes("type: question")) && !issue.data.milestone) { let collaborator; try { await github.rest.repos.checkCollaborator({ owner: context.repo.owner, repo: context.repo.repo, username: context.actor, }); collaborator = true; } catch (error) { collaborator = false; } if (collaborator) { await github.rest.issues.createComment({ issue_number: issue.data.number, owner: context.repo.owner, repo: context.repo.repo, body: "Please assign a milestone to this issue or label it with `type: task` or `type: question` – or assign a status label and close it as _not planned_.", }); await github.rest.issues.update({ issue_number: issue.data.number, owner: context.repo.owner, repo: context.repo.repo, state: "open", }); } else { const statusLabels = newLabels.filter(l => l.startsWith("status: ")); if (statusLabels.length === 0) { newLabels.push("status: invalid"); } await github.rest.issues.update({ issue_number: issue.data.number, owner: context.repo.owner, repo: context.repo.repo, labels: newLabels, state: "closed", state_reason: "not_planned", }); } } } ================================================ FILE: .github/workflows/zizmor-analysis.yml ================================================ name: GitHub Actions Security Analysis on: push: branches: - main - 'releases/**' paths: - '.github/**' pull_request: paths: - '.github/**' permissions: {} jobs: zizmor: name: Run zizmor 🌈 runs-on: ubuntu-latest permissions: security-events: write steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Run zizmor 🌈 uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3 ================================================ FILE: .github/zizmor.yml ================================================ rules: cache-poisoning: ignore: # reports issues for setup-node which isn't used while releasing - main.yml secrets-outside-env: disable: true ================================================ FILE: .gitignore ================================================ # Gradle .gradle .kotlin build # Ignore Gradle GUI config gradle-app.setting # Eclipse .classpath .settings/ .project /bin/ /*/bin /gradle/plugins/*/bin # IntelliJ *.iml *.ipr *.iws *.uml **/.idea/* !/.idea/icon.png !/.idea/vcs.xml /out/ /*/out/ # Misc *.log *.graphml coverage.db* .metadata /.sdkmanrc /.tool-versions checksums* # snapshot-tests *.snapshot_actual *.snapshot_raw # Antora /documentation/node_modules/ ================================================ FILE: .idea/vcs.xml ================================================ ================================================ FILE: .jitpack.yml ================================================ before_install: - sdk update - sdk install java 25-open - sdk use java 25-open install: - | ./gradlew \ --show-version \ -Pjitpack.version=$VERSION \ -Ppublishing.group=$GROUP.$ARTIFACT \ -Ppublishing.signArtifacts=false \ javaToolchains \ publishToMavenLocal ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing ## Getting Started We welcome new contributors to the project! If you're interested, please check for [issues labeled with `up-for-grabs` that are not yet in progress](https://github.com/junit-team/junit-framework/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20label%3Aup-for-grabs%20-label%3A%22status%3A%20in%20progress%22). Generally, before you work on an issue, post a comment and ask whether it can be started. Please wait for the core team to respond and assign the issue to you before making any code changes. ## JUnit Contributor License Agreement - You will only Submit Contributions where You have authored 100% of the content. - You will only Submit Contributions to which You have the necessary rights. This means that if You are employed You have received the necessary permissions from Your employer to make the Contributions. - Whatever content You Contribute will be provided under the Project License(s). ### Project Licenses - All modules use [Eclipse Public License v2.0](LICENSE.md). ## Commit Messages As a general rule, the style and formatting of commit messages should follow the guidelines in [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/). In addition, any commit that is related to an existing issue must reference the issue. For example, if a commit in a pull request addresses issue \#999, it must contain the following at the bottom of the commit message. ``` Issue: #999 ``` ## Pull Requests Our [Definition of Done](https://github.com/junit-team/junit-framework/wiki/Definition-of-Done) (DoD) offers some guidelines on what we expect from a pull request. Feel free to open a pull request that does not fulfill all criteria, e.g. to discuss a certain change before polishing it, but please be aware that we will only merge it once the DoD is met. Please add the following lines to your pull request description: ```markdown --- I hereby agree to the terms of the JUnit Contributor License Agreement. ``` ## Coding Conventions ### Naming Conventions Acronyms are words. Whenever an acronym is included as part of a type name or method name, keep the first letter of the acronym uppercase and use lowercase for the rest of the acronym. Otherwise, it becomes _impossible_ to perform camel-cased searches in IDEs, and it becomes potentially very difficult for mere humans to read or reason about the element without reading documentation (if documentation even exists). Consider for example a use case needing to support an HTTP URL. Calling the method `getHTTPURL()` is absolutely horrible in terms of usability; whereas, `getHttpUrl()` is great in terms of usability. The same applies for types `HTTPURLProvider` vs `HttpUrlProvider`, etc. Whenever an acronym is included as part of a field name or parameter name: - If the acronym comes at the start of the field or parameter name, use lowercase for the entire acronym -- for example, `String url;`. - Otherwise, keep the first letter of the acronym uppercase and use lowercase for the rest of the acronym -- for example, `String defaultUrl;`. ### Formatting #### Code Code formatting is enforced using the [Spotless](https://github.com/diffplug/spotless) Gradle plugin. You can use `gradle spotlessApply` to format new code and add missing license headers to source files. Formatter and import order settings for Eclipse are available in the repository under [junit-eclipse-formatter-settings.xml](gradle/config/eclipse/junit-eclipse-formatter-settings.xml) and [junit-eclipse.importorder](gradle/config/eclipse/junit-eclipse.importorder), respectively. For IntelliJ IDEA there's a [plugin](https://plugins.jetbrains.com/plugin/6546) you can use in conjunction with the Eclipse settings. It is forbidden to use _wildcard imports_ (e.g., `import static org.junit.jupiter.api.Assertions.*;`) in Java code. #### Documentation Text in `*.adoc` and `*.md` files should be wrapped at 90 characters whenever technically possible. In multi-line bullet point entries, subsequent lines should be indented. ### Spelling Use American English spelling rules when writing documentation as well as for code -- class names, method names, variable names, etc. ### Javadoc - Javadoc comments should be wrapped after 80 characters whenever possible. - This first paragraph must be a single, concise sentence that ends with a period (`.`). - Place `

` on the same line as the first line of a new paragraph and precede `

` with a blank line. - Insert a blank line before at-clauses/tags. - Favor `{@code foo}` over `foo`. - Favor literals (e.g., `{@literal @}`) over HTML entities. - New classes and methods should declare a `@since ...` tag. - Use `@since 5.10` instead of `@since 5.10.0`. - Do not use `@author` tags. Instead, contributors are listed on the [GitHub](https://github.com/junit-team/junit-framework/graphs/contributors) page. - Do not use verbs in third-person form in the first sentence of the Javadoc for a method -- for example, use "Discover tests..." instead of "Discovers tests...". #### Examples See [`ExtensionContext`](junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java) and [`ParameterContext`](junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterContext.java) for example Javadoc. ### Constant fields A constant field is a `static final` field whose value is immutable. If a static final field has a primitive type or an immutable reference type it is a constant field. - To minimize accessibility and mutability for all non-private `static final` fields under `src/main` should be constant fields. - Constant fields should be named using uppercase words separated by underscores. For example `DEFAULT_HTTP_URL_PROVIDER`. Note: `org.junit.platform.commons.logging.Logger` is considered mutable. ### Nullability This project uses JSpecify's annotation to indicate nullability. In general, the approach is as follows: - All packages are annotated with `@NullMarked` - Types of fields, parameters, return types etc. may be annotated with `@Nullable` ### Tests #### Naming - All test classes must end with a `Tests` suffix. - Example test classes that should not be picked up by the build must end with a `TestCase` suffix. #### Assertions - Use `org.junit.jupiter.api.Assertions` for simple assertions. - Use AssertJ when richer assertions are needed. - Do not use `org.junit.Assert` or `junit.framework.Assert`. #### Mocking and Stubbing - Use either [Mockito](https://github.com/mockito/mockito) or hand-written test doubles. ### Logging - In general, logging should be used sparingly. - All logging must be performed via the internal `Logger` façade provided via the JUnit [LoggerFactory](https://github.com/junit-team/junit-framework/blob/main/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LoggerFactory.java). - Levels defined in JUnit's [Logger](https://github.com/junit-team/junit-framework/blob/main/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/Logger.java) façade, which delegates to Java Util Logging (JUL) for the actual logging. - _error_ (JUL: `SEVERE`, Log4J: `ERROR`): extra information (in addition to an Exception) about errors that will halt execution - _warn_ (JUL: `WARNING`, Log4J: `WARN`): potential usage or configuration errors that should not halt execution - _info_ (JUL: `INFO`, Log4J: `INFO`): information the users might want to know but not by default - _config_ (JUL: `CONFIG`, Log4J: `CONFIG`): information related to configuration of the system (Example: `ServiceLoaderTestEngineRegistry` logs IDs of discovered engines) - _debug_ (JUL: `FINE`, Log4J: `DEBUG`) - _trace_ (JUL: `FINER`, Log4J: `TRACE`) ### Deprecation The JUnit project uses the `@API` annotation from [API Guardian](https://github.com/apiguardian-team/apiguardian). Publicly available interfaces, classes, and methods have a defined lifecycle which is described in detail in the [User Guide](https://docs.junit.org/current/api-evolution.html). That following describes the deprecation process followed for API items. To deprecate an item: - Update the `@API.status` to `DEPRECATED`. - Update `@API.since`. Please note `since` describes the version when the status was changed and not the introduction of the element. - Add the `@Deprecated` Java annotation on the item. - Add the `@deprecated` JavaDoc tag to describe the deprecation, and refer to an eventual replacement. - If the item is used in existing code, add `@SuppressWarnings("deprecation")` to make the build pass. ## Building the Project Please refer to [the readme](README.md#building-from-source) and [the documentation readme](documentation/README.md) for the most common build commands. ### Build Cache Local builds can reuse outputs from previous CI builds via Gradle's [Build Cache](https://docs.gradle.org/current/userguide/build_cache.html). The default build cache server is located in the US. If you're in Europe, you can configure the build to use a build cache node located in the EU by adding the following to the `gradle.properties` file in your [Gradle user home](https://docs.gradle.org/current/userguide/directory_layout.html#dir:gradle_user_home): ```properties junit.develocity.buildCache.server=https://eu-build-cache-ge.junit.org ``` ### Build Parameters The build can be influenced by a number of parameters. For example, measuring JaCoCo code coverage of Test tasks can be enabled, or Predictive Test Selection disabled. To see the full list, please run the following task: `./gradlew :plugins:build-parameters:parameters` ================================================ FILE: KEYS ================================================ This file contains the PGP key that is used to sign releases. Importing: `pgp < KEYS` or `gpg --import KEYS` Adding a key: `pgp -kxa >> KEYS`, or `(pgpk -ll && pgpk -xa ) >> KEYS`, or `(gpg --list-sigs && gpg --armor --export ) >> KEYS` ================================ pub rsa4096 2018-04-08 [SC] FF6E2C001948C5F2F38B0CC385911F425EC61B51 uid [ unknown] Open Source Development sig 3 85911F425EC61B51 2018-04-08 Open Source Development sub rsa4096 2018-04-08 [E] sig 85911F425EC61B51 2018-04-08 Open Source Development -----BEGIN PGP PUBLIC KEY BLOCK----- mQINBFrKW9IBEACkqUvM7hU1WqOOeb1gZ7pUsRliHuoUvYIrd+hdp+qhPmJ0NG0W YhZK5UtJBmqvtHKRkbwYxUuya9zlBmCfQFf0GpFKJ65JSrPSkZADI3aZ4aUkxIUw nIRoUHucmr10Xftpebr/zaJk5oR8RdaL5FapapmcZmAaHR9CDWB8XtI318u314jq M5rKatnAZMERoPugOvvuAOz4bfZKwdfCmZKfYUM/TMSrSinXrGExSW6z4RhtqmpC E5M/7OoVfvDynVJKqNazqgigpmMNhOyzAhQsiKh1K0akyxTZbjeZKsdYfhCXvq0q k9+KM/cTllQ54MPnFWiObLkHeK0Waw8bI/vAJ4h4x/XM9iGYpkXv7F2/FVsHQdPe YJcwD/CkD8KHyiPaRKMeApiUtZsdAHU0L4X/lNmcooea/7ipskruUgwcm+RdLhRZ P949t1e7nqDZfpEHy90NiFxmlRAPSNqBLwefxY/hwBgog2jabDALJVcLCMosFWPj MQhFlGSIODiVcW8folGIjzkyNZbNMWkwnl2QnWp/h2TAwYQJOMqcv2MG9o5pyzpx 97Iz1ngq1FlM/gJnGnNUydP2tAjT2L2U3MP1uX/EdRChdgPqdolqYhdFfwCr0Fpf W527bUZpReHCEiQ29ABSnQ711mO+d9+qM6edRyHUoBWz89IHt8sCunuvNwARAQAB tC1PcGVuIFNvdXJjZSBEZXZlbG9wbWVudCA8bWFpbEBtYXJjcGhpbGlwcC5kZT6J Ak4EEwEIADgWIQT/biwAGUjF8vOLDMOFkR9CXsYbUQUCWspb0gIbAwULCQgHAgYV CAkKCwIEFgIDAQIeAQIXgAAKCRCFkR9CXsYbUQyRD/9xm3BqdpWcRCE5UyB6nbwV 8TgzMmbOhpFhhcjzobly/pKAbcofKsjhreENJkfBVUo+zAFx21ToC5tbH20wRtIE vQVCP6sAIzhYWU1ohafqVFP4+PztNBuYTnS6vGvSwzp0IXLIIoxSxo0IOED9uUS9 DTxh1n9NnDLDe2pfjrXBblQtLSW3W5ISDoUvcoyO7Hk1OByW6MNsSoLvXIUNeVhB ju9TfYxFACJSWBhUxJfgip9Y2GrNBJaYGLZrTAoW1Lh1H1DfLV3wHDClQ1+H+oyx IOZULEGYY3MgZTd6Ner2yNAUCB7gVa50NiCZXCS74m+XzMrTEsdWjSMUaOe+dL0I 9MCrgi4ycUHWIfTKx9gGlIOo3hSDMN+8Nj33XPjLT8kcfoFeUX8jTOvC1HFfTuQJ x2t/dKHizdrS3F6A/JQa7v8GNTrZFnEXkwgRTf3ccLoo3gPwzNJeCm2xNjvne1VH fvxzwNmq8M05oicEigvEed2VMStMhvT7dSiMAf66rEJHjjaHAoNqbLDEATYrWUP2 I52txHSSxSJohxVP6Ec6dERnqqYi0mVyzBPo7mmFFBisq74w8RvZXyzvXE3BTiDL we1E/Z/AXbtJye9DickQ/G6RFtVLbUHQfzyRS/65JPtlH8rqJr58YWlylGImVLwE OsKNQrwLZ0UkfaWV7wqr3rkCDQRaylvSARAAnQG636wliEOLkXN662OZS6Qz2+cF ltCWboq9oX9FnA1PHnTY2cAtwS214RfWZxkjg6Stau+d1Wb8TsF/SUN3eKRSyrkA xlX0v552vj3xmmfNsslQX47e6aEWZ0du0M8jw7/f7Qxp0InkBfpQwjSg4ECoH4cA 6dOFJIdxBv8dgS4K90HNuIHa+QYfVSVMjGwOjD9St6Pwkbg1sLedITRo59Bbv0J1 4nE9LdWbCiwNrkDr24jTewdgrDaCpN6msUwcH1E0nYxuKAetHEi2OpgBhaY3RQ6Q PQB6NywvmD0xRllMqu4hSp70pHFtm8LvJdWOsJ5we3KijHuZzEbBVTTl+2DhNMI0 KMoh+P/OmyNOfWD8DL4NO3pVv+mPDZn82/eZ3XY1/oSQrpyJaCBjRKasVTtfiA/F gYqTml6qZMjy6iywg84rLezELgcxHHvjhAKd4CfxyuCCgnGT0iRLFZKw44ZmOUqP DkyvGRddIyHag1K7UaM/2UMn6iPMy7XWcaFiH5Huhz43SiOdsWGuwNk4dDxHdxmz Sjps0H5dkfCciOFhEc54AFcGEXCWHXuxVqIq/hwqTmVl1RY+PTcQUIOfx36WW1ix JQf8TpVxUbooK8vr1jOFF6khorDXoZDJNhI2VKomWp8Y38EPGyiUPZNcnmSiezx+ MoQwAbeqjFMKG7UAEQEAAYkCNgQYAQgAIBYhBP9uLAAZSMXy84sMw4WRH0JexhtR BQJaylvSAhsMAAoJEIWRH0JexhtR0LEP/RvYGlaokoosAYI5vNORAiYEc1Ow2McP I1ZafHhcVxZhlwF48dAC2bYcasDX/PbEdcD6pwo8ZU8eI8Ht0VpRQxeV/sP01m2Y EpAuyZ6jI7IQQCGcwQdN4qzQJxMAASl9JlplH2NniXV1/994FOtesT59ePMyexm5 7lzhYXP1PGcdt8dH37r6z3XQu0lHRG/KBn7YhyA3zwJcno324KdBRJiynlc7uqQq +ZptU9fR1+Nx0uoWZoFMsrQUmY34aAOPJu7jGMTG+VseMH6vDdNhhZs9JOlD/e/V aF7NyadjOUD4j/ud7c0z2EwqjDKMFTHGbIdawT/7jartT+9yGUO+EmScBMiMuJUT dCP4YDh3ExRdqefEBff3uE/rAP73ndNYdIVq9U0gY0uSNCD9JPfj4aCN52y9a2pS 7Dg7KB/Z8SH1R9IWP+t0HvVtAILdsLExNFTedJGHRh7uaC7pwRz01iivmtAKYICz ruqlJie/IdEFFK/sus6fZek29odTrQxx42HGHO5GCNyEdK9jKVAeuZ10vcaNbuBp iP7sf8/BsiEU4wHE8gjFeUPRiSjnERgXQwfJosLgf/K/SShQn2dCkYZRNF+SWJ6Z 2tQxcW5rpUjtclV/bRVkUX21EYfwA6SMB811mI7AVy8WPXCe8La72ukmaxEGbpJ8 mdzS2PJko7mm =l0XA -----END PGP PUBLIC KEY BLOCK----- ================================================ FILE: LICENSE.md ================================================ Eclipse Public License - v 2.0 ============================== THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE (“AGREEMENT”). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. ### 1. Definitions “Contribution” means: * **a)** in the case of the initial Contributor, the initial content Distributed under this Agreement, and * **b)** in the case of each subsequent Contributor: * **i)** changes to the Program, and * **ii)** additions to the Program; where such changes and/or additions to the Program originate from and are Distributed by that particular Contributor. A Contribution “originates” from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include changes or additions to the Program that are not Modified Works. “Contributor” means any person or entity that Distributes the Program. “Licensed Patents” mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. “Program” means the Contributions Distributed in accordance with this Agreement. “Recipient” means anyone who receives the Program under this Agreement or any Secondary License (as applicable), including Contributors. “Derivative Works” shall mean any work, whether in Source Code or other form, that is based on (or derived from) the Program and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. “Modified Works” shall mean any work in Source Code or other form that results from an addition to, deletion from, or modification of the contents of the Program, including, for purposes of clarity any new file in Source Code form that contains any contents of the Program. Modified Works shall not include works that contain only declarations, interfaces, types, classes, structures, or files of the Program solely in each case in order to link to, bind by name, or subclass the Program or Modified Works thereof. “Distribute” means the acts of **a)** distributing or **b)** making available in any manner that enables the transfer of a copy. “Source Code” means the form of a Program preferred for making modifications, including but not limited to software source code, documentation source, and configuration files. “Secondary License” means either the GNU General Public License, Version 2.0, or any later versions of that license, including any exceptions or additional permissions as identified by the initial Contributor. ### 2. Grant of Rights **a)** Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, Distribute and sublicense the Contribution of such Contributor, if any, and such Derivative Works. **b)** Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in Source Code or other form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. **c)** Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to Distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. **d)** Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. **e)** Notwithstanding the terms of any Secondary License, no Contributor makes additional grants to any Recipient (other than those set forth in this Agreement) as a result of such Recipient's receipt of the Program under the terms of a Secondary License (if permitted under the terms of Section 3). ### 3. Requirements **3.1** If a Contributor Distributes the Program in any form, then: * **a)** the Program must also be made available as Source Code, in accordance with section 3.2, and the Contributor must accompany the Program with a statement that the Source Code for the Program is available under this Agreement, and informs Recipients how to obtain it in a reasonable manner on or through a medium customarily used for software exchange; and * **b)** the Contributor may Distribute the Program under a license different than this Agreement, provided that such license: * **i)** effectively disclaims on behalf of all other Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; * **ii)** effectively excludes on behalf of all other Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; * **iii)** does not attempt to limit or alter the recipients' rights in the Source Code under section 3.2; and * **iv)** requires any subsequent distribution of the Program by any party to be under a license that satisfies the requirements of this section 3. **3.2** When the Program is Distributed as Source Code: * **a)** it must be made available under this Agreement, or if the Program **(i)** is combined with other material in a separate file or files made available under a Secondary License, and **(ii)** the initial Contributor attached to the Source Code the notice described in Exhibit A of this Agreement, then the Program may be made available under the terms of such Secondary Licenses, and * **b)** a copy of this Agreement must be included with each copy of the Program. **3.3** Contributors may not remove or alter any copyright, patent, trademark, attribution notices, disclaimers of warranty, or limitations of liability (“notices”) contained within the Program from any copy of the Program which they Distribute, provided that Contributors may add their own appropriate notices. ### 4. Commercial Distribution Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor (“Commercial Contributor”) hereby agrees to defend and indemnify every other Contributor (“Indemnified Contributor”) against any losses, damages and costs (collectively “Losses”) arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: **a)** promptly notify the Commercial Contributor in writing of such claim, and **b)** allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. ### 5. No Warranty EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. ### 6. Disclaimer of Liability EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. ### 7. General If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be Distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to Distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. Nothing in this Agreement is intended to be enforceable by any entity that is not a Contributor or Recipient. No third-party beneficiary rights are created under this Agreement. #### Exhibit A - Form of Secondary Licenses Notice > “This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), version(s), and exceptions or additional permissions here}.” Simply including a copy of this Agreement, including this Exhibit A is not sufficient to license the Source Code under Secondary Licenses. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. ================================================ FILE: NOTICE.md ================================================ Open Source Licenses ==================== This product may include a number of subcomponents with separate copyright notices and license terms. Your use of the source code for these subcomponents is subject to the terms and conditions of the subcomponent's license, as noted in the `LICENSE-[.md]` files. ================================================ FILE: README.md ================================================

JUnit

This repository is the home of JUnit Platform, Jupiter, and Vintage. ## Sponsors [![Support JUnit](https://img.shields.io/badge/%F0%9F%92%9A-Support%20JUnit-brightgreen.svg)](https://junit.org/sponsoring) * **Gold Sponsors:** [JetBrains](https://jb.gg/junit-logo), [Netflix](https://www.netflix.com/) * **Silver Sponsors:** [Micromata](https://www.micromata.de), [Quo Card](https://quo-digital.jp) * **Bronze Sponsors:** [Premium Minds](https://www.premium-minds.com), [codefortynine](https://codefortynine.com), [Info Support](https://www.infosupport.com), [Code Intelligence](https://www.code-intelligence.com), [Route4Me](https://route4me.com/), [Testiny](https://www.testiny.io/), [TestMu AI](https://www.testmuai.com/?utm_medium=sponsor&utm_source=junit) ## Latest Releases - General Availability (GA): [JUnit 6.0.3](https://github.com/junit-team/junit-framework/releases/tag/r6.0.3) (February 15, 2026) - Preview (Milestone/Release Candidate): [JUnit 6.1.0-RC1](https://github.com/junit-team/junit-framework/releases/tag/r6.1.0-RC1) (April 25, 2026) ## Documentation - [User Guide] - [Javadoc] - [Release Notes] - [Examples] ## Contributing Contributions to JUnit are both welcomed and appreciated. For specific guidelines regarding contributions, please see [CONTRIBUTING.md] in the root directory of the project. Those willing to use milestone or SNAPSHOT releases are encouraged to file feature requests and bug reports using the project's [issue tracker](https://github.com/junit-team/junit-framework/issues). Issues marked with an `up-for-grabs` label are specifically targeted for community contributions. ## Getting Help Ask JUnit-related questions on [StackOverflow] or use the Q&A category on [GitHub Discussions]. ## Continuous Integration Builds [![CI](https://github.com/junit-team/junit-framework/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/junit-team/junit-framework/actions/workflows/main.yml) [![Cross-Version](https://github.com/junit-team/junit-framework/actions/workflows/cross-version.yml/badge.svg?branch=main)](https://github.com/junit-team/junit-framework/actions/workflows/cross-version.yml) Official CI build server used to perform quick checks on submitted pull requests and for build matrices including the latest released OpenJDK and early access builds of the next OpenJDK. ## Code Coverage Code coverage using [JaCoCo] for the latest build is available on [Codecov]. A code coverage report can also be generated locally via the [Gradle Wrapper] by executing `./gradlew clean jacocoRootReport`. The results will be available in `build/reports/jacoco/jacocoRootReport/html/index.html`. ## Develocity [![Revved up by Develocity](https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.junit.org/scans) JUnit utilizes [Develocity](https://gradle.com/) for [Build Scans](https://scans.gradle.com/), [Build Cache](https://docs.gradle.org/current/userguide/build_cache.html), and [Predictive Test Selection](https://docs.gradle.com/enterprise/predictive-test-selection/). The latest Build Scans are available on [ge.junit.org](https://ge.junit.org/). Currently, only core team members can publish Build Scans on that server. You can, however, publish a Build Scan to [scans.gradle.com](https://scans.gradle.com/) by using the `--scan` parameter explicitly. The remote Build Cache is enabled by default for everyone so that local builds can reuse task outputs from previous CI builds. ## Building from Source You need [JDK 25] to build JUnit. [Gradle toolchains] are used to detect and potentially download additional JDKs for compilation and test execution. All modules can be _built_ and _tested_ with the [Gradle Wrapper] using the following command: `./gradlew build` All modules can be _installed_ in a local Maven repository for consumption in other local projects via the following command: `./gradlew publishToMavenLocal` ## Dependency Metadata [![JUnit Jupiter version](https://img.shields.io/maven-central/v/org.junit.jupiter/junit-jupiter/6..svg?color=25a162&label=Jupiter)](https://central.sonatype.com/search?namespace=org.junit.jupiter) [![JUnit Vintage version](https://img.shields.io/maven-central/v/org.junit.vintage/junit-vintage-engine/6..svg?color=25a162&label=Vintage)](https://central.sonatype.com/search?namespace=org.junit.vintage) [![JUnit Platform version](https://img.shields.io/maven-central/v/org.junit.platform/junit-platform-commons/6..svg?color=25a162&label=Platform)](https://central.sonatype.com/search?namespace=org.junit.platform) Consult the [Dependency Metadata] section of the [User Guide] for a list of all artifacts of the JUnit Platform, JUnit Jupiter, and JUnit Vintage. [Codecov]: https://codecov.io/gh/junit-team/junit-framework [CONTRIBUTING.md]: https://github.com/junit-team/junit-framework/blob/HEAD/CONTRIBUTING.md [Dependency Metadata]: https://docs.junit.org/current/appendix.html#dependency-metadata [GitHub Discussions]: https://github.com/junit-team/junit-framework/discussions/categories/q-a [Gradle toolchains]: https://docs.gradle.org/current/userguide/toolchains.html [Gradle Wrapper]: https://docs.gradle.org/current/userguide/gradle_wrapper.html#sec:using_wrapper [JaCoCo]: https://www.eclemma.org/jacoco/ [Javadoc]: https://api.junit.org [JDK 25]: https://javaalmanac.io/jdk/25/ [Release Notes]: https://docs.junit.org/current/release-notes.html [Examples]: https://github.com/junit-team/junit-examples [StackOverflow]: https://stackoverflow.com/questions/tagged/junit5 [User Guide]: https://docs.junit.org ================================================ FILE: RELEASING.md ================================================ # Releasing ## Pre-release steps - [ ] Switch or create the release branch for this feature release (e.g. `releases/5.12.x`) - [ ] Change `version` in `gradle.properties` to the versions about to be released - [ ] Change release date in Release Notes - [ ] Change release date in `README.MD` - [ ] Commit with message "Release ${VERSION}" - [ ] Execute `./gradlew --no-build-cache --no-configuration-cache clean build publishAggregationToCentralPortal` - [ ] Tag current commit: `git tag -s -m ${VERSION} r${VERSION}` - [ ] Change `version` in `gradle.properties` on release branch to new development versions and commit with message "Back to snapshots for further development" or similar - [ ] Push release branch and tag to GitHub: `git push --set-upstream --follow-tags origin HEAD` - [ ] Trigger a [release build](https://github.com/junit-team/junit-framework/actions/workflows/release.yml): `gh workflow run --ref r${VERSION} -f releaseVersion=${VERSION} -f deploymentId=${DEPLOYMENT_ID} release.yml` - Select the release branch - Enter the version to be released - Enter the staging repository ID from the output of above Gradle build ## Post-release steps - [ ] Post about the new release: - [ ] [Mastodon](https://fosstodon.org/@junit) - [ ] [Bluesky](https://bsky.app/profile/junit.org) ### Preview releases (milestones and release candidates) - [ ] Fast-forward merge the release branch to `main` and push to GitHub - [ ] Create release notes for the next preview or feature release from the template ### Feature releases (x.y.0) - [ ] Delete `accepted-breaking-changes.csv` (if it exists), change `previousVersion` in `gradle.properties` to `x.y.0` on the release branch, and commit with message "Update API baseline and clear accepted breaking changes" - [ ] Fast-forward merge the release branch to `main` and push to GitHub - [ ] Update the [security policy](https://github.com/junit-team/junit-framework/blob/main/SECURITY.md) and commit with message "Update security policy to reflect 5.x release" or similar - [ ] Create release notes for the next feature release from the template - [ ] Update [JBang catalog](https://github.com/junit-team/jbang-catalog/blob/main/jbang-catalog.json) ### Patch releases (x.y.z) - [ ] Cherry-pick the tagged commit from the release branch to `main` and resolve the conflict in `gradle.properties` by choosing the version of the `main` branch - [ ] Delete `accepted-breaking-changes.csv` (if it exists), change `previousVersion` in `gradle.properties` to `x.y.z` on the release branch, and commit with message "Update API baseline and clear accepted breaking changes" - [ ] Include the release notes of the patch release on `main` if not already present - [ ] Update [JBang catalog](https://github.com/junit-team/jbang-catalog/blob/main/jbang-catalog.json) ================================================ FILE: SECURITY.md ================================================ # Security Policy [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/9607/badge)](https://www.bestpractices.dev/projects/9607) [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/junit-team/junit-framework/badge)](https://scorecard.dev/viewer/?uri=github.com/junit-team/junit-framework) ## JAR Signing JUnit JARs released on Maven Central are signed. You'll find more information about the key here: [KEYS](./KEYS) ## Supported Versions | Version | Supported | |---------| ------------------ | | 6.0.x | :white_check_mark: | | 5.14.x | :white_check_mark: | | < 5.14 | :x: | ## Reporting a Vulnerability To report a security vulnerability, you have two options: - [Privately report a vulnerability](https://github.com/junit-team/junit-framework/security/advisories/new) on GitHub (see [docs](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability) for details) - Send an email to security@junit.org. You can use the [published OpenPGP key](https://keys.openpgp.org/search?q=security%40junit.org) with fingerprint `0152DA30EABC7ABADCB09D10D9A6B1329D191D25` to encrypt the message body. ================================================ FILE: build.gradle.kts ================================================ import junitbuild.extensions.dependencyProject plugins { id("junitbuild.base-conventions") id("junitbuild.build-metadata") id("junitbuild.checkstyle-nohttp") id("junitbuild.jacoco-aggregation-conventions") id("junitbuild.temp-maven-repo") } description = "JUnit" group = "org.junit" val license by extra(License( name = "Eclipse Public License v2.0", url = uri("https://www.eclipse.org/legal/epl-v20.html"), headerFile = layout.projectDirectory.file("gradle/config/spotless/eclipse-public-license-2.0.java") )) val platformProjects by extra(listOf( projects.junitPlatformCommons, projects.junitPlatformConsole, projects.junitPlatformConsoleStandalone, projects.junitPlatformEngine, projects.junitPlatformLauncher, projects.junitPlatformReporting, projects.junitPlatformSuite, projects.junitPlatformSuiteApi, projects.junitPlatformSuiteEngine, projects.junitPlatformTestkit ).map { dependencyProject(it) }) val jupiterProjects by extra(listOf( projects.junitJupiter, projects.junitJupiterApi, projects.junitJupiterEngine, projects.junitJupiterMigrationsupport, projects.junitJupiterParams ).map { dependencyProject(it) }) val vintageProjects by extra(listOf( dependencyProject(projects.junitVintageEngine) )) val mavenizedProjects by extra(listOf(dependencyProject(projects.junitStart)) + platformProjects + jupiterProjects + vintageProjects) val modularProjects by extra(mavenizedProjects - setOf(dependencyProject(projects.junitPlatformConsoleStandalone))) dependencies { modularProjects.forEach { jacocoAggregation(it) } jacocoAggregation(projects.documentation) jacocoAggregation(projects.jupiterTests) jacocoAggregation(projects.platformTests) } ================================================ FILE: documentation/.tool-versions ================================================ nodejs 24.15.0 ================================================ FILE: documentation/README.md ================================================ # JUnit User Guide This subproject contains the Antora/AsciiDoc sources for the JUnit User Guide. ## Structure - `modules/ROOT/pages`: AsciiDoc files - `src/test/java`: Java test source code that can be included in the AsciiDoc files - `src/test/kotlin`: Kotlin test source code that can be included in the AsciiDoc files - `src/test/resources`: Classpath resources that can be included in the AsciiDoc files or used in tests ## Usage ### Generate Antora site This following Gradle command generates the HTML version of the User Guide as `build/antora/build/site`. ``` ./gradlew antora ``` On Linux operating systems, the `graphviz` package providing `/usr/bin/dot` must be installed in order to generate the PlantUML images used in the User Guide. ================================================ FILE: documentation/antora-playbook.yml ================================================ antora: extensions: - '@antora/collector-extension' - require: '@antora/lunr-extension' index_latest_only: true index_by_heading: true - require: '@springio/antora-extensions/root-component-extension' root_component_name: junit - require: '@springio/antora-extensions/root-attachments-extension' - require: '@springio/antora-xref-extension' site: title: JUnit User Guide start_page: junit::overview.adoc content: sources: - url: @GIT_REPO_ROOT@ branches: @GIT_BRANCH_NAME@ start_path: documentation worktrees: true asciidoc: extensions: - '@asciidoctor/tabs' attributes: page-pagination: '' idprefix: '@' idseparator: '-@' table-caption: false table-frame: 'none@' table-grid: 'rows@' table-stripes: 'odd@' figure-caption: false example-caption: false listing-caption: false hide-uri-scheme: '@' attribute-missing: warn tabs-sync-option: '' runtime: log: failure_level: warn ui: bundle: url: https://gitlab.com/antora/antora-ui-default/-/jobs/artifacts/HEAD/raw/build/ui-bundle.zip?job=bundle-stable snapshot: true supplemental_files: - path: css/vendor/tabs.css contents: @GIT_REPO_ROOT@/documentation/node_modules/@asciidoctor/tabs/dist/css/tabs.css - path: js/vendor/tabs.js contents: @GIT_REPO_ROOT@/documentation/node_modules/@asciidoctor/tabs/dist/js/tabs.js - path: partials/footer-scripts.hbs contents: | {{#if env.SITE_SEARCH_PROVIDER}} {{> search-scripts}} {{/if}} - path: partials/head-styles.hbs contents: | ================================================ FILE: documentation/antora.yml ================================================ name: junit version: true title: JUnit nav: - modules/ROOT/nav.adoc - modules/ROOT/extra-site-nav.adoc ext: collector: run: command: gradlew --quiet :documentation:generateAntoraResources local: true scan: - dir: ./build/generated-antora-resources clean: true - dir: ./build/docs/fixedJavadoc clean: true into: modules/ROOT/attachments/api - dir: ./src/test into: modules/ROOT/examples - dir: ./build/generated/asciidoc clean: true into: modules/ROOT/partials - dir: ./build/plantuml clean: true into: modules/ROOT/images assembler: nav: - modules/ROOT/nav.adoc asciidoc: attributes: javadoc-root: "xref:attachment$api" # Snapshot Repository snapshot-repo: 'https://central.sonatype.com/service/rest/repository/browse/maven-snapshots' # Base Links junit-team: 'https://github.com/junit-team' junit-framework-repo: '{junit-team}/junit-framework' current-branch: '{junit-framework-repo}/tree/{release-branch}' # Platform Commons junit-platform-support-package: '{javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/support/package-summary.html[org.junit.platform.commons.support]' AnnotationSupport: '{javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/support/AnnotationSupport.html[AnnotationSupport]' ClassSupport: '{javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/support/ClassSupport.html[ClassSupport]' ConversionSupport: '{javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/support/conversion/ConversionSupport.html[ConversionSupport]' ModifierSupport: '{javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/support/ModifierSupport.html[ModifierSupport]' ReflectionSupport: '{javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/support/ReflectionSupport.html[ReflectionSupport]' Testable: '{javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/annotation/Testable.html[@Testable]' # Platform Console Launcher junit-platform-console: '{javadoc-root}/org.junit.platform.console/org/junit/platform/console/package-summary.html[junit-platform-console]' ConsoleLauncher: '{javadoc-root}/org.junit.platform.console/org/junit/platform/console/ConsoleLauncher.html[ConsoleLauncher]' # Platform Engine junit-platform-engine: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/package-summary.html[junit-platform-engine]' junit-platform-engine-support-discovery: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/support/discovery/package-summary.html[org.junit.platform.engine.support.discovery]' CancellationToken: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/CancellationToken.html[CancellationToken]' ClasspathResourceSelector: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/ClasspathResourceSelector.html[ClasspathResourceSelector]' ClasspathRootSelector: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/ClasspathRootSelector.html[ClasspathRootSelector]' ClassSelector: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/ClassSelector.html[ClassSelector]' DiscoveryIssue: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/DiscoveryIssue.html[DiscoveryIssue]' DiscoveryIssueReporter: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/support/discovery/DiscoveryIssueReporter.html[DiscoveryIssueReporter]' DirectorySelector: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DirectorySelector.html[DirectorySelector]' DiscoverySelectors: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html[DiscoverySelectors]' DiscoverySelectors_selectClasspathResource: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectClasspathResource(java.lang.String)[selectClasspathResource]' DiscoverySelectors_selectClasspathRoots: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectClasspathRoots(java.util.Set)[selectClasspathRoots]' DiscoverySelectors_selectClass: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectClass(java.lang.String)[selectClass]' DiscoverySelectors_selectDirectory: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectDirectory(java.lang.String)[selectDirectory]' DiscoverySelectors_selectFile: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectFile(java.lang.String)[selectFile]' DiscoverySelectors_selectIteration: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectIteration(org.junit.platform.engine.DiscoverySelector,int\...)[selectIteration]' DiscoverySelectors_selectMethod: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectMethod(java.lang.String)[selectMethod]' DiscoverySelectors_selectModule: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectModule(java.lang.String)[selectModule]' DiscoverySelectors_selectNestedClass: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectNestedClass(java.util.List,java.lang.Class)[selectNestedClass]' DiscoverySelectors_selectNestedMethod: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectNestedMethod(java.util.List,java.lang.Class,java.lang.String)[selectNestedMethod]' DiscoverySelectors_selectPackage: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectPackage(java.lang.String)[selectPackage]' DiscoverySelectors_selectUniqueId: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectUniqueId(java.lang.String)[selectUniqueId]' DiscoverySelectors_selectUri: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectUri(java.lang.String)[selectUri]' EngineDiscoveryListener: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/EngineDiscoveryListener.html[EngineDiscoveryListener]' EngineDiscoveryRequest: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/EngineDiscoveryRequest.html[EngineDiscoveryRequest]' FileSelector: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/FileSelector.html[FileSelector]' HierarchicalTestEngine: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/support/hierarchical/HierarchicalTestEngine.html[HierarchicalTestEngine]' IterationSelector: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/IterationSelector.html[IterationSelector]' MethodSelector: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/MethodSelector.html[MethodSelector]' ModuleSelector: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/ModuleSelector.html[ModuleSelector]' NamespacedHierarchicalStore: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/support/store/NamespacedHierarchicalStore.html[NamespacedHierarchicalStore]' NestedClassSelector: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/NestedClassSelector.html[NestedClassSelector]' NestedMethodSelector: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/NestedMethodSelector.html[NestedMethodSelector]' OutputDirectoryCreator: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/OutputDirectoryCreator.html[OutputDirectoryCreator]' PackageSelector: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/PackageSelector.html[PackageSelector]' ParallelExecutionConfigurationStrategy: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfigurationStrategy.html[ParallelExecutionConfigurationStrategy]' UniqueIdSelector: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/UniqueIdSelector.html[UniqueIdSelector]' UriSelector: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/UriSelector.html[UriSelector]' TestEngine: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/TestEngine.html[TestEngine]' # Platform Launcher API junit-platform-launcher: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/package-summary.html[junit-platform-launcher]' DiscoveryIssueException: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/core/DiscoveryIssueException.html[DiscoveryIssueException]' Launcher: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/Launcher.html[Launcher]' LauncherConfig: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/core/LauncherConfig.html[LauncherConfig]' LauncherDiscoveryListener: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherDiscoveryListener.html[LauncherDiscoveryListener]' LauncherDiscoveryRequest: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherDiscoveryRequest.html[LauncherDiscoveryRequest]' LauncherDiscoveryRequestBuilder: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.html[LauncherDiscoveryRequestBuilder]' LauncherExecutionRequest: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherExecutionRequest.html[LauncherExecutionRequest]' LauncherExecutionRequestBuilder: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/core/LauncherExecutionRequestBuilder.html[LauncherExecutionRequestBuilder]' LauncherFactory: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/core/LauncherFactory.html[LauncherFactory]' LauncherInterceptor: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherInterceptor.html[LauncherInterceptor]' LauncherSession: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherSession.html[LauncherSession]' LauncherSessionListener: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherSessionListener.html[LauncherSessionListener]' LoggingListener: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/listeners/LoggingListener.html[LoggingListener]' PostDiscoveryFilter: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/PostDiscoveryFilter.html[PostDiscoveryFilter]' SummaryGeneratingListener: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/listeners/SummaryGeneratingListener.html[SummaryGeneratingListener]' TestExecutionListener: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/TestExecutionListener.html[TestExecutionListener]' TestPlan: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/TestPlan.html[TestPlan]' UniqueIdTrackingListener: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/listeners/UniqueIdTrackingListener.html[UniqueIdTrackingListener]' # Platform Reporting LegacyXmlReportGeneratingListener: '{javadoc-root}/org.junit.platform.reporting/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListener.html[LegacyXmlReportGeneratingListener]' OpenTestReportGeneratingListener: '{javadoc-root}/org.junit.platform.reporting/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListener.html[OpenTestReportGeneratingListener]' # Platform Suite suite-api-package: '{javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/package-summary.html[org.junit.platform.suite.api]' junit-platform-suite-engine: '{javadoc-root}/org.junit.platform.suite.engine/org/junit/platform/suite/engine/package-summary.html[junit-platform-suite-engine]' Select: '{javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/Select.html[@Select]' SelectClasspathResource: '{javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/SelectClasspathResource.html[@SelectClasspathResource]' SelectClasses: '{javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/SelectClasses.html[@SelectClasses]' SelectDirectories: '{javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/SelectDirectories.html[@SelectDirectories]' SelectFile: '{javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/SelectFile.html[@SelectFile]' SelectMethod: '{javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/SelectMethod.html[@SelectMethod]' SelectModules: '{javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/SelectModules.html[@SelectModules]' SelectPackages: '{javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/SelectPackages.html[@SelectPackages]' SelectUris: '{javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/SelectUris.html[@SelectUris]' # Platform Test Kit testkit-engine-package: '{javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/package-summary.html[org.junit.platform.testkit.engine]' EngineExecutionResults: '{javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/EngineExecutionResults.html[EngineExecutionResults]' EngineTestKit: '{javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/EngineTestKit.html[EngineTestKit]' Event: '{javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/Event.html[Event]' EventConditions: '{javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/EventConditions.html[EventConditions]' Events: '{javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/Events.html[Events]' EventStatistics: '{javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/EventStatistics.html[EventStatistics]' EventType: '{javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/EventType.html[EventType]' Executions: '{javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/Executions.html[Executions]' TerminationInfo: '{javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/TerminationInfo.html[TerminationInfo]' TestExecutionResultConditions: '{javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/TestExecutionResultConditions.html[TestExecutionResultConditions]' # Jupiter Core API api-package: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/package-summary.html[org.junit.jupiter.api]' Assertions: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/Assertions.html[org.junit.jupiter.api.Assertions]' Assumptions: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/Assumptions.html[org.junit.jupiter.api.Assumptions]' AutoClose: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/AutoClose.html[@AutoClose]' ClassOrderer_ClassName: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.ClassName.html[ClassOrderer.ClassName]' ClassOrderer_Default: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.Default.html[ClassOrderer.Default]' ClassOrderer_DisplayName: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.DisplayName.html[ClassOrderer.DisplayName]' ClassOrderer_OrderAnnotation: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.OrderAnnotation.html[ClassOrderer.OrderAnnotation]' ClassOrderer_Random: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.Random.html[ClassOrderer.Random]' ClassOrderer: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.html[ClassOrderer]' ClassTemplate: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassTemplate.html[@ClassTemplate]' Disabled: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/Disabled.html[@Disabled]' MethodOrderer_Default: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.Default.html[MethodOrderer.Default]' MethodOrderer_DisplayName: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.DisplayName.html[MethodOrderer.DisplayName]' MethodOrderer_MethodName: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.MethodName.html[MethodOrderer.MethodName]' MethodOrderer_OrderAnnotation: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.OrderAnnotation.html[MethodOrderer.OrderAnnotation]' MethodOrderer_Random: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.Random.html[MethodOrderer.Random]' MethodOrderer: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.html[MethodOrderer]' Named: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/Named.html[Named]' Order: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/Order.html[@Order]' RepetitionInfo: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/RepetitionInfo.html[RepetitionInfo]' TestInfo: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/TestInfo.html[TestInfo]' TestClassOrder: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/TestClassOrder.html[@TestClassOrder]' TestMethodOrder: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/TestMethodOrder.html[@TestMethodOrder]' TestReporter: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/TestReporter.html[TestReporter]' TestTemplate: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/TestTemplate.html[@TestTemplate]' # @DefaultLocale and @DefaultTimeZone DefaultLocale: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/util/DefaultLocale.html[@DefaultLocale]' DefaultTimeZone: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/util/DefaultTimeZone.html[@DefaultTimeZone]' LocaleProvider: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/util/LocaleProvider.html[LocaleProvider]' TimeZoneProvider: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/util/TimeZoneProvider.html[TimeZoneProvider]' ReadsDefaultLocale: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/util/ReadsDefaultLocale.html[@ReadsDefaultLocale]' ReadsDefaultTimeZone: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/util/ReadsDefaultTimeZone.html[@ReadsDefaultTimeZone]' WritesDefaultLocale: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/util/WritesDefaultLocale.html[@WritesDefaultLocale]' WritesDefaultTimeZone: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/util/WritesDefaultTimeZone.html[@WritesDefaultTimeZone]' # System Property Extensions ClearSystemProperty: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/util/ClearSystemProperty.html[@ClearSystemProperty]' SetSystemProperty: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/util/SetSystemProperty.html[@SetSystemProperty]' RestoreSystemProperties: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/util/RestoreSystemProperties.html[@RestoreSystemProperties]' ReadsSystemProperty: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/util/ReadsSystemProperty.html[@ReadsSystemProperty]' WritesSystemProperty: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/util/WritesSystemProperty.html[@WritesSystemProperty]' # Jupiter Parallel API Execution: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/parallel/Execution.html[@Execution]' Isolated: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/parallel/Isolated.html[@Isolated]' ResourceLock: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/parallel/ResourceLock.html[@ResourceLock]' ResourceLockTarget: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/parallel/ResourceLockTarget.html[ResourceLockTarget]' ResourceLocksProvider: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/parallel/ResourceLocksProvider.html[ResourceLocksProvider]' Resources: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/parallel/Resources.html[Resources]' # Jupiter Extension APIs extension-api-package: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/package-summary.html[org.junit.jupiter.api.extension]' AfterAllCallback: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/AfterAllCallback.html[AfterAllCallback]' AfterClassTemplateInvocationCallback: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/AfterClassTemplateInvocationCallback.html[AfterClassTemplateInvocationCallback]' AfterEachCallback: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/AfterEachCallback.html[AfterEachCallback]' AfterTestExecutionCallback: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/AfterTestExecutionCallback.html[AfterTestExecutionCallback]' ParameterContext: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ParameterContext.html[ParameterContext]' BeforeAllCallback: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/BeforeAllCallback.html[BeforeAllCallback]' BeforeClassTemplateInvocationCallback: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/BeforeClassTemplateInvocationCallback.html[BeforeClassTemplateInvocationCallback]' BeforeEachCallback: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/BeforeEachCallback.html[BeforeEachCallback]' BeforeTestExecutionCallback: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/BeforeTestExecutionCallback.html[BeforeTestExecutionCallback]' ClassTemplateInvocationContext: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ClassTemplateInvocationContext.html[ClassTemplateInvocationContext]' ClassTemplateInvocationContextProvider: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ClassTemplateInvocationContextProvider.html[ClassTemplateInvocationContextProvider]' ExecutableInvoker: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ExecutableInvoker.html[ExecutableInvoker]' ExecutionCondition: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ExecutionCondition.html[ExecutionCondition]' ExtendWith: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ExtendWith.html[@ExtendWith]' ExtensionContext: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ExtensionContext.html[ExtensionContext]' ExtensionContext_Store: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ExtensionContext.Store.html[Store]' ExtensionContext_StoreScope: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ExtensionContext.StoreScope.html[StoreScope]' InvocationInterceptor: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/InvocationInterceptor.html[InvocationInterceptor]' LifecycleMethodExecutionExceptionHandler: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/LifecycleMethodExecutionExceptionHandler.html[LifecycleMethodExecutionExceptionHandler]' ParameterResolver: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ParameterResolver.html[ParameterResolver]' RegisterExtension: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/RegisterExtension.html[@RegisterExtension]' TestExecutionExceptionHandler: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestExecutionExceptionHandler.html[TestExecutionExceptionHandler]' TestInstanceFactory: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestInstanceFactory.html[TestInstanceFactory]' TestInstancePostProcessor: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestInstancePostProcessor.html[TestInstancePostProcessor]' TestInstancePreConstructCallback: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestInstancePreConstructCallback.html[TestInstancePreConstructCallback]' TestInstancePreDestroyCallback: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestInstancePreDestroyCallback.html[TestInstancePreDestroyCallback]' TestTemplateInvocationContext: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestTemplateInvocationContext.html[TestTemplateInvocationContext]' TestTemplateInvocationContextProvider: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.html[TestTemplateInvocationContextProvider]' TestWatcher: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestWatcher.html[TestWatcher]' PreInterruptCallback: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/PreInterruptCallback.html[PreInterruptCallback]' # Jupiter Conditions DisabledForJreRange: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledForJreRange.html[@DisabledForJreRange]' DisabledIf: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledIf.html[@DisabledIf]' DisabledIfEnvironmentVariable: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariable.html[@DisabledIfEnvironmentVariable]' DisabledIfSystemProperty: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledIfSystemProperty.html[@DisabledIfSystemProperty]' DisabledInNativeImage: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledInNativeImage.html[@DisabledInNativeImage]' DisabledOnJre: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledOnJre.html[@DisabledOnJre]' DisabledOnOs: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledOnOs.html[@DisabledOnOs]' EnabledForJreRange: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledForJreRange.html[@EnabledForJreRange]' EnabledIf: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledIf.html[@EnabledIf]' EnabledIfEnvironmentVariable: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariable.html[@EnabledIfEnvironmentVariable]' EnabledIfSystemProperty: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledIfSystemProperty.html[@EnabledIfSystemProperty]' EnabledInNativeImage: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledInNativeImage.html[@EnabledInNativeImage]' EnabledOnJre: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledOnJre.html[@EnabledOnJre]' EnabledOnOs: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledOnOs.html[@EnabledOnOs]' JRE: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/JRE.html[JRE]' # Jupiter I/O TempDir: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/io/TempDir.html[@TempDir]' TempDirDeletionStrategy: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/io/TempDirDeletionStrategy.html[TempDirDeletionStrategy]' TempDirDeletionStrategyIgnoreFailures: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/io/TempDirDeletionStrategy.IgnoreFailures.html[IgnoreFailures]' TempDirDeletionStrategyStandard: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/io/TempDirDeletionStrategy.Standard.html[Standard]' TempDirFactory: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/io/TempDirFactory.html[TempDirFactory]' # Jupiter Params params-provider-package: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/package-summary.html[org.junit.jupiter.params.provider]' AfterParameterizedClassInvocation: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/AfterParameterizedClassInvocation.html[@AfterParameterizedClassInvocation]' AnnotationBasedArgumentConverter: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/converter/AnnotationBasedArgumentConverter.html[AnnotationBasedArgumentConverter]' AnnotationBasedArgumentsProvider: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProvider.html[AnnotationBasedArgumentsProvider]' AggregateWith: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/aggregator/AggregateWith.html[@AggregateWith]' Arguments: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/Arguments.html[Arguments]' ArgumentsProvider: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/ArgumentsProvider.html[ArgumentsProvider]' ArgumentsAccessor: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/aggregator/ArgumentsAccessor.html[ArgumentsAccessor]' ArgumentsAggregator: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/aggregator/ArgumentsAggregator.html[ArgumentsAggregator]' ArgumentConverter: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/converter/ArgumentConverter.html[ArgumentConverter]' BeforeParameterizedClassInvocation: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/BeforeParameterizedClassInvocation.html[@BeforeParameterizedClassInvocation]' CsvArgumentsProvider: '{junit-framework-repo}/blob/main/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java[CsvArgumentsProvider]' EmptySource: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/EmptySource.html[@EmptySource]' FieldSource: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/FieldSource.html[@FieldSource]' MethodSource: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/MethodSource.html[@MethodSource]' NullAndEmptySource: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/NullAndEmptySource.html[@NullAndEmptySource]' NullSource: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/NullSource.html[@NullSource]' Parameter: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/Parameter.html[@Parameter]' ParameterizedClass: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/ParameterizedClass.html[@ParameterizedClass]' ParameterizedTest: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/ParameterizedTest.html[@ParameterizedTest]' ParameterInfo: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/ParameterInfo.html[ParameterInfo]' ValueArgumentsProvider: '{junit-framework-repo}/blob/main/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueArgumentsProvider.java[ValueArgumentsProvider]' # Jupiter Engine junit-jupiter-engine: '{javadoc-root}/org.junit.jupiter.engine/org/junit/jupiter/engine/package-summary.html[junit-jupiter-engine]' # Jupiter Extension Implementations AutoCloseExtension: '{current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/AutoCloseExtension.java[AutoCloseExtension]' DisabledCondition: '{current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DisabledCondition.java[DisabledCondition]' RepetitionExtension: '{current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepetitionExtension.java[RepetitionExtension]' TempDirectory: '{current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java[TempDirectory]' TestInfoParameterResolver: '{current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestInfoParameterResolver.java[TestInfoParameterResolver]' TestReporterParameterResolver: '{current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestReporterParameterResolver.java[TestReporterParameterResolver]' TypeBasedParameterResolver: '{current-branch}/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolver.java[TypeBasedParameterResolver]' # Jupiter Examples CustomAnnotationParameterResolver: '{current-branch}/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotationParameterResolver.java[CustomAnnotationParameterResolver]' CustomTypeParameterResolver: '{current-branch}/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomTypeParameterResolver.java[CustomTypeParameterResolver]' MapOfListsTypeBasedParameterResolver: '{current-branch}/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfListsTypeBasedParameterResolver.java[MapOfListsTypeBasedParameterResolver]' # Jupiter Migration Support EnableJUnit4MigrationSupport: '{javadoc-root}/org.junit.jupiter.migrationsupport/org/junit/jupiter/migrationsupport/EnableJUnit4MigrationSupport.html[@EnableJUnit4MigrationSupport]' EnableRuleMigrationSupport: '{javadoc-root}/org.junit.jupiter.migrationsupport/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupport.html[@EnableRuleMigrationSupport]' # JUnit Start JUnit: '{javadoc-root}/org.junit.start/org/junit/start/JUnit.html[JUnit]' # Vintage junit-vintage-engine: '{javadoc-root}/org.junit.vintage.engine/org/junit/vintage/engine/package-summary.html[junit-vintage-engine]' # Examples Repository junit-examples-repo: '{junit-team}/junit-examples' junit-jupiter-starter-ant: '{junit-examples-repo}/tree/{release-branch}/junit-jupiter-starter-ant[junit-jupiter-starter-ant]' junit-jupiter-starter-bazel: '{junit-examples-repo}/tree/{release-branch}/junit-jupiter-starter-bazel[junit-jupiter-starter-bazel]' junit-jupiter-starter-gradle-groovy: '{junit-examples-repo}/tree/{release-branch}/junit-jupiter-starter-gradle-groovy[junit-jupiter-starter-gradle-groovy]' junit-jupiter-starter-gradle-kotlin: '{junit-examples-repo}/tree/{release-branch}/junit-jupiter-starter-gradle-kotlin[junit-jupiter-starter-gradle-kotlin]' junit-jupiter-starter-gradle: '{junit-examples-repo}/tree/{release-branch}/junit-jupiter-starter-gradle[junit-jupiter-starter-gradle]' junit-jupiter-starter-maven: '{junit-examples-repo}/tree/{release-branch}/junit-jupiter-starter-maven[junit-jupiter-starter-maven]' junit-jupiter-starter-sbt: '{junit-examples-repo}/tree/{release-branch}/junit-jupiter-starter-sbt[junit-jupiter-starter-sbt]' # Third-party Links API: 'https://apiguardian-team.github.io/apiguardian/docs/current/api/[@API]' API_Guardian: 'https://github.com/apiguardian-team/apiguardian[@API Guardian]' AssertJ: 'https://assertj.github.io/doc/[AssertJ]' Checkstyle: 'https://checkstyle.sourceforge.io[Checkstyle]' DiscussionsQA: 'https://github.com/junit-team/junit-framework/discussions/categories/q-a' Hamcrest: 'https://hamcrest.org/JavaHamcrest/[Hamcrest]' Jimfs: 'https://google.github.io/jimfs/[Jimfs]' Log4j: 'https://logging.apache.org/log4j/2.x/[Log4j]' Log4j_JDK_Logging_Adapter: 'https://logging.apache.org/log4j/2.x/log4j-jul/index.html[Log4j JDK Logging Adapter]' Logback: 'https://logback.qos.ch/[Logback]' LogManager: 'https://docs.oracle.com/en/java/javase/17/docs/api/java.logging/java/util/logging/LogManager.html[LogManager]' Maven_Central: 'https://central.sonatype.com/[Maven Central]' MockitoExtension: 'https://github.com/mockito/mockito/blob/release/2.x/subprojects/junit-jupiter/src/main/java/org/mockito/junit/jupiter/MockitoExtension.java[MockitoExtension]' ServiceLoader: '{jdk-javadoc-base-url}/java.base/java/util/ServiceLoader.html[ServiceLoader]' SpringExtension: 'https://github.com/spring-projects/spring-framework/tree/HEAD/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/SpringExtension.java[SpringExtension]' StackOverflow: 'https://stackoverflow.com/questions/tagged/junit5[Stack Overflow]' Truth: 'https://truth.dev/[Truth]' OpenTestReporting: 'https://github.com/ota4j-team/open-test-reporting[Open Test Reporting]' OpenTestReportingCliTool: 'https://github.com/ota4j-team/open-test-reporting#cli-tool-for-validation-and-format-conversion[Open Test Reporting CLI Tool]' ================================================ FILE: documentation/documentation.gradle.kts ================================================ import junitbuild.exec.CaptureJavaExecOutput import junitbuild.exec.ClasspathSystemPropertyProvider import junitbuild.exec.GenerateStandaloneConsoleLauncherShadowedArtifactsFile import junitbuild.exec.RunConsoleLauncher import junitbuild.extensions.isSnapshot import junitbuild.extensions.javaModuleName import junitbuild.javadoc.JavadocValuesOption import junitbuild.javadoc.ModuleSpecificJavadocFileOption import junitbuild.javadoc.VersionNumber import org.gradle.api.tasks.PathSensitivity.RELATIVE import java.nio.file.Files import kotlin.io.path.writeLines plugins { alias(libs.plugins.plantuml) id("junitbuild.antora-conventions") id("junitbuild.build-parameters") id("junitbuild.kotlin-library-conventions") id("junitbuild.testing-conventions") } val mavenizedProjects: List by rootProject val modularProjects: List by rootProject // Because we need to set up Javadoc aggregation modularProjects.forEach { evaluationDependsOn(it.path) } javaLibrary { mainJavaVersion = JavaVersion.VERSION_17 testJavaVersion = JavaVersion.VERSION_17 } val apiReport = configurations.dependencyScope("apiReport") val apiReportClasspath = configurations.resolvable("apiReportClasspath") { extendsFrom(apiReport.get()) } val standaloneConsoleLauncher = configurations.dependencyScope("standaloneConsoleLauncher") val standaloneConsoleLauncherClasspath = configurations.resolvable("standaloneConsoleLauncherClasspath") { extendsFrom(standaloneConsoleLauncher.get()) } val attestation = configurations.dependencyScope("attestation") val attestationClasspath = configurations.resolvable("attestationClasspath") { extendsFrom(attestation.get()) isTransitive = false } val allJavadocSinceValues = configurations.dependencyScope("allJavadocSinceValues") val allJavadocSinceValuesClasspath = configurations.resolvable("allJavadocSinceValuesClasspath") { extendsFrom(allJavadocSinceValues.get()) attributes { attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, named("javadoc-since-values")) } } val tools by sourceSets.creating val toolsImplementation by configurations.getting dependencies { implementation(projects.junitJupiterApi) { because("Jupiter API is used in src/main/java") } compileOnlyApi(libs.jspecify) // Pull in all "modular projects" to ensure that they are included // in reports generated by the ApiReportGenerator. modularProjects.forEach { apiReport(it) } apiReport(libs.openTestReporting.tooling.spi) modularProjects.forEach { allJavadocSinceValues(it) } // Pull in all "mavenized projects" to ensure that they are included // in the generation of build provenance attestation. mavenizedProjects.forEach { attestation(it) } testImplementation(projects.junitJupiterMigrationsupport) testImplementation(projects.junitPlatformConsole) testImplementation(projects.junitPlatformSuite) testImplementation(projects.junitPlatformTestkit) testImplementation(projects.junitVintageEngine) testImplementation(kotlin("stdlib")) testImplementation(libs.kotlinx.coroutines.test) testRuntimeOnly(kotlin("reflect")) toolsImplementation(projects.junitPlatformCommons) toolsImplementation(libs.classgraph) toolsImplementation(libs.apiguardian) testImplementation(libs.jimfs) { because("Jimfs is used in src/test/java") } standaloneConsoleLauncher(projects.junitPlatformConsoleStandalone) } val buildRevision: String by rootProject.extra val snapshot = version.isSnapshot() val releaseBranch = if (snapshot) "HEAD" else "r${version}" val replaceCurrentDocs = buildParameters.documentation.replaceCurrentDocs val ota4jDocVersion = libs.versions.opentest4j.map { if (it.isSnapshot()) "snapshot" else it }.get() val apiGuardianDocVersion = libs.versions.apiguardian.map { if (it.isSnapshot()) "snapshot" else it }.get() val generatedAsciiDocPath = layout.buildDirectory.dir("generated/asciidoc") val consoleLauncherOptionsFile = generatedAsciiDocPath.map { it.file("console-launcher-options.txt") } val consoleLauncherDiscoverOptionsFile = generatedAsciiDocPath.map { it.file("console-launcher-discover-options.txt") } val consoleLauncherExecuteOptionsFile = generatedAsciiDocPath.map { it.file("console-launcher-execute-options.txt") } val consoleLauncherEnginesOptionsFile = generatedAsciiDocPath.map { it.file("console-launcher-engines-options.txt") } val experimentalApisTableFile = generatedAsciiDocPath.map { it.file("experimental-apis-table.adoc") } val deprecatedApisTableFile = generatedAsciiDocPath.map { it.file("deprecated-apis-table.adoc") } val standaloneConsoleLauncherShadowedArtifactsFile = generatedAsciiDocPath.map { it.file("console-launcher-standalone-shadowed-artifacts.adoc") } val jdkJavadocBaseUrl = "https://docs.oracle.com/en/java/javase/${JavaVersion.current().majorVersion}/docs/api" val elementListsDir = layout.buildDirectory.dir("elementLists") val externalModulesWithoutModularJavadoc = mapOf( "org.apiguardian.api" to "https://apiguardian-team.github.io/apiguardian/docs/$apiGuardianDocVersion/api/", "org.assertj.core" to "https://javadoc.io/doc/org.assertj/assertj-core/${libs.versions.assertj.get()}/", "org.opentest4j" to "https://ota4j-team.github.io/opentest4j/docs/$ota4jDocVersion/api/", "org.jspecify" to "https://jspecify.dev/docs/api/", ) require(externalModulesWithoutModularJavadoc.values.all { it.endsWith("/") }) { "all base URLs must end with a trailing slash: $externalModulesWithoutModularJavadoc" } tasks { val consoleLauncherTestReportsDir = project.layout.buildDirectory.dir("console-launcher-test-results") val consoleLauncherTestEventXmlFiles = files(consoleLauncherTestReportsDir.map { it.asFileTree.matching { include("**/open-test-report.xml") } }) val consoleLauncherTest by registering(RunConsoleLauncher::class) { args.addAll("execute") args.addAll("--scan-classpath") args.addAll("--config=junit.platform.reporting.open.xml.enabled=true") args.addAll("--config=junit.platform.reporting.open.xml.git.enabled=true") args.addAll("--config=junit.platform.output.capture.stdout=true") args.addAll("--config=junit.platform.output.capture.stderr=true") args.addAll("--config=junit.platform.discovery.issue.severity.critical=warning") outputs.dir(consoleLauncherTestReportsDir) argumentProviders.add(CommandLineArgumentProvider { listOf( "--reports-dir=${consoleLauncherTestReportsDir.get()}", ) }) args.addAll("--include-classname", ".*Tests") args.addAll("--include-classname", ".*Demo") args.addAll("--exclude-tag", "exclude") args.addAll("--exclude-tag", "timeout") doFirst { consoleLauncherTestReportsDir.get().asFile.deleteRecursively() } finalizedBy(generateOpenTestHtmlReport) } generateOpenTestHtmlReport { mustRunAfter(consoleLauncherTest) inputs.files(consoleLauncherTestEventXmlFiles).withPathSensitivity(RELATIVE).skipWhenEmpty() argumentProviders += CommandLineArgumentProvider { consoleLauncherTestEventXmlFiles.files.map { it.absolutePath }.toList() } } register("consoleLauncher") { hideOutput = false outputs.upToDateWhen { false } } test { include("**/*Demo.class") (options as JUnitPlatformOptions).apply { includeEngines("junit-vintage") includeTags("timeout") systemProperty("junit.platform.discovery.issue.severity.critical", "warning") } } check { dependsOn(consoleLauncherTest) } named(tools.compileJavaTaskName) { options.release.set(25) } named("checkstyleTools") { config = resources.text.fromFile(checkstyle.configDirectory.file("checkstyleMain.xml")) } val generateConsoleLauncherOptions by registering(CaptureJavaExecOutput::class) { classpath.from(standaloneConsoleLauncherClasspath) mainClass = "org.junit.platform.console.ConsoleLauncher" args.addAll("--help", "--disable-banner") outputFile = consoleLauncherOptionsFile } val generateConsoleLauncherDiscoverOptions by registering(CaptureJavaExecOutput::class) { classpath.from(standaloneConsoleLauncherClasspath) mainClass = "org.junit.platform.console.ConsoleLauncher" args.addAll("discover", "--help", "--disable-banner") outputFile = consoleLauncherDiscoverOptionsFile } val generateConsoleLauncherExecuteOptions by registering(CaptureJavaExecOutput::class) { classpath.from(standaloneConsoleLauncherClasspath) mainClass = "org.junit.platform.console.ConsoleLauncher" args.addAll("execute", "--help", "--disable-banner") outputFile = consoleLauncherExecuteOptionsFile } val generateConsoleLauncherEnginesOptions by registering(CaptureJavaExecOutput::class) { classpath.from(standaloneConsoleLauncherClasspath) mainClass = "org.junit.platform.console.ConsoleLauncher" args.addAll("engines", "--help", "--disable-banner") outputFile = consoleLauncherEnginesOptionsFile } val generateApiTables by registering(JavaExec::class) { classpath = tools.runtimeClasspath mainClass = "org.junit.api.tools.ApiReportGenerator" systemProperty("api.moduleNames", modularProjects.map { it.javaModuleName }.sorted().joinToString(",")) jvmArgumentProviders += ClasspathSystemPropertyProvider("api.modulePath", apiReportClasspath.get()) argumentProviders += CommandLineArgumentProvider { listOf( "DEPRECATED=${deprecatedApisTableFile.get().asFile.absolutePath}", "EXPERIMENTAL=${experimentalApisTableFile.get().asFile.absolutePath}", ) } outputs.cacheIf { true } outputs.file(deprecatedApisTableFile) outputs.file(experimentalApisTableFile) } val generateStandaloneConsoleLauncherShadowedArtifactsFile by registering(GenerateStandaloneConsoleLauncherShadowedArtifactsFile::class) { inputJar.fileProvider(standaloneConsoleLauncherClasspath.flatMap { it.elements.map { it.single().asFile } }) outputFile = standaloneConsoleLauncherShadowedArtifactsFile } plantUml { fileFormat = "SVG" outputs.cacheIf { true } } val plantUmlOutputDirectory = plantUml.flatMap { it.outputDirectory } val generateAsciidocInputs by registering { dependsOn( generateConsoleLauncherOptions, generateConsoleLauncherDiscoverOptions, generateConsoleLauncherExecuteOptions, generateConsoleLauncherEnginesOptions, generateApiTables, generateStandaloneConsoleLauncherShadowedArtifactsFile, plantUmlOutputDirectory ) } val downloadJavadocElementLists by registering { outputs.cacheIf { true } outputs.dir(elementListsDir).withPropertyName("elementListsDir") inputs.property("externalModulesWithoutModularJavadoc", externalModulesWithoutModularJavadoc) doFirst { externalModulesWithoutModularJavadoc.forEach { (moduleName, baseUrl) -> val resource = resources.text.fromUri("${baseUrl}element-list") elementListsDir.get().asFile.resolve(moduleName).apply { mkdir() resolve("element-list").writeText("module:$moduleName\n${resource.asString()}") } } } } val mergeJavadocSinceValues by registering { inputs.files(allJavadocSinceValuesClasspath).withPathSensitivity(PathSensitivity.NONE) val outputFile = layout.buildDirectory.file("docs/aggregated-javadoc-since-values.txt") outputs.file(outputFile) outputs.cacheIf { true } doFirst { val initialVersions = setOf("1.0", "4.12", "5.0") val noPublicItems = setOf("1.3.1", "5.0.3", "5.4.1", "5.11.3") val excludes = initialVersions + noPublicItems val values = allJavadocSinceValuesClasspath.get().files.asSequence() .flatMap { it.readLines().asSequence() } .filter { it.isNotBlank() && it !in excludes } .sortedBy { VersionNumber(it) } .distinct() with(outputFile.get().asFile.toPath()) { Files.createDirectories(parent) writeLines(values) } } } val aggregateJavadocs by registering(Javadoc::class) { dependsOn(modularProjects.map { it.tasks.jar }) dependsOn(downloadJavadocElementLists) group = "Documentation" description = "Generates aggregated Javadocs" title = "JUnit $version API" val additionalStylesheetFile = "src/javadoc/junit-stylesheet.css" inputs.file(additionalStylesheetFile) val overviewFile = "src/javadoc/junit-overview.html" inputs.file(overviewFile) options { memberLevel = JavadocMemberLevel.PROTECTED header = rootProject.description encoding = "UTF-8" locale = "en" overview = overviewFile jFlags("-Xmx1g") this as StandardJavadocDocletOptions splitIndex(true) addBooleanOption("Xdoclint:all,-missing", true) addBooleanOption("Werror", true) addBooleanOption("html5", true) addMultilineStringsOption("tag").value = listOf( "apiNote:a:API Note:", "implNote:a:Implementation Note:" ) links(jdkJavadocBaseUrl) links("https://junit.org/junit4/javadoc/${libs.versions.junit4.get()}/") externalModulesWithoutModularJavadoc.forEach { (moduleName, baseUrl) -> linksOffline(baseUrl, elementListsDir.get().asFile.resolve(moduleName).absolutePath) } groups = mapOf( "Jupiter" to listOf("org.junit.jupiter*"), "Vintage" to listOf("org.junit.vintage*"), "Platform" to listOf("org.junit.platform*") ) addStringOption("-add-stylesheet", additionalStylesheetFile) addBooleanOption("-no-fonts", true) use(true) noTimestamp(true) addStringsOption("-module", ",").value = modularProjects.map { it.javaModuleName } addOption(ModuleSpecificJavadocFileOption("-module-source-path", modularProjects.associate { project -> project.javaModuleName to provider { files( project.sourceSets.named { it.startsWith("main") }.map { it.allJava.srcDirs.filter { it.exists() } } ).asPath } })) addStringOption("-add-modules", "info.picocli,org.opentest4j.reporting.events,de.siegmar.fastcsv") addOption(ModuleSpecificJavadocFileOption("-add-reads", mapOf( "org.junit.platform.console" to provider { "info.picocli" }, "org.junit.platform.reporting" to provider { "org.opentest4j.reporting.events" }, "org.junit.jupiter.params" to provider { "de.siegmar.fastcsv" } ))) val javadocSinceValuesFile: Provider = mergeJavadocSinceValues.map { it.outputs.files.singleFile } inputs.file(javadocSinceValuesFile) .withPathSensitivity(PathSensitivity.NONE) .withPropertyName("javadoc-since-values-file") addOption(JavadocValuesOption("-since", javadocSinceValuesFile.map { file -> file.readLines().asSequence().filter { it.isNotBlank() }.toList() })) } source(modularProjects.map { project -> files(project.sourceSets.named { it.startsWith("main") }.map { it.allJava }) }) classpath = files(modularProjects.map { it.sourceSets.main.get().compileClasspath }) setMaxMemory("1024m") options.destinationDirectory = layout.buildDirectory.dir("docs/javadoc").get().asFile } val fixJavadoc by registering(Copy::class) { dependsOn(aggregateJavadocs) group = "Documentation" description = "Fix links to external API specs in the locally aggregated Javadoc HTML files" val inputDir = aggregateJavadocs.map { it.destinationDir!! } inputs.property("externalModulesWithoutModularJavadoc", externalModulesWithoutModularJavadoc) from(inputDir.map { File(it, "element-list") }) { // For compatibility with pre JDK 10 versions of the Javadoc tool rename { "package-list" } } from(inputDir) { filesMatching("**/*.html") { val favicon = """ """.trimIndent() val version = project.version.toString().replace("-SNAPSHOT", "") val targetUrl = if (buildParameters.ci) "https://docs.junit.org/$version" else project.antora.siteDir.get().asFile.toURI().resolve(version).toString() filter { line -> var result = if (line.startsWith("")) line.replace("", "$favicon") else line externalModulesWithoutModularJavadoc.forEach { (moduleName, baseUrl) -> result = result.replace("${baseUrl}$moduleName/", baseUrl) } result = result.replace("https://docs.junit.org/current", targetUrl) return@filter result } } filesMatching("**/stylesheet.css") { // Remove invalid import of `dejavu.css` due to `javadoc --no-fonts` filter { line -> if (line.startsWith("@import url('fonts/")) null else line } } } into(layout.buildDirectory.dir("docs/fixedJavadoc")) } val prepareGitHubAttestation by registering(Sync::class) { from(attestationClasspath) into(layout.buildDirectory.dir("attestation")) rename("(.*)-SNAPSHOT.jar", "$1-SNAPSHOT+${buildRevision.substring(0, 7)}.jar") } generateAntoraYml { asciidocAttributes.putAll(provider { mapOf( "version" to project.version, "junit4-version" to libs.versions.junit4.get(), "apiguardian-version" to libs.versions.apiguardian.get(), "ota4j-version" to libs.versions.opentest4j.get(), "surefire-version" to libs.versions.surefire.get(), "release-branch" to releaseBranch, "jdk-javadoc-base-url" to jdkJavadocBaseUrl ) }) } generateAntoraResources { dependsOn(generateAsciidocInputs, fixJavadoc) } } ================================================ FILE: documentation/modules/ROOT/extra-site-nav.adoc ================================================ * {javadoc-root}/index.html[Javadoc] ================================================ FILE: documentation/modules/ROOT/images/_source/extensions_BrokenLifecycleMethodConfigDemo.txt ================================================ @Starter(Platform) Result = JupiterEngine.execute(BrokenLifecycleMethodConfigDemo) { Extension1.beforeEach() Extension2.beforeEach() // @BeforeEach BrokenLifecycleMethodConfigDemo.insertTestDataIntoDatabase() // @BeforeEach BrokenLifecycleMethodConfigDemo.connectToDatabase() // @Test BrokenLifecycleMethodConfigDemo.testDatabaseFunctionality() // @AfterEach BrokenLifecycleMethodConfigDemo.disconnectFromDatabase() // @AfterEach BrokenLifecycleMethodConfigDemo.deleteTestDataFromDatabase() Extension2.afterEach() Extension1.afterEach() } ================================================ FILE: documentation/modules/ROOT/images/_source/extensions_DatabaseTestsDemo.txt ================================================ @Starter(Platform) Result = JupiterEngine.execute(DatabaseTestsDemo) { // @BeforeAll (static invocation) AbstractDatabaseTests.createDatabase() // @BeforeAll (static invocation) DatabaseTestsDemo.beforeAll() Extension1.beforeEach() Extension2.beforeEach() // @BeforeEach inherited from AbstractDatabaseTests DatabaseTestsDemo.connectToDatabase() // @BeforeEach DatabaseTestsDemo.insertTestDataIntoDatabase() // @Test DatabaseTestsDemo.testDatabaseFunctionality() // @AfterEach DatabaseTestsDemo.deleteTestDataFromDatabase() // @AfterEach inherited from AbstractDatabaseTests DatabaseTestsDemo.disconnectFromDatabase() Extension2.afterEach() Extension1.afterEach() // @AfterAll (static invocation) DatabaseTestsDemo.afterAll() // @AfterAll (static invocation) AbstractDatabaseTests.destroyDatabase() } ================================================ FILE: documentation/modules/ROOT/nav.adoc ================================================ * xref:overview.adoc[] * xref:writing-tests/intro.adoc[] ** xref:writing-tests/annotations.adoc[] ** xref:writing-tests/definitions.adoc[] ** xref:writing-tests/test-classes-and-methods.adoc[] ** xref:writing-tests/display-names.adoc[] ** xref:writing-tests/assertions.adoc[] ** xref:writing-tests/assumptions.adoc[] ** xref:writing-tests/exception-handling.adoc[] ** xref:writing-tests/disabling-tests.adoc[] ** xref:writing-tests/conditional-test-execution.adoc[] ** xref:writing-tests/tagging-and-filtering.adoc[] ** xref:writing-tests/test-execution-order.adoc[] ** xref:writing-tests/test-instance-lifecycle.adoc[] ** xref:writing-tests/nested-tests.adoc[] ** xref:writing-tests/dependency-injection-for-constructors-and-methods.adoc[] ** xref:writing-tests/test-interfaces-and-default-methods.adoc[] ** xref:writing-tests/repeated-tests.adoc[] ** xref:writing-tests/parameterized-classes-and-tests.adoc[] ** xref:writing-tests/class-templates.adoc[] ** xref:writing-tests/test-templates.adoc[] ** xref:writing-tests/dynamic-tests.adoc[] ** xref:writing-tests/timeouts.adoc[] ** xref:writing-tests/parallel-execution.adoc[] ** xref:writing-tests/built-in-extensions.adoc[] * xref:migrating-from-junit4.adoc[] * xref:running-tests/intro.adoc[] ** xref:running-tests/ide-support.adoc[] ** xref:running-tests/build-support.adoc[] ** xref:running-tests/console-launcher.adoc[] ** xref:running-tests/source-launcher.adoc[] ** xref:running-tests/discovery-selectors.adoc[] ** xref:running-tests/configuration-parameters.adoc[] ** xref:running-tests/tags.adoc[] ** xref:running-tests/capturing-standard-output-error.adoc[] ** xref:running-tests/using-listeners-and-interceptors.adoc[] ** xref:running-tests/stack-trace-pruning.adoc[] ** xref:running-tests/discovery-issues.adoc[] * xref:extensions/overview.adoc[] ** xref:extensions/registering-extensions.adoc[] ** xref:extensions/conditional-test-execution.adoc[] ** xref:extensions/test-instance-pre-construct-callback.adoc[] ** xref:extensions/test-instance-factories.adoc[] ** xref:extensions/test-instance-post-processing.adoc[] ** xref:extensions/test-instance-pre-destroy-callback.adoc[] ** xref:extensions/parameter-resolution.adoc[] ** xref:extensions/test-result-processing.adoc[] ** xref:extensions/test-lifecycle-callbacks.adoc[] ** xref:extensions/exception-handling.adoc[] ** xref:extensions/pre-interrupt-callback.adoc[] ** xref:extensions/intercepting-invocations.adoc[] ** xref:extensions/providing-invocation-contexts-for-class-templates.adoc[] ** xref:extensions/providing-invocation-contexts-for-test-templates.adoc[] ** xref:extensions/keeping-state-in-extensions.adoc[] ** xref:extensions/supported-utilities-in-extensions.adoc[] ** xref:extensions/relative-execution-order-of-user-code-and-extensions.adoc[] * Advanced Topics ** xref:advanced-topics/junit-platform-reporting.adoc[] ** xref:advanced-topics/junit-platform-suite-engine.adoc[] ** xref:advanced-topics/testkit.adoc[] ** xref:advanced-topics/launcher-api.adoc[] ** xref:advanced-topics/engines.adoc[] * xref:api-evolution.adoc[] * xref:release-notes.adoc[] * xref:appendix.adoc[] ================================================ FILE: documentation/modules/ROOT/pages/advanced-topics/engines.adoc ================================================ = Test Engines A `TestEngine` facilitates _discovery_ and _execution_ of tests for a particular programming model. For example, JUnit provides a `TestEngine` that discovers and executes tests written using the JUnit Jupiter programming model (see xref:writing-tests/intro.adoc[] and xref:extensions/overview.adoc[]). [[junit]] == JUnit Test Engines JUnit provides three `TestEngine` implementations. * `{junit-jupiter-engine}`: The core of JUnit Jupiter. * `{junit-vintage-engine}`: A thin layer on top of JUnit 4 to allow running _vintage_ tests (based on JUnit 3.8 and JUnit 4) with the JUnit Platform launcher infrastructure. * `{junit-platform-suite-engine}`: Executes declarative suites of tests with the JUnit Platform launcher infrastructure. [[custom]] == Custom Test Engines You can contribute your own custom `{TestEngine}` by implementing the interfaces in the {junit-platform-engine} module and _registering_ your engine. Every `TestEngine` must provide its own _unique ID_, _discover_ tests from an `EngineDiscoveryRequest`, and _execute_ those tests according to an `ExecutionRequest`. [WARNING] .The `junit-` unique ID prefix is reserved for TestEngines from the JUnit Team ==== The JUnit Platform `Launcher` enforces that only `TestEngine` implementations published by the JUnit Team may use the `junit-` prefix for their `TestEngine` IDs. * If any third-party `TestEngine` claims to be `junit-jupiter` or `junit-vintage`, an exception will be thrown, immediately halting execution of the JUnit Platform. * If any third-party `TestEngine` uses the `junit-` prefix for its ID, a warning message will be logged. Later releases of the JUnit Platform will throw an exception for such violations. ==== In order to facilitate test discovery within IDEs and tools prior to launching the JUnit Platform, `TestEngine` implementations are encouraged to make use of the `@Testable` annotation. For example, the `@Test` and `@TestFactory` annotations in JUnit Jupiter are meta-annotated with `@Testable`. Consult the Javadoc for `{Testable}` for further details. If your custom `TestEngine` needs to be configured, consider allowing users to supply configuration via xref:running-tests/configuration-parameters.adoc[configuration parameters]. Please note, however, that you are strongly encouraged to use a unique prefix for all configuration parameters supported by your test engine. Doing so will ensure that there are no conflicts between the names of your configuration parameters and those from other test engines. In addition, since configuration parameters may be supplied as JVM system properties, it is wise to avoid conflicts with the names of other system properties. For example, JUnit Jupiter uses `junit.jupiter.` as a prefix of all of its supported configuration parameters. Furthermore, as with the warning above regarding the `junit-` prefix for `TestEngine` IDs, you should not use `junit.` as a prefix for the names of your own configuration parameters. Although there is currently no official guide on how to implement a custom `TestEngine`, you can consult the implementation of <> or the implementation of third-party test engines listed in the https://github.com/junit-team/junit-framework/wiki/Third-party-Extensions#junit-platform-test-engines[JUnit wiki]. You will also find various tutorials and blogs on the Internet that demonstrate how to write a custom `TestEngine`. NOTE: `{HierarchicalTestEngine}` is a convenient abstract base implementation of the `TestEngine` SPI (used by the `{junit-jupiter-engine}`) that only requires implementors to provide the logic for test discovery. It implements execution of `TestDescriptors` that implement the `Node` interface, including support for parallel execution. [[registration]] == Registering a TestEngine `TestEngine` registration is supported via Java's `{ServiceLoader}` mechanism. For example, the `junit-jupiter-engine` module registers its `org.junit.jupiter.engine.JupiterTestEngine` in a file named `org.junit.platform.engine.TestEngine` within the `/META-INF/services` folder in the `junit-jupiter-engine` JAR. [[requirements]] == Requirements NOTE: The words "must", "must not", "required", "shall", "shall not", "should", "should not", "recommended", "may", and "optional" in this section are to be interpreted as described in https://www.ietf.org/rfc/rfc2119.txt[RFC 2119.] [[requirements-mandatory]] === Mandatory requirements For interoperability with build tools and IDEs, `TestEngine` implementations must adhere to the following requirements: * The `TestDescriptor` returned from `TestEngine.discover()` _must_ be the root of a tree of `TestDescriptor` instances. This implies that there _must not_ be any cycles between a node and its descendants. * The hierarchy of test descriptors returned from `TestEngine.discover()` _must_ be mutable, but the test descriptors _must_ otherwise be immutable after discovery. * A `TestEngine` _must_ be able to discover `UniqueIdSelectors` for any unique ID that it previously generated and returned from `TestEngine.discover()`. This enables selecting a subset of tests to execute or rerun. * The `executionSkipped`, `executionStarted`, and `executionFinished` methods of the `EngineExecutionListener` passed to `TestEngine.execute()` _must_ be called for every `TestDescriptor` node in the tree returned from `TestEngine.discover()` at most once. Parent nodes _must_ be reported as started before their children and as finished after their children. If a node is reported as skipped, there _must not_ be any events reported for its descendants. [[requirements-enhanced-compatibility]] === Enhanced compatibility Adhering to the following requirements is optional but recommended for enhanced compatibility with build tools and IDEs: * Unless to indicate an empty discovery result, the `TestDescriptor` returned from `TestEngine.discover()` _should_ have children rather than being completely dynamic. This allows tools to display the structure of the tests and to select a subset of tests to execute. * When resolving `UniqueIdSelectors`, a `TestEngine` _should_ only return `TestDescriptor` instances with matching unique IDs including their ancestors but _may_ return additional siblings or other nodes that are required for the execution of the selected tests. * `TestEngines` _should_ support xref:running-tests/tags.adoc[tagging] tests and containers so that tag filters can be applied when discovering tests. * [[requirements-cancellation]] A `TestEngine` _should_ cancel its execution when the `{CancellationToken}` it is passed as part of the `ExecutionRequest` indicates that cancellation has been requested. In this case, it _should_ report any remaining `TestDescriptors` as skipped but not report any events for their descendants. It _may_ report already started `TestDescriptors` as aborted in case they have not been executed completely. If a `TestEngine` supports cancellation, it should clean up any resources that it has created just like if execution had finished regularly. [[discovery-issues]] == Reporting Discovery Issues Test engines should report xref:running-tests/discovery-issues.adoc[discovery issues] if they encounter any problems or potential misconfigurations during test discovery. This is especially important if the issue could lead to tests not being executed at all or only partially. In order to report a `{DiscoveryIssue}`, a test engine should call the `issueEncountered()` method on the `{EngineDiscoveryListener}` available via the `{EngineDiscoveryRequest}` passed to its `discover()` method. Rather than passing the listener around, the `{DiscoveryIssueReporter}` interface should be used. It also provides a way to create a `Condition` that reports a discovery issue if its check fails and may be used as a `Predicate` or `Consumer`. Please refer to the implementations of the <> for examples. Moreover, xref:advanced-topics/testkit.adoc#engine-discovery[Engine Test Kit] provides a way to write tests for reported discovery issues. ================================================ FILE: documentation/modules/ROOT/pages/advanced-topics/junit-platform-reporting.adoc ================================================ = JUnit Platform Reporting The `junit-platform-reporting` artifact contains `{TestExecutionListener}` implementations that generate XML test reports in two flavors: <> and <>. NOTE: The module also contains other `TestExecutionListener` implementations that can be used to build custom reporting. See xref:running-tests/using-listeners-and-interceptors.adoc[] for details. [[output-directory]] == Output Directory The JUnit Platform provides an `{OutputDirectoryCreator}` via `{EngineDiscoveryRequest}` and `{TestPlan}` to registered xref:advanced-topics/engines.adoc[test engines] and xref:running-tests/using-listeners-and-interceptors.adoc[listeners], respectively. Its root directory can be configured via the following xref:running-tests/configuration-parameters.adoc[configuration parameter]: `junit.platform.reporting.output.dir=`:: Configure the output directory for reporting. By default, `build` is used if a Gradle build script is found, and `target` if a Maven POM is found; otherwise, the current working directory is used. To create a unique output directory per test run, you can use the `\{uniqueNumber}` placeholder in the path. For example, `reports/junit-\{uniqueNumber}` will create directories like `reports/junit-8803697269315188212`. This can be useful when using Gradle's or Maven's parallel execution capabilities which create multiple JVM forks that run concurrently. [[open-test-reporting]] == Open Test Reporting `{OpenTestReportGeneratingListener}` writes an XML report for the entire execution in the event-based format specified by {OpenTestReporting} which supports all features of the JUnit Platform such as hierarchical test structures, display names, tags, etc. The listener is auto-registered and can be configured via the following xref:running-tests/configuration-parameters.adoc[configuration parameters]: `junit.platform.reporting.open.xml.enabled=true|false`:: Enable/disable writing the report; defaults to `false`. `junit.platform.reporting.open.xml.git.enabled=true|false`:: Enable/disable including information about the Git repository (see https://github.com/ota4j-team/open-test-reporting#git[Git extension schema] of open-test-reporting); defaults to `false`. `junit.platform.reporting.open.xml.socket=`:: Optional port number to redirect events to a socket instead of a file. When specified, the listener will connect to `127.0.0.1:` and send the XML events to the socket. The socket connection is automatically closed when the test execution completes. If enabled, the listener creates an XML report file named `open-test-report.xml` in the configured <>, unless the `junit.platform.reporting.open.xml.socket` configuration parameter is set, in which case the events are sent to the specified socket instead. If xref:running-tests/capturing-standard-output-error.adoc[output capturing] is enabled, the captured output written to `System.out` and `System.err` will be included in the report as well. TIP: The {OpenTestReportingCliTool} can be used to convert from the event-based format to the hierarchical format which is more human-readable. === Gradle For Gradle, writing Open Test Reporting compatible XML reports can be enabled and configured via system properties. The following samples configure its output directory to be the same directory Gradle uses for its own XML reports. A `CommandLineArgumentProvider` is used to keep the tasks relocatable across different machines which is important when using Gradle's Build Cache. [source,groovy,indent=0] [subs=attributes+] .Groovy DSL ---- dependencies { testRuntimeOnly("org.junit.platform:junit-platform-reporting:{version}") } tasks.withType(Test).configureEach { def outputDir = reports.junitXml.outputLocation jvmArgumentProviders << ({ [ "-Djunit.platform.reporting.open.xml.enabled=true", "-Djunit.platform.reporting.output.dir=${outputDir.get().asFile.absolutePath}" ] } as CommandLineArgumentProvider) } ---- [source,kotlin,indent=0] [subs=attributes+] .Kotlin DSL ---- dependencies { testRuntimeOnly("org.junit.platform:junit-platform-reporting:{version}") } tasks.withType().configureEach { val outputDir = reports.junitXml.outputLocation jvmArgumentProviders += CommandLineArgumentProvider { listOf( "-Djunit.platform.reporting.open.xml.enabled=true", "-Djunit.platform.reporting.output.dir=${outputDir.get().asFile.absolutePath}" ) } } ---- === Maven For Maven Surefire/Failsafe, you can enable Open Test Reporting output and configure the resulting XML files to be written to the same directory Surefire/Failsafe uses for its own XML reports as follows: [source,xml,indent=0] [subs=attributes+] ---- org.junit.platform junit-platform-reporting {version} test maven-surefire-plugin {surefire-version} junit.platform.reporting.open.xml.enabled = true junit.platform.reporting.output.dir = target/surefire-reports ---- === Console Launcher When using the xref:running-tests/console-launcher.adoc[], you can enable Open Test Reporting output by setting the configuration parameters via `--config`: [source,console,subs=attributes+] ---- $ java -jar junit-platform-console-standalone-{version}.jar \ --config=junit.platform.reporting.open.xml.enabled=true \ --config=junit.platform.reporting.output.dir=reports ---- Configuration parameters can also be set in a custom properties file supplied as a classpath resource via the `--config-resource` option: [source,console,subs=attributes+] ---- $ java -jar junit-platform-console-standalone-{version}.jar \ --config-resource=configuration.properties ---- [[legacy-xml]] == Legacy XML format `{LegacyXmlReportGeneratingListener}` generates a separate XML report for each root in the `{TestPlan}`. Note that the generated XML format is compatible with the de facto standard for JUnit 4 based test reports that was made popular by the Ant build system. The `LegacyXmlReportGeneratingListener` is used by the xref:running-tests/console-launcher.adoc[] as well. ================================================ FILE: documentation/modules/ROOT/pages/advanced-topics/junit-platform-suite-engine.adoc ================================================ = JUnit Platform Suite Engine The Suite Engine supports the declarative selection and execution of tests from _any_ test engine on the JUnit Platform using the xref:advanced-topics/launcher-api.adoc[]. image::junit-platform-suite-engine-diagram.svg[role=text-center] [[setup]] == Setup In addition to the `junit-platform-suite-api` and `junit-platform-suite-engine` artifacts, you need _at least one_ other test engine and its dependencies on the classpath. See xref:appendix.adoc#dependency-metadata[Dependency Metadata] for details regarding group IDs, artifact IDs, and versions. [[setup-required-dependencies]] === Required Dependencies * `junit-platform-suite-api` in _test_ scope: artifact containing annotations needed to configure a test suite * `junit-platform-suite-engine` in _test runtime_ scope: implementation of the `TestEngine` API for declarative test suites NOTE: Both of the required dependencies are aggregated in the `junit-platform-suite` artifact which can be declared in _test_ scope instead of declaring explicit dependencies on `junit-platform-suite-api` and `junit-platform-suite-engine`. [[setup-transitive-dependencies]] === Transitive Dependencies * `junit-platform-launcher` in _test_ scope * `junit-platform-engine` in _test_ scope * `junit-platform-commons` in _test_ scope * `opentest4j` in _test_ scope [[example]] == @Suite Example Annotate a class with `@Suite` to have it marked as a test suite on the JUnit Platform. As seen in the following example, selector and filter annotations can be used to control the contents of the suite. [source,java,indent=0] ---- include::example$java/example/SuiteDemo.java[tags=user_guide] ---- .Additional Configuration Options NOTE: There are numerous configuration options for discovering and filtering tests in a test suite. Please consult the Javadoc of the `{suite-api-package}` package for a full list of supported annotations and further details. == @BeforeSuite and @AfterSuite `@BeforeSuite` and `@AfterSuite` annotations can be used on methods inside a `@Suite`-annotated class. They will be executed before and after all tests of the test suite, respectively. [source,java,indent=0] ---- include::example$java/example/BeforeAndAfterSuiteDemo.java[tags=user_guide] ---- [[duplicate-test-execution]] == Duplicate Test Execution Depending on the declared selectors, different suites may contain the same tests, potentially with different configurations. Moreover, tests in a suite are executed in addition to the tests executed by every other test engine, which can result in the same tests being executed twice. image::junit-platform-suite-engine-duplicate-test-execution-diagram.svg[role=text-center] To prevent duplicate execution of tests within a suite, configure your build tool to include only the `junit-platform-suite` engine, or use a custom naming pattern. For example, name all suites `*Suite` and all tests `*Test`, and configure your build tool to include only the former. Alternatively, consider xref:running-tests/tags.adoc[using tags] to select specific groups of tests. ================================================ FILE: documentation/modules/ROOT/pages/advanced-topics/launcher-api.adoc ================================================ = JUnit Platform Launcher API One of the prominent goals of the JUnit Platform is to make the interface between JUnit and its programmatic clients – build tools and IDEs – more powerful and stable. The purpose is to decouple the internals of discovering and executing tests from all the filtering and configuration that is necessary from the outside. JUnit Platform provides a `Launcher` API that can be used to discover, filter, and execute tests. Moreover, third party test libraries – like Spock or Cucumber – can plug into the JUnit Platform's launching infrastructure by providing a custom xref:advanced-topics/engines.adoc[TestEngine]. image::launcher-api-diagram.svg[role=text-center] The launcher API is in the `{junit-platform-launcher}` module. An example consumer of the launcher API is the `{ConsoleLauncher}` in the `{junit-platform-console}` project. [[discovery]] == Discovering Tests Having _test discovery_ as a dedicated feature of the platform frees IDEs and build tools from most of the difficulties they had to go through to identify test classes and test methods in previous versions of JUnit. Usage Example: [source,java,indent=0] ---- include::example$java/example/UsingTheLauncherForDiscoveryDemo.java[tags=imports] ---- [source,java,indent=0] ---- include::example$java/example/UsingTheLauncherForDiscoveryDemo.java[tags=discovery] ---- You can select classes, methods, and all classes in a package or even search for all tests in the class-path or module-path. Discovery takes place across all participating test engines. The resulting `TestPlan` is a hierarchical (and read-only) description of all engines, classes, and test methods that fit the `LauncherDiscoveryRequest`. The client can traverse the tree, retrieve details about a node, and get a link to the original source (like class, method, or file position). Every node in the test plan has a _unique ID_ that can be used to invoke a particular test or group of tests. Clients can register one or more `{LauncherDiscoveryListener}` implementations via the `{LauncherDiscoveryRequestBuilder}` to gain insight into events that occur during test discovery. By default, the builder registers an "abort on failure" listener that aborts test discovery after the first discovery failure is encountered. The default `LauncherDiscoveryListener` can be changed via the `junit.platform.discovery.listener.default` xref:running-tests/configuration-parameters.adoc[configuration parameter]. [[execution]] == Executing Tests To execute tests, clients can use the same `LauncherDiscoveryRequest` as in the discovery phase or create a new request. Test progress and reporting can be achieved by registering one or more `{TestExecutionListener}` implementations with the `Launcher` as in the following example. [source,java,indent=0] ---- include::example$java/example/UsingTheLauncherDemo.java[tags=execution] ---- There is no return value for the `execute()` method, but you can use a `TestExecutionListener` to aggregate the results. For examples see the `{SummaryGeneratingListener}`, `{LegacyXmlReportGeneratingListener}`, and `{UniqueIdTrackingListener}`. NOTE: All `TestExecutionListener` methods are called sequentially. Methods for start events are called in registration order while methods for finish events are called in reverse order. Test case execution won't start before all `executionStarted` calls have returned. [[engines-custom]] == Registering a TestEngine See the dedicated section on xref:advanced-topics/engines.adoc#registration[TestEngine registration] for details. [[post-discovery-filters-custom]] == Registering a PostDiscoveryFilter In addition to specifying post-discovery filters as part of a `{LauncherDiscoveryRequest}` passed to the `{Launcher}` API, `{PostDiscoveryFilter}` implementations will be discovered at runtime via Java's `{ServiceLoader}` mechanism and automatically applied by the `Launcher` in addition to those that are part of the request. For example, an `example.CustomTagFilter` class implementing `PostDiscoveryFilter` and declared within the `/META-INF/services/org.junit.platform.launcher.PostDiscoveryFilter` file is loaded and applied automatically. [[launcher-session-listeners-custom]] == Registering a LauncherSessionListener Registered implementations of `{LauncherSessionListener}` are notified when a `{LauncherSession}` is opened (before a `{Launcher}` first discovers and executes tests) and closed (when no more tests will be discovered or executed). They can be registered programmatically via the `{LauncherConfig}` that is passed to the `{LauncherFactory}`, or they can be discovered at runtime via Java's `{ServiceLoader}` mechanism and automatically registered with `LauncherSession` (unless automatic registration is disabled.) [[launcher-session-listeners-tool-support]] === Tool Support The following build tools and IDEs are known to provide full support for `LauncherSession`: * Gradle 4.6 and later * Maven Surefire/Failsafe 3.0.0-M6 and later * IntelliJ IDEA 2017.3 and later Other tools might also work but have not been tested explicitly. [[launcher-session-listeners-tool-example-usage]] === Example Usage A `LauncherSessionListener` is well suited for implementing once-per-JVM setup/teardown behavior since it's called before the first and after the last test in a launcher session, respectively. The scope of a launcher session depends on the used IDE or build tool but usually corresponds to the lifecycle of the test JVM. A custom listener that starts an HTTP server before executing the first test and stops it after the last test has been executed, could look like this: [source,java] .src/test/java/example/session/GlobalSetupTeardownListener.java ---- package example.session; include::example$java/example/session/GlobalSetupTeardownListener.java[tags=user_guide] ---- <1> Get the store from the launcher session <2> Lazily create the HTTP server and put it into the store <3> Start the HTTP server It uses a wrapper class to ensure the server is stopped when the launcher session is closed: [source,java] .src/test/java/example/session/CloseableHttpServer.java ---- package example.session; include::example$java/example/session/CloseableHttpServer.java[tags=user_guide] ---- <1> The `close()` method is called when the launcher session is closed <2> Stop the HTTP server This sample uses the HTTP server implementation from the jdk.httpserver module that comes with the JDK but would work similarly with any other server or resource. In order for the listener to be picked up by JUnit Platform, you need to register it as a service by adding a resource file with the following name and contents to your test runtime classpath (e.g. by adding the file to `src/test/resources`): [source] .src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener ---- include::example$resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener[] ---- You can now use the resource from your test: [source,java] .src/test/java/example/session/HttpTests.java ---- package example.session; include::example$java/example/session/HttpTests.java[tags=user_guide] ---- <1> Retrieve the HTTP server instance from the store <2> Get the host string directly from the injected HTTP server instance <3> Get the port number directly from the injected HTTP server instance <4> Send a request to the server <5> Check the status code of the response [[launcher-interceptors-custom]] == Registering a LauncherInterceptor In order to intercept the creation of instances of `{Launcher}` and `{LauncherSessionListener}` and calls to the `discover` and `execute` methods of the former, clients can register custom implementations of `{LauncherInterceptor}` via Java's `{ServiceLoader}` mechanism by setting the `junit.platform.launcher.interceptors.enabled` xref:running-tests/configuration-parameters.adoc[configuration parameter] to `true`. [NOTE] ==== Since interceptors are registered before the test run starts, the `junit.platform.launcher.interceptors.enabled` _configuration parameter_ can only be supplied as a JVM system property or via the JUnit Platform configuration file (see xref:running-tests/configuration-parameters.adoc[] for details). This _configuration parameter_ cannot be supplied in the `LauncherDiscoveryRequest` that is passed to the `{Launcher}`. ==== A typical use case is to create a custom interceptor to replace the `ClassLoader` used by the JUnit Platform to load test classes and engine implementations. [source,java] ---- include::example$java/example/CustomLauncherInterceptor.java[tags=user_guide] ---- [[launcher-discovery-listeners-custom]] == Registering a LauncherDiscoveryListener In addition to specifying discovery listeners as part of a `{LauncherDiscoveryRequest}` or registering them programmatically via the `{Launcher}` API, custom `LauncherDiscoveryListener` implementations can be discovered at runtime via Java's `{ServiceLoader}` mechanism and automatically registered with the `Launcher` created via the `{LauncherFactory}`. For example, an `example.CustomLauncherDiscoveryListener` class implementing `LauncherDiscoveryListener` and declared within the `/META-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener` file is loaded and registered automatically. [[listeners-custom]] == Registering a TestExecutionListener In addition to the public `{Launcher}` API method for registering test execution listeners programmatically, custom `{TestExecutionListener}` implementations will be discovered at runtime via Java's `{ServiceLoader}` mechanism and automatically registered with the `Launcher` created via the `{LauncherFactory}`. For example, an `example.CustomTestExecutionListener` class implementing `TestExecutionListener` and declared within the `/META-INF/services/org.junit.platform.launcher.TestExecutionListener` file is loaded and registered automatically. [[listeners-config]] == Configuring a TestExecutionListener When a `{TestExecutionListener}` is registered programmatically via the `{Launcher}` API, the listener may provide programmatic ways for it to be configured -- for example, via its constructor, setter methods, etc. However, when a `TestExecutionListener` is registered automatically via Java's `ServiceLoader` mechanism (see <>), there is no way for the user to directly configure the listener. In such cases, the author of a `TestExecutionListener` may choose to make the listener configurable via xref:running-tests/configuration-parameters.adoc[configuration parameters]. The listener can then access the configuration parameters via the `TestPlan` supplied to the `testPlanExecutionStarted(TestPlan)` and `testPlanExecutionFinished(TestPlan)` callback methods. See the `{UniqueIdTrackingListener}` for an example. [[listeners-custom-deactivation]] == Deactivating a TestExecutionListener Sometimes it can be useful to run a test suite _without_ certain execution listeners being active. For example, you might have custom a `{TestExecutionListener}` that sends the test results to an external system for reporting purposes, and while debugging you might not want these _debug_ results to be reported. To do this, provide a pattern for the `junit.platform.execution.listeners.deactivate` _configuration parameter_ to specify which execution listeners should be deactivated (i.e. not registered) for the current test run. [NOTE] ==== Only listeners registered via the `{ServiceLoader}` mechanism within the `/META-INF/services/org.junit.platform.launcher.TestExecutionListener` file can be deactivated. In other words, any `TestExecutionListener` registered explicitly via the `{LauncherDiscoveryRequest}` cannot be deactivated via the `junit.platform.execution.listeners.deactivate` _configuration parameter_. In addition, since execution listeners are registered before the test run starts, the `junit.platform.execution.listeners.deactivate` _configuration parameter_ can only be supplied as a JVM system property or via the JUnit Platform configuration file (see xref:running-tests/configuration-parameters.adoc[] for details). This _configuration parameter_ cannot be supplied in the `LauncherDiscoveryRequest` that is passed to the `{Launcher}`. ==== [[listeners-custom-deactivation-pattern]] === Pattern Matching Syntax Refer to xref:running-tests/configuration-parameters.adoc#pattern[Pattern Matching Syntax] for details. [[launcher-config]] == Configuring the Launcher If you require fine-grained control over automatic detection and registration of test engines and listeners, you may create an instance of `{LauncherConfig}` and supply that to the `{LauncherFactory}`. Typically, an instance of `LauncherConfig` is created via the built-in fluent _builder_ API, as demonstrated in the following example. [source,java,indent=0] ---- include::example$java/example/UsingTheLauncherDemo.java[tags=launcherConfig] ---- [[dry-run-mode]] == Dry-Run Mode When running tests via the `{Launcher}` API, you can enable _dry-run mode_ by setting the `junit.platform.execution.dryRun.enabled` xref:running-tests/configuration-parameters.adoc[configuration parameter] to `true`. In this mode, the `{Launcher}` will not actually execute any tests but will notify registered `{TestExecutionListener}` instances as if all tests had been skipped and their containers had been successful. This can be useful to test changes in the configuration of a build or to verify a listener is called as expected without having to wait for all tests to be executed. [[managing-state-across-test-engines]] == Managing State Across Test Engines When running tests on the JUnit Platform, multiple test engines may need to access shared resources. Rather than initializing these resources multiple times, JUnit Platform provides mechanisms to share state across test engines efficiently. Test engines can use the Platform's `{NamespacedHierarchicalStore}` API to lazily initialize and share resources, ensuring they are created only once regardless of execution order. Any resource that is put into the store and implements `AutoCloseable` will be closed automatically when the execution is finished. TIP: The Jupiter engine allows read and write access to such resources via its `{ExtensionContext_Store}` API. The following example demonstrates two custom test engines sharing a `ServerSocket` resource. `FirstCustomEngine` attempts to retrieve an existing `ServerSocket` from the global store or creates a new one if it doesn't exist: [source,java] ---- include::example$java/example/FirstCustomEngine.java[tags=user_guide] ---- `SecondCustomEngine` follows the same pattern, ensuring that regardless whether it runs before or after `FirstCustomEngine`, it will use the same socket instance: [source,java] ---- include::example$java/example/SecondCustomEngine.java[tags=user_guide] ---- TIP: In this case, the `ServerSocket` can be stored directly in the global store while ensuring since it gets closed because it implements `AutoCloseable`. If you need to use a type that does not do so, you can wrap it in a custom class that implements `AutoCloseable` and delegates to the original type. This is important to ensure that the resource is closed properly when the test run is finished. For illustration, the following test verifies that both engines are sharing the same `ServerSocket` instance and that it's closed after `Launcher.execute()` returns: [source,java,indent=0] ---- include::example$java/example/sharedresources/SharedResourceDemo.java[tags=user_guide] ---- By using the Platform's `{NamespacedHierarchicalStore}` API with shared namespaces in this way, test engines can coordinate resource creation and sharing without direct dependencies between them. Alternatively, it's possible to inject resources into test engines by <>. [[launcher-cancellation]] == Cancelling a Running Test Execution The launcher API provides the ability to cancel a running test execution mid-flight while allowing engines to clean up resources. To request an execution to be cancelled, you need to call `cancel()` on the `{CancellationToken}` that is passed to `Launcher.execute` as part of the `{LauncherExecutionRequest}`. For example, implementing a listener that cancels test execution after the first test failed can be achieved as follows. [source,java,indent=0] ---- include::example$java/example/UsingTheLauncherDemo.java[tags=cancellation-direct] ---- <1> Create a `{CancellationToken}` <2> Implement a `{TestExecutionListener}` that calls `cancel()` when a test fails <3> Register the cancellation token <4> Register the listener <5> Pass the `{LauncherExecutionRequest}` to `Launcher.execute` [NOTE] .Test Engine Support for Cancellation ==== Cancelling tests relies on xref:advanced-topics/engines.adoc[] checking and responding to the `{CancellationToken}` appropriately (see xref:advanced-topics/engines.adoc#requirements-cancellation[Test Engine Requirements] for details). The `Launcher` will also check the token and cancel test execution when multiple test engines are present at runtime. At the time of writing, the following test engines support cancellation: * `{junit-jupiter-engine}` * `{junit-vintage-engine}` * `{junit-platform-suite-engine}` * https://github.com/junit-team/testng-engine[`testng-engine`] (version 1.1.0 and later) * Any `{TestEngine}` extending `{HierarchicalTestEngine}` such as Spock and Cucumber ==== [[launcher-cancellation-execution-request-from-discovery-request]] === Building a LauncherExecutionRequest from a LauncherDiscoveryRequest Rather than building a `{LauncherExecutionRequest}` by starting with a `{LauncherDiscoveryRequestBuilder}` and calling `forExecution()`, you can create a `{LauncherExecutionRequestBuilder}` from a previously built `{LauncherDiscoveryRequest}`. [source,java,indent=0] ---- include::example$java/example/UsingTheLauncherDemo.java[tags=cancellation-discovery-request] ---- <1> Build a `{LauncherDiscoveryRequest}` <2> Create a `{LauncherExecutionRequestBuilder}` with it <3> Register the cancellation token <4> Register the listener <5> Pass the `{LauncherExecutionRequest}` to `Launcher.execute` [[launcher-cancellation-execution-request-from-test-plan]] === Building a LauncherExecutionRequest from a TestPlan Alternatively, a `{LauncherExecutionRequestBuilder}` can be created from a previously discovered `{TestPlan}`. [source,java,indent=0] ---- include::example$java/example/UsingTheLauncherDemo.java[tags=cancellation-test-plan] ---- <1> Build a `{LauncherDiscoveryRequest}` <2> Discover a `{TestPlan}` via `Launcher.discover` <3> Create a `{LauncherExecutionRequestBuilder}` with it <4> Register the cancellation token <5> Register the listener <6> Pass the `{LauncherExecutionRequest}` to `Launcher.execute` ================================================ FILE: documentation/modules/ROOT/pages/advanced-topics/testkit.adoc ================================================ = JUnit Platform Test Kit The `junit-platform-testkit` artifact provides support for executing a test plan on the JUnit Platform and then verifying the expected results. This support is currently limited to the execution of a single `TestEngine` (see <>). [[engine]] == Engine Test Kit The `{testkit-engine-package}` package provides support for discovering and executing a `{TestPlan}` for a given `{TestEngine}` running on the JUnit Platform and then accessing the results via convenient result objects. For execution, a fluent API may be used to verify the expected execution events were received. The key entry point into this API is the `{EngineTestKit}` which provides static factory methods named `engine()`, `discover()`, and `execute()`. It is recommended that you select one of the `engine()` variants to benefit from the fluent API for building a `LauncherDiscoveryRequest`. NOTE: If you prefer to use the `LauncherDiscoveryRequestBuilder` from the `Launcher` API to build your `LauncherDiscoveryRequest`, you must use one of the `discover()` or `execute()` variants in `EngineTestKit`. The following test class written using JUnit Jupiter will be used in subsequent examples. [[engine-ExampleTestCase]] [source,java,indent=0] ---- include::example$java/example/ExampleTestCase.java[tags=user_guide] ---- For the sake of brevity, the following sections demonstrate how to test JUnit's own `JupiterTestEngine` whose unique engine ID is `"junit-jupiter"`. If you want to test your own `TestEngine` implementation, you need to use its unique engine ID. Alternatively, you may test your own `TestEngine` by supplying an instance of it to the `EngineTestKit.engine(TestEngine)` static factory method. [[engine-discovery]] == Verifying Test Discovery The following test demonstrates how to verify that a `TestPlan` was discovered as expected by the JUnit Jupiter `TestEngine`. [source,java,indent=0] ---- include::example$java/example/testkit/EngineTestKitDiscoveryDemo.java[tags=user_guide] ---- <1> Select the JUnit Jupiter `TestEngine`. <2> Select the <> test class. <3> Discover the `TestPlan`. <4> Assert engine root descriptor has expected display name. <5> Assert no discovery issues were encountered. [[engine-statistics]] == Asserting Execution Statistics One of the most common features of the Test Kit is the ability to assert statistics against events fired during the execution of a `TestPlan`. The following tests demonstrate how to assert statistics for _containers_ and _tests_ in the JUnit Jupiter `TestEngine`. For details on what statistics are available, consult the Javadoc for `{EventStatistics}`. [source,java,indent=0] ---- include::example$java/example/testkit/EngineTestKitStatisticsDemo.java[tags=user_guide] ---- <1> Select the JUnit Jupiter `TestEngine`. <2> Select the <> test class. <3> Execute the `TestPlan`. <4> Filter by _container_ events. <5> Assert statistics for _container_ events. <6> Filter by _test_ events. <7> Assert statistics for _test_ events. NOTE: In the `verifyJupiterContainerStats()` test method, the counts for the `started` and `succeeded` statistics are `2` since the `JupiterTestEngine` and the <> class are both considered containers. [[engine-events]] == Asserting Events If you find that <> alone is insufficient for verifying the expected behavior of test execution, you can work directly with the recorded `{Event}` elements and perform assertions against them. For example, if you want to verify the reason that the `skippedTest()` method in <> was skipped, you can do that as follows. [TIP] ==== The `assertThatEvents()` method in the following example is a shortcut for `org.assertj.core.api.Assertions.assertThat(events.list())` from the {AssertJ} assertion library. For details on what _conditions_ are available for use with AssertJ assertions against events, consult the Javadoc for `{EventConditions}`. ==== [source,java,indent=0] ---- include::example$java/example/testkit/EngineTestKitSkippedMethodDemo.java[tags=user_guide] ---- <1> Select the JUnit Jupiter `TestEngine`. <2> Select the `skippedTest()` method in the <> test class. <3> Execute the `TestPlan`. <4> Filter by _test_ events. <5> Save the _test_ `Events` to a local variable. <6> Optionally assert the expected statistics. <7> Assert that the recorded _test_ events contain exactly one skipped test named `skippedTest` with `"for demonstration purposes"` as the _reason_. If you want to verify the type of exception thrown from the `failingTest()` method in <>, you can do that as follows. [TIP] ==== For details on what _conditions_ are available for use with AssertJ assertions against events and execution results, consult the Javadoc for `{EventConditions}` and `{TestExecutionResultConditions}`, respectively. ==== [source,java,indent=0] ---- include::example$java/example/testkit/EngineTestKitFailedMethodDemo.java[tags=user_guide] ---- <1> Select the JUnit Jupiter `TestEngine`. <2> Select the <> test class. <3> Execute the `TestPlan`. <4> Filter by _test_ events. <5> Assert that the recorded _test_ events contain exactly one failing test named `failingTest` with an exception of type `ArithmeticException` and an error message that ends with `"/ by zero"`. Although typically unnecessary, there are times when you need to verify **all** of the events fired during the execution of a `TestPlan`. The following test demonstrates how to achieve this via the `assertEventsMatchExactly()` method in the `EngineTestKit` API. [TIP] ==== Since `assertEventsMatchExactly()` matches conditions exactly in the order in which the events were fired, <> has been annotated with `@TestMethodOrder(OrderAnnotation.class)` and each test method has been annotated with `@Order(...)`. This allows us to enforce the order in which the test methods are executed, which in turn allows our `verifyAllJupiterEvents()` test to be reliable. ==== If you want to do a _partial_ match _with_ or _without_ ordering requirements, you can use the methods `assertEventsMatchLooselyInOrder()` and `assertEventsMatchLoosely()`, respectively. [source,java,indent=0] ---- include::example$java/example/testkit/EngineTestKitAllEventsDemo.java[tags=user_guide] ---- <1> Select the JUnit Jupiter `TestEngine`. <2> Select the <> test class. <3> Execute the `TestPlan`. <4> Filter by _all_ events. <5> Print all events to the supplied `writer` for debugging purposes. Debug information can also be written to an `OutputStream` such as `System.out` or `System.err`. <6> Assert _all_ events in exactly the order in which they were fired by the test engine. The `debug()` invocation from the preceding example results in output similar to the following. [source,options="nowrap"] ---- All Events: Event [type = STARTED, testDescriptor = JupiterEngineDescriptor: [engine:junit-jupiter], timestamp = 2018-12-14T12:45:14.082280Z, payload = null] Event [type = STARTED, testDescriptor = ClassTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase], timestamp = 2018-12-14T12:45:14.089339Z, payload = null] Event [type = SKIPPED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:skippedTest()], timestamp = 2018-12-14T12:45:14.094314Z, payload = 'for demonstration purposes'] Event [type = STARTED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:succeedingTest()], timestamp = 2018-12-14T12:45:14.095182Z, payload = null] Event [type = FINISHED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:succeedingTest()], timestamp = 2018-12-14T12:45:14.104922Z, payload = TestExecutionResult [status = SUCCESSFUL, throwable = null]] Event [type = STARTED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:abortedTest()], timestamp = 2018-12-14T12:45:14.106121Z, payload = null] Event [type = FINISHED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:abortedTest()], timestamp = 2018-12-14T12:45:14.109956Z, payload = TestExecutionResult [status = ABORTED, throwable = org.opentest4j.TestAbortedException: Assumption failed: abc does not contain Z]] Event [type = STARTED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:failingTest()], timestamp = 2018-12-14T12:45:14.110680Z, payload = null] Event [type = FINISHED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:failingTest()], timestamp = 2018-12-14T12:45:14.111217Z, payload = TestExecutionResult [status = FAILED, throwable = java.lang.ArithmeticException: / by zero]] Event [type = FINISHED, testDescriptor = ClassTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase], timestamp = 2018-12-14T12:45:14.113731Z, payload = TestExecutionResult [status = SUCCESSFUL, throwable = null]] Event [type = FINISHED, testDescriptor = JupiterEngineDescriptor: [engine:junit-jupiter], timestamp = 2018-12-14T12:45:14.113806Z, payload = TestExecutionResult [status = SUCCESSFUL, throwable = null]] ---- ================================================ FILE: documentation/modules/ROOT/pages/api-evolution.adoc ================================================ = API Evolution :page-toclevels: 1 One of the major goals of the JUnit Platform architecture is to improve maintainers' capabilities to evolve JUnit despite its being used in many projects. With JUnit 4 a lot of stuff that was originally added as an internal construct only got used by external extension writers and tool builders. That made changing JUnit 4 especially difficult and sometimes impossible. That's why JUnit now uses a defined lifecycle for all publicly available interfaces, classes, and methods. [[version-and-status]] == API Version and Status Every published artifact has a version number `..`, and all publicly available interfaces, classes, and methods are annotated with {API} from the {API_Guardian} project. The annotation's `status` attribute can be assigned one of the following values. [cols="20,80"] |=== | Status | Description | `INTERNAL` | Must not be used by any code other than JUnit itself. Might be removed without prior notice. | `DEPRECATED` | Should no longer be used; might disappear in the next minor release. | `EXPERIMENTAL` | Intended for new, experimental features where we are looking for feedback. + Use this element with caution; it might be promoted to `MAINTAINED` or `STABLE` in the future, but might also be removed without prior notice, even in a patch. | `MAINTAINED` | Intended for features that will not be changed in a backwards- incompatible way for *at least* the next minor release of the current major version. If scheduled for removal, it will be demoted to `DEPRECATED` first. | `STABLE` | Intended for features that will not be changed in a backwards- incompatible way in the current major version (`6.*`). |=== If the `@API` annotation is present on a type, it is considered to be applicable for all public members of that type as well. A member is allowed to declare a different `status` value of lower stability. [[experimental-apis]] == Experimental APIs The following tables list which APIs are currently designated as _experimental_ via `@API(status = EXPERIMENTAL)`. Caution should be taken when relying on such APIs. include::partial$experimental-apis-table.adoc[] [[deprecated-apis]] == Deprecated APIs The following tables list which APIs are currently designated as _deprecated_ via `@API(status = DEPRECATED)`. You should avoid using deprecated APIs whenever possible, since such APIs will likely be removed in an upcoming release. include::partial$deprecated-apis-table.adoc[] [[tooling]] == @API Tooling Support The {API_Guardian} project plans to provide tooling support for publishers and consumers of APIs annotated with {API}. For example, the tooling support will likely provide a means to check if JUnit APIs are being used in accordance with `@API` annotation declarations. ================================================ FILE: documentation/modules/ROOT/pages/appendix.adoc ================================================ = Appendix [[reproducible-builds]] == Reproducible Builds JUnit aims for its non-javadoc JARs to be https://reproducible-builds.org/[reproducible]. Under identical build conditions, such as Java version, repeated builds should provide the same output byte-for-byte. This means that anyone can reproduce the build conditions of the artifacts on Maven Central/Sonatype and produce the same output artifact locally, confirming that the artifacts in the repositories were actually generated from this source code. [[dependency-metadata]] == Dependency Metadata Artifacts for final releases and milestones are deployed to {Maven_Central}. Consult https://central.sonatype.org/consume/[Sonatype's documentation] for how to consume those artifacts with a build tool of your choice. Snapshot artifacts are deployed to Sonatype's {snapshot-repo}[snapshots repository] under {snapshot-repo}/org/junit/[/org/junit]. Please refer to https://central.sonatype.org/publish/publish-portal-snapshots/#consuming-snapshot-releases-for-your-project[Sonatype's documentation] for instructions on how to consume them with a build tool of your choice. The sections below list all artifacts with their versions for the three groups: <>, <>, and <>. The <> contains a list of all of the above artifacts and their versions. [TIP] .Aligning dependency versions ==== To ensure that all JUnit artifacts are compatible with each other, their versions should be aligned. If you rely on xref:running-tests/build-support.adoc#spring-boot[Spring Boot] for dependency management, please see the corresponding section. Otherwise, instead of managing individual versions of the JUnit artifacts, it is recommended to apply the <> to your project. Please refer to the corresponding sections for xref:running-tests/build-support.adoc#maven-bom[Maven] or xref:running-tests/build-support.adoc#gradle-bom[Gradle]. ==== [[dependency-metadata-junit-platform]] === JUnit Platform * *Group ID*: `org.junit.platform` * *Version*: `{version}` * *Artifact IDs*: `junit-platform-commons`:: Common APIs and support utilities for the JUnit Platform. Any API annotated with `@API(status = INTERNAL)` is intended solely for usage within the JUnit framework itself. _Any usage of internal APIs by external parties is not supported!_ `junit-platform-console`:: Support for discovering and executing tests on the JUnit Platform from the console. See xref:running-tests/console-launcher.adoc[] for details. `junit-platform-console-standalone`:: An executable _Fat JAR_ that contains all dependencies is provided in Maven Central under the https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone[junit-platform-console-standalone] directory. See xref:running-tests/console-launcher.adoc[] for details. `junit-platform-engine`:: Public API for test engines. See xref:advanced-topics/launcher-api.adoc#engines-custom[Registering a TestEngine] for details. `junit-platform-launcher`:: Public API for configuring and launching test plans -- typically used by IDEs and build tools. See xref:advanced-topics/launcher-api.adoc[] for details. `junit-platform-reporting`:: `TestExecutionListener` implementations that generate test reports -- typically used by IDEs and build tools. See xref:advanced-topics/junit-platform-reporting.adoc[] for details. `junit-platform-suite`:: JUnit Platform Suite artifact that transitively pulls in dependencies on `junit-platform-suite-api` and `junit-platform-suite-engine` for simplified dependency management in build tools such as Gradle and Maven. `junit-platform-suite-api`:: Annotations for configuring test suites on the JUnit Platform. Supported by the xref:advanced-topics/junit-platform-suite-engine.adoc[JUnit Platform Suite Engine]. `junit-platform-suite-engine`:: Engine that executes test suites on the JUnit Platform; only required at runtime. See xref:advanced-topics/junit-platform-suite-engine.adoc[JUnit Platform Suite Engine] for details. `junit-platform-testkit`:: Provides support for executing a test plan for a given `TestEngine` and then accessing the results via a fluent API to verify the expected results. [[dependency-metadata-junit-jupiter]] === JUnit Jupiter * *Group ID*: `org.junit.jupiter` * *Version*: `{version}` * *Artifact IDs*: `junit-jupiter`:: JUnit Jupiter aggregator artifact that transitively pulls in dependencies on `junit-jupiter-api`, `junit-jupiter-params`, and `junit-jupiter-engine` for simplified dependency management in build tools such as Gradle and Maven. `junit-jupiter-api`:: JUnit Jupiter API for xref:writing-tests/intro.adoc[writing tests] and xref:extensions/overview.adoc[extensions]. `junit-jupiter-engine`:: JUnit Jupiter test engine implementation; only required at runtime. `junit-jupiter-params`:: Support for xref:writing-tests/parameterized-classes-and-tests.adoc[] in JUnit Jupiter. `junit-jupiter-migrationsupport`:: _Deprecated_ support for migrating from JUnit 4 to JUnit Jupiter; only required for support for JUnit 4's `@Ignore` annotation and for running selected JUnit 4 rules. [[dependency-metadata-junit-vintage]] === JUnit Vintage * *Group ID*: `org.junit.vintage` * *Version*: `{version}` * *Artifact ID*: `junit-vintage-engine`:: JUnit Vintage test engine implementation that allows one to run _vintage_ JUnit tests on the JUnit Platform. _Vintage_ tests include those written using JUnit 3 or JUnit 4 APIs or tests written using testing frameworks built on those APIs. [[dependency-metadata-junit-bom]] === Bill of Materials (BOM) The _Bill of Materials_ POM provided under the following Maven coordinates can be used to ease dependency management when referencing multiple of the above artifacts using xref:running-tests/build-support.adoc#maven-bom[Maven] or xref:running-tests/build-support.adoc#gradle-bom[Gradle]. * *Group ID*: `org.junit` * *Artifact ID*: `junit-bom` * *Version*: `{version}` [[dependency-metadata-dependencies]] === Dependencies Most of the above artifacts have a dependency in their published Maven POMs on the following _@API Guardian_ JAR. * *Group ID*: `org.apiguardian` * *Artifact ID*: `apiguardian-api` * *Version*: `{apiguardian-version}` In addition, most of the above artifacts have a direct or transitive dependency on the following _OpenTest4J_ JAR. * *Group ID*: `org.opentest4j` * *Artifact ID*: `opentest4j` * *Version*: `{ota4j-version}` [[dependency-diagram]] == Dependency Diagram image::component-diagram.svg[] ================================================ FILE: documentation/modules/ROOT/pages/extensions/conditional-test-execution.adoc ================================================ = Conditional Test Execution `{ExecutionCondition}` defines the `Extension` API for programmatic, _conditional test execution_. An `ExecutionCondition` is _evaluated_ for each container (e.g., a test class) to determine if all the tests it contains should be executed based on the supplied `ExtensionContext`. Similarly, an `ExecutionCondition` is _evaluated_ for each test to determine if a given test method should be executed based on the supplied `ExtensionContext`. When multiple `ExecutionCondition` extensions are registered, a container or test is disabled as soon as one of the conditions returns _disabled_. Thus, there is no guarantee that a condition is evaluated because another extension might have already caused a container or test to be disabled. In other words, the evaluation works like the short-circuiting boolean OR operator. See the source code of `{DisabledCondition}` and `{Disabled}` for concrete examples. [[deactivation]] == Deactivating Conditions Sometimes it can be useful to run a test suite _without_ certain conditions being active. For example, you may wish to run tests even if they are annotated with `@Disabled` in order to see if they are still _broken_. To do this, provide a pattern for the `junit.jupiter.conditions.deactivate` _configuration parameter_ to specify which conditions should be deactivated (i.e., not evaluated) for the current test run. The pattern can be supplied as a JVM system property, as a _configuration parameter_ in the `LauncherDiscoveryRequest` that is passed to the `Launcher`, or via the JUnit Platform configuration file (see xref:running-tests/configuration-parameters.adoc[] for details). For example, to deactivate JUnit's `@Disabled` condition, you can start your JVM with the following system property. `-Djunit.jupiter.conditions.deactivate=org.junit.*DisabledCondition` [[deactivation-patterns]] === Pattern Matching Syntax Refer to xref:running-tests/configuration-parameters.adoc#pattern[Pattern Matching Syntax] for details. ================================================ FILE: documentation/modules/ROOT/pages/extensions/exception-handling.adoc ================================================ = Exception Handling Exceptions thrown during the test execution may be intercepted and handled accordingly before propagating further, so that certain actions like error logging or resource releasing may be defined in specialized `Extensions`. JUnit Jupiter offers API for `Extensions` that wish to handle exceptions thrown during `@Test` methods via `{TestExecutionExceptionHandler}` and for those thrown during one of test lifecycle methods (`@BeforeAll`, `@BeforeEach`, `@AfterEach` and `@AfterAll`) via `{LifecycleMethodExecutionExceptionHandler}`. The following example shows an extension which will swallow all instances of `IOException` but rethrow any other type of exception. [source,java,indent=0] .An exception handling extension that filters IOExceptions in test execution ---- include::example$java/example/exception/IgnoreIOExceptionExtension.java[tags=user_guide] ---- Another example shows how to record the state of an application under test exactly at the point of unexpected exception being thrown during setup and cleanup. Note that unlike relying on lifecycle callbacks, which may or may not be executed depending on the test status, this solution guarantees execution immediately after failing `@BeforeAll`, `@BeforeEach`, `@AfterEach` or `@AfterAll`. [source,java,indent=0] .An exception handling extension that records application state on error ---- include::example$java/example/exception/RecordStateOnErrorExtension.java[tags=user_guide] ---- Multiple execution exception handlers may be invoked for the same lifecycle method in order of declaration. If one of the handlers swallows the handled exception, subsequent ones will not be executed, and no failure will be propagated to JUnit engine, as if the exception was never thrown. Handlers may also choose to rethrow the exception or throw a different one, potentially wrapping the original. Extensions implementing `{LifecycleMethodExecutionExceptionHandler}` that wish to handle exceptions thrown during `@BeforeAll` or `@AfterAll` need to be registered on a class level, while handlers for `BeforeEach` and `AfterEach` may be also registered for individual test methods. [source,java,indent=0] .Registering multiple exception handling extensions ---- include::example$java/example/exception/MultipleHandlersTestCase.java[tags=user_guide] ---- ================================================ FILE: documentation/modules/ROOT/pages/extensions/intercepting-invocations.adoc ================================================ = Intercepting Invocations `{InvocationInterceptor}` defines the API for `Extensions` that wish to intercept calls to test code. The following example shows an extension that executes all test methods in Swing's Event Dispatch Thread. [source,java,indent=0] .An extension that executes tests in a user-defined thread ---- include::example$java/example/interceptor/SwingEdtInterceptor.java[tags=user_guide] ---- [NOTE] .Accessing the test-scoped `ExtensionContext` ==== You may override the `getTestInstantiationExtensionContextScope(...)` method to return `TEST_METHOD` to make test-specific data available to your extension implementation of `interceptTestClassConstructor` or if you want to xref:extensions/keeping-state-in-extensions.adoc[keep state] on the test method level. ==== ================================================ FILE: documentation/modules/ROOT/pages/extensions/keeping-state-in-extensions.adoc ================================================ = Keeping State in Extensions Usually, an extension is instantiated only once. So the question becomes relevant: How do you keep the state from one invocation of an extension to the next? The `{ExtensionContext}` API provides a `{ExtensionContext_Store}` exactly for this purpose. Extensions may put values into a store for later retrieval. TIP: See the `xref:extensions/test-lifecycle-callbacks.adoc#timing-extension[TimingExtension]` for an example of using the `Store` with a method-level scope. .The `ExtensionContext` and `Store` hierarchy image::extensions_StoreHierarchy.svg[alt=UML diagram,role=text-center] As illustrated by the diagram above, stores are hierarchical in nature. When looking up a value, if no value is stored in the current `ExtensionContext` for the supplied key, the stores of the context's ancestors will be queried for a value with the same key in the `Namespace` used to create this store. The root `ExtensionContext` represents the engine level so its `Store` may be used to store or cache values that are used by multiple test classes or extension. The `{ExtensionContext_StoreScope}` enum allows to go beyond even that and access the stores on the level of the current `{LauncherExecutionRequest}` or `{LauncherSession}` which can be used to share data across test engines or inject data from a registered xref:advanced-topics/launcher-api.adoc#launcher-session-listeners-custom[`LauncherSessionListener`], respectively. Please consult the Javadoc of `{ExtensionContext}`, `{ExtensionContext_Store}`, and `{ExtensionContext_StoreScope}` for details. [[support]] [NOTE] .Resource management via `_AutoCloseable_` ==== An extension context store is bound to its extension context lifecycle. When an extension context lifecycle ends it closes its associated store. All stored values that are instances of `AutoCloseable` are notified by an invocation of their `close()` method in the inverse order they were added in (unless the `junit.jupiter.extensions.store.close.autocloseable.enabled` xref:running-tests/configuration-parameters.adoc[configuration parameter] is set to `false`). Versions prior to 5.13 only supported `CloseableResource`, which is deprecated but still available for backward compatibility. ==== An example implementation of `AutoCloseable` is shown below, using an `HttpServer` resource. .`_HttpServer_` resource implementing `_AutoCloseable_` [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/extensions/HttpServerResource.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/extensions/HttpServerResource.kt[tags=user_guide] ---- -- ==== This resource can then be stored in the desired `ExtensionContext`. It may be stored at class or method level, if desired, but this may add unnecessary overhead for this type of resource. For this example it might be prudent to store it at root level and instantiate it lazily to ensure it's only created once per test run and reused across different test classes and methods. .Lazily storing in root context with `_Store.computeIfAbsent_` [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/extensions/HttpServerExtension.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/extensions/HttpServerExtension.kt[tags=user_guide] ---- -- ==== .A test case using the `_HttpServerExtension_` [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/HttpServerDemo.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/HttpServerDemo.kt[tags=user_guide] ---- -- ==== [[migration]] [TIP] .Migration Note for Resource Cleanup ====== The framework automatically closes resources stored in the `ExtensionContext.Store` that implement `AutoCloseable`. In versions prior to 5.13, only resources implementing `Store.CloseableResource` were automatically closed. If you're developing an extension that needs to support both JUnit Jupiter 5.13+ and earlier versions and your extension stores resources that need to be cleaned up, you should implement both interfaces: [tabs] ==== Java:: + -- [source,java,indent=0] ---- public class MyResource implements Store.CloseableResource, AutoCloseable { @Override public void close() throws Exception { // Resource cleanup code } } ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- class MyResource : Store.CloseableResource, AutoCloseable { override fun close() { // Resource cleanup code } } ---- -- ==== This ensures that your resource will be properly closed regardless of which JUnit Jupiter version is being used. ====== ================================================ FILE: documentation/modules/ROOT/pages/extensions/overview.adoc ================================================ = Extension Model In contrast to the competing `Runner`, `TestRule`, and `MethodRule` extension points in JUnit 4, the JUnit Jupiter extension model consists of a single, coherent concept: the `Extension` API. Note, however, that `Extension` itself is just a marker interface. ================================================ FILE: documentation/modules/ROOT/pages/extensions/parameter-resolution.adoc ================================================ = Parameter Resolution `{ParameterResolver}` defines the `Extension` API for dynamically resolving parameters at runtime. If a _test class_ constructor, _test method_, or _lifecycle method_ (see xref:writing-tests/definitions.adoc[]) declares a parameter, the parameter must be _resolved_ at runtime by a `ParameterResolver`. A `ParameterResolver` can either be built-in (see `{TestInfoParameterResolver}`) or xref:extensions/registering-extensions.adoc[registered by the user]. Generally speaking, parameters may be resolved by _name_, _type_, _annotation_, or any combination thereof. If you wish to implement a custom `{ParameterResolver}` that resolves parameters based solely on the type of the parameter, you may find it convenient to extend the `{TypeBasedParameterResolver}` which serves as a generic adapter for such use cases. For concrete examples, consult the source code for `{CustomTypeParameterResolver}`, `{CustomAnnotationParameterResolver}`, and `{MapOfListsTypeBasedParameterResolver}`. [WARNING] ==== Due to a bug in the byte code generated by `javac` on JDK versions prior to JDK 9, looking up annotations on parameters directly via the core `java.lang.reflect.Parameter` API will always fail for _inner class_ constructors (e.g., a constructor in a `@Nested` test class). The `{ParameterContext}` API supplied to `ParameterResolver` implementations therefore includes the following convenience methods for correctly looking up annotations on parameters. Extension authors are strongly encouraged to use these methods instead of those provided in `java.lang.reflect.Parameter` in order to avoid this bug in the JDK. * `boolean isAnnotated(Class annotationType)` * `Optional findAnnotation(Class annotationType)` * `List findRepeatableAnnotations(Class annotationType)` ==== [NOTE] .Accessing the test-scoped `ExtensionContext` ==== You may override the `getTestInstantiationExtensionContextScope(...)` method to return `TEST_METHOD` to support injecting test specific data into constructor parameters of the test class instance. Doing so causes a test-specific `{ExtensionContext}` to be used while resolving constructor parameters, unless the xref:writing-tests/test-instance-lifecycle.adoc[test instance lifecycle] is set to `PER_CLASS`. ==== [TIP] .Parameter resolution for methods called from extensions ==== Other extensions can also leverage registered `ParameterResolvers` for method and constructor invocations, using the `{ExecutableInvoker}` available via the `getExecutableInvoker()` method in the `ExtensionContext`. ==== [[conflicts]] == Parameter Conflicts If multiple implementations of `ParameterResolver` that support the same type are registered for a test, a `ParameterResolutionException` will be thrown, with a message to indicate that competing resolvers have been discovered. See the following example: .Conflicting parameter resolution due to multiple resolvers claiming support for integers [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/extensions/ParameterResolverConflictDemo.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/extensions/ParameterResolverConflictDemo.kt[tags=user_guide] ---- -- ==== If the conflicting `ParameterResolver` implementations are applied to different test methods as shown in the following example, no conflict occurs. .Fine-grained registration to avoid conflict [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/extensions/ParameterResolverNoConflictDemo.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/extensions/ParameterResolverNoConflictDemo.kt[tags=user_guide] ---- -- ==== If the conflicting `ParameterResolver` implementations need to be applied to the same test method, you can implement a custom type or custom annotation as illustrated by `{CustomTypeParameterResolver}` and `{CustomAnnotationParameterResolver}`, respectively. .Custom type to resolve duplicate types [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/extensions/ParameterResolverCustomTypeDemo.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/extensions/ParameterResolverCustomTypeDemo.kt[tags=user_guide] ---- -- ==== A custom annotation makes the duplicate type distinguishable from its counterpart: .Custom annotation to resolve duplicate types [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/extensions/ParameterResolverCustomAnnotationDemo.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/extensions/ParameterResolverCustomAnnotationDemo.kt[tags=user_guide] ---- -- ==== JUnit includes some built-in parameter resolvers that can cause conflicts if a resolver attempts to claim their supported types. For example, `{TestInfo}` provides metadata about tests. See xref:writing-tests/dependency-injection-for-constructors-and-methods.adoc[] for details. Third-party frameworks such as Spring may also define parameter resolvers. Apply one of the techniques in this section to resolve any conflicts. Parameterized tests are another potential source of conflict. Ensure that tests annotated with `@ParameterizedTest` are not also annotated with `@Test` and see xref:writing-tests/parameterized-classes-and-tests.adoc#consuming-arguments[Consuming Arguments] for more details. ================================================ FILE: documentation/modules/ROOT/pages/extensions/pre-interrupt-callback.adoc ================================================ = Pre-Interrupt Callback `{PreInterruptCallback}` defines the API for `Extensions` that wish to react on timeouts before the `Thread.interrupt()` is called. Please refer to xref:writing-tests/timeouts.adoc#debugging[Debugging Timeouts] for additional information. ================================================ FILE: documentation/modules/ROOT/pages/extensions/providing-invocation-contexts-for-class-templates.adoc ================================================ = Providing Invocation Contexts for Class Templates A `{ClassTemplate}` class can only be executed when at least one `{ClassTemplateInvocationContextProvider}` is registered. Each such provider is responsible for providing a `Stream` of `{ClassTemplateInvocationContext}` instances. Each context may specify a custom display name and a list of additional extensions that will only be used for the next invocation of the `{ClassTemplate}`. The following example shows how to write a class template as well as how to register and implement a `{ClassTemplateInvocationContextProvider}`. [source,java,indent=0] .A class template with accompanying extension ---- include::example$java/example/ClassTemplateDemo.java[tags=user_guide] ---- In this example, the class template will be invoked twice, meaning all test methods in the class template will be executed twice. The display names of the invocations will be `apple` and `banana` as specified by the invocation context. Each invocation registers a custom `{TestInstancePostProcessor}` which is used to inject a value into a field. The output when using the `ConsoleLauncher` is as follows. .... └─ ClassTemplateDemo ✔ ├─ apple ✔ │ ├─ notNull() ✔ │ └─ wellKnown() ✔ └─ banana ✔ ├─ notNull() ✔ └─ wellKnown() ✔ .... The `{ClassTemplateInvocationContextProvider}` extension API is primarily intended for implementing different kinds of tests that rely on repetitive invocation of _all_ test methods in a test class albeit in different contexts — for example, with different parameters, by preparing the test class instance differently, or multiple times without modifying the context. Please refer to the implementations of xref:writing-tests/parameterized-classes-and-tests.adoc[Parameterized Classes] which uses this extension point to provide its functionality. ================================================ FILE: documentation/modules/ROOT/pages/extensions/providing-invocation-contexts-for-test-templates.adoc ================================================ = Providing Invocation Contexts for Test Templates A `{TestTemplate}` method can only be executed when at least one `{TestTemplateInvocationContextProvider}` is registered. Each such provider is responsible for providing a `Stream` of `{TestTemplateInvocationContext}` instances. Each context may specify a custom display name and a list of additional extensions that will only be used for the next invocation of the `{TestTemplate}` method. The following example shows how to write a test template as well as how to register and implement a `{TestTemplateInvocationContextProvider}`. [source,java,indent=0] .A test template with accompanying extension ---- include::example$java/example/TestTemplateDemo.java[tags=user_guide] ---- In this example, the test template will be invoked twice. The display names of the invocations will be `apple` and `banana` as specified by the invocation context. Each invocation registers a custom `{ParameterResolver}` which is used to resolve the method parameter. The output when using the `ConsoleLauncher` is as follows. .... └─ testTemplate(String) ✔ ├─ apple ✔ └─ banana ✔ .... The `{TestTemplateInvocationContextProvider}` extension API is primarily intended for implementing different kinds of tests that rely on repetitive invocation of a test-like method albeit in different contexts — for example, with different parameters, by preparing the test class instance differently, or multiple times without modifying the context. Please refer to the implementations of xref:writing-tests/repeated-tests.adoc[] or xref:writing-tests/parameterized-classes-and-tests.adoc[Parameterized Tests] which use this extension point to provide their functionality. ================================================ FILE: documentation/modules/ROOT/pages/extensions/registering-extensions.adoc ================================================ = Registering Extensions Extensions can be registered _declaratively_ via <>, _programmatically_ via <>, or _automatically_ via Java's <> mechanism. [[registration-declarative]] == Declarative Extension Registration Developers can register one or more extensions _declaratively_ by annotating a test interface, test class, test method, or custom _xref:writing-tests/annotations.adoc#annotations[composed annotation]_ with `@ExtendWith(...)` and supplying class references for the extensions to register. `@ExtendWith` may also be declared on fields or on parameters in test class constructors, in test methods, and in `@BeforeAll`, `@AfterAll`, `@BeforeEach`, and `@AfterEach` lifecycle methods. For example, to register a `WebServerExtension` for a particular test method, you would annotate the test method as follows. We assume the `WebServerExtension` starts a local web server and injects the server's URL into parameters annotated with `@WebServerUrl`. [tabs] ==== Java:: + -- [source,java,indent=0] ---- @Test @ExtendWith(WebServerExtension.class) void getProductList(@WebServerUrl String serverUrl) { WebClient webClient = new WebClient(); // Use WebClient to connect to web server using serverUrl and verify response assertEquals(200, webClient.get(serverUrl + "/products").getResponseStatus()); } ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- @Test @ExtendWith(WebServerExtension::class) fun getProductList(@WebServerUrl serverUrl: String) { val webClient = WebClient() // Use WebClient to connect to web server using serverUrl and verify response assertEquals(200, webClient.get("$serverUrl/products").responseStatus) } ---- -- ==== To register the `WebServerExtension` for all tests in a particular class and its subclasses, you would annotate the test class as follows. [tabs] ==== Java:: + -- [source,java,indent=0] ---- @ExtendWith(WebServerExtension.class) class MyTests { // ... } ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- @ExtendWith(WebServerExtension::class) class MyTests { // ... } ---- -- ==== Multiple extensions can be registered together like this: [tabs] ==== Java:: + -- [source,java,indent=0] ---- @ExtendWith({ DatabaseExtension.class, WebServerExtension.class }) class MyFirstTests { // ... } ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- @ExtendWith(DatabaseExtension::class, WebServerExtension::class) class MyFirstTests { // ... } ---- -- ==== As an alternative, multiple extensions can be registered separately like this: [tabs] ==== Java:: + -- [source,java,indent=0] ---- @ExtendWith(DatabaseExtension.class) @ExtendWith(WebServerExtension.class) class MySecondTests { // ... } ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- @ExtendWith(DatabaseExtension::class) @ExtendWith(WebServerExtension::class) class MySecondTests { // ... } ---- -- ==== [TIP] .Extension Registration Order ==== Extensions registered declaratively via `@ExtendWith` at the class level, method level, or parameter level will be executed in the order in which they are declared in the source code. For example, the execution of tests in both `MyFirstTests` and `MySecondTests` will be extended by the `DatabaseExtension` and `WebServerExtension`, **in exactly that order**. ==== If you wish to combine multiple extensions in a reusable way, you can define a custom _xref:writing-tests/annotations.adoc#annotations[composed annotation]_ and use `@ExtendWith` as a _meta-annotation_ as in the following code listing. Then `@DatabaseAndWebServerExtension` can be used in place of `@ExtendWith({ DatabaseExtension.class, WebServerExtension.class })`. [tabs] ==== Java:: + -- [source,java,indent=0] ---- @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @ExtendWith({ DatabaseExtension.class, WebServerExtension.class }) public @interface DatabaseAndWebServerExtension { } ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) @ExtendWith(DatabaseExtension::class, WebServerExtension::class) annotation class DatabaseAndWebServerExtension ---- -- ==== The above examples demonstrate how `@ExtendWith` can be applied at the class level or at the method level; however, for certain use cases it makes sense for an extension to be registered declaratively at the field or parameter level. Consider a `RandomNumberExtension` which generates random numbers that can be injected into a field or via a parameter in a constructor, test method, or lifecycle method. If the extension provides a `@Random` annotation that is meta-annotated with `@ExtendWith(RandomNumberExtension.class)` (see listing below), the extension can be used transparently as in the following `RandomNumberDemo` example. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/extensions/Random.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/extensions/Random.kt[tags=user_guide] ---- -- ==== [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/extensions/RandomNumberDemo.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/extensions/RandomNumberDemo.kt[tags=user_guide] ---- -- ==== [[RandomNumberExtension]] The following code listing provides an example of how one might choose to implement such a `RandomNumberExtension`. This implementation works for the use cases in `RandomNumberDemo`; however, it may not prove robust enough to cover all use cases -- for example, the random number generation support is limited to integers; it uses `java.util.Random` instead of `java.security.SecureRandom`; etc. In any case, it is important to note which extension APIs are implemented and for what reasons. Specifically, `RandomNumberExtension` implements the following extension APIs: - `BeforeAllCallback`: to support static field injection - `TestInstancePostProcessor`: to support non-static field injection - `ParameterResolver`: to support constructor and method injection [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/extensions/RandomNumberExtension.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/extensions/RandomNumberExtension.kt[tags=user_guide] ---- -- ==== [TIP] .Extension Registration Order for `@ExtendWith` on Fields ==== Extensions registered declaratively via `@ExtendWith` on fields will be ordered relative to `@RegisterExtension` fields and other `@ExtendWith` fields using an algorithm that is deterministic but intentionally nonobvious. However, `@ExtendWith` fields can be ordered using the `@Order` annotation. See the <> tip for `@RegisterExtension` fields for details. ==== [TIP] .Extension Inheritance ==== Extensions registered declaratively via `@ExtendWith` on fields in superclasses will be inherited. See <> for details. ==== NOTE: `@ExtendWith` fields may be either `static` or non-static. The documentation on <> and <> for `@RegisterExtension` fields also applies to `@ExtendWith` fields. [[registration-programmatic]] == Programmatic Extension Registration Developers can register extensions _programmatically_ by annotating fields in test classes with `{RegisterExtension}`. When an extension is registered _declaratively_ via <>, it can typically only be configured via annotations. In contrast, when an extension is registered via `@RegisterExtension`, it can be configured _programmatically_ -- for example, in order to pass arguments to the extension's constructor, a static factory method, or a builder API. [[registration-programmatic-order]] [TIP] .Extension Registration Order ==== By default, extensions registered programmatically via `@RegisterExtension` or declaratively via `@ExtendWith` on fields will be ordered using an algorithm that is deterministic but intentionally nonobvious. This ensures that subsequent runs of a test suite execute extensions in the same order, thereby allowing for repeatable builds. However, there are times when extensions need to be registered in an explicit order. To achieve that, annotate `@RegisterExtension` fields or `@ExtendWith` fields with `{Order}`. Any `@RegisterExtension` field or `@ExtendWith` field not annotated with `@Order` will be ordered using the _default_ order which has a value of `Integer.MAX_VALUE / 2`. This allows `@Order` annotated extension fields to be explicitly ordered before or after non-annotated extension fields. Extensions with an explicit order value less than the default order value will be registered before non-annotated extensions. Similarly, extensions with an explicit order value greater than the default order value will be registered after non-annotated extensions. For example, assigning an extension an explicit order value that is greater than the default order value allows _before_ callback extensions to be registered last and _after_ callback extensions to be registered first, relative to other programmatically registered extensions. ==== [TIP] .Extension Inheritance ==== Extensions registered via `@RegisterExtension` or `@ExtendWith` on fields in superclasses will be inherited. See <> for details. ==== NOTE: `@RegisterExtension` fields must not be `null` (at evaluation time) but may be either `static` or non-static. [[registration-programmatic-static-fields]] === Static Fields If a `@RegisterExtension` field is `static`, the extension will be registered after extensions that are registered at the class level via `@ExtendWith`. Such _static extensions_ are not limited in which extension APIs they can implement. Extensions registered via static fields may therefore implement class-level and instance-level extension APIs such as `BeforeAllCallback`, `AfterAllCallback`, `TestInstancePostProcessor`, and `TestInstancePreDestroyCallback` as well as method-level extension APIs such as `BeforeEachCallback`, etc. In the following example, the `server` field in the test class is initialized programmatically by using a builder pattern supported by the `WebServerExtension`. The configured `WebServerExtension` will be automatically registered as an extension at the class level -- for example, in order to start the server before all tests in the class and then stop the server after all tests in the class have completed. In addition, static lifecycle methods annotated with `@BeforeAll` or `@AfterAll` as well as `@BeforeEach`, `@AfterEach`, and `@Test` methods can access the instance of the extension via the `server` field if necessary. [source,java,indent=0] .Registering an extension via a static field in Java ---- include::example$java/example/registration/WebServerDemo.java[tags=user_guide] ---- [[registration-programmatic-static-fields-kotlin]] ==== Static Fields in Kotlin The Kotlin programming language does not have the concept of a `static` field. However, the compiler can be instructed to generate a `private static` field using the `@JvmStatic` annotation in Kotlin. If you want the Kotlin compiler to generate a `public static` field, you can use the `@JvmField` annotation instead. The following example is a version of the `WebServerDemo` from the previous section that has been ported to Kotlin. [source,kotlin,indent=0] .Registering an extension via a static field in Kotlin ---- include::example$kotlin/example/registration/KotlinWebServerDemo.kt[tags=user_guide] ---- [[registration-programmatic-instance-fields]] === Instance Fields If a `@RegisterExtension` field is non-static (i.e., an instance field), the extension will be registered after the test class has been instantiated and after each registered `TestInstancePostProcessor` has been given a chance to post-process the test instance (potentially injecting the instance of the extension to be used into the annotated field). Thus, if such an _instance extension_ implements class-level or instance-level extension APIs such as `BeforeAllCallback`, `AfterAllCallback`, or `TestInstancePostProcessor`, those APIs will not be honored. Instance extensions will be registered _before_ extensions that are registered at the method level via `@ExtendWith`. In the following example, the `docs` field in the test class is initialized programmatically by invoking a custom `lookUpDocsDir()` method and supplying the result to the static `forPath()` factory method in the `DocumentationExtension`. The configured `DocumentationExtension` will be automatically registered as an extension at the method level. In addition, `@BeforeEach`, `@AfterEach`, and `@Test` methods can access the instance of the extension via the `docs` field if necessary. [tabs] ==== Java:: + -- [source,java,indent=0] .An extension registered via an instance field ---- include::example$java/example/registration/DocumentationDemo.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] .An extension registered via an instance field ---- include::example$kotlin/example/kotlin/registration/DocumentationDemo.kt[tags=user_guide] ---- -- ==== [[registration-automatic]] == Automatic Extension Registration In addition to <> and <> support using annotations, JUnit Jupiter also supports _global extension registration_ via Java's `{ServiceLoader}` mechanism, allowing third-party extensions to be auto-detected and automatically registered based on what is available in the classpath. Specifically, a custom extension can be registered by supplying its fully qualified class name in a file named `org.junit.jupiter.api.extension.Extension` within the `/META-INF/services` folder in its enclosing JAR file. [[registration-automatic-enabling]] === Enabling Automatic Extension Detection Auto-detection is an advanced feature and is therefore not enabled by default. To enable it, set the `junit.jupiter.extensions.autodetection.enabled` _configuration parameter_ to `true`. This can be supplied as a JVM system property, as a _configuration parameter_ in the `LauncherDiscoveryRequest` that is passed to the `Launcher`, or via the JUnit Platform configuration file (see xref:running-tests/configuration-parameters.adoc[] for details). For example, to enable auto-detection of extensions, you can start your JVM with the following system property. `-Djunit.jupiter.extensions.autodetection.enabled=true` When auto-detection is enabled, extensions discovered via the `{ServiceLoader}` mechanism will be added to the extension registry after JUnit Jupiter's global extensions (e.g., support for `TestInfo`, `TestReporter`, etc.). [[registration-automatic-filtering]] === Filtering Auto-detected Extensions The list of auto-detected extensions can be filtered using include and exclude patterns via the following xref:running-tests/configuration-parameters.adoc[configuration parameters]: `junit.jupiter.extensions.autodetection.include=`:: Comma-separated list of _include_ patterns for auto-detected extensions. `junit.jupiter.extensions.autodetection.exclude=`:: Comma-separated list of _exclude_ patterns for auto-detected extensions. Include patterns are applied _before_ exclude patterns. If both include and exclude patterns are provided, only extensions that match at least one include pattern and do not match any exclude pattern will be auto-detected. See xref:running-tests/configuration-parameters.adoc#pattern[Pattern Matching Syntax] for details on the pattern syntax. [[registration-inheritance]] == Extension Inheritance Registered extensions are inherited within test class hierarchies with top-down semantics. Similarly, extensions registered at the class-level are inherited at the method-level. This applies to all extensions, independent of how they are registered (declaratively or programmatically). This means that extensions registered declaratively via `@ExtendWith` on a superclass will be registered before extensions registered declaratively via `@ExtendWith` on a subclass. Similarly, extensions registered programmatically via `@RegisterExtension` or `@ExtendWith` on fields in a superclass will be registered before extensions registered programmatically via `@RegisterExtension` or `@ExtendWith` on fields in a subclass, unless `@Order` is used to alter that behavior (see <> for details). NOTE: A specific extension implementation can only be registered once for a given extension context and its parent contexts. Consequently, any attempt to register a duplicate extension implementation will be ignored. ================================================ FILE: documentation/modules/ROOT/pages/extensions/relative-execution-order-of-user-code-and-extensions.adoc ================================================ = Relative Execution Order of User Code and Extensions When executing a test class that contains one or more test methods, a number of extension callbacks are called in addition to the user-supplied test and lifecycle methods. NOTE: See also: xref:writing-tests/test-execution-order.adoc[] [[overview]] == User and Extension Code The following diagram illustrates the relative order of user-supplied code and extension code. User-supplied test and lifecycle methods are shown in orange, with callback code implemented by extensions shown in blue. The grey box denotes the execution of a single test method and will be repeated for every test method in the test class. [[diagram]] .User code and extension code image::extensions_lifecycle.png[] The following table further explains the sixteen steps in the <> diagram. . *interface* `*org.junit.jupiter.api.extension.BeforeAllCallback*` + extension code executed before all tests of the container are executed . *annotation* `*org.junit.jupiter.api.BeforeAll*` + user code executed before all tests of the container are executed . *interface* `*org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler #handleBeforeAllMethodExecutionException*` + extension code for handling exceptions thrown from `@BeforeAll` methods . *interface* `*org.junit.jupiter.api.extension.BeforeClassTemplateInvocationCallback*` + extension code executed before each class template invocation is executed (only applicable if the test class is a xref:writing-tests/class-templates.adoc[class template]) . *interface* `*org.junit.jupiter.api.extension.BeforeEachCallback*` + extension code executed before each test is executed . *annotation* `*org.junit.jupiter.api.BeforeEach*` + user code executed before each test is executed . *interface* `*org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler #handleBeforeEachMethodExecutionException*` + extension code for handling exceptions thrown from `@BeforeEach` methods . *interface* `*org.junit.jupiter.api.extension.BeforeTestExecutionCallback*` + extension code executed immediately before a test is executed . *annotation* `*org.junit.jupiter.api.Test*` + user code of the actual test method . *interface* `*org.junit.jupiter.api.extension.TestExecutionExceptionHandler*` + extension code for handling exceptions thrown during a test . *interface* `*org.junit.jupiter.api.extension.AfterTestExecutionCallback*` + extension code executed immediately after test execution and its corresponding exception handlers . *annotation* `*org.junit.jupiter.api.AfterEach*` + user code executed after each test is executed . *interface* `*org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler #handleAfterEachMethodExecutionException*` + extension code for handling exceptions thrown from `@AfterEach` methods . *interface* `*org.junit.jupiter.api.extension.AfterEachCallback*` + extension code executed after each test is executed . *interface* `*org.junit.jupiter.api.extension.AfterClassTemplateInvocationCallback*` + extension code executed after each class template invocation is executed (only applicable if the test class is a xref:writing-tests/class-templates.adoc[class template]) . *annotation* `*org.junit.jupiter.api.AfterAll*` + user code executed after all tests of the container are executed . *interface* `*org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler #handleAfterAllMethodExecutionException*` + extension code for handling exceptions thrown from `@AfterAll` methods . *interface* `*org.junit.jupiter.api.extension.AfterAllCallback*` + extension code executed after all tests of the container are executed In the simplest case only the actual test method will be executed (step 9); all other steps are optional depending on the presence of user code or extension support for the corresponding lifecycle callback. For further details on the various lifecycle callbacks please consult the respective Javadoc for each annotation and extension. All invocations of user code methods in the above table can additionally be intercepted by implementing xref:extensions/intercepting-invocations.adoc[`InvocationInterceptor`]. [[wrapping-behavior]] == Wrapping Behavior of Callbacks JUnit Jupiter always guarantees _wrapping_ behavior for multiple registered extensions that implement lifecycle callbacks such as `BeforeAllCallback`, `AfterAllCallback`, `BeforeClassTemplateInvocationCallback`, `AfterClassTemplateInvocationCallback`, `BeforeEachCallback`, `AfterEachCallback`, `BeforeTestExecutionCallback`, and `AfterTestExecutionCallback`. That means that, given two extensions `Extension1` and `Extension2` with `Extension1` registered before `Extension2`, any "before" callbacks implemented by `Extension1` are guaranteed to execute **before** any "before" callbacks implemented by `Extension2`. Similarly, given the two same two extensions registered in the same order, any "after" callbacks implemented by `Extension1` are guaranteed to execute **after** any "after" callbacks implemented by `Extension2`. `Extension1` is therefore said to _wrap_ `Extension2`. JUnit Jupiter also guarantees _wrapping_ behavior within class and interface hierarchies for user-supplied _lifecycle methods_ (see xref:writing-tests/definitions.adoc[]). * `@BeforeAll` methods are inherited from superclasses as long as they are not _overridden_. Furthermore, `@BeforeAll` methods from superclasses will be executed **before** `@BeforeAll` methods in subclasses. ** Similarly, `@BeforeAll` methods declared in an interface are inherited as long as they are not _overridden_, and `@BeforeAll` methods from an interface will be executed **before** `@BeforeAll` methods in the class that implements the interface. * `@AfterAll` methods are inherited from superclasses as long as they are not _overridden_. Furthermore, `@AfterAll` methods from superclasses will be executed **after** `@AfterAll` methods in subclasses. ** Similarly, `@AfterAll` methods declared in an interface are inherited as long as they are not _overridden_, and `@AfterAll` methods from an interface will be executed **after** `@AfterAll` methods in the class that implements the interface. * `@BeforeEach` methods are inherited from superclasses as long as they are not _overridden_. Furthermore, `@BeforeEach` methods from superclasses will be executed **before** `@BeforeEach` methods in subclasses. ** Similarly, `@BeforeEach` methods declared as interface default methods are inherited as long as they are not _overridden_, and `@BeforeEach` default methods will be executed **before** `@BeforeEach` methods in the class that implements the interface. * `@AfterEach` methods are inherited from superclasses as long as they are not _overridden_. Furthermore, `@AfterEach` methods from superclasses will be executed **after** `@AfterEach` methods in subclasses. ** Similarly, `@AfterEach` methods declared as interface default methods are inherited as long as they are not _overridden_, and `@AfterEach` default methods will be executed **after** `@AfterEach` methods in the class that implements the interface. The following examples demonstrate this behavior. Please note that the examples do not actually do anything realistic. Instead, they mimic common scenarios for testing interactions with the database. All methods imported statically from the `Logger` class log contextual information in order to help us better understand the execution order of user-supplied callback methods and callback methods in extensions. [source,java,indent=0] .Extension1 ---- include::example$java/example/callbacks/Extension1.java[tags=user_guide] ---- [source,java,indent=0] .Extension2 ---- include::example$java/example/callbacks/Extension2.java[tags=user_guide] ---- [source,java,indent=0] .AbstractDatabaseTests ---- include::example$java/example/callbacks/AbstractDatabaseTests.java[tags=user_guide] ---- [source,java,indent=0] .DatabaseTestsDemo ---- include::example$java/example/callbacks/DatabaseTestsDemo.java[tags=user_guide] ---- When the `DatabaseTestsDemo` test class is executed, the following is logged. ---- @BeforeAll AbstractDatabaseTests.createDatabase() @BeforeAll DatabaseTestsDemo.beforeAll() Extension1.beforeEach() Extension2.beforeEach() @BeforeEach AbstractDatabaseTests.connectToDatabase() @BeforeEach DatabaseTestsDemo.insertTestDataIntoDatabase() @Test DatabaseTestsDemo.testDatabaseFunctionality() @AfterEach DatabaseTestsDemo.deleteTestDataFromDatabase() @AfterEach AbstractDatabaseTests.disconnectFromDatabase() Extension2.afterEach() Extension1.afterEach() @BeforeAll DatabaseTestsDemo.afterAll() @AfterAll AbstractDatabaseTests.destroyDatabase() ---- The following sequence diagram helps to shed further light on what actually goes on within the `JupiterTestEngine` when the `DatabaseTestsDemo` test class is executed. //// PNG generated using ZenUML: https://app.zenuml.com See corresponding *.txt file in images folder for the source. //// image::extensions_DatabaseTestsDemo.png[caption='',title='DatabaseTestsDemo'] JUnit Jupiter does **not** guarantee the execution order of multiple lifecycle methods that are declared within a _single_ test class or test interface. It may at times appear that JUnit Jupiter invokes such methods in alphabetical order. However, that is not precisely true. The ordering is analogous to the ordering for `@Test` methods within a single test class. [NOTE] ==== Lifecycle methods that are declared within a _single_ test class or test interface will be ordered using an algorithm that is deterministic but intentionally non-obvious. This ensures that subsequent runs of a test suite execute lifecycle methods in the same order, thereby allowing for repeatable builds. ==== In addition, JUnit Jupiter does **not** support _wrapping_ behavior for multiple lifecycle methods declared within a single test class or test interface. The following example demonstrates this behavior. Specifically, the lifecycle method configuration is _broken_ due to the order in which the locally declared lifecycle methods are executed. * Test data is inserted _before_ the database connection has been opened, which results in a failure to connect to the database. * The database connection is closed _before_ deleting the test data, which results in a failure to connect to the database. [source,java,indent=0] .BrokenLifecycleMethodConfigDemo ---- include::example$java/example/callbacks/BrokenLifecycleMethodConfigDemo.java[tags=user_guide] ---- When the `BrokenLifecycleMethodConfigDemo` test class is executed, the following is logged. ---- Extension1.beforeEach() Extension2.beforeEach() @BeforeEach BrokenLifecycleMethodConfigDemo.insertTestDataIntoDatabase() @BeforeEach BrokenLifecycleMethodConfigDemo.connectToDatabase() @Test BrokenLifecycleMethodConfigDemo.testDatabaseFunctionality() @AfterEach BrokenLifecycleMethodConfigDemo.disconnectFromDatabase() @AfterEach BrokenLifecycleMethodConfigDemo.deleteTestDataFromDatabase() Extension2.afterEach() Extension1.afterEach() ---- The following sequence diagram helps to shed further light on what actually goes on within the `JupiterTestEngine` when the `BrokenLifecycleMethodConfigDemo` test class is executed. //// PNG generated using ZenUML: https://app.zenuml.com See corresponding *.txt file in images folder for the source. //// image::extensions_BrokenLifecycleMethodConfigDemo.png[caption='',title='BrokenLifecycleMethodConfigDemo'] [TIP] ==== Due to the aforementioned behavior, the JUnit Team recommends that developers declare at most one of each type of _lifecycle method_ (see xref:writing-tests/definitions.adoc[]) per test class or test interface unless there are no dependencies between such lifecycle methods. ==== ================================================ FILE: documentation/modules/ROOT/pages/extensions/supported-utilities-in-extensions.adoc ================================================ = Supported Utilities in Extensions The `junit-platform-commons` artifact provides _maintained_ utilities for working with annotations, classes, reflection, classpath scanning, and conversion tasks. These utilities can be found in the `{junit-platform-support-package}` and its subpackages. `TestEngine` and `Extension` authors are encouraged to use these supported utilities in order to align with the behavior of the JUnit Platform and JUnit Jupiter. [[annotations]] == Annotation Support `AnnotationSupport` provides static utility methods that operate on annotated elements (e.g., packages, annotations, classes, interfaces, constructors, methods, and fields). These include methods to check whether an element is annotated or meta-annotated with a particular annotation, to search for specific annotations, and to find annotated methods and fields in a class or interface. Some of these methods search on implemented interfaces and within class hierarchies to find annotations. Consult the Javadoc for `{AnnotationSupport}` for further details. NOTE: The `isAnnotated()` methods do not find repeatable annotations. To check for repeatable annotations, use one of the `findRepeatableAnnotations()` methods and verify that the returned list is not empty. NOTE: See also: <> [[classes]] == Class Support `ClassSupport` provides static utility methods for working with classes (i.e., instances of `java.lang.Class`). Consult the Javadoc for `{ClassSupport}` for further details. [[reflection]] == Reflection Support `ReflectionSupport` provides static utility methods that augment the standard JDK reflection and class-loading mechanisms. These include methods to scan the classpath in search of classes matching specified predicates, to load and create new instances of a class, and to find and invoke methods. Some of these methods traverse class hierarchies to locate matching methods. Consult the Javadoc for `{ReflectionSupport}` for further details. NOTE: See also: <> [[modifier]] == Modifier Support `ModifierSupport` provides static utility methods for working with member and class modifiers -- for example, to determine if a member is declared as `public`, `private`, `abstract`, `static`, etc. Consult the Javadoc for `{ModifierSupport}` for further details. [[conversion]] == Conversion Support `ConversionSupport` (in the `org.junit.platform.commons.support.conversion` package) provides support for converting from strings to primitive types and their corresponding wrapper types, date and time types from the `java.time package`, and some additional common Java types such as `File`, `BigDecimal`, `BigInteger`, `Currency`, `Locale`, `URI`, `URL`, `UUID`, etc. Consult the Javadoc for `{ConversionSupport}` for further details. [[search-semantics]] == Field and Method Search Semantics Various methods in `AnnotationSupport` and `ReflectionSupport` use search algorithms that traverse type hierarchies to locate matching fields and methods – for example, `AnnotationSupport.findAnnotatedFields(...)`, `ReflectionSupport.findMethods(...)`, etc. The field and method search algorithms adhere to standard Java semantics regarding whether a given field or method is visible or overridden according to the rules of the Java language. ================================================ FILE: documentation/modules/ROOT/pages/extensions/test-instance-factories.adoc ================================================ = Test Instance Factories `{TestInstanceFactory}` defines the API for `Extensions` that wish to _create_ test class instances. Common use cases include acquiring the test instance from a dependency injection framework or invoking a static factory method to create the test class instance. If no `TestInstanceFactory` is registered, the framework will invoke the _sole_ constructor for the test class to instantiate it, potentially resolving constructor arguments via registered `ParameterResolver` extensions. Extensions that implement `TestInstanceFactory` can be registered on test interfaces, top-level test classes, or `@Nested` test classes. [WARNING] ==== Registering multiple extensions that implement `TestInstanceFactory` for any single class will result in an exception being thrown for all tests in that class, in any subclass, and in any nested class. Note that any `TestInstanceFactory` registered in a superclass or _enclosing_ class (i.e., in the case of a `@Nested` test class) is _inherited_. It is the user's responsibility to ensure that only a single `TestInstanceFactory` is registered for any specific test class. ==== [NOTE] .Accessing the test-scoped `ExtensionContext` ==== You may override the `getTestInstantiationExtensionContextScope(...)` method to return `TEST_METHOD` to make test-specific data available to your extension implementation or if you want to xref:extensions/keeping-state-in-extensions.adoc[keep state] on the test method level. ==== ================================================ FILE: documentation/modules/ROOT/pages/extensions/test-instance-post-processing.adoc ================================================ = Test Instance Post-processing `{TestInstancePostProcessor}` defines the API for `Extensions` that wish to _post process_ test instances. Common use cases include injecting dependencies into the test instance, invoking custom initialization methods on the test instance, etc. For a concrete example, consult the source code for the `{MockitoExtension}` and the `{SpringExtension}`. [NOTE] .Accessing the test-scoped `ExtensionContext` ==== You may override the `getTestInstantiationExtensionContextScope(...)` method to return `TEST_METHOD` to make test-specific data available to your extension implementation or if you want to xref:extensions/keeping-state-in-extensions.adoc[keep state] on the test method level. ==== ================================================ FILE: documentation/modules/ROOT/pages/extensions/test-instance-pre-construct-callback.adoc ================================================ = Test Instance Pre-construct Callback `{TestInstancePreConstructCallback}` defines the API for `Extensions` that wish to be invoked _prior_ to test instances being constructed (by a constructor call or via `{TestInstanceFactory}`). This extension provides a symmetric call to `{TestInstancePreDestroyCallback}` and is useful in combination with other extensions to prepare constructor parameters or keeping track of test instances and their lifecycle. [NOTE] .Accessing the test-scoped `ExtensionContext` ==== You may override the `getTestInstantiationExtensionContextScope(...)` method to return `TEST_METHOD` to make test-specific data available to your extension implementation or if you want to xref:extensions/keeping-state-in-extensions.adoc[keep state] on the test method level. ==== ================================================ FILE: documentation/modules/ROOT/pages/extensions/test-instance-pre-destroy-callback.adoc ================================================ = Test Instance Pre-destroy Callback `{TestInstancePreDestroyCallback}` defines the API for `Extensions` that wish to process test instances _after_ they have been used in tests and _before_ they are destroyed. Common use cases include cleaning dependencies that have been injected into the test instance, invoking custom de-initialization methods on the test instance, etc. ================================================ FILE: documentation/modules/ROOT/pages/extensions/test-lifecycle-callbacks.adoc ================================================ = Test Lifecycle Callbacks The following interfaces define the APIs for extending tests at various points in the test execution lifecycle. Consult the following sections for examples and the Javadoc for each of these interfaces in the `{extension-api-package}` package for further details. * `{BeforeAllCallback}` ** `{BeforeClassTemplateInvocationCallback}` (only applicable for xref:writing-tests/class-templates.adoc[class templates]) *** `{BeforeEachCallback}` **** `{BeforeTestExecutionCallback}` **** `{AfterTestExecutionCallback}` *** `{AfterEachCallback}` ** `{AfterClassTemplateInvocationCallback}` (only applicable for xref:writing-tests/class-templates.adoc[class templates]) * `{AfterAllCallback}` .Implementing Multiple Extension APIs NOTE: Extension developers may choose to implement any number of these interfaces within a single extension. Consult the source code of the `{SpringExtension}` for a concrete example. [[before-after-execution]] == Before and After Test Execution Callbacks `{BeforeTestExecutionCallback}` and `{AfterTestExecutionCallback}` define the APIs for `Extensions` that wish to add behavior that will be executed _immediately before_ and _immediately after_ a test method is executed, respectively. As such, these callbacks are well suited for timing, tracing, and similar use cases. If you need to implement callbacks that are invoked _around_ `@BeforeEach` and `@AfterEach` methods, implement `BeforeEachCallback` and `AfterEachCallback` instead. The following example shows how to use these callbacks to calculate and log the execution time of a test method. `TimingExtension` implements both `BeforeTestExecutionCallback` and `AfterTestExecutionCallback` in order to time and log the test execution. [[timing-extension]] .An extension that times and logs the execution of test methods [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/timing/TimingExtension.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/timing/TimingExtension.kt[tags=user_guide] ---- -- ==== Since the `TimingExtensionTests` class registers the `TimingExtension` via `@ExtendWith`, its tests will have this timing applied when they execute. .A test class that uses the example TimingExtension [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/timing/TimingExtensionTests.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/timing/TimingExtensionTests.kt[tags=user_guide] ---- -- ==== The following is an example of the logging produced when `TimingExtensionTests` is run. .... INFO: Method [sleep20ms] took 24 ms. INFO: Method [sleep50ms] took 53 ms. .... ================================================ FILE: documentation/modules/ROOT/pages/extensions/test-result-processing.adoc ================================================ = Test Result Processing `{TestWatcher}` defines the API for extensions that wish to process the results of _test method_ executions. Specifically, a `TestWatcher` will be invoked with contextual information for the following events. * `testDisabled`: invoked after a disabled _test method_ has been skipped * `testSuccessful`: invoked after a _test method_ has completed successfully * `testAborted`: invoked after a _test method_ has been aborted * `testFailed`: invoked after a _test method_ has failed NOTE: In contrast to the definition of "test method" presented in xref:writing-tests/definitions.adoc[], in this context _test method_ refers to any `@Test` method or `@TestTemplate` method (for example, a `@RepeatedTest` or `@ParameterizedTest`). Extensions implementing this interface can be registered at the class level, instance level, or method level. When registered at the class level, a `TestWatcher` will be invoked for any contained _test method_ including those in `@Nested` classes. When registered at the method level, a `TestWatcher` will only be invoked for the _test method_ for which it was registered. [WARNING] ==== If a `TestWatcher` is registered via a non-static (instance) field – for example, using `@RegisterExtension` – and the test class is configured with `@TestInstance(Lifecycle.PER_METHOD)` semantics (which is the default lifecycle mode), the `TestWatcher` will **not** be invoked with events for `@TestTemplate` methods (for example, `@RepeatedTest` or `@ParameterizedTest`). To ensure that a `TestWatcher` is invoked for all _test methods_ in a given class, it is therefore recommended that the `TestWatcher` be registered at the class level with `@ExtendWith` or via a `static` field with `@RegisterExtension` or `@ExtendWith`. ==== If there is a failure at the class level — for example, an exception thrown by a `@BeforeAll` method — no test results will be reported. Similarly, if the test class is disabled via an `ExecutionCondition` — for example, `@Disabled` — no test results will be reported. In contrast to other Extension APIs, a `TestWatcher` is not permitted to adversely influence the execution of tests. Consequently, any exception thrown by a method in the `TestWatcher` API will be logged at `WARNING` level and will not be allowed to propagate or fail test execution. [WARNING] ==== Any instances of `ExtensionContext.Store.CloseableResource` stored in the `Store` of the provided `{ExtensionContext}` will be closed _before_ methods in the `TestWatcher` API are invoked (see xref:extensions/keeping-state-in-extensions.adoc[]). You can use the parent context's `Store` to work with such resources. ==== ================================================ FILE: documentation/modules/ROOT/pages/migrating-from-junit4.adoc ================================================ = Migrating from JUnit 4 Although the JUnit Jupiter programming model and extension model do not support JUnit 4 features such as `Rules` and `Runners` natively, it is not expected that source code maintainers will need to update all of their existing tests, test extensions, and custom build test infrastructure to migrate to JUnit Jupiter. Instead, JUnit provides a gentle migration path via a _JUnit Vintage test engine_ which allows existing tests based on JUnit 3 and JUnit 4 to be executed using the JUnit Platform infrastructure. Since all classes and annotations specific to JUnit Jupiter reside under the `org.junit.jupiter` base package, having both JUnit 4 and JUnit Jupiter in the classpath does not lead to any conflicts. It is therefore safe to maintain existing JUnit 4 tests alongside JUnit Jupiter tests and migrate them gradually. [[running]] == Running JUnit 4 Tests on the JUnit Platform [WARNING] ==== The JUnit Vintage engine is deprecated and should only be used temporarily while migrating tests to JUnit Jupiter or another testing framework with native JUnit Platform support. By default, if the JUnit Vintage engine is registered and discovers at least one test class, it reports a xref:running-tests/discovery-issues.adoc[discovery issue] of INFO severity. You can prevent this discovery issue from being reported by setting the `junit.vintage.discovery.issue.reporting.enabled` xref:running-tests/configuration-parameters.adoc[configuration parameter] to `false`. ==== Make sure that the `junit-vintage-engine` artifact is in your test runtime path. In that case JUnit 3 and JUnit 4 tests will automatically be picked up by the JUnit Platform launcher. See the example projects in the {junit-examples-repo}[`junit-examples`] repository to find out how this is done with Gradle and Maven. [[categories-support]] === Categories Support For test classes or methods that are annotated with `@Category`, the _JUnit Vintage test engine_ exposes the category's fully qualified class name as a xref:running-tests/tags.adoc[tag] for the corresponding test class or test method. For example, if a test method is annotated with `@Category(Example.class)`, it will be tagged with `"com.acme.Example"`. Similar to the `Categories` runner in JUnit 4, this information can be used to filter the discovered tests before executing them (see xref:running-tests/intro.adoc[] for details). [[parallel-execution]] == Parallel Execution The JUnit Vintage test engine supports parallel execution of top-level test classes and test methods, allowing existing JUnit 3 and JUnit 4 tests to benefit from improved performance through concurrent test execution. It can be enabled and configured using the following xref:running-tests/configuration-parameters.adoc[configuration parameters]: `junit.vintage.execution.parallel.enabled=true|false`:: Enable/disable parallel execution (defaults to `false`). Requires opt-in for `classes` or `methods` to be executed in parallel using the configuration parameters below. `junit.vintage.execution.parallel.classes=true|false`:: Enable/disable parallel execution of test classes (defaults to `false`). `junit.vintage.execution.parallel.methods=true|false`:: Enable/disable parallel execution of test methods (defaults to `false`). `junit.vintage.execution.parallel.pool-size=`:: Specifies the size of the thread pool to be used for parallel execution. By default, the number of available processors is used. [[parallel-execution-class-level]] === Parallelization at Class Level Let's assume we have two test classes `FooTest` and `BarTest` with each class containing three unit tests. Now, let's enable parallel execution of test classes: [source,properties] ---- junit.vintage.execution.parallel.enabled=true junit.vintage.execution.parallel.classes=true ---- With this setup, the `VintageTestEngine` will use two different threads, one for each test class: [source,plaintext] ---- ForkJoinPool-1-worker-1 - BarTest::test1 ForkJoinPool-1-worker-2 - FooTest::test1 ForkJoinPool-1-worker-1 - BarTest::test2 ForkJoinPool-1-worker-2 - FooTest::test2 ForkJoinPool-1-worker-1 - BarTest::test3 ForkJoinPool-1-worker-2 - FooTest::test3 ---- [[parallel-execution-method-level]] === Parallelization at Method Level Alternatively, we can enable parallel test execution at a method level, rather than the class level: [source,properties] ---- junit.vintage.execution.parallel.enabled=true junit.vintage.execution.parallel.methods=true ---- Therefore, the test methods within each class will be executed in parallel, while different test classes will be executed sequentially: [source,plaintext] ---- ForkJoinPool-1-worker-1 - BarTest::test1 ForkJoinPool-1-worker-2 - BarTest::test2 ForkJoinPool-1-worker-3 - BarTest::test3 ForkJoinPool-1-worker-3 - FooTest::test1 ForkJoinPool-1-worker-2 - FooTest::test2 ForkJoinPool-1-worker-1 - FooTest::test3 ---- [[parallel-execution-class-and-method-level]] === Full Parallelization Finally, we can also enable parallelization at both class and method level: [source,properties] ---- junit.vintage.execution.parallel.enabled=true junit.vintage.execution.parallel.classes=true junit.vintage.execution.parallel.methods=true ---- With these properties set, the `VintageTestEngine` will execute all tests classes and methods in parallel, potentially significantly reducing the overall test suite execution time: [source,plaintext] ---- ForkJoinPool-1-worker-6 - FooTest::test2 ForkJoinPool-1-worker-7 - BarTest::test3 ForkJoinPool-1-worker-3 - FooTest::test1 ForkJoinPool-1-worker-8 - FooTest::test3 ForkJoinPool-1-worker-5 - BarTest::test2 ForkJoinPool-1-worker-4 - BarTest::test1 ---- [[parallel-execution-pool-size]] === Configuring the Pool Size The default thread pool size is equal to the number of available processors. However, we can also configure the pool size explicitly: [source,properties] ---- junit.vintage.execution.parallel.enabled=true junit.vintage.execution.parallel.classes=true junit.vintage.execution.parallel.methods=true junit.vintage.execution.parallel.pool-size=4 ---- For instance, if we update our previous example that uses full parallelization and configure the pool size to four, we can expect to see our six test methods executed with a parallelism of four: [source,plaintext] ---- ForkJoinPool-1-worker-2 - FooTest::test1 ForkJoinPool-1-worker-4 - BarTest::test2 ForkJoinPool-1-worker-3 - BarTest::test1 ForkJoinPool-1-worker-4 - BarTest::test3 ForkJoinPool-1-worker-2 - FooTest::test2 ForkJoinPool-1-worker-3 - FooTest::test3 ---- As we can see, even though we set the thread pool size was four, only three threads were used in this case. This happens because the pool adjusts the number of active threads based on workload and system needs. [[parallel-execution-disabled]] === Sequential Execution On the other hand, if we disable parallel execution, the `VintageTestEngine` will execute all tests sequentially, regardless of the other properties: [source,properties] ---- junit.vintage.execution.parallel.enabled=false junit.vintage.execution.parallel.classes=true junit.vintage.execution.parallel.methods=true ---- Similarly, tests will be executed sequentially if you enable parallel execution in general but enable neither class-level nor method-level parallelization. [[tips]] == Migration Tips The following are topics that you should be aware of when migrating existing JUnit 4 tests to JUnit Jupiter. * Annotations reside in the `org.junit.jupiter.api` package. * Assertions reside in `org.junit.jupiter.api.Assertions`. - Note that you may continue to use assertion methods from `org.junit.Assert` or any other assertion library such as {AssertJ}, {Hamcrest}, {Truth}, etc. * Assumptions reside in `org.junit.jupiter.api.Assumptions`. - Note that JUnit Jupiter supports methods from JUnit 4's `org.junit.Assume` class for assumptions. Specifically, JUnit Jupiter supports JUnit 4's `AssumptionViolatedException` to signal that a test should be aborted instead of marked as a failure. * `@Before` and `@After` no longer exist; use `@BeforeEach` and `@AfterEach` instead. * `@BeforeClass` and `@AfterClass` no longer exist; use `@BeforeAll` and `@AfterAll` instead. * `@Ignore` no longer exists: use `@Disabled` or one of the other built-in xref:writing-tests/conditional-test-execution.adoc[execution conditions] instead - See also <>. * `@Category` no longer exists; use `@Tag` instead. * `@RunWith` no longer exists; superseded by `@ExtendWith`. - For `@RunWith(Enclosed.class)` use `@Nested`. - For `@RunWith(Parameterized.class)` see <>. * `@Rule` and `@ClassRule` no longer exist; superseded by `@ExtendWith` and `@RegisterExtension`. - See also <>. * `@Test(expected = ...)` and the `ExpectedException` rule no longer exist; use `Assertions.assertThrows(...)` instead. - See <> if you still need to use `ExpectedException`. * Assertions and assumptions in JUnit Jupiter accept the failure message as their last argument instead of the first one. - See <> for details. [[tips-parameterized]] === Parameterized test classes Unless `@UseParametersRunnerFactory` is used, a JUnit 4 parameterized test class can be converted into a JUnit Jupiter xref:writing-tests/parameterized-classes-and-tests.adoc[`@ParameterizedClass`] by following these steps: . Replace `@RunWith(Parameterized.class)` with `@ParameterizedClass`. . Add a class-level `@MethodSource("methodName")` annotation where `methodName` is the name of the method annotated with `@Parameters` and remove the `@Parameters` annotation from the method. . Replace `@BeforeParam` and `@AfterParam` with `@BeforeParameterizedClassInvocation` and `@AfterParameterizedClassInvocation`, respectively, if there are any methods with such annotations. . Change the imports of the `@Test` and `@Parameter` annotations to use the `org.junit.jupiter.params` package. . Change assertions etc. to use the `org.junit.jupiter.api` package as usual. . Optionally, remove all `public` modifiers from the class and its methods and fields. ==== [source,java,indent=0] .Before ---- include::example$java/example/ParameterizedMigrationDemo.java[tags=before] ---- [source,java,indent=0] .After ---- include::example$java/example/ParameterizedMigrationDemo.java[tags=after] ---- ==== [[rule-support]] == Limited JUnit 4 Rule Support WARNING: _JUnit 4 rule support_ is deprecated for removal since version 6.0.0. Please migrate to the corresponding APIs and extensions provided by JUnit Jupiter. As stated above, JUnit Jupiter does not and will not support JUnit 4 rules natively. The JUnit team realizes, however, that many organizations, especially large ones, are likely to have large JUnit 4 code bases that make use of custom rules. To serve these organizations and enable a gradual migration path the JUnit team has decided to support a selection of JUnit 4 rules verbatim within JUnit Jupiter. This support is based on adapters and is limited to those rules that are semantically compatible to the JUnit Jupiter extension model, i.e. those that do not completely change the overall execution flow of the test. The `junit-jupiter-migrationsupport` module from JUnit Jupiter currently supports the following three `Rule` types including subclasses of these types: * `org.junit.rules.ExternalResource` (including `org.junit.rules.TemporaryFolder`) * `org.junit.rules.Verifier` (including `org.junit.rules.ErrorCollector`) * `org.junit.rules.ExpectedException` As in JUnit 4, Rule-annotated fields as well as methods are supported. By using these class-level extensions on a test class such `Rule` implementations in legacy code bases can be _left unchanged_ including the JUnit 4 rule import statements. This limited form of `Rule` support can be switched on by the class-level annotation `{EnableRuleMigrationSupport}`. This annotation is a _composed annotation_ which enables all rule migration support extensions: `VerifierSupport`, `ExternalResourceSupport`, and `ExpectedExceptionSupport`. You may alternatively choose to annotate your test class with `@EnableJUnit4MigrationSupport` which registers migration support for rules _and_ JUnit 4's `@Ignore` annotation (see <>). However, if you intend to develop a new extension for JUnit Jupiter please use the new extension model of JUnit Jupiter instead of the rule-based model of JUnit 4. [[ignore-annotation-support]] == JUnit 4 @Ignore Support WARNING: _JUnit 4 `@Ignore` support_ is deprecated for removal since version 6.0.0. Please use JUnit Jupiter's `@Disabled` annotation instead. In order to provide a smooth migration path from JUnit 4 to JUnit Jupiter, the `junit-jupiter-migrationsupport` module provides support for JUnit 4's `@Ignore` annotation analogous to Jupiter's `{Disabled}` annotation. To use `@Ignore` with JUnit Jupiter based tests, configure a _test_ dependency on the `junit-jupiter-migrationsupport` module in your build and then annotate your test class with `@ExtendWith(IgnoreCondition.class)` or `{EnableJUnit4MigrationSupport}` (which automatically registers the `IgnoreCondition` along with <>). The `IgnoreCondition` is an `{ExecutionCondition}` that disables test classes or test methods that are annotated with `@Ignore`. [source,java,indent=0] ---- include::example$java/example/IgnoredTestsDemo.java[tags=user_guide] ---- [[failure-message-arguments]] == Failure Message Arguments The `Assumptions` and `Assertions` classes in JUnit Jupiter declare arguments in a different order than in JUnit 4. In JUnit 4 assertion and assumption methods accept the failure message as the first argument; whereas, in JUnit Jupiter assertion and assumption methods accept the failure message as the last argument. For instance, the method `assertEquals` in JUnit 4 is declared as `assertEquals(String message, Object expected, Object actual)`, but in JUnit Jupiter it is declared as `assertEquals(Object expected, Object actual, String message)`. The rationale for this is that a failure message is _optional_, and optional arguments should be declared after required arguments in a method signature. The methods affected by this change are the following: - Assertions * `assertTrue` * `assertFalse` * `assertNull` * `assertNotNull` * `assertEquals` * `assertNotEquals` * `assertArrayEquals` * `assertSame` * `assertNotSame` * `assertThrows` - Assumptions * `assumeTrue` * `assumeFalse` ================================================ FILE: documentation/modules/ROOT/pages/overview.adoc ================================================ = Overview :page-aliases: index.adoc, user-guide/index.adoc The goal of this document is to provide comprehensive reference documentation for programmers writing tests, extension authors, and engine authors as well as build tool and IDE vendors. [[what-is-junit]] == What is JUnit? JUnit is composed of several different modules from three different sub-projects. [.text-center] **JUnit {version} = _JUnit Platform_ + _JUnit Jupiter_ + _JUnit Vintage_** The **JUnit Platform** serves as a foundation for xref:advanced-topics/launcher-api.adoc[launching testing frameworks] on the JVM. It also defines the `{TestEngine}` API for developing a testing framework that runs on the platform. Furthermore, the platform provides a xref:running-tests/console-launcher.adoc[Console Launcher] to launch the platform from the command line and the xref:advanced-topics/junit-platform-suite-engine.adoc[] for running a custom test suite using one or more test engines on the platform. First-class support for the JUnit Platform also exists in popular IDEs (see xref:running-tests/ide-support.adoc#intellij-idea[IntelliJ IDEA], xref:running-tests/ide-support.adoc#eclipse[Eclipse], xref:running-tests/ide-support.adoc#netbeans[NetBeans], and xref:running-tests/ide-support.adoc#vscode[Visual Studio Code]) and build tools (see xref:running-tests/build-support.adoc#gradle[Gradle], xref:running-tests/build-support.adoc#maven[Maven], xref:running-tests/build-support.adoc#ant[Ant], xref:running-tests/build-support.adoc#bazel[Bazel], and xref:running-tests/build-support.adoc#sbt[sbt]). **JUnit Jupiter** is the combination of the xref:writing-tests/intro.adoc[programming model] and xref:extensions/overview.adoc[extension model] for writing JUnit tests and extensions. The Jupiter sub-project provides a `TestEngine` for running Jupiter based tests on the platform. **JUnit Vintage** provides a `TestEngine` for running JUnit 3 and JUnit 4 based tests on the platform. It requires JUnit 4.12 or later to be present on the class path or module path. Note, however, that the JUnit Vintage engine is deprecated and should only be used temporarily while migrating tests to JUnit Jupiter or another testing framework with native JUnit Platform support. [[java-versions]] == Supported Java Versions JUnit requires Java 17 (or higher) at runtime. However, you can still test code that has been compiled with previous versions of the JDK. [[getting-help]] == Getting Help Ask JUnit-related questions on {StackOverflow} or use the {DiscussionsQA}[Q&A category on GitHub Discussions]. [[getting-started]] == Getting Started [[getting-started-junit-artifacts]] === Downloading JUnit Artifacts To find out what artifacts are available for download and inclusion in your project, refer to xref:appendix.adoc#dependency-metadata[Dependency Metadata]. To set up dependency management for your build, refer to xref:running-tests/build-support.adoc[] and the <>. [[getting-started-features]] === JUnit Features To find out what features are available in JUnit {version} and how to use them, read the corresponding sections of this User Guide, organized by topic. * xref:writing-tests/intro.adoc[Writing Tests in JUnit Jupiter] * xref:migrating-from-junit4.adoc[Migrating from JUnit 4 to JUnit Jupiter] * xref:running-tests/intro.adoc[] * xref:extensions/overview.adoc[Extension Model for JUnit Jupiter] * Advanced Topics - xref:advanced-topics/launcher-api.adoc[] - xref:advanced-topics/testkit.adoc[] [[getting-started-example-projects]] === Example Projects To see complete, working examples of projects that you can copy and experiment with, the {junit-examples-repo}[`junit-examples`] repository is a good place to start. The `junit-examples` repository hosts a collection of example projects based on JUnit Jupiter, JUnit Vintage, and other testing frameworks. You'll find appropriate build scripts (e.g., `build.gradle`, `pom.xml`, etc.) in the example projects. The links below highlight some of the combinations you can choose from. * For Gradle and Java, check out the `{junit-jupiter-starter-gradle}` project. * For Gradle and Kotlin, check out the `{junit-jupiter-starter-gradle-kotlin}` project. * For Gradle and Groovy, check out the `{junit-jupiter-starter-gradle-groovy}` project. * For Maven, check out the `{junit-jupiter-starter-maven}` project. * For Ant, check out the `{junit-jupiter-starter-ant}` project. * For Bazel, check out the `{junit-jupiter-starter-bazel}` project. * For sbt, check out the `{junit-jupiter-starter-sbt}` project. ================================================ FILE: documentation/modules/ROOT/pages/release-notes.adoc ================================================ = Release Notes :page-aliases: release-notes/index.adoc // // This document contains the change log for all JUnit releases since 6.0 GA. include::partial$release-notes/release-notes-6.1.0.adoc[] include::partial$release-notes/release-notes-6.1.0-RC1.adoc[] include::partial$release-notes/release-notes-6.1.0-M1.adoc[] include::partial$release-notes/release-notes-6.0.3.adoc[] include::partial$release-notes/release-notes-6.0.2.adoc[] include::partial$release-notes/release-notes-6.0.1.adoc[] include::partial$release-notes/release-notes-6.0.0.adoc[] ================================================ FILE: documentation/modules/ROOT/pages/running-tests/build-support.adoc ================================================ = Build Support [[gradle]] == Gradle Starting with https://docs.gradle.org/4.6/release-notes.html[version 4.6], Gradle provides https://docs.gradle.org/current/userguide/java_testing.html#using_junit5[native support] for executing tests on the JUnit Platform. To enable it, you need to specify `useJUnitPlatform()` within a `test` task declaration in `build.gradle`: [source,groovy,indent=0] [subs=attributes+] ---- test { useJUnitPlatform() } ---- Filtering by xref:running-tests/tags.adoc[tags], xref:running-tests/tags.adoc#expressions[tag expressions], or engines is also supported: [source,groovy,indent=0] [subs=attributes+] ---- test { useJUnitPlatform { includeTags("fast", "smoke & feature-a") // excludeTags("slow", "ci") includeEngines("junit-jupiter") // excludeEngines("junit-vintage") } } ---- Please refer to the https://docs.gradle.org/current/userguide/java_testing.html[official Gradle documentation] for a comprehensive list of options. [[gradle-bom]] === Aligning dependency versions TIP: See <> for details on how to override the version of JUnit used in your Spring Boot application. Unless you're using Spring Boot which defines its own way of managing dependencies, it is recommended to use the JUnit Platform xref:appendix.adoc#dependency-metadata-junit-bom[Bill of Materials (BOM)] to align the versions of all JUnit artifacts. [source,groovy,indent=0] [subs=attributes+] .Explicit platform dependency on the BOM ---- dependencies { testImplementation(platform("org.junit:junit-bom:{version}")) testImplementation("org.junit.jupiter:junit-jupiter") testRuntimeOnly("org.junit.platform:junit-platform-launcher") } ---- Using the BOM allows you to omit the version when declaring dependencies on all artifacts with the `org.junit.platform`, `org.junit.jupiter`, and `org.junit.vintage` group IDs. Since all JUnit artifacts declare a https://docs.gradle.org/current/userguide/platforms.html[platform] dependency on the BOM, you usually don't need to declare an explicit dependency on it yourself. Instead, it's sufficient to declare _one_ regular dependency that includes a version number. Gradle will then pull in the BOM automatically so you can omit the version for all other JUnit artifacts. [source,groovy,indent=0] [subs=attributes+] .Implicit platform dependency on the BOM ---- dependencies { testImplementation("org.junit.jupiter:junit-jupiter:{version}") // <1> testRuntimeOnly("org.junit.platform:junit-platform-launcher") // <2> } ---- <1> Dependency declaration with explicit version. Pulls in the `junit-bom` automatically. <2> Dependency declaration without version. The version is supplied by the `junit-bom`. [WARNING] .Declaring a dependency on junit-platform-launcher ==== Even though pre-8.0 versions of Gradle don't require declaring an explicit dependency on `junit-platform-launcher`, it is recommended to do so to ensure the versions of JUnit artifacts on the test runtime classpath are aligned. Moreover, doing so is recommended and in some cases even required when importing the project into an IDE like xref:running-tests/ide-support.adoc#eclipse[Eclipse] or xref:running-tests/ide-support.adoc#intellij-idea[IntelliJ IDEA]. ==== [[gradle-engines-configure]] === Configuring Test Engines In order to run any tests at all, a `TestEngine` implementation must be on the classpath. To configure support for JUnit Jupiter based tests, configure a `testImplementation` dependency on the dependency-aggregating JUnit Jupiter artifact similar to the following. [source,groovy,indent=0] [subs=attributes+] ---- dependencies { testImplementation("org.junit.jupiter:junit-jupiter:{version}") testRuntimeOnly("org.junit.platform:junit-platform-launcher") } ---- Alternatively, you can use Gradle's https://docs.gradle.org/current/userguide/jvm_test_suite_plugin.html[JVM Test Suite] support. [source,kotlin,indent=0] [subs=attributes+] .Kotlin DSL ---- testing { suites { named("test") { useJUnitJupiter("{version}") } } } ---- [source,groovy,indent=0] [subs=attributes+] .Groovy DSL ---- testing { suites { test { useJUnitJupiter("{version}") } } } ---- The JUnit Platform can run JUnit 4 based tests as long as you configure a `testImplementation` dependency on JUnit 4 and a `testRuntimeOnly` dependency on the JUnit Vintage `TestEngine` implementation similar to the following. [source,groovy,indent=0] [subs=attributes+] ---- dependencies { testImplementation("junit:junit:{junit4-version}") testRuntimeOnly("org.junit.vintage:junit-vintage-engine:{version}") testRuntimeOnly("org.junit.platform:junit-platform-launcher") } ---- [[gradle-config-params]] === Configuration Parameters The standard Gradle `test` task currently does not provide a dedicated DSL to set JUnit Platform xref:running-tests/configuration-parameters.adoc[configuration parameters] to influence test discovery and execution. However, you can provide configuration parameters within the build script via system properties (as shown below) or via the `junit-platform.properties` file. [source,groovy,indent=0] ---- test { // ... systemProperty("junit.jupiter.conditions.deactivate", "*") systemProperty("junit.jupiter.extensions.autodetection.enabled", true) systemProperty("junit.jupiter.testinstance.lifecycle.default", "per_class") // ... } ---- [[gradle-logging]] === Configuring Logging (optional) JUnit uses the Java Logging APIs in the `java.util.logging` package (a.k.a. _JUL_) to emit warnings and debug information. Please refer to the official documentation of `{LogManager}` for configuration options. Alternatively, it's possible to redirect log messages to other logging frameworks such as {Log4j} or {Logback}. To use a logging framework that provides a custom implementation of `{LogManager}`, set the `java.util.logging.manager` system property to the _fully qualified class name_ of the `{LogManager}` implementation to use. The example below demonstrates how to configure Log4j{nbsp}2.x (see {Log4j_JDK_Logging_Adapter} for details). [source,groovy,indent=0] [subs=attributes+] ---- test { systemProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager") // Avoid overhead (see https://logging.apache.org/log4j/2.x/manual/jmx.html#enabling-jmx) systemProperty("log4j2.disableJmx", "true") } ---- Other logging frameworks provide different means to redirect messages logged using `java.util.logging`. For example, for {Logback} you can use the https://www.slf4j.org/legacy.html#jul-to-slf4j[JUL to SLF4J Bridge] by adding it as a dependency to the test runtime classpath. [[maven]] == Maven Maven Surefire and Maven Failsafe provide https://maven.apache.org/surefire/maven-surefire-plugin/examples/junit-platform.html[native support] for executing tests on the JUnit Platform. The `pom.xml` file in the `{junit-jupiter-starter-maven}` project demonstrates how to use the Maven Surefire plugin and can serve as a starting point for configuring your Maven build. [WARNING] .Minimum required version of Maven Surefire/Failsafe ==== As of JUnit 6.0, the minimum required version of Maven Surefire/Failsafe is 3.0.0. ==== [[maven-bom]] === Aligning dependency versions Unless you're using Spring Boot which defines its own way of managing dependencies, it is recommended to use the JUnit Platform xref:appendix.adoc#dependency-metadata-junit-bom[Bill of Materials (BOM)] to align the versions of all JUnit artifacts. [source,xml,indent=0] [subs=attributes+] ---- org.junit junit-bom {version} pom import ---- Using the BOM allows you to omit the version when declaring dependencies on all artifacts with the `org.junit.platform`, `org.junit.jupiter`, and `org.junit.vintage` group IDs. TIP: See <> for details on how to override the version of JUnit used in your Spring Boot application. [[maven-engines-configure]] === Configuring Test Engines In order to have Maven Surefire or Maven Failsafe run any tests at all, at least one `TestEngine` implementation must be added to the test classpath. To configure support for JUnit Jupiter based tests, configure `test` scoped dependencies on the JUnit Jupiter API and the JUnit Jupiter `TestEngine` implementation similar to the following. [source,xml,indent=0] [subs=attributes+] ---- org.junit.jupiter junit-jupiter {version} test maven-surefire-plugin {surefire-version} maven-failsafe-plugin {surefire-version} ---- Maven Surefire and Maven Failsafe can run JUnit 4 based tests alongside Jupiter tests as long as you configure `test` scoped dependencies on JUnit 4 and the JUnit Vintage `TestEngine` implementation similar to the following. [source,xml,indent=0] [subs=attributes+] ---- junit junit {junit4-version} test org.junit.vintage junit-vintage-engine {version} test maven-surefire-plugin {surefire-version} maven-failsafe-plugin {surefire-version} ---- [[maven-filter-test-class-names]] === Filtering by Test Class Names The Maven Surefire Plugin will scan for test classes whose fully qualified names match the following patterns. - `+++**/Test*.java+++` - `+++**/*Test.java+++` - `+++**/*Tests.java+++` - `+++**/*TestCase.java+++` Moreover, it will exclude all nested classes (including static member classes) by default. Note, however, that you can override this default behavior by configuring explicit `include` and `exclude` rules in your `pom.xml` file. For example, to keep Maven Surefire from excluding static member classes, you can override its exclude rules as follows. [source,xml,indent=0] [subs=attributes+] .Overriding exclude rules of Maven Surefire ---- maven-surefire-plugin {surefire-version} ---- Please see the https://maven.apache.org/surefire/maven-surefire-plugin/examples/inclusion-exclusion.html[Inclusions and Exclusions of Tests] documentation for Maven Surefire for details. [[maven-filter-tags]] === Filtering by Tags You can filter tests by xref:running-tests/tags.adoc[tags] or xref:running-tests/tags.adoc#expressions[tag expressions] using the following configuration properties. - to include _tags_ or _tag expressions_, use `groups`. - to exclude _tags_ or _tag expressions_, use `excludedGroups`. [source,xml,indent=0] [subs=attributes+] ---- maven-surefire-plugin {surefire-version} acceptance | !feature-a integration, regression ---- [[maven-config-params]] === Configuration Parameters You can set JUnit Platform xref:running-tests/configuration-parameters.adoc[configuration parameters] to influence test discovery and execution by declaring the `configurationParameters` property and providing key-value pairs using the Java `Properties` file syntax (as shown below) or via the `junit-platform.properties` file. [source,xml,indent=0] [subs=attributes+] ---- maven-surefire-plugin {surefire-version} junit.jupiter.conditions.deactivate = * junit.jupiter.extensions.autodetection.enabled = true junit.jupiter.testinstance.lifecycle.default = per_class ---- [[ant]] == Ant Starting with version `1.10.3`, link:https://ant.apache.org/[Ant] has a link:https://ant.apache.org/manual/Tasks/junitlauncher.html[`junitlauncher`] task that provides native support for launching tests on the JUnit Platform. The `junitlauncher` task is solely responsible for launching the JUnit Platform and passing it the selected collection of tests. The JUnit Platform then delegates to registered test engines to discover and execute the tests. The `junitlauncher` task attempts to align as closely as possible with native Ant constructs such as link:https://ant.apache.org/manual/Types/resources.html#collection[resource collections] for allowing users to select the tests that they want executed by test engines. This gives the task a consistent and natural feel when compared to many other core Ant tasks. Starting with version `1.10.6` of Ant, the `junitlauncher` task supports link:https://ant.apache.org/manual/Tasks/junitlauncher.html#fork[forking the tests in a separate JVM]. The `build.xml` file in the `{junit-jupiter-starter-ant}` project demonstrates how to use the task and can serve as a starting point. === Basic Usage The following example demonstrates how to configure the `junitlauncher` task to select a single test class (i.e., `com.example.project.CalculatorTests`). [source,xml,indent=0] ---- ---- The `test` element allows you to specify a single test class that you want to be selected and executed. The `classpath` element allows you to specify the classpath to be used to launch the JUnit Platform. This classpath will also be used to locate test classes that are part of the execution. The following example demonstrates how to configure the `junitlauncher` task to select test classes from multiple locations. [source,xml,indent=0] ---- ---- In the above example, the `testclasses` element allows you to select multiple test classes that reside in different locations. For further details on usage and configuration options please refer to the official Ant documentation for the link:https://ant.apache.org/manual/Tasks/junitlauncher.html[`junitlauncher` task]. [[bazel]] == Bazel For Bazel, add dependencies on https://github.com/bazel-contrib/rules_jvm_external[rules_jvm_external] and https://github.com/bazel-contrib/rules_jvm[contrib_rules_jvm] to your module's manifest and install JUnit's artifacts. [source,skylark] [subs=attributes+] .MODULE.bazel ---- bazel_dep(name = "rules_jvm_external", version = "6.9") bazel_dep(name = "contrib_rules_jvm", version = "0.31.1") maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven") maven.install( artifacts = [ "org.junit.jupiter:junit-jupiter-api:{version}", "org.junit.jupiter:junit-jupiter-params:{version}", "org.junit.jupiter:junit-jupiter-engine:{version}", "org.junit.platform:junit-platform-launcher:{version}", "org.junit.platform:junit-platform-reporting:{version}", ], repositories = [ "https://repo1.maven.org/maven2", ], ) use_repo(maven, "maven") ---- Next, in your `BUILD` file, define a `java_test_suite` for your tests. [source,skylark] [subs=attributes+] .BUILD ---- load("@rules_jvm_external//:defs.bzl", "artifact") load("@contrib_rules_jvm//java:defs.bzl", "java_test_suite") java_library( name = "your-library-name", srcs = glob(["src/main/java/**/*.java"]), ) java_test_suite( name = "your-library-name-tests", size = "small", srcs = glob(["src/test/java/**/*.java"]), runner = "junit5", test_suffixes = ["Test.java", "Tests.java"], runtime_deps = runtime_deps = [ artifact("org.junit.jupiter:junit-jupiter-engine"), artifact("org.junit.platform:junit-platform-launcher"), artifact("org.junit.platform:junit-platform-reporting"), ], deps = [ ":your-library-name", artifact("org.junit.jupiter:junit-jupiter-api"), artifact("org.junit.jupiter:junit-jupiter-params"), ], ) ---- To execute your tests, run `bazel test //...`. For a full example, please check out the `{junit-jupiter-starter-bazel}` example project. [[sbt]] == sbt For sbt, you first need to declare a dependency on the https://github.com/sbt/sbt-jupiter-interface[sbt-jupiter-interface] plugin. Despite its name, it supports running any class-based JUnit Platform test engine. [source,scala] .project/plugins.sbt ---- addSbtPlugin("com.github.sbt.junit" % "sbt-jupiter-interface" % "0.17.0") ---- In your build definition, add dependencies on the corresponding `jupiter-interface` along JUnit's artifacts. Moreover, you should configure the `jupiterTestFramework` and its https://github.com/sbt/sbt-jupiter-interface#framework-options[options]. [source,scala] [subs=attributes+] .build.sbt ---- lazy val root = project .in(file(".")) .settings( name := "junit-jupiter-starter-sbt", libraryDependencies ++= Seq( "com.github.sbt.junit" % "jupiter-interface" % JupiterKeys.jupiterVersion.value % Test, "org.junit.jupiter" % "junit-jupiter" % "{version}" % Test, "org.junit.platform" % "junit-platform-launcher" % "{version}" % Test, ), testOptions += Tests.Argument(jupiterTestFramework, "--display-mode=tree") ) ---- Finally, execute your tests by running `sbt test`. For a full example, please check out the `{junit-jupiter-starter-sbt}` example project. [[spring-boot]] == Spring Boot link:https://spring.io/projects/spring-boot[Spring Boot] provides automatic support for managing the version of JUnit used in your project. In addition, the `spring-boot-starter-test` artifact automatically includes testing libraries such as JUnit Jupiter, AssertJ, Mockito, etc. If your build relies on dependency management support from Spring Boot, you should not import JUnit's xref:appendix.adoc#dependency-metadata-junit-bom[Bill of Materials (BOM)] in your build script since that would result in duplicate (and potentially conflicting) management of JUnit dependencies. If you need to override the version of a dependency used in your Spring Boot application, you have to override the exact name of the link:https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#appendix.dependency-versions.properties[version property] defined in the BOM used by the Spring Boot plugin. For example, the name of the JUnit Jupiter version property in Spring Boot is `junit-jupiter.version`. The mechanism for changing a dependency version is documented for both link:https://docs.spring.io/spring-boot/docs/current/gradle-plugin/reference/htmlsingle/#managing-dependencies.dependency-management-plugin.customizing[Gradle] and link:https://docs.spring.io/spring-boot/docs/current/maven-plugin/reference/htmlsingle/#using.parent-pom[Maven]. With Gradle you can override the JUnit Jupiter version by including the following in your `build.gradle` file. [source,groovy,indent=0] [subs=attributes+] ---- ext['junit-jupiter.version'] = '{version}' ---- With Maven you can override the JUnit Jupiter version by including the following in your `pom.xml` file. [source,xml,indent=0] [subs=attributes+] ---- {version} ---- ================================================ FILE: documentation/modules/ROOT/pages/running-tests/capturing-standard-output-error.adoc ================================================ = Capturing Standard Output/Error The JUnit Platform provides opt-in support for capturing output printed to `System.out` and `System.err`. To enable it, set the `junit.platform.output.capture.stdout` and/or `junit.platform.output.capture.stderr` xref:running-tests/configuration-parameters.adoc[configuration parameter] to `true`. In addition, you may configure the maximum number of buffered bytes to be used per executed test or container using `junit.platform.output.capture.maxBuffer`. If enabled, the JUnit Platform captures the corresponding output and publishes it as a report entry using the `stdout` or `stderr` keys to all registered `{TestExecutionListener}` instances immediately before reporting the test or container as finished. Please note that the captured output will only contain output emitted by the thread that was used to execute a container or test. Any output by other threads will be omitted because particularly when xref:writing-tests/parallel-execution.adoc[executing tests in parallel] it would be impossible to attribute it to a specific test or container. ================================================ FILE: documentation/modules/ROOT/pages/running-tests/configuration-parameters.adoc ================================================ = Configuration Parameters In addition to instructing the platform which test classes and test engines to include, which packages to scan, etc., it is sometimes necessary to provide additional custom configuration parameters that are specific to a particular test engine, listener, or registered extension. For example, the JUnit Jupiter `TestEngine` supports _configuration parameters_ for the following use cases. - xref:writing-tests/test-instance-lifecycle.adoc#default[Changing the Default Test Instance Lifecycle] - xref:extensions/registering-extensions.adoc#registration-automatic-enabling[Enabling Automatic Extension Detection] - xref:extensions/conditional-test-execution.adoc#deactivation[Deactivating Conditions] - xref:writing-tests/display-names.adoc#generator-default[Setting the Default Display Name Generator] _Configuration Parameters_ are text-based key-value pairs that can be supplied to test engines running on the JUnit Platform via one of the following mechanisms. 1. The `configurationParameter()` and `configurationParameters()` methods in `LauncherDiscoveryRequestBuilder` which is used to build a request supplied to the xref:advanced-topics/launcher-api.adoc[Launcher API]. + When running tests via one of the tools provided by the JUnit Platform you can specify configuration parameters as follows: * xref:running-tests/console-launcher.adoc[Console Launcher]: use the `--config` command-line option. * xref:running-tests/build-support.adoc#gradle-config-params[Gradle]: use the `systemProperty` or `systemProperties` DSL. * xref:running-tests/build-support.adoc#maven-config-params[Maven Surefire provider]: use the `configurationParameters` property. 2. The `configurationParametersResources()` method in `LauncherDiscoveryRequestBuilder`. + When running tests via the xref:running-tests/console-launcher.adoc[Console Launcher] you can specify custom configuration files using the `--config-resource` command-line option. 3. JVM system properties. 4. The JUnit Platform default configuration file: a file named `junit-platform.properties` in the root of the class path that follows the syntax rules for Java `Properties` files. NOTE: Configuration parameters are looked up in the exact order defined above. Consequently, configuration parameters supplied directly to the `Launcher` take precedence over those supplied via custom configuration files, system properties, and the default configuration file. Similarly, configuration parameters supplied via system properties take precedence over those supplied via the default configuration file. [[pattern]] == Pattern Matching Syntax This section describes the pattern matching syntax that is applied to the _configuration parameters_ used for the following features. - xref:extensions/conditional-test-execution.adoc#deactivation[Deactivating Conditions] - xref:advanced-topics/launcher-api.adoc#listeners-custom-deactivation[Deactivating a TestExecutionListener] - xref:running-tests/stack-trace-pruning.adoc[] - xref:extensions/registering-extensions.adoc#registration-automatic-filtering[Filtering Auto-detected Extensions] If the value for the given _configuration parameter_ consists solely of an asterisk (`+++*+++`), the pattern will match against all candidate classes. Otherwise, the value will be treated as a comma-separated list of patterns where each pattern will be matched against the fully qualified class name (_FQCN_) of each candidate class. Any dot (`.`) in a pattern will match against a dot (`.`) or a dollar sign (`$`) in a FQCN. Any asterisk (`+++*+++`) will match against one or more characters in a FQCN. All other characters in a pattern will be matched one-to-one against a FQCN. Examples: - `+++*+++`: matches all candidate classes. - `+++org.junit.*+++`: matches all candidate classes under the `org.junit` base package and any of its subpackages. - `+++*.MyCustomImpl+++`: matches every candidate class whose simple class name is exactly `MyCustomImpl`. - `+++*System*+++`: matches every candidate class whose FQCN contains `System`. - `+++*System*+++, +++*Unit*+++`: matches every candidate class whose FQCN contains `System` or `Unit`. - `org.example.MyCustomImpl`: matches the candidate class whose FQCN is exactly `org.example.MyCustomImpl`. - `org.example.MyCustomImpl, org.example.TheirCustomImpl`: matches candidate classes whose FQCN is exactly `org.example.MyCustomImpl` or `org.example.TheirCustomImpl`. ================================================ FILE: documentation/modules/ROOT/pages/running-tests/console-launcher.adoc ================================================ = Console Launcher The `{ConsoleLauncher}` is a command-line Java application that lets you launch the JUnit Platform from the console. For example, it can be used to run JUnit Vintage and JUnit Jupiter tests and print test execution results to the console. An executable _Fat JAR_ (`junit-platform-console-standalone-{version}.jar`) that contains the contents of all of its dependencies is published in the {Maven_Central} repository under the https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone[junit-platform-console-standalone] directory. It contains the contents of the following artifacts: include::partial$console-launcher-standalone-shadowed-artifacts.adoc[] [NOTE] ==== Since the `junit-platform-console-standalone` JAR contains the contents of all of its dependencies, its Maven POM does not declare any dependencies. Furthermore, it is not very likely that you would need to include a dependency on the `junit-platform-console-standalone` artifact in your project's Maven POM or Gradle build script. On the contrary, the executable `junit-platform-console-standalone` JAR is typically invoked directly from the command line or a shell script without a build script. If you need to declare dependencies in your build script on some of the artifacts contained in the `junit-platform-console-standalone` artifact, you should declare dependencies only on the JUnit artifacts that are used in your project. To simplify dependency management of JUnit artifacts in your build, you may wish to use the `junit-jupiter` aggregator artifact or `junit-bom`. See xref:appendix.adoc#dependency-metadata[Dependency Metadata] for details. ==== You can https://docs.oracle.com/javase/tutorial/deployment/jar/run.html[run] the standalone `ConsoleLauncher` as shown below. [source,console,subs=attributes+] ---- $ java -jar junit-platform-console-standalone-{version}.jar execute ├─ JUnit Vintage │ └─ example.JUnit4Tests │ └─ standardJUnit4Test ✔ └─ JUnit Jupiter ├─ StandardTests │ ├─ succeedingTest() ✔ │ └─ skippedTest() ↷ for demonstration purposes └─ A special test case ├─ Custom test name containing spaces ✔ ├─ ╯°□°)╯ ✔ └─ 😱 ✔ Test run finished after 64 ms [ 5 containers found ] [ 0 containers skipped ] [ 5 containers started ] [ 0 containers aborted ] [ 5 containers successful ] [ 0 containers failed ] [ 6 tests found ] [ 1 tests skipped ] [ 5 tests started ] [ 0 tests aborted ] [ 5 tests successful ] [ 0 tests failed ] ---- You can also run the standalone `ConsoleLauncher` as shown below (for example, to include all jars in a directory): [source,console,subs=attributes+] ---- $ java -cp classes:testlib/* org.junit.platform.console.ConsoleLauncher ---- [[options]] == Subcommands and Options The `{ConsoleLauncher}` provides the following subcommands: ---- include::partial$console-launcher-options.txt[] ---- [[options-discovering-tests]] === Discovering tests ---- include::partial$console-launcher-discover-options.txt[] ---- [[options-executing-tests]] === Executing tests .Exit Code NOTE: On successful runs, the `{ConsoleLauncher}` exits with a status code of `0`. All non-zero codes indicate an error of some sort. For example, status code `1` is returned if any containers or tests failed. If no tests are discovered and the `--fail-if-no-tests` command-line option is supplied, the `ConsoleLauncher` exits with a status code of `2`. Unexpected or invalid user input yields a status code of `3`. An exit code of `-1` indicates an unspecified error condition. ---- include::partial$console-launcher-execute-options.txt[] ---- [[options-listing-test-engines]] === Listing test engines ---- include::partial$console-launcher-engines-options.txt[] ---- [[argument-files]] == Argument Files (@-files) On some platforms you may run into system limitations on the length of a command line when creating a command line with lots of options or with long arguments. The `ConsoleLauncher` supports _argument files_, also known as _@-files_. Argument files are files that themselves contain arguments to be passed to the command. When the underlying https://github.com/remkop/picocli[picocli] command line parser encounters an argument beginning with the character `@`, it expands the contents of that file into the argument list. The arguments within a file can be separated by spaces or newlines. If an argument contains embedded whitespace, the whole argument should be wrapped in double or single quotes -- for example, `"-f=My Files/Stuff.java"`. If the argument file does not exist or cannot be read, the argument will be treated literally and will not be removed. This will likely result in an "unmatched argument" error message. You can troubleshoot such errors by executing the command with the `picocli.trace` system property set to `DEBUG`. Multiple _@-files_ may be specified on the command line. The specified path may be relative to the current directory or absolute. You can pass a real parameter with an initial `@` character by escaping it with an additional `@` symbol. For example, `@@somearg` will become `@somearg` and will not be subject to expansion. [[redirecting-stdout-and-stderr]] == Redirecting Standard Output/Error to Files You can redirect the `System.out` (stdout) and `System.err` (stderr) output streams to files using the `--redirect-stdout` and `--redirect-stderr` options: [source,console,subs=attributes+] ---- $ java -jar junit-platform-console-standalone-{version}.jar \ --redirect-stdout=stdout.txt \ --redirect-stderr=stderr.txt ---- [NOTE] ==== If the `--redirect-stdout` and `--redirect-stderr` arguments point to the same file, both output streams will be redirected to that file. The default charset is used for writing to the files. ==== [[color-customization]] == Color Customization The colors used in the output of the `{ConsoleLauncher}` can be customized. The option `--single-color` will apply a built-in monochrome style, while `--color-palette` will accept a properties file to override the https://en.wikipedia.org/wiki/ANSI_escape_code#Colors[ANSI SGR] color styling. The properties file below demonstrates the default style: [source,properties,indent=0] ---- SUCCESSFUL = 32 ABORTED = 33 FAILED = 31 SKIPPED = 35 CONTAINER = 35 TEST = 34 DYNAMIC = 35 REPORTED = 37 ---- ================================================ FILE: documentation/modules/ROOT/pages/running-tests/discovery-issues.adoc ================================================ = Discovery Issues Test engines may encounter issues during test discovery. For example, the declaration of a test class or method may be invalid. To avoid such issues from going unnoticed, the JUnit Platform provides a xref:advanced-topics/engines.adoc#discovery-issues[mechanism for test engines] to report them with different severity levels: INFO:: Indicates that the engine encountered something that could be potentially problematic, but could also happen due to a valid setup or configuration. WARNING:: Indicates that the engine encountered something that is problematic and might lead to unexpected behavior or will be removed or changed in a future release. ERROR:: Indicates that the engine encountered something that is definitely problematic and will lead to unexpected behavior. If an engine reports an issue with a severity equal to or higher than a configurable _critical_ severity, its tests will not be executed. Instead, the engine will be reported as failed during execution with a `{DiscoveryIssueException}` listing all critical issues. Non-critical issues will be logged but will not prevent the engine from executing its tests. The `junit.platform.discovery.issue.severity.critical` xref:running-tests/configuration-parameters.adoc[configuration parameter] can be used to set the critical severity level. Currently, the default value is `ERROR` but it may be changed in a future release. TIP: To surface all discovery issues in your project, it is recommended to set the `junit.platform.discovery.issue.severity.critical` configuration parameter to `INFO`. In addition, registered `{LauncherDiscoveryListener}` implementations can receive discovery issues via the `issueEncountered()` method. This allows IDEs and build tools to report issues to the user in a more user-friendly way. For example, IDEs may choose to display all issues in a list or table. ================================================ FILE: documentation/modules/ROOT/pages/running-tests/discovery-selectors.adoc ================================================ = Discovery Selectors The JUnit Platform provides a rich set of discovery selectors that can be used to specify which tests should be discovered or executed. Discovery selectors can be created programmatically using the factory methods in the `{DiscoverySelectors}` class, specified declaratively via annotations when using the xref:advanced-topics/junit-platform-suite-engine.adoc[], via options of the xref:running-tests/console-launcher.adoc[], or generically as strings via their identifiers. The following discovery selectors are provided out of the box: |=== | Java Type | API | Annotation | Console Launcher | Identifier | `{ClasspathResourceSelector}` | `{DiscoverySelectors_selectClasspathResource}` | `{SelectClasspathResource}` | `--select-resource /foo.csv` | `resource:/foo.csv` | `{ClasspathRootSelector}` | `{DiscoverySelectors_selectClasspathRoots}` | -- | `--scan-classpath bin` | `classpath-root:bin` | `{ClassSelector}` | `{DiscoverySelectors_selectClass}` | `{SelectClasses}` | `--select-class com.acme.Foo` | `class:com.acme.Foo` | `{DirectorySelector}` | `{DiscoverySelectors_selectDirectory}` | `{SelectDirectories}` | `--select-directory foo/bar` | `directory:foo/bar` | `{FileSelector}` | `{DiscoverySelectors_selectFile}` | `{SelectFile}` | `--select-file dir/foo.txt` | `file:dir/foo.txt` | `{IterationSelector}` | `{DiscoverySelectors_selectIteration}` | `{Select}("")` | `--select-iteration method=com.acme.Foo#m[1..2]` | `iteration:method:com.acme.Foo#m[1..2]` | `{MethodSelector}` | `{DiscoverySelectors_selectMethod}` | `{SelectMethod}` | `--select-method com.acme.Foo#m` | `method:com.acme.Foo#m` | `{ModuleSelector}` | `{DiscoverySelectors_selectModule}` | `{SelectModules}` | `--select-module com.acme` | `module:com.acme` | `{NestedClassSelector}` | `{DiscoverySelectors_selectNestedClass}` | `{Select}("")` | `--select ` | `nested-class:com.acme.Foo/Bar` | `{NestedMethodSelector}` | `{DiscoverySelectors_selectNestedMethod}` | `{Select}("")` | `--select ` | `nested-method:com.acme.Foo/Bar#m` | `{PackageSelector}` | `{DiscoverySelectors_selectPackage}` | `{SelectPackages}` | `--select-package com.acme.foo` | `package:com.acme.foo` | `{UniqueIdSelector}` | `{DiscoverySelectors_selectUniqueId}` | `{Select}("")` | `--select-unique-id ` | `uid:[engine:Foo]/[segment:Bar]` | `{UriSelector}` | `{DiscoverySelectors_selectUri}` | `{SelectUris}` | `--select-uri \file:///foo.txt` | `uri:file:///foo.txt` |=== ================================================ FILE: documentation/modules/ROOT/pages/running-tests/ide-support.adoc ================================================ = IDE Support [[intellij-idea]] == IntelliJ IDEA IntelliJ IDEA supports running tests on the JUnit Platform since version 2016.2. For more information, please consult this https://jb.gg/junit-idea/[IntelliJ IDEA resource]. Note, however, that it is recommended to use IDEA 2017.3 or newer since more recent versions of IDEA download the following JARs automatically based on the API version used in the project: `junit-platform-launcher`, `junit-jupiter-engine`, and `junit-vintage-engine`. In order to use a different JUnit version (e.g., {version}), you may need to include the corresponding versions of the `junit-platform-launcher`, `junit-jupiter-engine`, and `junit-vintage-engine` JARs in the classpath. .Additional Gradle Dependencies [source,groovy] [subs=attributes+] ---- testImplementation(platform("org.junit:junit-bom:{version}")) testRuntimeOnly("org.junit.platform:junit-platform-launcher") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") testRuntimeOnly("org.junit.vintage:junit-vintage-engine") ---- .Additional Maven Dependencies [source,xml] [subs=attributes+] ---- org.junit.platform junit-platform-launcher test org.junit.jupiter junit-jupiter-engine test org.junit.vintage junit-vintage-engine test org.junit junit-bom {version} pom import ---- [[eclipse]] == Eclipse Eclipse IDE offers support for the JUnit Platform since the Eclipse Oxygen.1a (4.7.1a) release. For more information on using JUnit Platform in Eclipse consult the official _Eclipse support for JUnit 5_ section of the https://www.eclipse.org/eclipse/news/4.7.1a/#junit-5-support[Eclipse Project Oxygen.1a (4.7.1a) - New and Noteworthy] documentation. [[netbeans]] == NetBeans NetBeans offers support for JUnit Jupiter and the JUnit Platform since the https://netbeans.apache.org/download/nb100/nb100.html[Apache NetBeans 10.0 release]. For more information consult the JUnit 5 section of the https://netbeans.apache.org/download/nb100/index.html#_junit_5[Apache NetBeans 10.0 release notes]. [[vscode]] == Visual Studio Code https://code.visualstudio.com/[Visual Studio Code] supports JUnit Jupiter and the JUnit Platform via the https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-test[Java Test Runner] extension which is installed by default as part of the https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-pack[Java Extension Pack]. For more information consult the _Testing_ section of the https://code.visualstudio.com/docs/languages/java#_testing[Java in Visual Studio Code] documentation. [[other]] == Other IDEs If you are using an editor or IDE other than one of those listed in the previous sections, and it doesn't support running tests on the JUnit Platform, you can use the xref:running-tests/console-launcher.adoc[] to run them from the command line. ================================================ FILE: documentation/modules/ROOT/pages/running-tests/intro.adoc ================================================ = Running Tests This section explains how to run tests from IDEs and build tools. ================================================ FILE: documentation/modules/ROOT/pages/running-tests/source-launcher.adoc ================================================ = Source Launcher Starting with Java 25 it is possible to write minimal source code test programs using the `org.junit.start` module. For example, create a `HelloTests.java` file with the following content: [source,java] ---- import module org.junit.start; void main() { JUnit.run(); } @Test void stringLength() { Assertions.assertEquals(11, "Hello JUnit".length()); } ---- With all required modular JAR files available in a local `lib/` directory, the following Java 25+ command will discover and execute tests using the JUnit Platform. It will also print the result tree to the console. [source,shell] ---- java --module-path lib --add-modules org.junit.start HelloTests.java ╷ └─ JUnit Jupiter ✔ └─ HelloTests ✔ └─ stringLength() ✔ ---- For details, please refer to the Javadoc of the `{JUnit}` class. ================================================ FILE: documentation/modules/ROOT/pages/running-tests/stack-trace-pruning.adoc ================================================ = Stack Trace Pruning The JUnit Platform provides built-in support for pruning stack traces produced by failing tests. This feature is enabled by default but can be disabled by setting the `junit.platform.stacktrace.pruning.enabled` _configuration parameter_ to `false`. When enabled, all calls from the `org.junit`, `jdk.internal.reflect`, and `sun.reflect` packages are removed from the stack trace, unless the calls occur after the test itself or any of its ancestors. For that reason, calls to `{Assertions}` or `{Assumptions}` will never be excluded. In addition, all elements prior to and including the first call from the JUnit Platform `Launcher` will be removed. ================================================ FILE: documentation/modules/ROOT/pages/running-tests/tags.adoc ================================================ = Tags Tags are a JUnit Platform concept for marking and filtering tests. The programming model for adding tags to containers and tests is defined by the testing framework. For example, in JUnit Jupiter based tests, the `@Tag` annotation (see xref:writing-tests/tagging-and-filtering.adoc[]) should be used. For JUnit 4 based tests, the Vintage engine maps `@Category` annotations to tags (see xref:migrating-from-junit4.adoc#categories-support[Categories Support]). Other testing frameworks may define their own annotation or other means for users to specify tags. [[syntax-rules]] == Syntax Rules for Tags Regardless how a tag is specified, the JUnit Platform enforces the following rules: * A tag must not be `null` or _blank_. * A _stripped_ tag must not contain whitespace. * A _stripped_ tag must not contain ISO control characters. * A _stripped_ tag must not contain any of the following _reserved characters_. - `,`: _comma_ - `(`: _left parenthesis_ - `)`: _right parenthesis_ - `&`: _ampersand_ - `|`: _vertical bar_ - `!`: _exclamation point_ NOTE: In the above context, "stripped" means that leading and trailing whitespace characters have been removed using `java.lang.String.strip()`. [[expressions]] == Tag Expressions Tag expressions are boolean expressions with the operators `!`, `&` and `|`. In addition, `(` and `)` can be used to adjust for operator precedence. Two special expressions are supported, `any()` and `none()`, which select all tests _with_ any tags at all, and all tests _without_ any tags, respectively. These special expressions may be combined with other expressions just like normal tags. .Operators (in descending order of precedence) |=== | Operator | Meaning | Associativity | `!` | not | right | `&` | and | left | `\|` | or | left |=== If you are tagging your tests across multiple dimensions, tag expressions help you to select which tests to execute. When tagging by test type (e.g., _micro_, _integration_, _end-to-end_) and feature (e.g., *product*, *catalog*, *shipping*), the following tag expressions can be useful. [%header,cols="40,60"] |=== | Tag Expression | Selection | `+++product+++` | all tests for *product* | `+++catalog \| shipping+++` | all tests for *catalog* plus all tests for *shipping* | `+++catalog & shipping+++` | all tests for the intersection between *catalog* and *shipping* | `+++product & !end-to-end+++` | all tests for *product*, but not the _end-to-end_ tests | `+++(micro \| integration) & (product \| shipping)+++` | all _micro_ or _integration_ tests for *product* or *shipping* |=== ================================================ FILE: documentation/modules/ROOT/pages/running-tests/using-listeners-and-interceptors.adoc ================================================ = Using Listeners and Interceptors The JUnit Platform provides the following listener APIs that allow JUnit, third parties, and custom user code to react to events fired at various points during the discovery and execution of a `TestPlan`. * `{LauncherSessionListener}`: receives events when a `{LauncherSession}` is opened and closed. * `{LauncherInterceptor}`: intercepts test discovery and execution in the context of a `LauncherSession`. * `{LauncherDiscoveryListener}`: receives events that occur during test discovery. * `{TestExecutionListener}`: receives events that occur during test execution. The `LauncherSessionListener` API is typically implemented by build tools or IDEs and registered automatically for you in order to support some feature of the build tool or IDE. The `LauncherDiscoveryListener` and `TestExecutionListener` APIs are often implemented in order to produce some form of report or to display a graphical representation of the test plan in an IDE. Such listeners may be implemented and automatically registered by a build tool or IDE, or they may be included in a third-party library – potentially registered for you automatically. You can also implement and register your own listeners. For details on registering and configuring listeners, see the following sections of this guide. * xref:advanced-topics/launcher-api.adoc#launcher-session-listeners-custom[Registering a LauncherSessionListener] * xref:advanced-topics/launcher-api.adoc#launcher-interceptors-custom[Registering a LauncherInterceptor] * xref:advanced-topics/launcher-api.adoc#launcher-discovery-listeners-custom[Registering a LauncherDiscoveryListener] * xref:advanced-topics/launcher-api.adoc#listeners-custom[Registering a TestExecutionListener] * xref:advanced-topics/launcher-api.adoc#listeners-config[Configuring a TestExecutionListener] * xref:advanced-topics/launcher-api.adoc#listeners-custom-deactivation[Deactivating a TestExecutionListener] The JUnit Platform provides the following listeners which you may wish to use with your test suite. xref:advanced-topics/junit-platform-reporting.adoc[] :: `{LegacyXmlReportGeneratingListener}` can be used via the xref:running-tests/console-launcher.adoc[] or registered manually to generate XML reports compatible with the de facto standard for JUnit 4 based test reports. + `{OpenTestReportGeneratingListener}` generates an XML report in the event-based format specified by {OpenTestReporting}. It is auto-registered and can be enabled and configured via xref:running-tests/configuration-parameters.adoc[]. + See xref:advanced-topics/junit-platform-reporting.adoc[] for details. <> :: `FlightRecordingExecutionListener` and `FlightRecordingDiscoveryListener` that generate Java Flight Recorder events during test discovery and execution. `{LoggingListener}` :: `TestExecutionListener` for logging informational messages for all events via a `BiConsumer` that consumes `Throwable` and `Supplier`. `{SummaryGeneratingListener}` :: `TestExecutionListener` that generates a summary of the test execution which can be printed via a `PrintWriter`. `{UniqueIdTrackingListener}` :: `TestExecutionListener` that that tracks the unique IDs of all tests that were skipped or executed during the execution of the `TestPlan` and generates a file containing the unique IDs once execution of the `TestPlan` has finished. [[recorder]] == Flight Recorder Support The JUnit Platform provides opt-in support for generating Flight Recorder events. https://openjdk.java.net/jeps/328[JEP 328] describes the Java Flight Recorder (JFR) as follows. > Flight Recorder records events originating from applications, the JVM, and the OS. Events are stored in a single file that can be attached to bug reports and examined by support engineers, allowing after-the-fact analysis of issues in the period leading up to a problem. In order to record Flight Recorder events generated while running tests, you need to start flight recording when launching a test suite via the following java command line option. -XX:StartFlightRecording:filename=... Please consult the manual of your build tool for the appropriate commands. To analyze the recorded events, use the https://docs.oracle.com/en/java/javase/17/docs/specs/man/jfr.html[jfr] command line tool shipped with recent JDKs or open the recording file with https://jdk.java.net/jmc/[JDK Mission Control]. ================================================ FILE: documentation/modules/ROOT/pages/writing-tests/annotations.adoc ================================================ = Annotations JUnit Jupiter supports the following annotations for configuring tests and extending the framework. Unless otherwise stated, all core annotations are located in the `{api-package}` package in the `junit-jupiter-api` module. `*@Test*`:: Denotes that a method is a test method. Unlike JUnit 4's `@Test` annotation, this annotation does not declare any attributes, since test extensions in JUnit Jupiter operate based on their own dedicated annotations. Such methods are inherited unless they are overridden. `*@ParameterizedTest*`:: Denotes that a method is a xref:writing-tests/parameterized-classes-and-tests.adoc[parameterized test]. Such methods are inherited unless they are overridden. `*@RepeatedTest*`:: Denotes that a method is a test template for a xref:writing-tests/repeated-tests.adoc[repeated test]. Such methods are inherited unless they are overridden. `*@TestFactory*`:: Denotes that a method is a test factory for xref:writing-tests/dynamic-tests.adoc[dynamic tests]. Such methods are inherited unless they are overridden. `*@TestTemplate*`:: Denotes that a method is a xref:writing-tests/test-templates.adoc[template for a test case] designed to be invoked multiple times depending on the number of invocation contexts returned by the registered xref:extensions/providing-invocation-contexts-for-test-templates.adoc[providers]. Such methods are inherited unless they are overridden. `*@TestClassOrder*`:: Used to configure the xref:writing-tests/test-execution-order.adoc#classes[test class execution order] for `@Nested` test classes in the annotated test class. Such annotations are inherited. `*@TestMethodOrder*`:: Used to configure the xref:writing-tests/test-execution-order.adoc#methods[test method execution order] for the annotated test class; similar to JUnit 4's `@FixMethodOrder`. Such annotations are inherited. `*@TestInstance*`:: Used to configure the xref:writing-tests/test-instance-lifecycle.adoc[test instance lifecycle] for the annotated test class. Such annotations are inherited. `*@DisplayName*`:: Declares a custom xref:writing-tests/display-names.adoc[display name] for the test class or test method. Such annotations are not inherited. `*@DisplayNameGeneration*`:: Declares a custom xref:writing-tests/display-names.adoc#generator[display name generator] for the test class. Such annotations are inherited. `*@BeforeEach*`:: Denotes that the annotated method should be executed _before_ *each* `@Test`, `@RepeatedTest`, `@ParameterizedTest`, or `@TestFactory` method in the current class; analogous to JUnit 4's `@Before`. Such methods are inherited unless they are overridden. `*@AfterEach*`:: Denotes that the annotated method should be executed _after_ *each* `@Test`, `@RepeatedTest`, `@ParameterizedTest`, or `@TestFactory` method in the current class; analogous to JUnit 4's `@After`. Such methods are inherited unless they are overridden. `*@BeforeAll*`:: Denotes that the annotated method should be executed _before_ *all* `@Test`, `@RepeatedTest`, `@ParameterizedTest`, and `@TestFactory` methods in the current top-level or `@Nested` test class; analogous to JUnit 4's `@BeforeClass`. Such methods are inherited unless they are overridden and must be `static` unless the "per-class" xref:writing-tests/test-instance-lifecycle.adoc[test instance lifecycle] is used. `*@AfterAll*`:: Denotes that the annotated method should be executed _after_ *all* `@Test`, `@RepeatedTest`, `@ParameterizedTest`, and `@TestFactory` methods in the current top-level or `@Nested` test class; analogous to JUnit 4's `@AfterClass`. Such methods are inherited unless they are overridden and must be `static` unless the "per-class" xref:writing-tests/test-instance-lifecycle.adoc[test instance lifecycle] is used. `*@ParameterizedClass*`:: Denotes that the annotated class is a xref:writing-tests/parameterized-classes-and-tests.adoc[parameterized class]. Such annotations are inherited. `*@BeforeParameterizedClassInvocation*`:: Denotes that the annotated method should be executed once _before_ each invocation of a xref:writing-tests/parameterized-classes-and-tests.adoc[parameterized class]. Such methods are inherited unless they are overridden. `*@AfterParameterizedClassInvocation*`:: Denotes that the annotated method should be executed once _after_ each invocation of a xref:writing-tests/parameterized-classes-and-tests.adoc[parameterized class]. Such methods are inherited unless they are overridden. `*@ClassTemplate*`:: Denotes that the annotated class is a xref:writing-tests/class-templates.adoc[template for a test class] designed to be executed multiple times depending on the number of invocation contexts returned by the registered xref:extensions/providing-invocation-contexts-for-class-templates.adoc[providers]. Such annotations are inherited. `*@Nested*`:: Denotes that the annotated class is a non-static xref:writing-tests/nested-tests.adoc[nested test class]. Such annotations are not inherited. `*@Tag*`:: Used to declare xref:writing-tests/tagging-and-filtering.adoc[tags for filtering tests], either at the class or method level; analogous to test groups in TestNG or Categories in JUnit 4. Such annotations are inherited at the class level but not at the method level. `*@Disabled*`:: Used to xref:writing-tests/disabling-tests.adoc[disable] a test class or test method; analogous to JUnit 4's `@Ignore`. Such annotations are not inherited. `*@AutoClose*`:: Denotes that the annotated field represents a resource that will be xref:writing-tests/built-in-extensions.adoc#AutoClose[automatically closed] after test execution. Such fields are inherited. `*@Timeout*`:: Used to fail a test, test factory, test template, or lifecycle method if its execution exceeds a given duration. Such annotations are inherited. `*@TempDir*`:: Used to supply a xref:writing-tests/built-in-extensions.adoc#TempDirectory[temporary directory] via field injection or parameter injection in a test class constructor, lifecycle method, or test method; located in the `org.junit.jupiter.api.io` package. Such fields are inherited. `*@ExtendWith*`:: Used to xref:extensions/registering-extensions.adoc#registration-declarative[register extensions declaratively]. Such annotations are inherited. `*@RegisterExtension*`:: Used to xref:extensions/registering-extensions.adoc#registration-programmatic[register extensions programmatically] via fields. Such fields are inherited. WARNING: Some annotations may currently be _experimental_. Consult the table in xref:api-evolution.adoc#experimental-apis[Experimental APIs] for details. [[annotations]] == Meta-Annotations and Composed Annotations JUnit Jupiter annotations can be used as _meta-annotations_. That means that you can define your own _composed annotation_ that will automatically _inherit_ the semantics of its meta-annotations. For example, instead of copying and pasting `@Tag("fast")` throughout your code base (see xref:writing-tests/tagging-and-filtering.adoc[]), you can create a custom _composed annotation_ named `@Fast` as follows. `@Fast` can then be used as a drop-in replacement for `@Tag("fast")`. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/Fast.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/Fast.kt[tags=user_guide] ---- -- ==== The following `@Test` method demonstrates usage of the `@Fast` annotation. [tabs] ==== Java:: + -- [source,java,indent=0] ---- @Fast @Test void myFastTest() { // ... } ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- @Fast @Test fun myFastTest() { // ... } ---- -- ==== You can even take that one step further by introducing a custom `@FastTest` annotation that can be used as a drop-in replacement for `@Tag("fast")` _and_ `@Test`. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/FastTest.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/FastTest.kt[tags=user_guide] ---- -- ==== JUnit automatically recognizes the following as a `@Test` method that is tagged with "fast". [tabs] ==== Java:: + -- [source,java,indent=0] ---- @FastTest void myFastTest() { // ... } ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- @FastTest fun myFastTest() { // ... } ---- -- ==== ================================================ FILE: documentation/modules/ROOT/pages/writing-tests/assertions.adoc ================================================ = Assertions JUnit Jupiter comes with many of the assertion methods that JUnit 4 has and adds a few that lend themselves well to being used with Java lambdas. All JUnit Jupiter assertions are `static` methods in the `{Assertions}` class. Assertion methods optionally accept the assertion message as their third parameter, which can be either a `String` or a `Supplier`. When using a `Supplier` (e.g., a lambda expression), the message is evaluated lazily. This can provide a performance benefit, especially if message construction is complex or time-consuming, as it is only evaluated when the assertion fails. [source,java,indent=0] ---- include::example$java/example/AssertionsDemo.java[tags=user_guide] ---- [[preemptive-timeouts]] [WARNING] .Preemptive Timeouts with `assertTimeoutPreemptively()` ==== The various `assertTimeoutPreemptively()` methods in the `Assertions` class execute the provided `executable` or `supplier` in a different thread than that of the calling code. This behavior can lead to undesirable side effects if the code that is executed within the `executable` or `supplier` relies on `java.lang.ThreadLocal` storage. One common example of this is the transactional testing support in the Spring Framework. Specifically, Spring's testing support binds transaction state to the current thread (via a `ThreadLocal`) before a test method is invoked. Consequently, if an `executable` or `supplier` provided to `assertTimeoutPreemptively()` invokes Spring-managed components that participate in transactions, any actions taken by those components will not be rolled back with the test-managed transaction. On the contrary, such actions will be committed to the persistent store (e.g., relational database) even though the test-managed transaction is rolled back. Similar side effects may be encountered with other frameworks that rely on `ThreadLocal` storage. ==== [[kotlin]] == Kotlin Assertion Support JUnit Jupiter also comes with a few assertion methods that lend themselves well to being used in https://kotlinlang.org/[Kotlin]. All JUnit Jupiter Kotlin assertions are top-level functions in the `org.junit.jupiter.api` package. [source,kotlin,indent=0] ---- include::example$kotlin/example/KotlinAssertionsDemo.kt[tags=user_guide] ---- [[third-party]] == Third-party Assertion Libraries Even though the assertion facilities provided by JUnit Jupiter are sufficient for many testing scenarios, there are times when more power and additional functionality are desired or required. In such cases, the JUnit team recommends the use of third-party assertion libraries such as {AssertJ}, {Hamcrest}, {Truth}, etc. Developers are therefore free to use the assertion library of their choice. For example, the following demonstrates how to use the `assertThat()` support from AssertJ in a JUnit Jupiter test. As long as the AssertJ library has been added to the classpath, you can statically import methods such as `assertThat()`, `assertThatException()`, etc. from `org.assertj.core.api.Assertions` and then use them in tests like in the `assertWithAssertJ()` method below. [source,java,indent=0] ---- include::example$java/example/AssertJAssertionsDemo.java[tags=user_guide] ---- [TIP] .Excluding Jupiter’s Assertions From a Project’s Classpath ==== If you would like to enforce that all your tests use a certain third-party assertion library instead of Jupiter's, you can set up a rule using {Checkstyle} or another static analysis tool that fails the build if Jupiter's `Assertions` class is used. [source,xml] ---- ---- ==== ================================================ FILE: documentation/modules/ROOT/pages/writing-tests/assumptions.adoc ================================================ = Assumptions Assumptions are typically used whenever it does not make sense to continue execution of a given test — for example, if the test depends on something that does not exist in the current runtime environment. * When an assumption is valid, the assumption method does not throw an exception, and execution of the test continues as usual. * When an assumption is invalid, the assumption method throws an exception of type `org.opentest4j.TestAbortedException` to signal that the test should be aborted instead of marked as a failure. JUnit Jupiter comes with a subset of the _assumption_ methods that JUnit 4 provides and adds a few that lend themselves well to being used with Java lambda expressions and method references. All JUnit Jupiter assumptions are static methods in the `{Assumptions}` class. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/AssumptionsDemo.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/AssumptionsDemo.kt[tags=user_guide] ---- -- ==== NOTE: It is also possible to use methods from JUnit 4's `org.junit.Assume` class for assumptions. Specifically, JUnit Jupiter supports JUnit 4's `AssumptionViolatedException` to signal that a test should be aborted instead of marked as a failure. TIP: If you use AssertJ for assertions, you may also wish to use AssertJ for assumptions. To do so, you can statically import the `assumeThat()` method from `org.assertj.core.api.Assumptions` and then use AssertJ's fluent API to specify your assumptions. ================================================ FILE: documentation/modules/ROOT/pages/writing-tests/built-in-extensions.adoc ================================================ = Built-in Extensions While the JUnit team encourages reusable extensions to be packaged and maintained in separate libraries, JUnit Jupiter includes a few user-facing extension implementations that are considered so generally useful that users shouldn't have to add another dependency. [[TempDirectory]] == The @TempDir Extension The `{TempDirectory}` extension is used to create and clean up a temporary directory for an individual test or all tests in a test class. It is registered by default. To use it, annotate a non-final, unassigned field of type `java.nio.file.Path` or `java.io.File` with `{TempDir}` or add a parameter of type `java.nio.file.Path` or `java.io.File` annotated with `@TempDir` to a test class constructor, lifecycle method, or test method. For example, the following test declares a parameter annotated with `@TempDir` for a single test method, creates and writes to a file in the temporary directory, and checks its content. .A test method that requires a temporary directory [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/TempDirectoryDemo.java[tags=user_guide_parameter_injection] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/TempDirectoryDemo.kt[tags=user_guide_parameter_injection] ---- -- ==== You can inject multiple temporary directories by specifying multiple annotated parameters. .A test method that requires multiple temporary directories [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/TempDirectoryDemo.java[tags=user_guide_multiple_directories] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/TempDirectoryDemo.kt[tags=user_guide_multiple_directories] ---- -- ==== The following example stores a _shared_ temporary directory in a `static` field. This allows the same `sharedTempDir` to be used in all lifecycle methods and test methods of the test class. For better isolation, you should use an instance field or constructor injection so that each test method uses a separate directory. .A test class that shares a temporary directory across test methods [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/TempDirectoryDemo.java[tags=user_guide_field_injection] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/TempDirectoryDemo.kt[tags=user_guide_field_injection] ---- -- ==== The `@TempDir` annotation has an optional `cleanup` attribute that can be set to either `NEVER`, `ON_SUCCESS`, or `ALWAYS`. If the cleanup mode is set to `NEVER`, the temporary directory will not be deleted after the test completes. If it is set to `ON_SUCCESS`, the temporary directory will only be deleted after the test if the test completed successfully. The default cleanup mode is `ALWAYS`. You can use the `junit.jupiter.tempdir.cleanup.mode.default` xref:running-tests/configuration-parameters.adoc[configuration parameter] to override this default. .A test class with a temporary directory that doesn't get cleaned up [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/TempDirectoryDemo.java[tags=user_guide_cleanup_mode] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/TempDirectoryDemo.kt[tags=user_guide_cleanup_mode] ---- -- ==== [[TempDirFactory]] === Factories `@TempDir` supports the programmatic creation of temporary directories via the optional `factory` attribute. This is typically used to gain control over the temporary directory creation, like defining the parent directory or the file system that should be used. Factories can be created by implementing `{TempDirFactory}`. Implementations must provide a no-args constructor and should not make any assumptions regarding when and how many times they are instantiated, but they can assume that their `createTempDirectory(...)` and `close()` methods will both be called once per instance, in this order, and from the same thread. The default implementation available in Jupiter delegates directory creation to `java.nio.file.Files::createTempDirectory` which uses the default file system and the system's temporary directory as the parent directory. It passes `junit-` as the prefix string of the generated directory name to help identify it as a created by JUnit. The following example defines a factory that uses the test name as the directory name prefix instead of the `junit` constant value. .A test class with a temporary directory having the test name as the directory name prefix [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/TempDirectoryDemo.java[tags=user_guide_factory_name_prefix] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/TempDirectoryDemo.kt[tags=user_guide_factory_name_prefix] ---- -- ==== It is also possible to use an in-memory file system like `{Jimfs}` for the creation of the temporary directory. The following example demonstrates how to achieve that. .A test class with a temporary directory created with the Jimfs in-memory file system [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/TempDirectoryDemo.java[tags=user_guide_factory_jimfs] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/TempDirectoryDemo.kt[tags=user_guide_factory_jimfs] ---- -- ==== `@TempDir` can also be used as a xref:writing-tests/annotations.adoc#annotations[meta-annotation] to reduce repetition. The following code listing shows how to create a custom `@JimfsTempDir` annotation that can be used as a drop-in replacement for `@TempDir(factory = JimfsTempDirFactory.class)`. .A custom annotation meta-annotated with `@TempDir` [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/TempDirectoryDemo.java[tags=user_guide_composed_annotation] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/TempDirectoryDemo.kt[tags=user_guide_composed_annotation] ---- -- ==== The following example demonstrates how to use the custom `@JimfsTempDir` annotation. .A test class using the custom annotation [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/TempDirectoryDemo.java[tags=user_guide_composed_annotation_usage] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/TempDirectoryDemo.kt[tags=user_guide_composed_annotation_usage] ---- -- ==== Meta-annotations or additional annotations on the field or parameter the `TempDir` annotation is declared on might expose additional attributes to configure the factory. Such annotations and related attributes can be accessed via the `AnnotatedElementContext` parameter of the `createTempDirectory(...)` method. You can use the `junit.jupiter.tempdir.factory.default` xref:running-tests/configuration-parameters.adoc[configuration parameter] to specify the fully qualified class name of the `{TempDirFactory}` you would like to use by default. Just like for factories configured via the `factory` attribute of the `@TempDir` annotation, the supplied class has to implement the `{TempDirFactory}` interface. The default factory will be used for all `@TempDir` annotations unless the `factory` attribute of the annotation specifies a different factory. In summary, the factory for a temporary directory is determined according to the following precedence rules: 1. The `factory` attribute of the `@TempDir` annotation, if present 2. The default `{TempDirFactory}` configured via the configuration parameter, if present 3. Otherwise, `org.junit.jupiter.api.io.TempDirFactory$Standard` will be used. [[TempDirDeletionStrategy]] === Deletion `@TempDir` supports the programmatic deletion of temporary directories via the optional `deletionStrategy` attribute. This is typically used to gain control over what happens when deletion of a file or directory fails. Deletion strategies can be created by implementing `{TempDirDeletionStrategy}`. Implementations must provide a no-args constructor. Jupiter ships with two built-in deletion strategies: * `{TempDirDeletionStrategyStandard}` (the default): attempts to delete all files and directories recursively, retrying with permission resets on failure. Paths that still cannot be deleted are scheduled for deletion on JVM exit, if possible. Additionally, the test is failed. * `{TempDirDeletionStrategyIgnoreFailures}`: delegates to `{TempDirDeletionStrategyStandard}` but suppresses deletion failures by logging a warning instead of failing the test. The following example uses `{TempDirDeletionStrategyIgnoreFailures}` so that any deletion failures are only logged. .A test class with a temporary directory that ignores deletion failures [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/TempDirectoryDemo.java[tags=user_guide_deletion_strategy] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/TempDirectoryDemo.kt[tags=user_guide_deletion_strategy] ---- -- ==== You can use the `junit.jupiter.tempdir.deletion.strategy.default` xref:running-tests/configuration-parameters.adoc[configuration parameter] to specify the fully qualified class name of the `{TempDirDeletionStrategy}` you would like to use by default. Just like for strategies configured via the `deletionStrategy` attribute of the `@TempDir` annotation, the supplied class has to implement the `{TempDirDeletionStrategy}` interface. The default strategy will be used for all `@TempDir` annotations unless the `deletionStrategy` attribute of the annotation specifies a different strategy. In summary, the deletion strategy for a temporary directory is determined according to the following precedence rules: 1. The `deletionStrategy` attribute of the `@TempDir` annotation, if present 2. The default `{TempDirDeletionStrategy}` configured via the configuration parameter, if present 3. Otherwise, `org.junit.jupiter.api.io.TempDirDeletionStrategy$Standard` will be used. [[AutoClose]] == The @AutoClose Extension The `{AutoCloseExtension}` automatically closes resources associated with fields. It is registered by default. To use it, annotate a field in a test class with `{AutoClose}`. `@AutoClose` fields may be either `static` or non-static. If the value of an `@AutoClose` field is `null` when it is evaluated the field will be ignored, but a warning message will be logged to inform you. By default, `@AutoClose` expects the value of the annotated field to implement a `close()` method that will be invoked to close the resource. However, developers can customize the name of the close method via the `value` attribute. For example, `@AutoClose("shutdown")` instructs JUnit to look for a `shutdown()` method to close the resource. `@AutoClose` fields are inherited from superclasses. Furthermore, `@AutoClose` fields from subclasses will be closed before `@AutoClose` fields in superclasses. When multiple `@AutoClose` fields exist within a given test class, the order in which the resources are closed depends on an algorithm that is deterministic but intentionally nonobvious. This ensures that subsequent runs of a test suite close resources in the same order, thereby allowing for repeatable builds. The `AutoCloseExtension` implements the `AfterAllCallback` and `TestInstancePreDestroyCallback` extension APIs. Consequently, a `static` `@AutoClose` field will be closed after all tests in the current test class have completed, effectively after `@AfterAll` methods have executed for the test class. A non-static `@AutoClose` field will be closed before the current test class instance is destroyed. Specifically, if the test class is configured with `@TestInstance(Lifecycle.PER_METHOD)` semantics, a non-static `@AutoClose` field will be closed after the execution of each test method, test factory method, or test template method. However, if the test class is configured with `@TestInstance(Lifecycle.PER_CLASS)` semantics, a non-static `@AutoClose` field will not be closed until the current test class instance is no longer needed, which means after `@AfterAll` methods and after all `static` `@AutoClose` fields have been closed. The following example demonstrates how to annotate an instance field with `@AutoClose` so that the resource is automatically closed after test execution. In this example, we assume that the default `@TestInstance(Lifecycle.PER_METHOD)` semantics apply. .A test class using `@AutoClose` to close a resource [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/AutoCloseDemo.java[tags=user_guide_example] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/AutoCloseDemo.kt[tags=user_guide_example] ---- -- ==== <1> Annotate an instance field with `@AutoClose`. <2> `WebClient` implements `java.lang.AutoCloseable` which defines a `close()` method that will be invoked after each `@Test` method. [[DefaultLocaleAndTimeZone]] == The @DefaultLocale and @DefaultTimeZone Extensions The `{DefaultLocale}` and `{DefaultTimeZone}` annotations can be used to change the values returned from `Locale.getDefault()` and `TimeZone.getDefault()`, respectively, which are often used implicitly when no specific locale or time zone is chosen. Both annotations work on the test class level and on the test method level, and are inherited from higher-level containers. After the annotated element has been executed, the initial default value is restored. [[DefaultLocale]] === @DefaultLocale The default `Locale` can be specified using an {jdk-javadoc-base-url}/java.base/java/util/Locale.html#forLanguageTag-java.lang.String-[IETF BCP 47 language tag string]. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/DefaultLocaleTimezoneExtensionDemo.java[tags=default_locale_language] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/DefaultLocaleTimezoneExtensionDemo.kt[tags=default_locale_language] ---- -- ==== Alternatively, the default `Locale` can be created using the following attributes from which a {jdk-javadoc-base-url}/java.base/java/util/Locale.Builder.html[`Locale.Builder`] can create an instance: * `language` * `language` and `country` * `language`, `country`, and `variant` NOTE: The variant needs to be a string which follows the https://www.rfc-editor.org/rfc/rfc5646.html[IETF BCP 47 / RFC 5646] syntax. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/DefaultLocaleTimezoneExtensionDemo.java[tag=default_locale_language_alternatives] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/DefaultLocaleTimezoneExtensionDemo.kt[tag=default_locale_language_alternatives] ---- -- ==== Mixing language tag configuration (via the annotation's `value` attribute) and attribute-based configuration will cause an exception to be thrown. Furthermore, a `variant` can only be specified if `country` is also specified. Otherwise, an exception will be thrown. Method-level `@DefaultLocale` configuration overrides class-level configuration. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/DefaultLocaleTimezoneExtensionDemo.java[tag=default_locale_class_level] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/DefaultLocaleTimezoneExtensionDemo.kt[tag=default_locale_class_level] ---- -- ==== NOTE: With class-level configuration, the specified locale is set before and reset after each individual test in the annotated class. If your use case is not covered, you can implement the `{LocaleProvider}` interface. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/DefaultLocaleTimezoneExtensionDemo.java[tag=default_locale_with_provider] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/DefaultLocaleTimezoneExtensionDemo.kt[tag=default_locale_with_provider] ---- -- ==== NOTE: The provider implementation must have a no-args (or default) constructor. [[DefaultTimeZone]] === @DefaultTimeZone The default `TimeZone` is specified according to the {jdk-javadoc-base-url}/java.base/java/util/TimeZone.html#getTimeZone(java.lang.String)[TimeZone.getTimeZone(String)] method. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/DefaultLocaleTimezoneExtensionDemo.java[tag=default_timezone_zone] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/DefaultLocaleTimezoneExtensionDemo.kt[tag=default_timezone_zone] ---- -- ==== Method-level `@DefaultTimeZone` configuration overrides class-level configuration. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/DefaultLocaleTimezoneExtensionDemo.java[tag=default_timezone_class_level] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/DefaultLocaleTimezoneExtensionDemo.kt[tag=default_timezone_class_level] ---- -- ==== NOTE: With class-level configuration, the specified time zone is set before and reset after each individual test in the annotated class. If your use case is not covered, you can implement the `{TimeZoneProvider}` interface. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/DefaultLocaleTimezoneExtensionDemo.java[tag=default_time_zone_with_provider] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/DefaultLocaleTimezoneExtensionDemo.kt[tag=default_time_zone_with_provider] ---- -- ==== NOTE: The provider implementation must have a no-args (or default) constructor. === Thread Safety Since the default locale and time zone are global state, reading and writing them during xref:writing-tests/parallel-execution.adoc[parallel test execution] can lead to unpredictable results and flaky tests. The `@DefaultLocale` and `@DefaultTimeZone` extensions are prepared for that and tests annotated with them will never execute in parallel (thanks to `{ResourceLock}`) to guarantee correct test results. However, this does not cover all possible cases. Tested code that reads or writes the default locale or time zone _independently_ of the extensions can still run in parallel and may thus behave erratically when, for example, such code unexpectedly reads a locale set by the extension in another thread. Consequently, tests that cover code that reads or writes the default locale or time zone need to be annotated with one of the following respective annotations. * `{ReadsDefaultLocale}` * `{ReadsDefaultTimeZone}` * `{WritesDefaultLocale}` * `{WritesDefaultTimeZone}` Tests annotated with one of the above annotations will never execute in parallel with tests annotated with `@DefaultLocale` or `@DefaultTimeZone`. [[system-properties]] == The System Properties Extension The system properties extension supports a set of annotations that work together to clear, set, and restore JVM system properties. [[system-properties-clear-and-set]] === @ClearSystemProperty and @SetSystemProperty The `{ClearSystemProperty}` and `{SetSystemProperty}` annotations can be used to clear and set, respectively, the values of JVM system properties for test execution. Both annotations work on the test method and class level and are repeatable, combinable, and inherited from higher-level containers. After the annotated method has been executed, the properties configured in the annotation will be restored to their original value or the value of the higher-level container, or will be cleared if they did not previously have a value. Other system properties that are changed during the test are _not_ restored (unless restoration is <> via `{RestoreSystemProperties}`). For example, clearing a system property for test execution can be done as follows. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/SystemPropertyExtensionDemo.java[tag=systemproperty_clear_simple] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/SystemPropertyExtensionDemo.kt[tag=systemproperty_clear_simple] ---- -- ==== The following demonstrates how to set a system property for test execution. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/SystemPropertyExtensionDemo.java[tag=systemproperty_set_simple] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/SystemPropertyExtensionDemo.kt[tag=systemproperty_set_simple] ---- -- ==== As mentioned before, both annotations are repeatable, and they can also be combined. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/SystemPropertyExtensionDemo.java[tag=systemproperty_using_set_and_clear] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/SystemPropertyExtensionDemo.kt[tag=systemproperty_using_set_and_clear] ---- -- ==== Note that class-level configuration is overridden by method-level configuration. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/SystemPropertyExtensionDemo.java[tag=systemproperty_using_at_class_level] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/SystemPropertyExtensionDemo.kt[tag=systemproperty_using_at_class_level] ---- -- ==== [NOTE] ==== Method-level configuration is visible in both `@BeforeEach` methods and `@AfterEach` methods (see xref:extensions/relative-execution-order-of-user-code-and-extensions.adoc#overview[user code and extension code execution order]). With class-level configuration, the specified system properties are cleared or set before and reset after each individual test in the annotated class. ==== [[system-properties-restore]] === @RestoreSystemProperties The `{RestoreSystemProperties}` annotation can be used to restore changes to system properties made directly in the test or in the code being tested. Although `@ClearSystemProperty` and `@SetSystemProperty` clear or set properties and values that are statically declared, they do not allow property values to be calculated dynamically. Thus, there are times you may want to directly set properties in your test code. `@RestoreSystemProperties` can be placed on test methods or test classes and will completely restore all system properties to their original state after the test or test class has finished. [NOTE] ==== During the execution of the annotated scope, the JVM system properties are set to a clone of the original `Properties` object. However, the clone does not include {jdk-javadoc-base-url}/java.base/java/util/Properties.html#defaults[the defaults] from the original. Consequently, the extension will perform a best effort attempt to detect default properties and fail if any were detected. For classes that extend `Properties` it is assumed that `clone()` is implemented with sufficient fidelity. ==== In the following example, `@RestoreSystemProperties` is used on a test method, ensuring any changes made in that method are restored. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/SystemPropertyExtensionDemo.java[tag=systemproperty_restore_test] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/SystemPropertyExtensionDemo.kt[tag=systemproperty_restore_test] ---- -- ==== When `@RestoreSystemProperties` is used on a test class, any changes to system properties during the entire lifecycle of the test class, including test methods, `@BeforeAll`, `@BeforeEach`, and 'after' methods, are restored after the lifecycle of the test class is complete. In addition, the annotation is inherited by each test method just as if each one were annotated with `@RestoreSystemProperties`. In the following example, both test methods see the system property changes made in `@BeforeAll` and `@BeforeEach`; however, the test methods are isolated from each other (`isolatedTest2` does not _see_ changes made in `isolatedTest1`). As shown in the second example below, the class-level `@RestoreSystemProperties` annotation ensures that system property changes made within the annotated class are completely restored after the class's lifecycle, ensuring that changes are not visible to `SomeOtherTestClass`. Note that `SomeOtherTestClass` uses the `@ReadsSystemProperty` annotation, which ensures that JUnit does not schedule the class to run during any test known to modify system properties (see <>). [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/SystemPropertyExtensionDemo.java[tag=systemproperty_class_restore_setup] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/SystemPropertyExtensionDemo.kt[tag=systemproperty_class_restore_setup] ---- -- ==== Some other test class, running later: [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/SystemPropertyExtensionDemo.java[tag=systemproperty_class_restore_isolated_class] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/SystemPropertyExtensionDemo.kt[tag=systemproperty_class_restore_isolated_class] ---- -- ==== [[system-properties-combined-usage]] === Combining @ClearSystemProperty, @SetSystemProperty, and @RestoreSystemProperties The three system property annotations can be combined, which can be useful when some system properties are set dynamically in code and others are not. For instance, imagine you need to test an image generation utility that takes configuration from system properties. Basic configuration can be specified declaratively using the `Clear` and `Set` annotations, and the image size could be set programmatically. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/SystemPropertyExtensionDemo.java[tag=systemproperty_method_combine_all_test] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/SystemPropertyExtensionDemo.kt[tag=systemproperty_method_combine_all_test] ---- -- ==== [NOTE] ==== Using `@RestoreSystemProperties` is not necessary to restore system properties modified via `@ClearSystemProperty` or `@SetSystemProperty` since they both automatically restore the referenced properties. `@RestoreSystemProperties` is only needed if system properties are modified during a test in some way _other than_ via the `Clear` and `Set` annotations. ==== [[system-properties-thread-safety]] === Thread Safety Since system properties are global state, reading and writing them during xref:writing-tests/parallel-execution.adoc[parallel execution] can lead to unpredictable results and flaky tests. The system property extension is prepared for that and tests annotated with `@ClearSystemProperty`, `@SetSystemProperty`, or `@RestoreSystemProperties` will never execute in parallel (thanks to xref:writing-tests/parallel-execution.adoc#synchronization[resource locks]) to guarantee correct test results. However, this does not cover all possible cases. Tested code that reads or writes system properties _independently_ of the extension can still run in parallel to it and may thus behave erratically when, for example, it unexpectedly reads a property set by the extension in another thread. Tests that cover code that reads or writes system properties need to be annotated with the respective annotation: * `{ReadsSystemProperty}` * `{WritesSystemProperty}` (though consider using `@RestoreSystemProperties` instead) Tests annotated in this way will never execute in parallel with tests annotated with `@ClearSystemProperty`, `@SetSystemProperty`, or `@RestoreSystemProperties`. ================================================ FILE: documentation/modules/ROOT/pages/writing-tests/class-templates.adoc ================================================ = Class Templates A `{ClassTemplate}` is not a regular test class but rather a template for the contained test cases. As such, it is designed to be invoked multiple times depending on invocation contexts returned by the registered providers. Thus, it must be used in conjunction with a registered `{ClassTemplateInvocationContextProvider}` extension. Each invocation of a class template behaves like the execution of a regular test class with full support for the same lifecycle callbacks and extensions. Please refer to xref:extensions/providing-invocation-contexts-for-class-templates.adoc[] for usage examples. NOTE: xref:writing-tests/parameterized-classes-and-tests.adoc[Parameterized Classes] are a built-in specialization of class templates. ================================================ FILE: documentation/modules/ROOT/pages/writing-tests/conditional-test-execution.adoc ================================================ = Conditional Test Execution The xref:extensions/conditional-test-execution.adoc[`ExecutionCondition`] extension API in JUnit Jupiter allows developers to either _enable_ or _disable_ a test class or test method based on certain conditions _programmatically_. The simplest example of such a condition is the built-in `{DisabledCondition}` which supports the `{Disabled}` annotation (see xref:writing-tests/disabling-tests.adoc[]). In addition to `@Disabled`, JUnit Jupiter also supports several other annotation-based conditions in the `org.junit.jupiter.api.condition` package that allow developers to enable or disable test classes and test methods _declaratively_. If you wish to provide details about why they might be disabled, every annotation associated with these built-in conditions has a `disabledReason` attribute available for that purpose. When multiple `ExecutionCondition` extensions are registered, a test class or test method is disabled as soon as one of the conditions returns _disabled_. If a test class is disabled, all test methods within that class are automatically disabled as well. If a test method is disabled, that prevents execution of the test method and method-level lifecycle callbacks such as `@BeforeEach` methods, `@AfterEach` methods, and corresponding extension APIs. However, that does not prevent the test class from being instantiated, and it does not prevent the execution of class-level lifecycle callbacks such as `@BeforeAll` methods, `@AfterAll` methods, and corresponding extension APIs. See xref:extensions/conditional-test-execution.adoc[`ExecutionCondition`] and the following sections for details. [TIP] .Composed Annotations ==== Note that any of the _conditional_ annotations listed in the following sections may also be used as a meta-annotation in order to create a custom _composed annotation_. For example, the `@TestOnMac` annotation in the <> shows how you can combine `@Test` and `@EnabledOnOs` in a single, reusable annotation. ==== [NOTE] ==== _Conditional_ annotations in JUnit Jupiter are not `@Inherited`. Consequently, if you wish to apply the same semantics to subclasses, each conditional annotation must be redeclared on each subclass. ==== [WARNING] ==== Unless otherwise stated, each of the _conditional_ annotations listed in the following sections can only be declared once on a given test interface, test class, or test method. If a conditional annotation is directly present, indirectly present, or meta-present multiple times on a given element, only the first such annotation discovered by JUnit will be used; any additional declarations will be silently ignored. Note, however, that each conditional annotation may be used in conjunction with other conditional annotations in the `org.junit.jupiter.api.condition` package. ==== [[os]] == Operating System and Architecture Conditions A container or test may be enabled or disabled on a particular operating system, architecture, or combination of both via the `{EnabledOnOs}` and `{DisabledOnOs}` annotations. [[os-demo]] .Conditional execution based on operating system [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/ConditionalTestExecutionDemo.java[tags=user_guide_os] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/ConditionalTestExecutionDemo.kt[tags=user_guide_os] ---- -- ==== [[architectures-demo]] .Conditional execution based on architecture [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/ConditionalTestExecutionDemo.java[tags=user_guide_architecture] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/ConditionalTestExecutionDemo.kt[tags=user_guide_architecture] ---- -- ==== [[jre]] == Java Runtime Environment Conditions A container or test may be enabled or disabled on particular versions of the Java Runtime Environment (JRE) via the `{EnabledOnJre}` and `{DisabledOnJre}` annotations or on a particular range of versions of the JRE via the `{EnabledForJreRange}` and `{DisabledForJreRange}` annotations. The range effectively defaults to `JRE.JAVA_8` as the lower bound and `JRE.OTHER` as the upper bound, which allows usage of half open ranges. The following listing demonstrates the use of these annotations with predefined {JRE} enum constants. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/ConditionalTestExecutionDemo.java[tags=user_guide_jre] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/ConditionalTestExecutionDemo.kt[tags=user_guide_jre] ---- -- ==== Since the enum constants defined in {JRE} are static for any given JUnit release, you might find that you need to configure a Java version that is not supported by the `JRE` enum. For example, when JUnit Jupiter 5.12 was released the `JRE` enum defined `JAVA_25` as the highest supported Java version. However, you may wish to run your tests against later versions of Java. To support such use cases, you can specify arbitrary Java versions via the `versions` attributes in `@EnabledOnJre` and `@DisabledOnJre` and via the `minVersion` and `maxVersion` attributes in `@EnabledForJreRange` and `@DisabledForJreRange`. The following listing demonstrates the use of these annotations with arbitrary Java versions. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/ConditionalTestExecutionDemo.java[tags=user_guide_jre_arbitrary_versions] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/ConditionalTestExecutionDemo.kt[tags=user_guide_jre_arbitrary_versions] ---- -- ==== [[native]] == Native Image Conditions A container or test may be enabled or disabled within a https://www.graalvm.org/reference-manual/native-image/[GraalVM native image] via the `{EnabledInNativeImage}` and `{DisabledInNativeImage}` annotations. These annotations are typically used when running tests within a native image using the Gradle and Maven plug-ins from the GraalVM https://graalvm.github.io/native-build-tools/latest/[Native Build Tools] project. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/ConditionalTestExecutionDemo.java[tags=user_guide_native] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/ConditionalTestExecutionDemo.kt[tags=user_guide_native] ---- -- ==== [[system-properties]] == System Property Conditions A container or test may be enabled or disabled based on the value of the `named` JVM system property via the `{EnabledIfSystemProperty}` and `{DisabledIfSystemProperty}` annotations. The value supplied via the `matches` attribute will be interpreted as a regular expression. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/ConditionalTestExecutionDemo.java[tags=user_guide_system_property] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/ConditionalTestExecutionDemo.kt[tags=user_guide_system_property] ---- -- ==== [TIP] ==== `{EnabledIfSystemProperty}` and `{DisabledIfSystemProperty}` are _repeatable annotations_. Consequently, these annotations may be declared multiple times on a test interface, test class, or test method. Specifically, these annotations will be found if they are directly present, indirectly present, or meta-present on a given element. ==== [[environment-variables]] == Environment Variable Conditions A container or test may be enabled or disabled based on the value of the `named` environment variable from the underlying operating system via the `{EnabledIfEnvironmentVariable}` and `{DisabledIfEnvironmentVariable}` annotations. The value supplied via the `matches` attribute will be interpreted as a regular expression. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/ConditionalTestExecutionDemo.java[tags=user_guide_environment_variable] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/ConditionalTestExecutionDemo.kt[tags=user_guide_environment_variable] ---- -- ==== [TIP] ==== `{EnabledIfEnvironmentVariable}` and `{DisabledIfEnvironmentVariable}` are _repeatable annotations_. Consequently, these annotations may be declared multiple times on a test interface, test class, or test method. Specifically, these annotations will be found if they are directly present, indirectly present, or meta-present on a given element. ==== [[custom]] == Custom Conditions As an alternative to implementing an xref:extensions/conditional-test-execution.adoc[`ExecutionCondition`], a container or test may be enabled or disabled based on a _condition method_ configured via the `{EnabledIf}` and `{DisabledIf}` annotations. A condition method must have a `boolean` return type and may accept either no arguments or a single `ExtensionContext` argument. The following test class demonstrates how to configure a local method named `customCondition` via `@EnabledIf` and `@DisabledIf`. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/ConditionalTestExecutionDemo.java[tags=user_guide_custom] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/ConditionalTestExecutionDemo.kt[tags=user_guide_custom] ---- -- ==== Alternatively, the condition method can be located outside the test class. In this case, it must be referenced by its _fully qualified name_ as demonstrated in the following example. [tabs] ==== Java:: + -- [source,java,indent=0] ---- package example; include::example$java/example/ExternalCustomConditionDemo.java[tags=user_guide_external_custom_condition] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- package example.kotlin include::example$kotlin/example/kotlin/ExternalCustomConditionDemo.kt[tags=user_guide_external_custom_condition] ---- -- ==== [NOTE] ==== There are several cases where a condition method would need to be `static`: - when `@EnabledIf` or `@DisabledIf` is used at class level - when `@EnabledIf` or `@DisabledIf` is used on a `@ParameterizedTest` or a `@TestTemplate` method - when the condition method is located in an external class In any other case, you can use either static methods or instance methods as condition methods. ==== [TIP] ==== It is often the case that you can use an existing static method in a utility class as a custom condition. For example, `java.awt.GraphicsEnvironment` provides a `public static boolean isHeadless()` method that can be used to determine if the current environment does not support a graphical display. Thus, if you have a test that depends on graphical support you can disable it when such support is unavailable as follows. [tabs] ====== Java:: + -- [source,java,indent=0] ---- @DisabledIf(value = "java.awt.GraphicsEnvironment#isHeadless", disabledReason = "headless environment") ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- @DisabledIf(value = "java.awt.GraphicsEnvironment#isHeadless", disabledReason = "headless environment") ---- -- ====== ==== ================================================ FILE: documentation/modules/ROOT/pages/writing-tests/definitions.adoc ================================================ = Definitions == Platform Concepts Container:: a node in the test tree that contains other containers or tests as its children (e.g. a _test class_). Test:: a node in the test tree that verifies expected behavior when executed (e.g. a `@Test` method). == Jupiter Concepts Lifecycle Method:: any method that is directly annotated or meta-annotated with `@BeforeAll`, `@AfterAll`, `@BeforeEach`, or `@AfterEach`. Test Class:: any top-level class, `static` member class, or xref:writing-tests/nested-tests.adoc[`@Nested` class] that contains at least one _test method_, i.e. a _container_. Test classes must not be `abstract` and must have a single constructor. Java `record` classes are supported as well. Test Method:: any instance method that is directly annotated or meta-annotated with `@Test`, `@RepeatedTest`, `@ParameterizedTest`, `@TestFactory`, or `@TestTemplate`. With the exception of `@Test`, these create a _container_ in the test tree that groups _tests_ or, potentially (for `@TestFactory`), other _containers_. ================================================ FILE: documentation/modules/ROOT/pages/writing-tests/dependency-injection-for-constructors-and-methods.adoc ================================================ = Dependency Injection for Constructors and Methods In JUnit Jupiter, both test constructors and methods are permitted to have parameters. This allows for greater flexibility and enables _Dependency Injection_ for constructors and methods. `{ParameterResolver}` defines the API for test extensions that wish to _dynamically_ resolve parameters at runtime. If a _test class_ constructor, a _test method_, or a _lifecycle method_ (see xref:writing-tests/definitions.adoc[]) accepts a parameter, the parameter must be resolved at runtime by a registered `ParameterResolver`. [[built-in-parameter-resolvers]] == Built-In Parameter Resolvers There are currently three built-in resolvers that are registered automatically. [[test-info]] === TestInfo If a constructor or method parameter is of type `{TestInfo}`, the `{TestInfoParameterResolver}` will supply an instance of `TestInfo` corresponding to the current container or test as the value for the parameter. The `TestInfo` can then be used to retrieve information about the current container or test such as the display name, the test class, the test method, and associated tags. The display name is either a technical name, such as the name of the test class or test method, or a custom name configured via `@DisplayName`. `{TestInfo}` acts as a drop-in replacement for the `TestName` rule from JUnit 4. The following demonstrates how to have `TestInfo` injected into a `@BeforeAll` method, test class constructor, `@BeforeEach` method, and `@Test` method. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/TestInfoDemo.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/TestInfoDemo.kt[tags=user_guide] ---- -- ==== [[repetition-info]] === RepetitionInfo If a method parameter in a `@RepeatedTest`, `@BeforeEach`, or `@AfterEach` method is of type `{RepetitionInfo}`, the `{RepetitionExtension}` will supply an instance of `RepetitionInfo`. `RepetitionInfo` can then be used to retrieve information about the current repetition, the total number of repetitions, the number of repetitions that have failed, and the failure threshold for the corresponding `@RepeatedTest`. Note, however, that `RepetitionExtension` is not registered outside the context of a `@RepeatedTest`. See xref:writing-tests/repeated-tests.adoc#examples[Repeated Test Examples]. [[test-reporter]] === TestReporter If a constructor or method parameter is of type `{TestReporter}`, the `{TestReporterParameterResolver}` will supply an instance of `TestReporter`. The `TestReporter` can be used to publish additional data about the current test run or attach files to it. The data can be consumed in a `{TestExecutionListener}` via the `reportingEntryPublished()` or `fileEntryPublished()` method, respectively. This allows them to be viewed in IDEs or included in reports. In JUnit Jupiter you should use `TestReporter` where you used to print information to `stdout` or `stderr` in JUnit 4. Some IDEs print report entries to `stdout` or display them in the user interface for test results. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/TestReporterDemo.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/TestReporterDemo.kt[tags=user_guide] ---- -- ==== [[custom-parameter-resolvers]] == Custom Parameter Resolvers NOTE: Custom parameter resolvers must be explicitly enabled by xref:extensions/registering-extensions.adoc[registering] appropriate xref:extensions/overview.adoc[extensions]. Check out the `xref:extensions/registering-extensions.adoc#RandomNumberExtension[RandomNumberExtension]` for an example of a custom `{ParameterResolver}`. While not intended to be production-ready, it demonstrates the simplicity and expressiveness of both the extension model and the parameter resolution process. `MyRandomParametersTest` demonstrates how to inject random values into a constructor and a `@Test` method. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/MyRandomParametersTest.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/MyRandomParametersTest.kt[tags=user_guide] ---- -- ==== For real-world use cases, check out the source code for the `{MockitoExtension}` and the `{SpringExtension}`. When the type of the parameter to inject is the only condition for your `{ParameterResolver}`, you can use the generic `{TypeBasedParameterResolver}` base class. The `supportsParameters` method is implemented behind the scenes and supports parameterized types. ================================================ FILE: documentation/modules/ROOT/pages/writing-tests/disabling-tests.adoc ================================================ = Disabling Tests Entire test classes or individual test methods may be _disabled_ via the `{Disabled}` annotation, via one of the annotations discussed in xref:writing-tests/conditional-test-execution.adoc[], or via a custom xref:extensions/conditional-test-execution.adoc[`ExecutionCondition`]. When `@Disabled` is applied at the class level, all test methods within that class are automatically disabled as well. If a test method is disabled via `@Disabled`, that prevents execution of the test method and method-level lifecycle callbacks such as `@BeforeEach` methods, `@AfterEach` methods, and corresponding extension APIs. However, that does not prevent the test class from being instantiated, and it does not prevent the execution of class-level lifecycle callbacks such as `@BeforeAll` methods, `@AfterAll` methods, and corresponding extension APIs. Here's a `@Disabled` test class. [source,java,indent=0] ---- include::example$java/example/DisabledClassDemo.java[tags=user_guide] ---- And here's a test class that contains a `@Disabled` test method. [source,java,indent=0] ---- include::example$java/example/DisabledTestsDemo.java[tags=user_guide] ---- [TIP] ==== `@Disabled` may be declared without providing a _reason_; however, the JUnit team recommends that developers provide a short explanation for why a test class or test method has been disabled. Consequently, the above examples both show the use of a reason -- for example, `@Disabled("Disabled until bug #42 has been resolved")`. Some development teams even require the presence of issue tracking numbers in the _reason_ for automated traceability, etc. ==== [NOTE] ==== `@Disabled` is not `@Inherited`. Consequently, if you wish to disable a class whose superclass is `@Disabled`, you must redeclare `@Disabled` on the subclass. ==== ================================================ FILE: documentation/modules/ROOT/pages/writing-tests/display-names.adoc ================================================ = Display Names Test classes and test methods can declare custom display names via `@DisplayName` -- with spaces, special characters, and even emojis -- that will be displayed in test reports and by test runners and IDEs. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/DisplayNameDemo.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/DisplayNameDemo.kt[tags=user_guide] ---- -- ==== [TIP] ==== In Kotlin, you can use backtick-enclosed function names as an alternative to `@DisplayName` for simple display names: [source,kotlin,indent=0] ---- @Test fun `custom test name containing spaces`() { // will be displayed as "custom test name containing spaces" } ---- ==== [NOTE] ==== Control characters in text-based arguments in display names for parameterized tests are escaped by default. See xref:writing-tests/parameterized-classes-and-tests.adoc#display-names-quoted-text[Quoted Text-based Arguments] for details. Any remaining ISO control characters in a display name will be replaced as follows. [cols="25%,15%,60%"] |=== | Original | Replacement | Description | ```\r``` | `````` | Textual representation of a carriage return | ```\n``` | `````` | Textual representation of a line feed | Other control character | ```�``` | Unicode replacement character (U+FFFD) |=== ==== [[generator]] == Display Name Generators JUnit Jupiter supports custom display name generators that can be configured via the `@DisplayNameGeneration` annotation. Generators can be created by implementing the `DisplayNameGenerator` API. The following table lists the default display name generators available in Jupiter. [cols="20,80"] |=== | DisplayNameGenerator | Behavior | `Standard` | Matches the standard display name generation behavior in place since JUnit Jupiter was introduced. | `Simple` | Extends the functionality of `Standard` by removing trailing parentheses for methods with no parameters. | `ReplaceUnderscores` | Replaces underscores with spaces. | `IndicativeSentences` | Generates complete sentences by concatenating the names of the test and the enclosing classes. |=== NOTE: Values provided via `@DisplayName` annotations always take precedence over display names generated by a `DisplayNameGenerator`. ====== The following example demonstrates the use of the `ReplaceUnderscores` display name generator. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/DisplayNameGeneratorDemo.java[tags=user_guide_replace_underscores] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/DisplayNameGeneratorDemo.kt[tags=user_guide_replace_underscores] ---- -- ==== Running the above test class results in the following display names. ``` A year is not supported ✔ ├─ if it is zero ✔ └─ A negative value for year is not supported by the leap year computation. ✔ ├─ For example, year -1 is not supported. ✔ └─ For example, year -4 is not supported. ✔ ``` ====== ====== With the `IndicativeSentences` display name generator, you can customize the separator and the underlying generator by using `@IndicativeSentencesGeneration` as shown in the following example. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/DisplayNameGeneratorDemo.java[tags=user_guide_indicative_sentences] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/DisplayNameGeneratorDemo.kt[tags=user_guide_indicative_sentences] ---- -- ==== Running the above test class results in the following display names. ``` A year is a leap year ✔ ├─ A year is a leap year -> if it is divisible by 4 but not by 100 ✔ └─ A year is a leap year -> if it is one of the following years ✔ ├─ Year 2016 is a leap year. ✔ ├─ Year 2020 is a leap year. ✔ └─ Year 2048 is a leap year. ✔ ``` ====== ====== With `IndicativeSentences`, you can optionally specify custom sentence fragments via the `@SentenceFragment` annotation as demonstrated in the following example. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/DisplayNameGeneratorDemo.java[tags=user_guide_custom_sentence_fragments] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/DisplayNameGeneratorDemo.kt[tags=user_guide_custom_sentence_fragments] ---- -- ==== Running the above test class results in the following display names. ``` A year is a leap year ✔ ├─ A year is a leap year, if it is divisible by 4 but not by 100 ✔ └─ A year is a leap year, if it is one of the following years ✔ ├─ 2016 ✔ ├─ 2020 ✔ └─ 2048 ✔ ``` ====== [[generator-default]] == Setting the Default Display Name Generator You can use the `junit.jupiter.displayname.generator.default` xref:running-tests/configuration-parameters.adoc[configuration parameter] to specify the fully qualified class name of the `DisplayNameGenerator` you would like to use by default. Just like for display name generators configured via the `@DisplayNameGeneration` annotation, the supplied class has to implement the `DisplayNameGenerator` interface. The default display name generator will be used for all tests unless the `@DisplayNameGeneration` annotation is present on an enclosing test class or test interface. Values provided via `@DisplayName` annotations always take precedence over display names generated by a `DisplayNameGenerator`. For example, to use the `ReplaceUnderscores` display name generator by default, you should set the configuration parameter to the corresponding fully qualified class name (e.g., in `src/test/resources/junit-platform.properties`): [source,properties,indent=0] ---- junit.jupiter.displayname.generator.default = \ org.junit.jupiter.api.DisplayNameGenerator$ReplaceUnderscores ---- Similarly, you can specify the fully qualified name of any custom class that implements `DisplayNameGenerator`. [[generator-precedence-rules]] In summary, the display name for a test class or method is determined according to the following precedence rules: 1. value of the `@DisplayName` annotation, if present 2. by calling the `DisplayNameGenerator` specified in the `@DisplayNameGeneration` annotation, if present 3. by calling the default `DisplayNameGenerator` configured via the configuration parameter, if present 4. by calling `org.junit.jupiter.api.DisplayNameGenerator.Standard` ================================================ FILE: documentation/modules/ROOT/pages/writing-tests/dynamic-tests.adoc ================================================ = Dynamic Tests The standard `@Test` annotation in JUnit Jupiter described in xref:writing-tests/annotations.adoc[] is very similar to the `@Test` annotation in JUnit 4. Both describe methods that implement test cases. These test cases are static in the sense that they are fully specified at compile time, and their behavior cannot be changed by anything happening at runtime. _Assumptions provide a basic form of dynamic behavior but are intentionally rather limited in their expressiveness._ In addition to these standard tests a completely new kind of test programming model has been introduced in JUnit Jupiter. This new kind of test is a _dynamic test_ which is generated at runtime by a factory method that is annotated with `@TestFactory`. In contrast to `@Test` methods, a `@TestFactory` method is not itself a test case but rather a factory for test cases. Thus, a dynamic test is the product of a factory. Technically speaking, a `@TestFactory` method must return a single `DynamicNode` or a _stream_ of `DynamicNode` instances or any of its subclasses. In this context, a "stream" is anything that JUnit can reliably convert into a `Stream`, such as `Stream`, `Collection`, `Iterator`, `Iterable`, an array of objects, or any type that provides an `iterator(): Iterator` method (such as, for example, a `kotlin.sequences.Sequence`). Instantiable subclasses of `DynamicNode` are `DynamicContainer` and `DynamicTest`. `DynamicContainer` instances are composed of a _display name_ and a list of dynamic child nodes, enabling the creation of arbitrarily nested hierarchies of dynamic nodes. `DynamicTest` instances will be executed lazily, enabling dynamic and even non-deterministic generation of test cases. Any `Stream` returned by a `@TestFactory` will be properly closed by calling `stream.close()`, making it safe to use a resource such as `Files.lines()`. As with `@Test` methods, `@TestFactory` methods must not be `private` or `static` and may optionally declare parameters to be resolved by `ParameterResolvers`. A `DynamicTest` is a test case generated at runtime. It is composed of a _display name_ and an `Executable`. `Executable` is a `@FunctionalInterface` which means that the implementations of dynamic tests can be provided as _lambda expressions_ or _method references_. .Dynamic Test Lifecycle WARNING: The execution lifecycle of a dynamic test is quite different than it is for a standard `@Test` case. Specifically, there are no lifecycle callbacks for individual dynamic tests. This means that `@BeforeEach` and `@AfterEach` methods and their corresponding extension callbacks are executed for the `@TestFactory` method but not for each _dynamic test_. In other words, if you access fields from the test instance within a lambda expression for a dynamic test, those fields will not be reset by callback methods or extensions between the execution of individual dynamic tests generated by the same `@TestFactory` method. [[examples]] == Dynamic Test Examples The following `DynamicTestsDemo` class demonstrates several examples of test factories and dynamic tests. The first method returns an invalid return type and will cause a warning to be reported by JUnit during test discovery. Such methods are not executed. The next six methods demonstrate the generation of a `Collection`, `Iterable`, `Iterator`, array, or `Stream` of `DynamicTest` instances. Most of these examples do not really exhibit dynamic behavior but merely demonstrate the supported return types in principle. However, `dynamicTestsFromStream()` and `dynamicTestsFromIntStream()` demonstrate how to generate dynamic tests for a given set of strings or a range of input numbers. The next method is truly dynamic in nature. `generateRandomNumberOfTests()` implements an `Iterator` that generates random numbers, a display name generator, and a test executor and then provides all three to `DynamicTest.stream()`. Although the non-deterministic behavior of `generateRandomNumberOfTests()` is of course in conflict with test repeatability and should thus be used with care, it serves to demonstrate the expressiveness and power of dynamic tests. The next method is similar to `generateRandomNumberOfTests()` in terms of flexibility; however, `dynamicTestsFromStreamFactoryMethod()` generates a stream of dynamic tests from an existing `Stream` via the `DynamicTest.stream()` factory method. For demonstration purposes, the `dynamicNodeSingleTest()` method generates a single `DynamicTest` instead of a stream, and the `dynamicNodeSingleContainer()` method generates a nested hierarchy of dynamic tests utilizing `DynamicContainer`. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/DynamicTestsDemo.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/DynamicTestsDemo.kt[tags=user_guide] ---- -- ==== [[named-support]] == Dynamic Tests and Named In some cases, it can be more natural to specify inputs together with a descriptive name using the {Named} API and the corresponding `stream()` factory methods on `DynamicTest` as shown in the first example below. The second example takes it one step further and allows to provide the code block that should be executed by implementing the `Executable` interface along with `Named` via the `NamedExecutable` base class. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/DynamicTestsNamedDemo.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/DynamicTestsNamedDemo.kt[tags=user_guide] ---- -- ==== [[uri-test-source]] == URI Test Sources for Dynamic Tests The JUnit Platform provides `TestSource`, a representation of the source of a test or container used to navigate to its location by IDEs and build tools. The `TestSource` for a dynamic test or dynamic container can be constructed from a `java.net.URI` which can be supplied via the `DynamicTest.dynamicTest(String, URI, Executable)` or `DynamicContainer.dynamicContainer(String, URI, Stream)` factory method, respectively. The `URI` will be converted to one of the following `TestSource` implementations. `ClasspathResourceSource` :: If the `URI` contains the `classpath` scheme -- for example, `classpath:/test/foo.xml?line=20,column=2`. `DirectorySource` :: If the `URI` represents a directory present in the file system. `FileSource` :: If the `URI` represents a file present in the file system. `MethodSource` :: If the `URI` contains the `method` scheme and the fully qualified method name (FQMN) -- for example, `method:org.junit.Foo#bar(java.lang.String, java.lang.String[])`. Please refer to the Javadoc for `{DiscoverySelectors}.{DiscoverySelectors_selectMethod}` for the supported formats for a FQMN. `ClassSource` :: If the `URI` contains the `class` scheme and the fully qualified class name -- for example, `class:org.junit.Foo?line=42`. `UriSource` :: If none of the above `TestSource` implementations are applicable. [[parallel-execution]] == Parallel Execution Dynamic tests and containers support xref:writing-tests/parallel-execution.adoc[parallel execution]. You can configure their `ExecutionMode` by using the `dynamicTest(Consumer)` and `dynamicContainer(Consumer)` factory methods as illustrated by the following example. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/DynamicTestsDemo.java[tags=execution_mode] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/DynamicTestsDemo.kt[tags=execution_mode] ---- -- ==== Executing the above test factory method results in the following test tree and execution modes: * dynamicTestsWithConfiguredExecutionMode() -- `CONCURRENT` (from `@Execution` annotation) ** Container A -- `CONCURRENT` (from `@Execution` annotation) *** not null -- `SAME_THREAD` (from `executionMode(...)` call) *** properties -- `CONCURRENT` (from `@Execution` annotation) **** length > 0 -- `CONCURRENT` (from `executionMode(...)` call) **** not empty -- `SAME_THREAD` (from `childExecutionMode(...)` call) ** ... (same for "Container B" and "Container C") ================================================ FILE: documentation/modules/ROOT/pages/writing-tests/exception-handling.adoc ================================================ = Exception Handling JUnit Jupiter provides robust support for handling test exceptions. This includes the built-in mechanisms for managing test failures due to exceptions, the role of exceptions in implementing assertions and assumptions, and how to specifically assert non-throwing conditions in code. [[uncaught]] == Uncaught Exceptions In JUnit Jupiter, if an exception is thrown from a test method, a lifecycle method, or an extension and not caught within that test method, lifecycle method, or extension, the framework will mark the test or test class as failed. [TIP] ==== Failed assumptions deviate from this general rule. In contrast to failed assertions, failed assumptions do not result in a test failure; rather, a failed assumption results in a test being aborted. See xref:writing-tests/assumptions.adoc[] for further details and examples. ==== In the following example, the `failsDueToUncaughtException()` method throws an `ArithmeticException`. Since the exception is not caught within the test method, JUnit Jupiter will mark the test as failed. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/exception/UncaughtExceptionHandlingDemo.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/exception/UncaughtExceptionHandlingDemo.kt[tags=user_guide] ---- -- ==== NOTE: It's important to note that specifying a `throws` clause in the test method has no effect on the outcome of the test. JUnit Jupiter does not interpret a `throws` clause as an expectation or assertion about what exceptions the test method should throw. A test fails only if an exception is thrown unexpectedly or if an assertion fails. [[failed-assertions]] == Failed Assertions Assertions in JUnit Jupiter are implemented using exceptions. The framework provides a set of assertion methods in the `org.junit.jupiter.api.Assertions` class, which throw `AssertionError` when an assertion fails. This mechanism is a core aspect of how JUnit handles assertion failures as exceptions. See the xref:writing-tests/assertions.adoc[] section for further information about JUnit Jupiter's assertion support. NOTE: Third-party assertion libraries may choose to throw an `AssertionError` to signal a failed assertion; however, they may also choose to throw different types of exceptions to signal failures. See also: xref:writing-tests/assertions.adoc#third-party[Third-party Assertion Libraries]. TIP: JUnit Jupiter itself does not differentiate between failed assertions (`AssertionError`) and other types of exceptions. All uncaught exceptions lead to a test failure. However, Integrated Development Environments (IDEs) and other tools may distinguish between these two types of failures by checking whether the thrown exception is an instance of `AssertionError`. In the following example, the `failsDueToUncaughtAssertionError()` method throws an `AssertionError`. Since the exception is not caught within the test method, JUnit Jupiter will mark the test as failed. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/exception/FailedAssertionDemo.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/exception/FailedAssertionDemo.kt[tags=user_guide] ---- -- ==== [[expected]] == Asserting Expected Exceptions JUnit Jupiter offers specialized assertions for testing that specific exceptions are thrown under expected conditions. The `assertThrows()` and `assertThrowsExactly()` assertions are critical tools for validating that your code responds correctly to error conditions by throwing the appropriate exceptions. [[expected-assertThrows]] === Using `assertThrows()` The `assertThrows()` method is used to verify that a particular type of exception is thrown during the execution of a provided executable block. It not only checks for the type of the thrown exception but also its subclasses, making it suitable for more generalized exception handling tests. The `assertThrows()` assertion method returns the thrown exception object to allow performing additional assertions on it. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/exception/ExceptionAssertionDemo.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/exception/ExceptionAssertionDemo.kt[tags=user_guide] ---- -- ==== [[expected-assertThrowsExactly]] === Using `assertThrowsExactly()` The `assertThrowsExactly()` method is used when you need to assert that the exception thrown is exactly of a specific type, not allowing for subclasses of the expected exception type. This is useful when precise exception handling behavior needs to be validated. Similar to `assertThrows()`, the `assertThrowsExactly()` assertion method also returns the thrown exception object to allow performing additional assertions on it. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/exception/ExceptionAssertionExactDemo.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/exception/ExceptionAssertionExactDemo.kt[tags=user_guide] ---- -- ==== [[not-expected]] == Asserting That no Exception is Expected Although any exception thrown from a test method will cause the test to fail, there are certain use cases where it can be beneficial to explicitly assert that an exception is _not_ thrown for a given code block within a test method. The `assertDoesNotThrow()` assertion can be used when you want to verify that a particular piece of code does not throw any exceptions. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/exception/AssertDoesNotThrowExceptionDemo.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/exception/AssertDoesNotThrowExceptionDemo.kt[tags=user_guide] ---- -- ==== NOTE: Third-party assertion libraries often provide similar support. For example, AssertJ has `assertThatNoException().isThrownBy(() -> ...)`. See also: xref:writing-tests/assertions.adoc#third-party[Third-party Assertion Libraries]. ================================================ FILE: documentation/modules/ROOT/pages/writing-tests/intro.adoc ================================================ = Writing Tests The following example provides a glimpse at the minimum requirements for writing a test in JUnit Jupiter. Subsequent sections of this chapter will provide further details on all available features. .A first test case [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/MyFirstJUnitJupiterTests.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/MyFirstJUnitJupiterTests.kt[tags=user_guide] ---- -- ==== ================================================ FILE: documentation/modules/ROOT/pages/writing-tests/nested-tests.adoc ================================================ = Nested Tests `@Nested` tests give the test writer more capabilities to express the relationship among several groups of tests. Such nested tests make use of Java's nested classes and facilitate hierarchical thinking about the test structure. Here's an elaborate example, both as source code and as a screenshot of the execution within an IDE. .Nested test suite for testing a stack [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/TestingAStackDemo.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/TestingAStackDemo.kt[tags=user_guide] ---- -- ==== When executing this example in an IDE, the test execution tree in the GUI will look similar to the following image. image::writing-tests_nested_test_ide.png[caption='',title='Executing a nested test in an IDE'] In this example, preconditions from outer tests are used in inner tests by defining hierarchical lifecycle methods for the setup code. For example, `createNewStack()` is a `@BeforeEach` lifecycle method that is used in the test class in which it is defined and in all levels in the nesting tree below the class in which it is defined. The fact that setup code from outer tests is run before inner tests are executed gives you the ability to run all tests independently. You can even run inner tests alone without running the outer tests, because the setup code from the outer tests is always executed. NOTE: _Only non-static nested classes_ (i.e. _inner classes_) can serve as `@Nested` test classes. Nesting can be arbitrarily deep, and those inner classes are subject to full lifecycle support, including `@BeforeAll` and `@AfterAll` methods on each level. [[interoperability]] == Interoperability `@Nested` may be combined with xref:writing-tests/parameterized-classes-and-tests.adoc[`@ParameterizedClass`] in which case the nested test class is parameterized. The following example illustrates how to combine `@Nested` with `@ParameterizedClass` and `@ParameterizedTest`. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/ParameterizedClassDemo.java[tags=nested] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/ParameterizedClassDemo.kt[tags=nested] ---- -- ==== Executing the above test class yields the following output: .... FruitTests ✔ ├─ [1] fruit = "apple" ✔ │ └─ QuantityTests ✔ │ ├─ [1] quantity = 23 ✔ │ │ └─ test(Duration) ✔ │ │ ├─ [1] duration = "PT1H" ✔ │ │ └─ [2] duration = "PT2H" ✔ │ └─ [2] quantity = 42 ✔ │ └─ test(Duration) ✔ │ ├─ [1] duration = "PT1H" ✔ │ └─ [2] duration = "PT2H" ✔ └─ [2] fruit = "banana" ✔ └─ QuantityTests ✔ ├─ [1] quantity = 23 ✔ │ └─ test(Duration) ✔ │ ├─ [1] duration = "PT1H" ✔ │ └─ [2] duration = "PT2H" ✔ └─ [2] quantity = 42 ✔ └─ test(Duration) ✔ ├─ [1] duration = "PT1H" ✔ └─ [2] duration = "PT2H" ✔ .... ================================================ FILE: documentation/modules/ROOT/pages/writing-tests/parallel-execution.adoc ================================================ = Parallel Execution By default, JUnit Jupiter tests are run sequentially in a single thread; however, running tests in parallel -- for example, to speed up execution -- is available as an opt-in feature. To enable parallel execution, set the `junit.jupiter.execution.parallel.enabled` configuration parameter to `true` -- for example, in `junit-platform.properties` (see xref:running-tests/configuration-parameters.adoc[] for other options). Please note that enabling this property is only the first step required to execute tests in parallel. If enabled, test classes and methods will still be executed sequentially by default. Whether or not a node in the test tree is executed concurrently is controlled by its execution mode. The following two modes are available. `SAME_THREAD`:: Force execution in the same thread used by the parent. For example, when used on a test method, the test method will be executed in the same thread as any `@BeforeAll` or `@AfterAll` methods of the containing test class. `CONCURRENT`:: Execute concurrently unless a resource lock forces execution in the same thread. By default, nodes in the test tree use the `SAME_THREAD` execution mode. You can change the default by setting the `junit.jupiter.execution.parallel.mode.default` configuration parameter. Alternatively, you can use the `{Execution}` annotation to change the execution mode for the annotated element and its subelements (if any) which allows you to activate parallel execution for individual test classes, one by one. [source,properties] .Configuration parameters to execute all tests in parallel ---- junit.jupiter.execution.parallel.enabled = true junit.jupiter.execution.parallel.mode.default = concurrent ---- The default execution mode is applied to all nodes of the test tree with a few notable exceptions, namely test classes that use the `Lifecycle.PER_CLASS` mode or a `{MethodOrderer}`. In the former case, test authors have to ensure that the test class is thread-safe; in the latter, concurrent execution might conflict with the configured execution order. Thus, in both cases, test methods in such test classes are only executed concurrently if the `@Execution(CONCURRENT)` annotation is present on the test class or method. You can use the `@Execution` annotation to explicitly configure the execution mode for a test class or method: [source,java] ---- include::example$java/example/ExplicitExecutionModeDemo.java[tags=user_guide] ---- This allows test classes or methods to opt in or out of concurrent execution regardless of the globally configured default. When parallel execution is enabled and a default `{ClassOrderer}` is registered (see xref:writing-tests/test-execution-order.adoc#classes[Class Order] for details), top-level test classes will initially be sorted accordingly and scheduled in that order. However, they are not guaranteed to be started in exactly that order since the threads they are executed on are not controlled directly by JUnit. All nodes of the test tree that are configured with the `CONCURRENT` execution mode will be executed fully in parallel according to the provided <> while observing the declarative <> mechanism. Please note that xref:running-tests/capturing-standard-output-error.adoc[] needs to be enabled separately. In addition, you can configure the default execution mode for top-level classes by setting the `junit.jupiter.execution.parallel.mode.classes.default` configuration parameter. By combining both configuration parameters, you can configure classes to run in parallel but their methods in the same thread: [source,properties] .Configuration parameters to execute top-level classes in parallel but methods in same thread ---- junit.jupiter.execution.parallel.enabled = true junit.jupiter.execution.parallel.mode.default = same_thread junit.jupiter.execution.parallel.mode.classes.default = concurrent ---- The opposite combination will run all methods within one class in parallel, but top-level classes will run sequentially: [source,properties] .Configuration parameters to execute top-level classes sequentially but their methods in parallel ---- junit.jupiter.execution.parallel.enabled = true junit.jupiter.execution.parallel.mode.default = concurrent junit.jupiter.execution.parallel.mode.classes.default = same_thread ---- The following diagram illustrates how the execution of two top-level test classes `A` and `B` with two test methods per class behaves for all four combinations of `junit.jupiter.execution.parallel.mode.default` and `junit.jupiter.execution.parallel.mode.classes.default` (see labels in first column). //// Source: https://mermaid-js.github.io/mermaid-live-editor/edit#pako:eNqFlE1u2zAQha9CEChio7IQKfVGXfUH_QEatICyKAIBwYQaW0QkUiDHhV3X2x4gvWFPUlKUbTmpEq2kN2-GHx403HKhS-QZn81mhSqlbWvYXDopY0I3LQgqVFcq1BIUuS_mnhIIP2jTALHvQYG1tL3ywgaJpLj7rAjND6hZsteoRvb39x9GlUEoLfvltMZL9_4M77EoSGrFJhYavAm-iA0-psH3Jia0lEymLANrk4idR_tjQintS2nEYOE4WLClwfP22H7b6QeP818MPWnvOcwJ_ldPAwutxMoYVPQ_XjHOKwa8YoT3tP0EUwww-_YHmEey52IV47EKH8dDhEAnBmmKR4mnvScdeNLnMJ8MU4yHKcQ45XiGgy4e8Qbdby1LtyNbby04VdhgwTP3qnBFBuqCR6EUdsSVtmFqwWtc0DcoS6mWXk_TebQv3YL5CK1Xk_ODuDSy_CIV5gRm2DiwuL5PKJdVd9DFUV9oRbn82aElc6_uogHxuzwP0DGBvbvCtcs17tO-6vZyy_yI2QIaWW8ydva1RcVyUPbsdahYNz1L5u2a7VjsSVnst5yRG-a6--sjU1rhqSNTVM1EJetykqqXyfSRueCF2rmwYUU63yjBMzIrjPiq9XfNewlLAw3PFlBbp2IpSZvLcHN1F1jEW1DXWu89u3-YPX1X --- displayMode: compact --- gantt dateFormat X axisFormat %s tickInterval 1 title ↓ threads | time → section (same_thread, same_thread) A.test1() :ass1, 0, 1 A.test2() :ass2, after ass1, 2 B.test1() :bss1, after ass2, 3 B.test2() :bss2, after bss1, 4 section (same_thread, concurrent) A.test1() :asc1, 0, 1 A.test2() :asc2, after asc1, 2 B.test1() :bsc1, 0, 1 B.test2() :bsc2, after bsc1, 2 section (concurrent, same_thread) A.test1() :acs1, 0, 1 A.test2() :acs2, 0, 1 B.test1() :bcs1, after acs1, 2 B.test2() :bcs2, after acs2, 2 section (concurrent, concurrent) A.test1() :acc1, 0, 1 A.test2() :acc2, 0, 1 B.test1() :bcc1, 0, 1 B.test2() :bcc2, 0, 1 //// image::writing-tests_execution_mode.svg[caption='',title='Default execution mode configuration combinations'] If the `junit.jupiter.execution.parallel.mode.classes.default` configuration parameter is not explicitly set, the value for `junit.jupiter.execution.parallel.mode.default` will be used instead. [[config]] == Configuration [[config-executor-service]] === Executor Service If parallel execution is enabled, a thread pool is used behind the scenes to execute tests concurrently. You can configure which implementation of `HierarchicalTestExecutorService` is used be setting the `junit.jupiter.execution.parallel.config.executor-service` configuration parameter to one of the following options: `fork_join_pool` (default):: Use an executor service that is backed by a `ForkJoinPool` from the JDK. This will cause tests to be executed in a `ForkJoinWorkerThread`. In some cases, usages of `ForkJoinPool` in test or production code or calls to blocking JDK APIs may cause the number of concurrently executing tests to increase. To avoid this situation, please use `worker_thread_pool`. `worker_thread_pool` (experimental):: Use an executor service that is backed by a regular thread pool and does not create additional threads if test or production code uses `ForkJoinPool` or calls a blocking API in the JDK. WARNING: Using `worker_thread_pool` is currently an _experimental_ feature. You're invited to give it a try and provide feedback to the JUnit team so they can improve and eventually xref:api-evolution.adoc[promote] this feature. [[config-strategies]] === Strategies Properties such as the desired parallelism and the maximum pool size can be configured using a `{ParallelExecutionConfigurationStrategy}`. The JUnit Platform provides two implementations out of the box: `dynamic` and `fixed`. Alternatively, you may implement a `custom` strategy. To select a strategy, set the `junit.jupiter.execution.parallel.config.strategy` configuration parameter to one of the following options. `dynamic`:: Computes the desired parallelism based on the number of available processors/cores multiplied by the `junit.jupiter.execution.parallel.config.dynamic.factor` configuration parameter (defaults to `1`). The optional `junit.jupiter.execution.parallel.config.dynamic.max-pool-size-factor` configuration parameter can be used to limit the maximum number of threads. `fixed`:: Uses the mandatory `junit.jupiter.execution.parallel.config.fixed.parallelism` configuration parameter as the desired parallelism. The optional `junit.jupiter.execution.parallel.config.fixed.max-pool-size` configuration parameter can be used to limit the maximum number of threads. `custom`:: Allows you to specify a custom `{ParallelExecutionConfigurationStrategy}` implementation via the mandatory `junit.jupiter.execution.parallel.config.custom.class` configuration parameter to determine the desired configuration. If no configuration strategy is set, JUnit Jupiter uses the `dynamic` configuration strategy with a factor of `1`. Consequently, the desired parallelism will be equal to the number of available processors/cores. .Parallelism alone does not imply maximum number of concurrent threads NOTE: By default, JUnit Jupiter does not guarantee that the number of threads used to execute test will not exceed the configured parallelism. For example, when using one of the synchronization mechanisms described in the next section, the executor service implementation may spawn additional threads to ensure execution continues with sufficient parallelism. If you require such guarantees, it is possible to limit the maximum number of threads by configuring the maximum pool size of the `dynamic`, `fixed` and `custom` strategies. [[config-properties]] === Relevant properties The following table lists relevant properties for configuring parallel execution. See xref:running-tests/configuration-parameters.adoc[] for details on how to set such properties. ==== General `junit.jupiter.execution.parallel.enabled=true|false`:: Enable/disable parallel test execution (defaults to `false`). `junit.jupiter.execution.parallel.mode.default=concurrent|same_thread`:: Default execution mode of nodes in the test tree (defaults to `same_thread`). `junit.jupiter.execution.parallel.mode.classes.default=concurrent|same_thread`:: Default execution mode of top-level classes (defaults to `same_thread`). `junit.jupiter.execution.parallel.config.executor-service=fork_join_pool|worker_thread_pool`:: Type of `HierarchicalTestExecutorService` to use for parallel execution (defaults to `fork_join_pool`). `junit.jupiter.execution.parallel.config.strategy=dynamic|fixed|custom`:: Execution strategy for desired parallelism, maximum pool size, etc. (defaults to `dynamic`). ==== Dynamic strategy `junit.jupiter.execution.parallel.config.dynamic.factor=decimal`:: Factor to be multiplied by the number of available processors/cores to determine the desired parallelism for the ```dynamic``` configuration strategy. Must be a positive decimal number (defaults to `1.0`). `junit.jupiter.execution.parallel.config.dynamic.max-pool-size-factor=decimal`:: Factor to be multiplied by the number of available processors/cores and the value of `junit.jupiter.execution.parallel.config.dynamic.factor` to determine the desired parallelism for the ```dynamic``` configuration strategy. Must be a positive decimal number greater than or equal to `1.0` (defaults to 256 plus the value of `junit.jupiter.execution.parallel.config.dynamic.factor` multiplied by the number of available processors/cores) `junit.jupiter.execution.parallel.config.dynamic.saturate=true|false`:: Enable/disable saturation of the underlying `ForkJoinPool` for the ```dynamic``` configuration strategy (defaults to `true`). Only used if `junit.jupiter.execution.parallel.config.executor-service` is set to `fork_join_pool`. ==== Fixed strategy `junit.jupiter.execution.parallel.config.fixed.parallelism=integer`:: Desired parallelism for the ```fixed``` configuration strategy (no default value). Must be a positive integer. `junit.jupiter.execution.parallel.config.fixed.max-pool-size=integer`:: Desired maximum pool size of the underlying fork-join pool for the ```fixed``` configuration strategy. Must be a positive integer greater than or equal to `junit.jupiter.execution.parallel.config.fixed.parallelism` (defaults to 256 plus the value of `junit.jupiter.execution.parallel.config.fixed.parallelism`). `junit.jupiter.execution.parallel.config.fixed.saturate=true|false`:: Enable/disable saturation of the underlying `ForkJoinPool` for the ```fixed``` configuration strategy (defaults to `true`). Only used if `junit.jupiter.execution.parallel.config.executor-service` is set to `fork_join_pool`. ==== Custom strategy `junit.jupiter.execution.parallel.config.custom.class=classname`:: Fully qualified class name of the `ParallelExecutionConfigurationStrategy` to be used for the ```custom``` configuration strategy (no default value). [[synchronization]] == Synchronization In addition to controlling the execution mode using the `{Execution}` annotation, JUnit Jupiter provides another annotation-based declarative synchronization mechanism. The `{ResourceLock}` annotation allows you to declare that a test class or method uses a specific shared resource that requires synchronized access to ensure reliable test execution. The shared resource is identified by a unique name which is a `String`. The name can be user-defined or one of the predefined constants in `{Resources}`: `SYSTEM_PROPERTIES`, `SYSTEM_OUT`, `SYSTEM_ERR`, `LOCALE`, or `TIME_ZONE`. In addition to declaring these shared resources statically, the `{ResourceLock}` annotation has a `providers` attribute that allows registering implementations of the `{ResourceLocksProvider}` interface that can add shared resources dynamically at runtime. Note that resources declared statically with `{ResourceLock}` annotation are combined with resources added dynamically by `{ResourceLocksProvider}` implementations. If the tests in the following example were run in parallel _without_ the use of `{ResourceLock}`, they would be _flaky_. Sometimes they would pass, and at other times they would fail due to the inherent race condition of writing and then reading the same JVM System Property. When access to shared resources is declared using the `{ResourceLock}` annotation, the JUnit Jupiter engine uses this information to ensure that no conflicting tests are run in parallel. This guarantee extends to lifecycle methods of a test class or method. For example, if a test method is annotated with a `{ResourceLock}` annotation, the "lock" will be acquired before any `@BeforeEach` methods are executed and released after all `@AfterEach` methods have been executed. [NOTE] .Running tests in isolation ==== If most of your test classes can be run in parallel without any synchronization but you have some test classes that need to run in isolation, you can mark the latter with the `{Isolated}` annotation. Tests in such classes are executed sequentially without any other tests running at the same time. ==== In addition to the `String` that uniquely identifies the shared resource, you may specify an access mode. Two tests that require `READ` access to a shared resource may run in parallel with each other but not while any other test that requires `READ_WRITE` access to the same shared resource is running. [source,java] .Declaring shared resources "statically" with `{ResourceLock}` annotation ---- include::example$java/example/sharedresources/StaticSharedResourcesDemo.java[tags=user_guide] ---- [source,java] .Adding shared resources "dynamically" with `{ResourceLocksProvider}` implementation ---- include::example$java/example/sharedresources/DynamicSharedResourcesDemo.java[tags=user_guide] ---- Also, "static" shared resources can be declared for _direct_ child nodes via the `target` attribute in the `{ResourceLock}` annotation, the attribute accepts a value from the `{ResourceLockTarget}` enum. Specifying `target = CHILDREN` in a class-level `{ResourceLock}` annotation has the same semantics as adding an annotation with the same `value` and `mode` to each test method and nested test class declared in this class. This may improve parallelization when a test class declares a `READ` lock, but only a few methods hold a `READ_WRITE` lock. Tests in the following example would run in the `SAME_THREAD` if the `{ResourceLock}` didn't have `target = CHILDREN`. This is because the test class declares a `READ` shared resource, but one test method holds a `READ_WRITE` lock, which would force the `SAME_THREAD` execution mode for all the test methods. [source,java] .Declaring shared resources for child nodes with `target` attribute ---- include::example$java/example/sharedresources/ChildrenSharedResourcesDemo.java[tags=user_guide] ---- ================================================ FILE: documentation/modules/ROOT/pages/writing-tests/parameterized-classes-and-tests.adoc ================================================ = Parameterized Classes and Tests _Parameterized tests_ make it possible to run a test method multiple times with different arguments. They are declared just like regular `@Test` methods but use the `{ParameterizedTest}` annotation instead. _Parameterized classes_ make it possible to run _all_ tests in a test class, including xref:writing-tests/nested-tests.adoc[], multiple times with different arguments. They are declared just like regular test classes and may contain any supported test method type (including `@ParameterizedTest`) but annotated with the `{ParameterizedClass}` annotation. WARNING: _Parameterized classes_ are currently an _experimental_ feature. You're invited to give it a try and provide feedback to the JUnit team so they can improve and eventually xref:api-evolution.adoc[promote] this feature. Regardless of whether you are parameterizing a test method or a test class, you must declare at least one <> that will provide the arguments for each invocation and then <> the arguments in the parameterized method or class, respectively. The following example demonstrates a parameterized test that uses the `@ValueSource` annotation to specify a `String` array as the source of arguments. [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=first_example] ---- When executing the above parameterized test method, each invocation will be reported separately. For instance, the `ConsoleLauncher` will print output similar to the following. .... palindromes(String) ✔ ├─ [1] candidate = "racecar" ✔ ├─ [2] candidate = "radar" ✔ └─ [3] candidate = "able was I ere I saw elba" ✔ .... The same `@ValueSource` annotation can be used to specify the source of arguments for a `@ParameterizedClass`. [source,java,indent=0] ---- include::example$java/example/ParameterizedClassDemo.java[tags=first_example] ---- When executing the above parameterized test class, each invocation will be reported separately. For instance, the `ConsoleLauncher` will print output similar to the following. .... PalindromeTests ✔ ├─ [1] candidate = "racecar" ✔ │ ├─ palindrome() ✔ │ └─ reversePalindrome() ✔ ├─ [2] candidate = "radar" ✔ │ ├─ palindrome() ✔ │ └─ reversePalindrome() ✔ └─ [3] candidate = "able was I ere I saw elba" ✔ ├─ palindrome() ✔ └─ reversePalindrome() ✔ .... [[setup]] == Required Setup In order to use parameterized classes or tests you need to add a dependency on the `junit-jupiter-params` artifact. Please refer to xref:appendix.adoc#dependency-metadata[Dependency Metadata] for details. [[consuming-arguments]] == Consuming Arguments [[consuming-arguments-methods]] === Parameterized Tests Parameterized test methods _consume_ arguments directly from the configured source (see <>) following a one-to-one correlation between argument source index and method parameter index (see examples in <>). However, a parameterized test method may also choose to _aggregate_ arguments from the source into a single object passed to the method (see <>). Additional arguments may also be provided by a `ParameterResolver` — for example, to obtain an instance of `TestInfo`, `TestReporter`, and so forth. Specifically, a parameterized test method must declare formal parameters according to the following rules. * Zero or more _indexed parameters_ must be declared first. * Zero or more _aggregators_ must be declared next. * Zero or more arguments supplied by a `ParameterResolver` must be declared last. In this context, an _indexed parameter_ is an argument for a given index in the `{Arguments}` provided by an `{ArgumentsProvider}` that is passed as an argument to the parameterized method at the same index in the method's formal parameter list. An _aggregator_ is any parameter of type `{ArgumentsAccessor}` or any parameter annotated with `{AggregateWith}`. [[consuming-arguments-classes]] === Parameterized Classes Parameterized classes _consume_ arguments directly from the configured source (see <>); either via their unique constructor or via field injection. If a `{Parameter}`-annotated field is declared in the parameterized class or one of its superclasses, field injection will be used. Otherwise, constructor injection will be used. [[consuming-arguments-constructor-injection]] ==== Constructor Injection WARNING: Constructor injection can only be used with the (default) `PER_METHOD` xref:writing-tests/test-instance-lifecycle.adoc[test instance lifecycle] mode. Please use <> with the `PER_CLASS` mode instead. For constructor injection, the same rules apply as defined for <> above. In the following example, two arguments are injected into the constructor of the test class. [source,java,indent=0] ---- include::example$java/example/ParameterizedClassDemo.java[tags=constructor_injection] ---- You may use _records_ to implement parameterized classes that avoid the boilerplate code of declaring a test class constructor. [source,java,indent=0] ---- include::example$java/example/ParameterizedRecordDemo.java[tags=example] ---- [[consuming-arguments-field-injection]] ==== Field Injection For field injection, the following rules apply for fields annotated with `@Parameter`. * Zero or more _indexed parameters_ may be declared; each must have a unique index specified in its `@Parameter(index)` annotation. The index may be omitted if there is only one indexed parameter. If there are at least two indexed parameter declarations, there must be declarations for all indexes from 0 to the largest declared index. * Zero or more _aggregators_ may be declared; each without specifying an index in its `@Parameter` annotation. * Zero or more other fields may be declared as usual as long as they're not annotated with `@Parameter`. In this context, an _indexed parameter_ is an argument for a given index in the `{Arguments}` provided by an `{ArgumentsProvider}` that is injected into a field annotated with `@Parameter(index)`. An _aggregator_ is any `@Parameter`-annotated field of type {ArgumentsAccessor} or any field annotated with {AggregateWith}. The following example demonstrates how to use field injection to consume multiple arguments in a parameterized class. [source,java,indent=0] ---- include::example$java/example/ParameterizedClassDemo.java[tags=field_injection] ---- If field injection is used, no constructor parameters will be resolved with arguments from the source. Other xref:writing-tests/dependency-injection-for-constructors-and-methods.adoc[`ParameterResolver` extensions] may resolve constructor parameters as usual, though. [[consuming-arguments-lifecycle-method]] ==== Lifecycle Methods `{BeforeParameterizedClassInvocation}` and `{AfterParameterizedClassInvocation}` can also be used to consume arguments if their `injectArguments` attribute is set to `true` (the default). If so, their method signatures must follow the same rules apply as defined for <> and additionally use the same parameter types as the _indexed parameters_ of the parameterized test class. Please refer to the Javadoc of `{BeforeParameterizedClassInvocation}` and `{AfterParameterizedClassInvocation}` for details and to the <> section for an example. [NOTE] .AutoCloseable arguments ==== Arguments that implement `java.lang.AutoCloseable` (or `java.io.Closeable` which extends `java.lang.AutoCloseable`) will be automatically closed after the parameterized class or test invocation. To prevent this from happening, set the `autoCloseArguments` attribute in `@ParameterizedTest` to `false`. Specifically, if an argument that implements `AutoCloseable` is reused for multiple invocations of the same parameterized class or test method, you must specify the `autoCloseArguments = false` on the `{ParameterizedClass}` or `{ParameterizedTest}` annotation to ensure that the argument is not closed between invocations. ==== [[consuming-arguments-other-extensions]] === Other Extensions Other extensions can access the parameters and resolved arguments of a parameterized test or class by retrieving a `{ParameterInfo}` object from the `{ExtensionContext_Store}`. Please refer to the Javadoc of `{ParameterInfo}` for details. [[sources]] == Sources of Arguments Out of the box, JUnit Jupiter provides quite a few _source_ annotations. Each of the following subsections provides a brief overview and an example for each of them. Please refer to the Javadoc in the `{params-provider-package}` package for additional information. TIP: All source annotations in this section are applicable to both `{ParameterizedClass}` and `{ParameterizedTest}`. For the sake of brevity, the examples in this section will only show how to use them with `{ParameterizedTest}` methods. [[sources-ValueSource]] === @ValueSource `@ValueSource` is one of the simplest possible sources. It lets you specify a single array of literal values and can only be used for providing a single argument per parameterized test invocation. The following types of literal values are supported by `@ValueSource`. - `short` - `byte` - `int` - `long` - `float` - `double` - `char` - `boolean` - `java.lang.String` - `java.lang.Class` For example, the following `@ParameterizedTest` method will be invoked three times, with the values `1`, `2`, and `3` respectively. [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=ValueSource_example] ---- [[sources-null-and-empty]] === Null and Empty Sources In order to check corner cases and verify proper behavior of our software when it is supplied _bad input_, it can be useful to have `null` and _empty_ values supplied to our parameterized tests. The following annotations serve as sources of `null` and empty values for parameterized tests that accept a single argument. * `{NullSource}`: provides a single `null` argument to the annotated `@ParameterizedClass` or `@ParameterizedTest`. - `@NullSource` cannot be used for a parameter that has a primitive type. * `{EmptySource}`: provides a single _empty_ argument to the annotated `@ParameterizedClass` or `@ParameterizedTest` for parameters of the following types: `java.lang.String`, `java.lang.Iterable`, `java.util.Iterator`, `java.util.ListIterator` `java.util.Collection` (and concrete subtypes with a `public` no-arg constructor), `java.util.List`, `java.util.Set`, `java.util.SortedSet`, `java.util.NavigableSet`, `java.util.Map` (and concrete subtypes with a `public` no-arg constructor), `java.util.SortedMap`, `java.util.NavigableMap`, primitive arrays (for example, `int[]`, `char[][]`, etc.), object arrays (for example, `String[]`, `Integer[][]`, etc.). * `{NullAndEmptySource}`: a _composed annotation_ that combines the functionality of `@NullSource` and `@EmptySource`. If you need to supply multiple varying types of _blank_ strings to a parameterized class or test, you can achieve that using <> -- for example, `@ValueSource(strings = {"{nbsp}", "{nbsp}{nbsp}{nbsp}", "\t", "\n"})`. You can also combine `@NullSource`, `@EmptySource`, and `@ValueSource` to test a wider range of `null`, _empty_, and _blank_ input. The following example demonstrates how to achieve this for strings. [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=NullAndEmptySource_example1] ---- Making use of the composed `@NullAndEmptySource` annotation simplifies the above as follows. [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=NullAndEmptySource_example2] ---- NOTE: Both variants of the `nullEmptyAndBlankStrings(String)` parameterized test method result in six invocations: 1 for `null`, 1 for the empty string, and 4 for the explicit blank strings supplied via `@ValueSource`. [[sources-EnumSource]] === @EnumSource `@EnumSource` provides a convenient way to use `Enum` constants. [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=EnumSource_example] ---- The annotation's `value` attribute is optional. When omitted, the declared type of the first parameter is used. The test will fail if it does not reference an enum type. Thus, the `value` attribute is required in the above example because the method parameter is declared as `TemporalUnit`, i.e. the interface implemented by `ChronoUnit`, which isn't an enum type. Changing the method parameter type to `ChronoUnit` allows you to omit the explicit enum type from the annotation as follows. [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=EnumSource_example_autodetection] ---- The annotation provides an optional `names` attribute that lets you specify which constants shall be used, like in the following example. [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=EnumSource_include_example] ---- In addition to `names`, you can use the `from` and `to` attributes to specify a range of constants. The range starts from the constant specified in the `from` attribute and includes all subsequent constants up to and including the one specified in the `to` attribute, based on the natural order of the enum constants. If `from` and `to` attributes are omitted, they default to the first and last constants in the enum type, respectively. If all `names`, `from`, and `to` attributes are omitted, all constants will be used. The following example demonstrates how to specify a range of constants. [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=EnumSource_range_example] ---- The `@EnumSource` annotation also provides an optional `mode` attribute that enables fine-grained control over which constants are passed to the test method. For example, you can exclude names from the enum constant pool or specify regular expressions as in the following examples. [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=EnumSource_exclude_example] ---- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=EnumSource_regex_example] ---- You can also combine `mode` with the `from`, `to` and `names` attributes to define a range of constants while excluding specific values from that range as shown below. [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=EnumSource_range_exclude_example] ---- [[sources-MethodSource]] === @MethodSource `{MethodSource}` allows you to refer to one or more _factory_ methods of the test class or external classes. Factory methods within the test class must be `static` unless the test class is annotated with `@TestInstance(Lifecycle.PER_CLASS)`; whereas, factory methods in external classes must always be `static`. Each factory method must generate a _stream_ of _arguments_, and each set of arguments within the stream will be provided as the physical arguments for individual invocations of the annotated `@ParameterizedClass` or `@ParameterizedTest`. Generally speaking this translates to a `Stream` of `Arguments` (i.e., `Stream`); however, the actual concrete return type can take on many forms. In this context, a "stream" is anything that JUnit can reliably convert into a `Stream`, such as `Stream`, `DoubleStream`, `LongStream`, `IntStream`, `Collection`, `Iterator`, `Iterable`, an array of objects or primitives, or any type that provides an `iterator(): Iterator` method (such as, for example, a `kotlin.sequences.Sequence`). The "arguments" within the stream can be supplied as an instance of `Arguments`, an array of objects (for example, `Object[]`), or a single value if the parameterized class or test method accepts a single argument. If the return type is `Stream` or one of the primitive streams, JUnit will properly close it by calling `BaseStream.close()`, making it safe to use a resource such as `Files.lines()`. If you only need a single parameter, you can return a `Stream` of instances of the parameter type as demonstrated in the following example. [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=simple_MethodSource_example] ---- For a `@ParameterizedClass`, providing a factory method name via `@MethodSource` is mandatory. For a `@ParameterizedTest`, if you do not explicitly provide a factory method name, JUnit Jupiter will search for a _factory_ method with the same name as the current `@ParameterizedTest` method by convention. This is demonstrated in the following example. [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=simple_MethodSource_without_value_example] ---- Streams for primitive types (`DoubleStream`, `IntStream`, and `LongStream`) are also supported as demonstrated by the following example. [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=primitive_MethodSource_example] ---- If a parameterized class or test method declares multiple parameters, you need to return a collection, stream, or array of `Arguments` instances or object arrays as shown below (see the Javadoc for `{MethodSource}` for further details on supported return types). Note that `arguments(Object...)` is a static factory method defined in the `Arguments` interface. In addition, `Arguments.of(Object...)` may be used as an alternative to `arguments(Object...)`. [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=multi_arg_MethodSource_example] ---- An external, `static` _factory_ method can be referenced by providing its _fully qualified method name_ as demonstrated in the following example. [source,java,indent=0] ---- package example; include::example$java/example/ExternalMethodSourceDemo.java[tags=external_MethodSource_example] ---- Factory methods can declare parameters, which will be provided by registered implementations of the `ParameterResolver` extension API. In the following example, the factory method is referenced by its name since there is only one such method in the test class. If there are several local methods with the same name, parameters can also be provided to differentiate them – for example, `@MethodSource("factoryMethod()")` or `@MethodSource("factoryMethod(java.lang.String)")`. Alternatively, the factory method can be referenced by its fully qualified method name — for example, `@MethodSource("example.MyTests#factoryMethod(java.lang.String)")`. [source,java,indent=0] ---- include::example$java/example/MethodSourceParameterResolutionDemo.java[tags=parameter_resolution_MethodSource_example] ---- [[sources-FieldSource]] === @FieldSource `{FieldSource}` allows you to refer to one or more fields of the test class or external classes. Fields within the test class must be `static` unless the test class is annotated with `@TestInstance(Lifecycle.PER_CLASS)`; whereas, fields in external classes must always be `static`. Each field must be able to supply a _stream_ of arguments, and each set of "arguments" within the "stream" will be provided as the physical arguments for individual invocations of the annotated `@ParameterizedClass` or `@ParameterizedTest`. In this context, a "stream" is anything that JUnit can reliably convert to a `Stream`; however, the actual concrete field type can take on many forms. Generally speaking this translates to a `Collection`, an `Iterable`, a `Supplier` of a stream (`Stream`, `DoubleStream`, `LongStream`, or `IntStream`), a `Supplier` of an `Iterator`, an array of objects or primitives, or any type that provides an `iterator(): Iterator` method (such as, for example, a `kotlin.sequences.Sequence`). Each set of "arguments" within the "stream" can be supplied as an instance of `Arguments`, an array of objects (for example, `Object[]`, `String[]`, etc.), or a single value if the parameterized class or test method accepts a single argument. [WARNING] ==== In contrast to the supported return types for <> factory methods, the value of a `@FieldSource` field cannot be an instance of `Stream`, `DoubleStream`, `LongStream`, `IntStream`, or `Iterator`, since the values of such types are _consumed_ the first time they are processed. However, if you wish to use one of these types, you can wrap it in a `Supplier` — for example, `Supplier`. ==== If the `Supplier` return type is `Stream` or one of the primitive streams, JUnit will properly close it by calling `BaseStream.close()`, making it safe to use a resource such as `Files.lines()`. Please note that a one-dimensional array of objects supplied as a set of "arguments" will be handled differently than other types of arguments. Specifically, all the elements of a one-dimensional array of objects will be passed as individual physical arguments to the `@ParameterizedClass` or `@ParameterizedTest`. See the Javadoc for `{FieldSource}` for further details. For a `@ParameterizedClass`, providing a field name via `@FieldSource` is mandatory. For a `@ParameterizedTest`, if you do not explicitly provide a field name, JUnit Jupiter will search in the test class for a field that has the same name as the current `@ParameterizedTest` method by convention. This is demonstrated in the following example. This parameterized test method will be invoked twice: with the values `"apple"` and `"banana"`. [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=default_field_FieldSource_example] ---- The following example demonstrates how to provide a single explicit field name via `@FieldSource`. This parameterized test method will be invoked twice: with the values `"apple"` and `"banana"`. [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=explicit_field_FieldSource_example] ---- The following example demonstrates how to provide multiple explicit field names via `@FieldSource`. This example uses the `listOfFruits` field from the previous example as well as the `additionalFruits` field. Consequently, this parameterized test method will be invoked four times: with the values `"apple"`, `"banana"`, `"cherry"`, and `"dewberry"`. [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=multiple_fields_FieldSource_example] ---- It is also possible to provide a `Stream`, `DoubleStream`, `IntStream`, `LongStream`, or `Iterator` as the source of arguments via a `@FieldSource` field as long as the stream or iterator is wrapped in a `java.util.function.Supplier`. The following example demonstrates how to provide a `Supplier` of a `Stream` of named arguments. This parameterized test method will be invoked twice: with the values `"apple"` and `"banana"` and with display names `"Apple"` and `"Banana"`, respectively. [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=named_arguments_FieldSource_example] ---- [NOTE] ==== Note that `arguments(Object...)` is a static factory method defined in the `org.junit.jupiter.params.provider.Arguments` interface. Similarly, `named(String, Object)` is a static factory method defined in the `org.junit.jupiter.api.Named` interface. ==== If a parameterized class or test method declares multiple parameters, the corresponding `@FieldSource` field must be able to provide a collection, stream supplier, or array of `Arguments` instances or object arrays as shown below (see the Javadoc for `{FieldSource}` for further details on supported types). [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=multi_arg_FieldSource_example] ---- [NOTE] ==== Note that `arguments(Object...)` is a static factory method defined in the `org.junit.jupiter.params.provider.Arguments` interface. ==== An external, `static` `@FieldSource` field can be referenced by providing its _fully qualified field name_ as demonstrated in the following example. [source,java,indent=0] ---- include::example$java/example/ExternalFieldSourceDemo.java[tags=external_field_FieldSource_example] ---- [[sources-CsvSource]] === @CsvSource `@CsvSource` allows you to express argument lists as comma-separated values (i.e., CSV `String` literals). Each string provided via the `value` attribute in `@CsvSource` represents a CSV record and results in one invocation of the parameterized class or test. The first record may optionally be used to supply CSV headers (see the Javadoc for the `useHeadersInDisplayName` attribute for details and an example). [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=CsvSource_example] ---- The default delimiter is a comma (`,`), but you can use another character by setting the `delimiter` attribute. Alternatively, the `delimiterString` attribute allows you to use a `String` delimiter instead of a single character. However, both delimiter attributes cannot be set simultaneously. By default, `@CsvSource` uses a single quote (`'`) as its quote character, but this can be changed via the `quoteCharacter` attribute. See the `'lemon, lime'` value in the example above and in the table below. An empty, quoted value (`''`) results in an empty `String` unless the `emptyValue` attribute is set; whereas, an entirely _empty_ value is interpreted as a `null` reference. By specifying one or more `nullValues`, a custom value can be interpreted as a `null` reference (see the `NIL` example in the table below). An `ArgumentConversionException` is thrown if the target type of a `null` reference is a primitive type. NOTE: An _unquoted_ empty value will always be converted to a `null` reference regardless of any custom values configured via the `nullValues` attribute. Except within a quoted string, leading and trailing whitespace in a CSV column is trimmed by default. This behavior can be changed by setting the `ignoreLeadingAndTrailingWhitespace` attribute to `true`. [cols="50,50"] |=== | Example Input | Resulting Argument List | `@CsvSource({ "apple, banana" })` | `"apple"`, `"banana"` | `@CsvSource({ "apple, 'lemon, lime'" })` | `"apple"`, `"lemon, lime"` | `@CsvSource({ "apple, ''" })` | `"apple"`, `""` | `@CsvSource({ "apple, " })` | `"apple"`, `null` | `@CsvSource(value = { "apple, banana, NIL" }, nullValues = "NIL")` | `"apple"`, `"banana"`, `null` | `@CsvSource(value = { " apple , banana" }, ignoreLeadingAndTrailingWhitespace = false)` | `" apple "`, `" banana"` |=== If the programming language you are using supports Java _text blocks_ or equivalent multi-line string literals, you can alternatively use the `textBlock` attribute of `@CsvSource`. Each record within a text block represents a CSV record and results in one invocation of the parameterized class or test. The first record may optionally be used to supply CSV headers by setting the `useHeadersInDisplayName` attribute to `true` as in the example below. Using a text block, the previous example can be implemented as follows. [source,java,indent=0] ---- @ParameterizedTest @CsvSource(useHeadersInDisplayName = true, textBlock = """ FRUIT, RANK apple, 1 banana, 2 'lemon, lime', 0xF1 strawberry, 700_000 """) void testWithCsvSource(String fruit, int rank) { // ... } ---- The generated display names for the previous example include the CSV header names. ---- [1] FRUIT = "apple", RANK = "1" [2] FRUIT = "banana", RANK = "2" [3] FRUIT = "lemon, lime", RANK = "0xF1" [4] FRUIT = "strawberry", RANK = "700_000" ---- In contrast to CSV records supplied via the `value` attribute, a text block can contain comments. Any line beginning with the value of the `commentCharacter` attribute (`+++#+++` by default) will be treated as a comment and ignored. Note that there is one exception to this rule: if the comment character appears within a quoted field, it loses its special meaning. The comment character must be the first character on the line without any leading whitespace. It is therefore recommended that the closing text block delimiter (`"""`) be placed either at the end of the last line of input or on the following line, left aligned with the rest of the input (as can be seen in the example below which demonstrates formatting similar to a table). [source,java,indent=0] ---- @ParameterizedTest @CsvSource(delimiter = '|', quoteCharacter = '"', textBlock = """ #----------------------------- # FRUIT | RANK #----------------------------- apple | 1 #----------------------------- banana | 2 #----------------------------- "lemon lime" | 0xF1 #----------------------------- strawberry | 700_000 #----------------------------- """) void testWithCsvSource(String fruit, int rank) { // ... } ---- [NOTE] ==== Java's https://docs.oracle.com/en/java/javase/17/text-blocks/index.html[text block] feature automatically removes _incidental whitespace_ when the code is compiled. However other JVM languages such as Groovy and Kotlin do not. Thus, if you are using a programming language other than Java and your text block contains comments or new lines within quoted strings, you will need to ensure that there is no leading whitespace within your text block. ==== [[sources-CsvFileSource]] === @CsvFileSource `@CsvFileSource` lets you use comma-separated value (CSV) files from the classpath or the local file system. Each record from a CSV file results in one invocation of the parameterized class or test. The first record may optionally be used to supply CSV headers. You can instruct JUnit to ignore the headers via the `numLinesToSkip` attribute. If you would like for the headers to be used in the display names, you can set the `useHeadersInDisplayName` attribute to `true`. The examples below demonstrate the use of `numLinesToSkip` and `useHeadersInDisplayName`. The default delimiter is a comma (`,`), but you can use another character by setting the `delimiter` attribute. Alternatively, the `delimiterString` attribute allows you to use a `String` delimiter instead of a single character. However, both delimiter attributes cannot be set simultaneously. .Comments in CSV files NOTE: Any line beginning with the value of the `commentCharacter` attribute (`+++#+++` by default) will be interpreted as a comment and will be ignored. [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=CsvFileSource_example] ---- [source,csv,indent=0] .two-column.csv ---- include::example$resources/two-column.csv[] ---- The following listing shows the generated display names for the first two parameterized test methods above. ---- [1] country = "Sweden", reference = "1" [2] country = "Poland", reference = "2" [3] country = "United States of America", reference = "3" [4] country = "France", reference = "700_000" ---- The following listing shows the generated display names for the last parameterized test method above that uses CSV header names. ---- [1] COUNTRY = "Sweden", REFERENCE = "1" [2] COUNTRY = "Poland", REFERENCE = "2" [3] COUNTRY = "United States of America", REFERENCE = "3" [4] COUNTRY = "France", REFERENCE = "700_000" ---- In contrast to the default syntax used in `@CsvSource`, `@CsvFileSource` uses a double quote (`+++"+++`) as the quote character by default, but this can be changed via the `quoteCharacter` attribute. See the `"United States of America"` value in the example above. An empty, quoted value (`+++""+++`) results in an empty `String` unless the `emptyValue` attribute is set; whereas, an entirely _empty_ value is interpreted as a `null` reference. By specifying one or more `nullValues`, a custom value can be interpreted as a `null` reference. An `ArgumentConversionException` is thrown if the target type of a `null` reference is a primitive type. NOTE: An _unquoted_ empty value will always be converted to a `null` reference regardless of any custom values configured via the `nullValues` attribute. Except within a quoted string, leading and trailing whitespace in a CSV column is trimmed by default. This behavior can be changed by setting the `ignoreLeadingAndTrailingWhitespace` attribute to `true`. [[sources-ArgumentsSource]] === @ArgumentsSource `@ArgumentsSource` can be used to specify a custom, reusable `ArgumentsProvider`. Note that an implementation of `ArgumentsProvider` must be declared as either a top-level class or as a `static` nested class. [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=ArgumentsSource_example] ---- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=ArgumentsProvider_example] ---- If you wish to implement a custom `ArgumentsProvider` that also consumes an annotation (like built-in providers such as `{ValueArgumentsProvider}` or `{CsvArgumentsProvider}`), you have the possibility to extend the `{AnnotationBasedArgumentsProvider}` class. Moreover, `ArgumentsProvider` implementations may declare constructor parameters in case they need to be resolved by a registered `ParameterResolver` as demonstrated in the following example. [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=ArgumentsProviderWithConstructorInjection_example] ---- [[repeatable-sources]] === Multiple sources using repeatable annotations Repeatable annotations provide a convenient way to specify multiple sources from different providers. [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=repeatable_annotations] ---- Following the above parameterized test, a test case will run for each argument: ---- [1] foo [2] bar ---- The following annotations are repeatable: * `@ValueSource` * `@EnumSource` * `@MethodSource` * `@FieldSource` * `@CsvSource` * `@CsvFileSource` * `@ArgumentsSource` [[argument-count-validation]] == Argument Count Validation By default, when an arguments source provides more arguments than the test method needs, those additional arguments are ignored and the test executes as usual. This can lead to bugs where arguments are never passed to the parameterized class or method. To prevent this, you can set argument count validation to 'strict'. Then, any additional arguments will cause an error instead. To change this behavior for all tests, set the `junit.jupiter.params.argumentCountValidation` xref:running-tests/configuration-parameters.adoc[configuration parameter] to `strict`. To change this behavior for a single parameterized class or test method, use the `argumentCountValidation` attribute of the `@ParameterizedClass` or `@ParameterizedTest` annotation: [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=argument_count_validation] ---- [[argument-conversion]] == Argument Conversion [[argument-conversion-widening]] === Widening Conversion JUnit Jupiter supports https://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.1.2[Widening Primitive Conversion] for arguments supplied to a `@ParameterizedClass` or `@ParameterizedTest`. For example, a parameterized class or test method annotated with `@ValueSource(ints = { 1, 2, 3 })` can be declared to accept not only an argument of type `int` but also an argument of type `long`, `float`, or `double`. [[argument-conversion-implicit]] === Implicit Conversion To support use cases like `@CsvSource`, JUnit Jupiter provides a number of built-in implicit type converters. The conversion process depends on the declared type of each method parameter. For example, if a `@ParameterizedClass` or `@ParameterizedTest` declares a parameter of type `TimeUnit` and the actual type supplied by the declared source is a `String`, the string will be automatically converted into the corresponding `TimeUnit` enum constant. [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=implicit_conversion_example] ---- `String` instances are implicitly converted to the following target types. NOTE: Decimal, hexadecimal, and octal `String` literals will be converted to their integral types: `byte`, `short`, `int`, `long`, and their boxed counterparts. [[argument-conversion-implicit-table]] [cols="10,90"] |=== | Target Type | Example | `boolean`/`Boolean` | `"true"` -> `true` _(only accepts values 'true' or 'false', case-insensitive)_ | `byte`/`Byte` | `"15"`, `"0xF"`, or `"017"` -> `(byte) 15` | `char`/`Character` | `"o"` -> `'o'` | `short`/`Short` | `"15"`, `"0xF"`, or `"017"` -> `(short) 15` | `int`/`Integer` | `"15"`, `"0xF"`, or `"017"` -> `15` | `long`/`Long` | `"15"`, `"0xF"`, or `"017"` -> `15L` | `float`/`Float` | `"1.0"` -> `1.0f` | `double`/`Double` | `"1.0"` -> `1.0d` | `Enum` subclass | `"SECONDS"` -> `TimeUnit.SECONDS` | `java.io.File` | `"/path/to/file"` -> `new File("/path/to/file")` | `java.lang.Class` | `"java.lang.Integer"` -> `java.lang.Integer.class` _(use `$` for nested classes: `"java.lang.Thread$State"`)_ | `java.lang.Class` | `"byte"` -> `byte.class` _(primitive types are supported)_ | `java.lang.Class` | `"char[]"` -> `char[].class` _(array types are supported)_ | `java.math.BigDecimal` | `"123.456e789"` -> `new BigDecimal("123.456e789")` | `java.math.BigInteger` | `"1234567890123456789"` -> `new BigInteger("1234567890123456789")` | `java.net.URI` | `"https://junit.org/"` -> `URI.create("https://junit.org/")` | `java.net.URL` | `"https://junit.org/"` -> `URI.create("https://junit.org/").toURL()` | `java.nio.charset.Charset` | `"UTF-8"` -> `Charset.forName("UTF-8")` | `java.nio.file.Path` | `"/path/to/file"` -> `Path.of("/path/to/file")` | `java.time.Duration` | `"PT3S"` -> `Duration.ofSeconds(3)` | `java.time.Instant` | `"1970-01-01T00:00:00Z"` -> `Instant.ofEpochMilli(0)` | `java.time.LocalDateTime` | `"2017-03-14T12:34:56.789"` -> `LocalDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000)` | `java.time.LocalDate` | `"2017-03-14"` -> `LocalDate.of(2017, 3, 14)` | `java.time.LocalTime` | `"12:34:56.789"` -> `LocalTime.of(12, 34, 56, 789_000_000)` | `java.time.MonthDay` | `"--03-14"` -> `MonthDay.of(3, 14)` | `java.time.OffsetDateTime` | `"2017-03-14T12:34:56.789Z"` -> `OffsetDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000, ZoneOffset.UTC)` | `java.time.OffsetTime` | `"12:34:56.789Z"` -> `OffsetTime.of(12, 34, 56, 789_000_000, ZoneOffset.UTC)` | `java.time.Period` | `"P2M6D"` -> `Period.of(0, 2, 6)` | `java.time.YearMonth` | `"2017-03"` -> `YearMonth.of(2017, 3)` | `java.time.Year` | `"2017"` -> `Year.of(2017)` | `java.time.ZonedDateTime` | `"2017-03-14T12:34:56.789Z"` -> `ZonedDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000, ZoneOffset.UTC)` | `java.time.ZoneId` | `"Europe/Berlin"` -> `ZoneId.of("Europe/Berlin")` | `java.time.ZoneOffset` | `"+02:30"` -> `ZoneOffset.ofHoursMinutes(2, 30)` | `java.util.Currency` | `"JPY"` -> `Currency.getInstance("JPY")` | `java.util.Locale` | `"en-US"` -> `Locale.forLanguageTag("en-US")` | `java.util.UUID` | `"d043e930-7b3b-48e3-bdbe-5a3ccfb833db"` -> `UUID.fromString("d043e930-7b3b-48e3-bdbe-5a3ccfb833db")` |=== [[argument-conversion-implicit-fallback]] ==== Fallback String-to-Object Conversion In addition to implicit conversion from strings to the target types listed in the above table, JUnit Jupiter also provides a fallback mechanism for automatic conversion from a `String` to a given target type if the target type declares a suitable _factory method_ or _factory constructor_ as defined below. - __factory method__: a non-private, `static` method declared in the target type that accepts either a single `String` argument or a single `CharSequence` argument and returns an instance of the target type. The name of the method can be arbitrary and need not follow any particular convention. - __factory constructor__: a non-private constructor in the target type that accepts a either a single `String` argument or a single `CharSequence` argument. Note that the target type must be declared as either a top-level class or as a `static` nested class. If there are multiple _factory methods_ or _factory constructors_, matching proceeds in the following order: 1. A single _factory method_ accepting a `String` argument. 2. A single _factory constructor_ accepting a `String` argument. 3. A single _factory method_ accepting a `CharSequence` argument. 4. A single _factory constructor_ accepting a `CharSequence` argument. 5. A single _factory method_ accepting a `String` argument once all `@Deprecated` factory methods have been removed from the set of methods being considered. 6. A single _factory method_ accepting a `CharSequence` argument once all `@Deprecated` factory methods have been removed from the set of methods being considered. For example, in the following `@ParameterizedTest` method, the `Book` argument will be created by invoking the `Book.fromTitle(String)` factory method and passing `"42 Cats"` as the title of the book. [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=implicit_fallback_conversion_example] ---- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=implicit_fallback_conversion_example_Book] ---- [[argument-conversion-explicit]] === Explicit Conversion Instead of relying on implicit argument conversion, you may explicitly specify an `{ArgumentConverter}` to use for a certain parameter using the `@ConvertWith` annotation like in the following example. Note that an implementation of `ArgumentConverter` must be declared as either a top-level class or as a `static` nested class. [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=explicit_conversion_example] ---- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=explicit_conversion_example_ToStringArgumentConverter] ---- If the converter is only meant to convert one type to another, you can extend `TypedArgumentConverter` to avoid boilerplate type checks. [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=explicit_conversion_example_TypedArgumentConverter] ---- Explicit argument converters are meant to be implemented by test and extension authors. Thus, `junit-jupiter-params` only provides a single explicit argument converter that may also serve as a reference implementation: `JavaTimeArgumentConverter`. It is used via the composed annotation `JavaTimeConversionPattern`. [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=explicit_java_time_converter] ---- If you wish to implement a custom `{ArgumentConverter}` that also consumes an annotation (like `JavaTimeArgumentConverter`), you have the possibility to extend the `{AnnotationBasedArgumentConverter}` class. [[argument-aggregation]] == Argument Aggregation By default, each _argument_ provided to a `@ParameterizedClass` or `@ParameterizedTest` corresponds to a single method parameter. Consequently, argument sources which are expected to supply a large number of arguments can lead to large constructor or method signatures, respectively. In such cases, an `{ArgumentsAccessor}` can be used instead of multiple parameters. Using this API, you can access the provided arguments through a single argument passed to your test method. In addition, type conversion is supported as discussed in <>. Besides, you can retrieve the current test invocation index with `ArgumentsAccessor.getInvocationIndex()`. [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=ArgumentsAccessor_example] ---- _An instance of `ArgumentsAccessor` is automatically injected into any parameter of type `ArgumentsAccessor`._ [[argument-aggregation-custom]] === Custom Aggregators Apart from direct access to the arguments of a `@ParameterizedClass` or `@ParameterizedTest` using an `ArgumentsAccessor`, JUnit Jupiter also supports the usage of custom, reusable _aggregators_. To use a custom aggregator, implement the `{ArgumentsAggregator}` interface and register it via the `@AggregateWith` annotation on a compatible parameter of the `@ParameterizedClass` or `@ParameterizedTest`. The result of the aggregation will then be provided as an argument for the corresponding parameter when the parameterized test is invoked. Note that an implementation of `ArgumentsAggregator` must be declared as either a top-level class or as a `static` nested class. [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=ArgumentsAggregator_example] ---- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=ArgumentsAggregator_example_PersonAggregator] ---- If you find yourself repeatedly declaring `@AggregateWith(MyTypeAggregator.class)` for multiple parameterized classes or methods across your codebase, you may wish to create a custom _composed annotation_ such as `@CsvToMyType` that is meta-annotated with `@AggregateWith(MyTypeAggregator.class)`. The following example demonstrates this in action with a custom `@CsvToPerson` annotation. [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=ArgumentsAggregator_with_custom_annotation_example] ---- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=ArgumentsAggregator_with_custom_annotation_example_CsvToPerson] ---- [[display-names]] == Customizing Display Names By default, the display name of a parameterized class or test invocation contains the invocation index and a comma-separated list of the `String` representations of all arguments for that specific invocation. If parameter names are present in the bytecode, each argument will be preceded by its parameter name and an equals sign (unless the argument is only available via an `ArgumentsAccessor` or `ArgumentAggregator`) – for example, `firstName = "Jane"`. [TIP] ==== To ensure that parameter names are present in the bytecode, test code must be compiled with the `-parameters` compiler flag for Java or with the `-java-parameters` compiler flag for Kotlin. ==== However, you can customize invocation display names via the `name` attribute of the `@ParameterizedClass` or `@ParameterizedTest` annotation as in the following example. ====== [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=custom_display_names] ---- When executing the above method using the `ConsoleLauncher` you will see output similar to the following. .... Display name of container ✔ ├─ 1 ==> the rank of "apple" is "1" ✔ ├─ 2 ==> the rank of "banana" is "2" ✔ └─ 3 ==> the rank of "lemon, lime" is "3" ✔ .... ====== [NOTE] ==== Please note that `name` is a `MessageFormat` pattern. Thus, a single quote (`'`) needs to be represented as a doubled single quote (`''`) in order to be displayed. ==== The following placeholders are supported within custom display names. [cols="20,80"] |=== | Placeholder | Description | `\{displayName}` | the display name of the method | `\{index}` | the current invocation index (1-based) | `\{arguments}` | the complete, comma-separated arguments list | `\{argumentsWithNames}` | the complete, comma-separated arguments list with parameter names | `\{argumentSetName}` | the name of the argument set | `\{argumentSetNameOrArgumentsWithNames}` | `\{argumentSetName}` or `\{argumentsWithNames}`, depending on how the arguments are supplied | `\{0}`, `\{1}`, ... | an individual argument |=== NOTE: When including arguments in display names, their string representations are truncated if they exceed the configured maximum length. The limit is configurable via the `junit.jupiter.params.displayname.argument.maxlength` configuration parameter and defaults to 512 characters. When using `@MethodSource`, `@FieldSource`, or `@ArgumentsSource`, you can provide custom names for individual arguments or custom names for entire sets of arguments. Use the `{Named}` API to provide a custom name for an individual argument, and the custom name will be used if the argument is included in the invocation display name, like in the example below. ====== [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=named_arguments] ---- When executing the above method using the `ConsoleLauncher` you will see output similar to the following. .... A parameterized test with named arguments ✔ ├─ 1: An important file ✔ └─ 2: Another file ✔ .... ====== [NOTE] ==== Note that `arguments(Object...)` is a static factory method defined in the `org.junit.jupiter.params.provider.Arguments` interface. Similarly, `named(String, Object)` is a static factory method defined in the `org.junit.jupiter.api.Named` interface. ==== Use the `ArgumentSet` API to provide a custom name for the entire set of arguments, and the custom name will be used as the display name, like in the example below. ====== [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=named_argument_set] ---- When executing the above method using the `ConsoleLauncher` you will see output similar to the following. .... A parameterized test with named argument sets ✔ ├─ [1] Important files ✔ └─ [2] Other files ✔ .... ====== [NOTE] ==== Note that `argumentSet(String, Object...)` is a static factory method defined in the `org.junit.jupiter.params.provider.Arguments` interface. ==== [[display-names-quoted-text]] === Quoted Text-based Arguments As of JUnit Jupiter 6.0, text-based arguments in display names for parameterized tests are quoted by default. In this context, any `CharSequence` (such as a `String`) or `Character` is considered text. A `CharSequence` is wrapped in double quotes (`"`), and a `Character` is wrapped in single quotes (`'`). Special characters will be escaped in the quoted text. For example, carriage returns and line feeds will be escaped as `\\r` and `\\n`, respectively. [TIP] ==== This feature can be disabled by setting the `quoteTextArguments` attributes in `@ParameterizedClass` and `@ParameterizedTest` to `false`. ==== For example, given a string argument `"line 1\nline 2"`, the physical representation in the display name will be `"\"line 1\\nline 2\""` which is printed as `"line 1\nline 2"`. Similarly, given a string argument `"\t"`, the physical representation in the display name will be `"\"\\t\""` which is printed as `"\t"` instead of a blank string or invisible tab character. The same applies for a character argument `'\t'`, whose physical representation in the display name would be `"'\\t'"` which is printed as `'\t'`. For a concrete example, if you run the first `nullValuesAndBlankStrings(String text)` parameterized test method from the <> section above, the following display names are generated. ---- [1] text = null [2] text = "" [3] text = " " [4] text = " " [5] text = "\t" [6] text = "\n" ---- If you run the first `testWithCsvSource(String fruit, int rank)` parameterized test method from the <> section above, the following display names are generated. ---- [1] fruit = "apple", rank = "1" [2] fruit = "banana", rank = "2" [3] fruit = "lemon, lime", rank = "0xF1" [4] fruit = "strawberry", rank = "700_000" ---- [NOTE] ==== The original source arguments are quoted when generating a display name, and this occurs before any implicit or explicit argument conversion is performed. For example, if a parameterized test accepts `3.14` as a `float` argument that was converted from `"3.14"` as an input string, `"3.14"` will be present in the display name instead of `3.14`. You can see the effect of this with the `rank` values in the above example. ==== [[display-names-default-pattern]] === Default Display Name Pattern If you'd like to set a default name pattern for all parameterized classes and tests in your project, you can declare the `junit.jupiter.params.displayname.default` configuration parameter in the `junit-platform.properties` file as demonstrated in the following example (see xref:running-tests/configuration-parameters.adoc[] for other options). [source,properties,indent=0] ---- junit.jupiter.params.displayname.default = {index} ---- [[display-names-precedence-rules]] === Precedence Rules The display name for a parameterized class or test is determined according to the following precedence rules: 1. `name` attribute in `@ParameterizedClass` or `@ParameterizedTest`, if present 2. value of the `junit.jupiter.params.displayname.default` configuration parameter, if present 3. `DEFAULT_DISPLAY_NAME` constant defined in `org.junit.jupiter.params.ParameterizedInvocationConstants` [[lifecycle-interop]] == Lifecycle and Interoperability [[lifecycle-interop-methods]] === Parameterized Tests Each invocation of a parameterized test has the same lifecycle as a regular `@Test` method. For example, `@BeforeEach` methods will be executed before each invocation. Similar to xref:writing-tests/dynamic-tests.adoc[], invocations will appear one by one in the test tree of an IDE. You may at will mix regular `@Test` methods and `@ParameterizedTest` methods within the same test class. You may use `ParameterResolver` extensions with `@ParameterizedTest` methods. However, method parameters that are resolved by argument sources need to come first in the parameter list. Since a test class may contain regular tests as well as parameterized tests with different parameter lists, values from argument sources are not resolved for lifecycle methods (for example, `@BeforeEach`) and test class constructors. [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=ParameterResolver_example] ---- [[lifecycle-interop-classes]] === Parameterized Classes Each invocation of a parameterized class has the same lifecycle as a regular test class. For example, `@BeforeAll` methods will be executed _once_ before all invocations and `@BeforeEach` methods will be executed before each _test method_ invocation. Similar to xref:writing-tests/dynamic-tests.adoc[], invocations will appear one by one in the test tree of an IDE. You may use `ParameterResolver` extensions with `@ParameterizedClass` constructors. However, if constructor injection is used, constructor parameters that are resolved by argument sources need to come first in the parameter list. Values from argument sources are not resolved for regular lifecycle methods (for example, `@BeforeEach`). In addition to regular lifecycle methods, parameterized classes may declare `{BeforeParameterizedClassInvocation}` and `{AfterParameterizedClassInvocation}` lifecycle methods that are called once before/after each invocation of the parameterized class. These methods must be `static` unless the parameterized class is configured to use `@TestInstance(Lifecycle.PER_CLASS)` (see xref:writing-tests/test-instance-lifecycle.adoc[]). These lifecycle methods may optionally declare parameters that are resolved depending on the setting of the `injectArguments` annotation attribute. If it is set to `false`, the parameters must be resolved by other registered {ParameterResolver} extensions. If the attribute is set to `true` (the default), the method may declare parameters that match the arguments of the parameterized class (see the Javadoc of `{BeforeParameterizedClassInvocation}` and `{AfterParameterizedClassInvocation}` for details). This may, for example, be used to initialize the used arguments as demonstrated by the following example. [source,java,indent=0] .Using parameterized class lifecycle methods ---- include::example$java/example/ParameterizedLifecycleDemo.java[tags=example] ---- <1> Initialization of the argument _before_ each invocation of the parameterized class <2> Usage of the previously initialized argument in a test method <3> Validation and cleanup of the argument _after_ each invocation of the parameterized class ================================================ FILE: documentation/modules/ROOT/pages/writing-tests/repeated-tests.adoc ================================================ = Repeated Tests JUnit Jupiter provides the ability to repeat a test a specified number of times by annotating a method with `@RepeatedTest` and specifying the total number of repetitions desired. Each invocation of a repeated test behaves like the execution of a regular `@Test` method with full support for the same lifecycle callbacks and extensions. The following example demonstrates how to declare a test named `repeatedTest()` that will be automatically repeated 10 times. [tabs] ==== Java:: + -- [source,java] ---- @RepeatedTest(10) void repeatedTest() { // ... } ---- -- Kotlin:: + -- [source,kotlin] ---- @RepeatedTest(10) fun repeatedTest() { // ... } ---- -- ==== `@RepeatedTest` can be configured with a failure threshold which signifies the number of failures after which remaining repetitions will be automatically skipped. Set the `failureThreshold` attribute to a positive number less than the total number of repetitions in order to skip the invocations of remaining repetitions after the specified number of failures has been encountered. For example, if you are using `@RepeatedTest` to repeatedly invoke a test that you suspect to be _flaky_, a single failure is sufficient to demonstrate that the test is flaky, and there is no need to invoke the remaining repetitions. To support that specific use case, set `failureThreshold = 1`. You can alternatively set the threshold to a number greater than 1 depending on your use case. By default, the `failureThreshold` attribute is set to `Integer.MAX_VALUE`, signaling that no failure threshold will be applied, which effectively means that the specified number of repetitions will be invoked regardless of whether any repetitions fail. WARNING: If the repetitions of a `@RepeatedTest` method are executed in parallel, no guarantees can be made regarding the failure threshold. It is therefore recommended that a `@RepeatedTest` method be annotated with `@Execution(SAME_THREAD)` when parallel execution is configured. See xref:writing-tests/parallel-execution.adoc[] for further details. In addition to specifying the number of repetitions and failure threshold, a custom display name can be configured for each repetition via the `name` attribute of the `@RepeatedTest` annotation. Furthermore, the display name can be a pattern composed of a combination of static text and dynamic placeholders. The following placeholders are currently supported. - `+{displayName}+`: display name of the `@RepeatedTest` method - `+{currentRepetition}+`: the current repetition count - `+{totalRepetitions}+`: the total number of repetitions The default display name for a given repetition is generated based on the following pattern: `"repetition +{currentRepetition}+ of +{totalRepetitions}+"`.Thus, the display names for individual repetitions of the previous `repeatedTest()` example would be: `repetition 1 of 10`, `repetition 2 of 10`, etc.If you would like the display name of the `@RepeatedTest` method included in the name of each repetition, you can define your own custom pattern or use the predefined `RepeatedTest.LONG_DISPLAY_NAME` pattern.The latter is equal to `"+{displayName}+ :: repetition +{currentRepetition}+ of +{totalRepetitions}+"` which results in display names for individual repetitions like `repeatedTest() :: repetition 1 of 10`, `repeatedTest() :: repetition 2 of 10`, etc. In order to retrieve information about the current repetition, the total number of repetitions, the number of repetitions that have failed, and the failure threshold, a developer can choose to have an instance of `{RepetitionInfo}` injected into a `@RepeatedTest`, `@BeforeEach`, or `@AfterEach` method. [[examples]] == Repeated Test Examples The `RepeatedTestsDemo` class at the end of this section demonstrates several examples of repeated tests. The `repeatedTest()` method is identical to the example from the previous section; whereas, `repeatedTestWithRepetitionInfo()` demonstrates how to have an instance of `RepetitionInfo` injected into a test to access the total number of repetitions for the current repeated test. `repeatedTestWithFailureThreshold()` demonstrates how to set a failure threshold and simulates an unexpected failure for every second repetition.The resulting behavior can be viewed in the `ConsoleLauncher` output at the end of this section. The next two methods demonstrate how to include a custom `@DisplayName` for the `@RepeatedTest` method in the display name of each repetition. `customDisplayName()` combines a custom display name with a custom pattern and then uses `TestInfo` to verify the format of the generated display name. `Repeat!` is the `+{displayName}+` which comes from the `@DisplayName` declaration, and `1/1` comes from `+{currentRepetition}+/+{totalRepetitions}+`.In contrast, `customDisplayNameWithLongPattern()` uses the aforementioned predefined `RepeatedTest.LONG_DISPLAY_NAME` pattern. `repeatedTestInGerman()` demonstrates the ability to translate display names of repeated tests into foreign languages -- in this case German, resulting in names for individual repetitions such as: `Wiederholung 1 von 5`, `Wiederholung 2 von 5`, etc. Since the `beforeEach()` method is annotated with `@BeforeEach` it will get executed before each repetition of each repeated test. By having the `TestInfo` and `RepetitionInfo` injected into the method, we see that it's possible to obtain information about the currently executing repeated test. Executing `RepeatedTestsDemo` with the `INFO` log level enabled results in the following output. .... INFO: About to execute repetition 1 of 10 for repeatedTest INFO: About to execute repetition 2 of 10 for repeatedTest INFO: About to execute repetition 3 of 10 for repeatedTest INFO: About to execute repetition 4 of 10 for repeatedTest INFO: About to execute repetition 5 of 10 for repeatedTest INFO: About to execute repetition 6 of 10 for repeatedTest INFO: About to execute repetition 7 of 10 for repeatedTest INFO: About to execute repetition 8 of 10 for repeatedTest INFO: About to execute repetition 9 of 10 for repeatedTest INFO: About to execute repetition 10 of 10 for repeatedTest INFO: About to execute repetition 1 of 5 for repeatedTestWithRepetitionInfo INFO: About to execute repetition 2 of 5 for repeatedTestWithRepetitionInfo INFO: About to execute repetition 3 of 5 for repeatedTestWithRepetitionInfo INFO: About to execute repetition 4 of 5 for repeatedTestWithRepetitionInfo INFO: About to execute repetition 5 of 5 for repeatedTestWithRepetitionInfo INFO: About to execute repetition 1 of 8 for repeatedTestWithFailureThreshold INFO: About to execute repetition 2 of 8 for repeatedTestWithFailureThreshold INFO: About to execute repetition 3 of 8 for repeatedTestWithFailureThreshold INFO: About to execute repetition 4 of 8 for repeatedTestWithFailureThreshold INFO: About to execute repetition 1 of 1 for customDisplayName INFO: About to execute repetition 1 of 1 for customDisplayNameWithLongPattern INFO: About to execute repetition 1 of 5 for repeatedTestInGerman INFO: About to execute repetition 2 of 5 for repeatedTestInGerman INFO: About to execute repetition 3 of 5 for repeatedTestInGerman INFO: About to execute repetition 4 of 5 for repeatedTestInGerman INFO: About to execute repetition 5 of 5 for repeatedTestInGerman .... [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/RepeatedTestsDemo.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/RepeatedTestsDemo.kt[tags=user_guide] ---- -- ==== When using the `ConsoleLauncher` with the unicode theme enabled, execution of `RepeatedTestsDemo` results in the following output to the console. .... ├─ RepeatedTestsDemo ✔ │ ├─ repeatedTest() ✔ │ │ ├─ repetition 1 of 10 ✔ │ │ ├─ repetition 2 of 10 ✔ │ │ ├─ repetition 3 of 10 ✔ │ │ ├─ repetition 4 of 10 ✔ │ │ ├─ repetition 5 of 10 ✔ │ │ ├─ repetition 6 of 10 ✔ │ │ ├─ repetition 7 of 10 ✔ │ │ ├─ repetition 8 of 10 ✔ │ │ ├─ repetition 9 of 10 ✔ │ │ └─ repetition 10 of 10 ✔ │ ├─ repeatedTestWithRepetitionInfo(RepetitionInfo) ✔ │ │ ├─ repetition 1 of 5 ✔ │ │ ├─ repetition 2 of 5 ✔ │ │ ├─ repetition 3 of 5 ✔ │ │ ├─ repetition 4 of 5 ✔ │ │ └─ repetition 5 of 5 ✔ │ ├─ repeatedTestWithFailureThreshold(RepetitionInfo) ✔ │ │ ├─ repetition 1 of 8 ✔ │ │ ├─ repetition 2 of 8 ✘ Boom! │ │ ├─ repetition 3 of 8 ✔ │ │ ├─ repetition 4 of 8 ✘ Boom! │ │ ├─ repetition 5 of 8 ↷ Failure threshold [2] exceeded │ │ ├─ repetition 6 of 8 ↷ Failure threshold [2] exceeded │ │ ├─ repetition 7 of 8 ↷ Failure threshold [2] exceeded │ │ └─ repetition 8 of 8 ↷ Failure threshold [2] exceeded │ ├─ Repeat! ✔ │ │ └─ Repeat! 1/1 ✔ │ ├─ Details... ✔ │ │ └─ Details... :: repetition 1 of 1 ✔ │ └─ repeatedTestInGerman() ✔ │ ├─ Wiederholung 1 von 5 ✔ │ ├─ Wiederholung 2 von 5 ✔ │ ├─ Wiederholung 3 von 5 ✔ │ ├─ Wiederholung 4 von 5 ✔ │ └─ Wiederholung 5 von 5 ✔ .... ================================================ FILE: documentation/modules/ROOT/pages/writing-tests/tagging-and-filtering.adoc ================================================ = Tagging and Filtering Test classes and methods can be tagged via the `@Tag` annotation. Those tags can later be used to filter xref:running-tests/intro.adoc[test discovery and execution]. Please refer to the xref:running-tests/tags.adoc[] section for more information about tag support in the JUnit Platform. [source,java,indent=0] ---- include::example$java/example/TaggingDemo.java[tags=user_guide] ---- TIP: See xref:writing-tests/annotations.adoc#annotations[Meta-Annotations and Composed Annotations] for examples demonstrating how to create custom annotations for tags. ================================================ FILE: documentation/modules/ROOT/pages/writing-tests/test-classes-and-methods.adoc ================================================ = Test Classes and Methods Test methods and lifecycle methods may be declared locally within the current test class, inherited from superclasses, or inherited from interfaces (see xref:writing-tests/test-interfaces-and-default-methods.adoc[]). In addition, test methods and lifecycle methods must not be `abstract` and must not return a value (except `@TestFactory` methods which are required to return a value). [NOTE] .Class and method visibility ==== Test classes, test methods, and lifecycle methods are not required to be `public`, but they must _not_ be `private`. It is generally recommended to omit the `public` modifier for test classes, test methods, and lifecycle methods unless there is a technical reason for doing so – for example, when a test class is extended by a test class in another package. Another technical reason for making classes and methods `public` is to simplify testing on the module path when using the Java Module System. ==== [NOTE] .Field and method inheritance ==== Fields in test classes are inherited. For example, a `@TempDir` field from a superclass will always be applied in a subclass. Test methods and lifecycle methods are inherited unless they are overridden according to the visibility rules of the Java language. For example, a `@Test` method from a superclass will always be applied in a subclass unless the subclass explicitly overrides the method. Similarly, if a package-private `@Test` method is declared in a superclass that resides in a different package than the subclass, that `@Test` method will always be applied in the subclass since the subclass cannot override a package-private method from a superclass in a different package. See also: xref:extensions/supported-utilities-in-extensions.adoc#search-semantics[Field and Method Search Semantics] ==== The following test class demonstrates the use of `@Test` methods and all supported lifecycle methods. For further information on runtime semantics, see xref:writing-tests/test-execution-order.adoc[] and xref:extensions/relative-execution-order-of-user-code-and-extensions.adoc#wrapping-behavior[Wrapping Behavior of Callbacks]. .A standard test class [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/StandardTests.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/StandardTests.kt[tags=user_guide] ---- -- ==== It is also possible to use Java `record` classes as test classes as illustrated by the following example. [source,java,indent=0] .A test class written as a Java record ---- include::example$java/example/MyFirstJUnitJupiterRecordTests.java[tags=user_guide] ---- [[kotlin-coroutines]] == Kotlin Coroutines Test methods and lifecycle methods may be written in Kotlin and may optionally use the `suspend` keyword for testing code using coroutines. [source,kotlin] .A test class written in Kotlin ---- include::example$kotlin/example/KotlinCoroutinesDemo.kt[tags=user_guide] ---- NOTE: Using suspending functions as test or lifecycle methods requires https://central.sonatype.com/artifact/org.jetbrains.kotlin/kotlin-stdlib[`kotlin-stdlib`], https://central.sonatype.com/artifact/org.jetbrains.kotlin/kotlin-reflect[`kotlin-reflect`], and https://central.sonatype.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core[`kotlinx-coroutines-core`] to be present on the classpath or module path. When using `suspend` test or lifecycle methods, JUnit internally uses `runBlocking` to execute them. This is sufficient for simple coroutine-based tests. However, `runBlocking` does not support skipping calls to `delay` and does not provide control over virtual time or dispatchers. As an alternative, tests can be written as regular `@Test` methods and wrapped in `runTest` from https://central.sonatype.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-test[`kotlinx-coroutines-test`] to support skipping delays during tests and gain control over virtual time and dispatchers. [source,kotlin] ---- include::example$kotlin/example/KotlinCoroutinesRunTestDemo.kt[tags=user_guide] ---- ================================================ FILE: documentation/modules/ROOT/pages/writing-tests/test-execution-order.adoc ================================================ = Test Execution Order By default, test classes and methods will be ordered using an algorithm that is deterministic but intentionally nonobvious. This ensures that subsequent runs of a test suite execute test classes and test methods in the same order, thereby allowing for repeatable builds. NOTE: See xref:writing-tests/definitions.adoc[] for a definition of _test method_ and _test class_. [[methods]] == Method Order Although true _unit tests_ typically should not rely on the order in which they are executed, there are times when it is necessary to enforce a specific test method execution order -- for example, when writing _integration tests_ or _functional tests_ where the sequence of the tests is important, especially in conjunction with `@TestInstance(Lifecycle.PER_CLASS)`. To control the order in which test methods are executed, annotate your test class or test interface with `{TestMethodOrder}` and specify the desired `{MethodOrderer}` implementation. You can implement your own custom `MethodOrderer` or use one of the following built-in `MethodOrderer` implementations. * `{MethodOrderer_DisplayName}`: sorts test methods _alphanumerically_ based on their display names (see xref:writing-tests/display-names.adoc#generator-precedence-rules[display name generation precedence rules]) * `{MethodOrderer_MethodName}`: sorts test methods _alphanumerically_ based on their names and formal parameter lists * `{MethodOrderer_OrderAnnotation}`: sorts test methods _numerically_ based on values specified via the `{Order}` annotation * `{MethodOrderer_Random}`: orders test methods _pseudo-randomly_ and supports configuration of a custom _seed_ The `MethodOrderer` configured on a test class is inherited by the `@Nested` test classes it contains, recursively. If you want to avoid that a `@Nested` test class uses the same `MethodOrderer` as its enclosing class, you can specify `{MethodOrderer_Default}` together with `{TestMethodOrder}`. NOTE: See also: xref:extensions/relative-execution-order-of-user-code-and-extensions.adoc#wrapping-behavior[Wrapping Behavior of Callbacks] The following example demonstrates how to guarantee that test methods are executed in the order specified via the `@Order` annotation. [source,java,indent=0] ---- include::example$java/example/OrderedTestsDemo.java[tags=user_guide] ---- [[methods-default]] === Setting the Default Method Orderer You can use the `junit.jupiter.testmethod.order.default` xref:running-tests/configuration-parameters.adoc[configuration parameter] to specify the fully qualified class name of the `{MethodOrderer}` you would like to use by default. Just like for the orderer configured via the `{TestMethodOrder}` annotation, the supplied class has to implement the `MethodOrderer` interface. The default orderer will be used for all tests unless the `@TestMethodOrder` annotation is present on an enclosing test class or test interface. For example, to use the `{MethodOrderer_OrderAnnotation}` method orderer by default, you should set the configuration parameter to the corresponding fully qualified class name (e.g., in `src/test/resources/junit-platform.properties`): [source,properties,indent=0] ---- junit.jupiter.testmethod.order.default = \ org.junit.jupiter.api.MethodOrderer$OrderAnnotation ---- Similarly, you can specify the fully qualified name of any custom class that implements `MethodOrderer`. [[classes]] == Class Order Although test classes typically should not rely on the order in which they are executed, there are times when it is desirable to enforce a specific test class execution order. You may wish to execute test classes in a random order to ensure there are no accidental dependencies between test classes, or you may wish to order test classes to optimize build time as outlined in the following scenarios. * Run previously failing tests and faster tests first: "fail fast" mode * With parallel execution enabled, schedule longer tests first: "shortest test plan execution duration" mode * Various other use cases To configure test class execution order _globally_ for the entire test suite, use the `junit.jupiter.testclass.order.default` xref:running-tests/configuration-parameters.adoc[configuration parameter] to specify the fully qualified class name of the `{ClassOrderer}` you would like to use. The supplied class must implement the `ClassOrderer` interface. You can implement your own custom `ClassOrderer` or use one of the following built-in `ClassOrderer` implementations. * `{ClassOrderer_ClassName}`: sorts test classes _alphanumerically_ based on their fully qualified class names * `{ClassOrderer_DisplayName}`: sorts test classes _alphanumerically_ based on their display names (see xref:writing-tests/display-names.adoc#generator-precedence-rules[display name generation precedence rules]) * `{ClassOrderer_OrderAnnotation}`: sorts test classes _numerically_ based on values specified via the `{Order}` annotation * `{ClassOrderer_Random}`: orders test classes _pseudo-randomly_ and supports configuration of a custom _seed_ For example, for the `@Order` annotation to be honored on _test classes_, you should configure the `{ClassOrderer_OrderAnnotation}` class orderer using the configuration parameter with the corresponding fully qualified class name (e.g., in `src/test/resources/junit-platform.properties`): [source,properties,indent=0] ---- junit.jupiter.testclass.order.default = \ org.junit.jupiter.api.ClassOrderer$OrderAnnotation ---- The configured `ClassOrderer` will be applied to all top-level test classes (including `static` nested test classes) and `@Nested` test classes. NOTE: Top-level test classes will be ordered relative to each other; whereas, `@Nested` test classes will be ordered relative to other `@Nested` test classes sharing the same _enclosing class_. To configure test class execution order _locally_ for `@Nested` test classes, declare the `{TestClassOrder}` annotation on the enclosing class for the `@Nested` test classes you want to order, and supply a class reference to the `ClassOrderer` implementation you would like to use directly in the `@TestClassOrder` annotation. The configured `ClassOrderer` will be applied recursively to `@Nested` test classes and their `@Nested` test classes. If you want to avoid that a `@Nested` test class uses the same `ClassOrderer` as its enclosing class, you can specify `{ClassOrderer_Default}` together with `@TestClassOrder`. Note that a local `@TestClassOrder` declaration always overrides an inherited `@TestClassOrder` declaration or a `ClassOrderer` configured globally via the `junit.jupiter.testclass.order.default` configuration parameter. The following example demonstrates how to guarantee that `@Nested` test classes are executed in the order specified via the `@Order` annotation. [source,java,indent=0] ---- include::example$java/example/OrderedNestedTestClassesDemo.java[tags=user_guide] ---- ================================================ FILE: documentation/modules/ROOT/pages/writing-tests/test-instance-lifecycle.adoc ================================================ = Test Instance Lifecycle In order to allow individual test methods to be executed in isolation and to avoid unexpected side effects due to mutable test instance state, JUnit creates a new instance of each test class before executing each _test method_ (see xref:writing-tests/definitions.adoc[]). This "per-method" test instance lifecycle is the default behavior in JUnit Jupiter and is analogous to all previous versions of JUnit. NOTE: Please note that the test class will still be instantiated if a given _test method_ is _disabled_ via a xref:writing-tests/conditional-test-execution.adoc[condition] (e.g., `@Disabled`, `@DisabledOnOs`, etc.) even when the "per-method" test instance lifecycle mode is active. If you would prefer that JUnit Jupiter execute all test methods on the same test instance, annotate your test class with `@TestInstance(Lifecycle.PER_CLASS)`. When using this mode, a new test instance will be created once per test class. Thus, if your test methods rely on state stored in instance variables, you may need to reset that state in `@BeforeEach` or `@AfterEach` methods. The "per-class" mode has some additional benefits over the default "per-method" mode. Specifically, with the "per-class" mode it becomes possible to declare `@BeforeAll` and `@AfterAll` on non-static methods as well as on interface `default` methods. If you are authoring tests using the Kotlin programming language, you may also find it easier to implement non-static `@BeforeAll` and `@AfterAll` lifecycle methods as well as `@MethodSource` factory methods by switching to the "per-class" test instance lifecycle mode. [[default]] == Changing the Default Test Instance Lifecycle If a test class or test interface is not annotated with `@TestInstance`, JUnit Jupiter will use a _default_ lifecycle mode. The standard _default_ mode is `PER_METHOD`; however, it is possible to change the _default_ for the execution of an entire test plan. To change the default test instance lifecycle mode, set the `junit.jupiter.testinstance.lifecycle.default` _configuration parameter_ to the name of an enum constant defined in `TestInstance.Lifecycle`, ignoring case. This can be supplied as a JVM system property, as a _configuration parameter_ in the `LauncherDiscoveryRequest` that is passed to the `Launcher`, or via the JUnit Platform configuration file (see xref:running-tests/configuration-parameters.adoc[] for details). For example, to set the default test instance lifecycle mode to `Lifecycle.PER_CLASS`, you can start your JVM with the following system property. `-Djunit.jupiter.testinstance.lifecycle.default=per_class` Note, however, that setting the default test instance lifecycle mode via the JUnit Platform configuration file is a more robust solution since the configuration file can be checked into a version control system along with your project and can therefore be used within IDEs and your build software. To set the default test instance lifecycle mode to `Lifecycle.PER_CLASS` via the JUnit Platform configuration file, create a file named `junit-platform.properties` in the root of the class path (e.g., `src/test/resources`) with the following content. `junit.jupiter.testinstance.lifecycle.default = per_class` WARNING: Changing the _default_ test instance lifecycle mode can lead to unpredictable results and fragile builds if not applied consistently. For example, if the build configures "per-class" semantics as the default but tests in the IDE are executed using "per-method" semantics, that can make it difficult to debug errors that occur on the build server. It is therefore recommended to change the default in the JUnit Platform configuration file instead of via a JVM system property. ================================================ FILE: documentation/modules/ROOT/pages/writing-tests/test-interfaces-and-default-methods.adoc ================================================ = Test Interfaces and Default Methods JUnit Jupiter allows `@Test`, `@RepeatedTest`, `@ParameterizedTest`, `@TestFactory`, `@TestTemplate`, `@BeforeEach`, and `@AfterEach` to be declared on interface `default` methods. `@BeforeAll` and `@AfterAll` can either be declared on `static` methods in a test interface or on interface `default` methods _if_ the test interface or test class is annotated with `@TestInstance(Lifecycle.PER_CLASS)` (see xref:writing-tests/test-instance-lifecycle.adoc[]). Here are some examples. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/testinterface/TestLifecycleLogger.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/testinterface/TestLifecycleLogger.kt[tags=user_guide] ---- -- ==== [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/testinterface/TestInterfaceDynamicTestsDemo.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/testinterface/TestInterfaceDynamicTestsDemo.kt[tags=user_guide] ---- -- ==== `@ExtendWith` and `@Tag` can be declared on a test interface so that classes that implement the interface automatically inherit its tags and extensions. See xref:extensions/test-lifecycle-callbacks.adoc#before-after-execution[Before and After Test Execution Callbacks] for the source code of the xref:extensions/test-lifecycle-callbacks.adoc#timing-extension[TimingExtension]. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/testinterface/TimeExecutionLogger.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/testinterface/TimeExecutionLogger.kt[tags=user_guide] ---- -- ==== In your test class you can then implement these test interfaces to have them applied. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/testinterface/TestInterfaceDemo.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/testinterface/TestInterfaceDemo.kt[tags=user_guide] ---- -- ==== Running the `TestInterfaceDemo` results in output similar to the following: .... INFO example.TestLifecycleLogger - Before all tests INFO example.TestLifecycleLogger - About to execute [dynamicTestsForPalindromes()] INFO example.TimingExtension - Method [dynamicTestsForPalindromes] took 19 ms. INFO example.TestLifecycleLogger - Finished executing [dynamicTestsForPalindromes()] INFO example.TestLifecycleLogger - About to execute [isEqualValue()] INFO example.TimingExtension - Method [isEqualValue] took 1 ms. INFO example.TestLifecycleLogger - Finished executing [isEqualValue()] INFO example.TestLifecycleLogger - After all tests .... Another possible application of this feature is to write tests for interface contracts. For example, you can write tests for how implementations of `Object.equals` or `Comparable.compareTo` should behave as follows. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/defaultmethods/Testable.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/defaultmethods/Testable.kt[tags=user_guide] ---- -- ==== [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/defaultmethods/EqualsContract.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/defaultmethods/EqualsContract.kt[tags=user_guide] ---- -- ==== [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/defaultmethods/ComparableContract.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/defaultmethods/ComparableContract.kt[tags=user_guide] ---- -- ==== In your test class you can then implement both contract interfaces thereby inheriting the corresponding tests. Of course you'll have to implement the abstract methods. [tabs] ==== Java:: + -- [source,java,indent=0] ---- include::example$java/example/defaultmethods/StringTests.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin,indent=0] ---- include::example$kotlin/example/kotlin/defaultmethods/StringTests.kt[tags=user_guide] ---- -- ==== NOTE: The above tests are merely meant as examples and therefore not complete. ================================================ FILE: documentation/modules/ROOT/pages/writing-tests/test-templates.adoc ================================================ = Test Templates A `{TestTemplate}` method is not a regular test case but rather a template for a test case. As such, it is designed to be invoked multiple times depending on the number of invocation contexts returned by the registered providers. Thus, it must be used in conjunction with a registered `{TestTemplateInvocationContextProvider}` extension. Each invocation of a test template method behaves like the execution of a regular `@Test` method with full support for the same lifecycle callbacks and extensions. Please refer to xref:extensions/providing-invocation-contexts-for-test-templates.adoc[] for usage examples. NOTE: xref:writing-tests/repeated-tests.adoc[] and xref:writing-tests/parameterized-classes-and-tests.adoc[Parameterized Tests] are built-in specializations of test templates. ================================================ FILE: documentation/modules/ROOT/pages/writing-tests/timeouts.adoc ================================================ = Timeouts The `@Timeout` annotation allows one to declare that a test, test factory, test template, or lifecycle method should fail if its execution time exceeds a given duration. The time unit for the duration defaults to seconds but is configurable. The following example shows how `@Timeout` is applied to lifecycle and test methods. [tabs] ==== Java:: + -- [source,java] ---- include::example$java/example/TimeoutDemo.java[tags=user_guide] ---- -- Kotlin:: + -- [source,kotlin] ---- include::example$kotlin/example/kotlin/TimeoutDemo.kt[tags=user_guide] ---- -- ==== To apply the same timeout to all test methods within a test class and all of its `@Nested` classes, you can declare the `@Timeout` annotation at the class level. It will then be applied to all test, test factory, and test template methods within that class and its `@Nested` classes unless overridden by a `@Timeout` annotation on a specific method or `@Nested` class. Please note that `@Timeout` annotations declared at the class level are not applied to lifecycle methods. Declaring `@Timeout` on a `@TestFactory` method checks that the factory method returns within the specified duration but does not verify the execution time of each individual `DynamicTest` generated by the factory. Please use `assertTimeout()` or `assertTimeoutPreemptively()` for that purpose. If `@Timeout` is present on a `@TestTemplate` method — for example, a `@RepeatedTest` or `@ParameterizedTest` — each invocation will have the given timeout applied to it. [[thread-mode]] == Thread mode The timeout can be applied using one of the following three thread modes: `SAME_THREAD`, `SEPARATE_THREAD`, or `INFERRED`. When `SAME_THREAD` is used, the execution of the annotated method proceeds in the main thread of the test. If the timeout is exceeded, the main thread is interrupted from another thread. This is done to ensure interoperability with frameworks such as Spring that make use of mechanisms that are sensitive to the currently running thread — for example, `ThreadLocal` transaction management. On the contrary when `SEPARATE_THREAD` is used, like the `assertTimeoutPreemptively()` assertion, the execution of the annotated method proceeds in a separate thread, this can lead to undesirable side effects, see xref:writing-tests/assertions.adoc#preemptive-timeouts[Preemptive Timeouts with `assertTimeoutPreemptively()`]. When `INFERRED` (default) thread mode is used, the thread mode is resolved via the `junit.jupiter.execution.timeout.thread.mode.default` configuration parameter. If the provided configuration parameter is invalid or not present then `SAME_THREAD` is used as fallback. [[default-timeouts]] == Default Timeouts The following xref:running-tests/configuration-parameters.adoc[configuration parameters] can be used to specify default timeouts for all methods of a certain category unless they or an enclosing test class is annotated with `@Timeout`: `junit.jupiter.execution.timeout.default`:: Default timeout for all testable and lifecycle methods `junit.jupiter.execution.timeout.testable.method.default`:: Default timeout for all testable methods `junit.jupiter.execution.timeout.test.method.default`:: Default timeout for `@Test` methods `junit.jupiter.execution.timeout.testtemplate.method.default`:: Default timeout for `@TestTemplate` methods `junit.jupiter.execution.timeout.testfactory.method.default`:: Default timeout for `@TestFactory` methods `junit.jupiter.execution.timeout.lifecycle.method.default`:: Default timeout for all lifecycle methods `junit.jupiter.execution.timeout.beforeall.method.default`:: Default timeout for `@BeforeAll` methods `junit.jupiter.execution.timeout.beforeeach.method.default`:: Default timeout for `@BeforeEach` methods `junit.jupiter.execution.timeout.aftereach.method.default`:: Default timeout for `@AfterEach` methods `junit.jupiter.execution.timeout.afterall.method.default`:: Default timeout for `@AfterAll` methods More specific configuration parameters override less specific ones. For example, `junit.jupiter.execution.timeout.test.method.default` overrides `junit.jupiter.execution.timeout.testable.method.default` which overrides `junit.jupiter.execution.timeout.default`. The values of such configuration parameters must be in the following, case-insensitive format: ` [ns|μs|ms|s|m|h|d]`. The space between the number and the unit may be omitted. Specifying no unit is equivalent to using seconds. .Example timeout configuration parameter values [cols="20,80"] |=== | Parameter value | Equivalent annotation | `42` | `@Timeout(42)` | `42 ns` | `@Timeout(value = 42, unit = NANOSECONDS)` | `42 μs` | `@Timeout(value = 42, unit = MICROSECONDS)` | `42 ms` | `@Timeout(value = 42, unit = MILLISECONDS)` | `42 s` | `@Timeout(value = 42, unit = SECONDS)` | `42 m` | `@Timeout(value = 42, unit = MINUTES)` | `42 h` | `@Timeout(value = 42, unit = HOURS)` | `42 d` | `@Timeout(value = 42, unit = DAYS)` |=== [[polling]] == Using @Timeout for Polling Tests When dealing with asynchronous code, it is common to write tests that poll while waiting for something to happen before performing any assertions. In some cases you can rewrite the logic to use a `CountDownLatch` or another synchronization mechanism, but sometimes that is not possible — for example, if the subject under test sends a message to a channel in an external message broker and assertions cannot be performed until the message has been successfully sent through the channel. Asynchronous tests like these require some form of timeout to ensure they don't hang the test suite by executing indefinitely, as would be the case if an asynchronous message never gets successfully delivered. By configuring a timeout for an asynchronous test that polls, you can ensure that the test does not execute indefinitely. The following example demonstrates how to achieve this with JUnit Jupiter's `@Timeout` annotation. This technique can be used to implement "poll until" logic very easily. [tabs] ==== Java:: + -- [source,java] ---- include::example$java/example/PollingTimeoutDemo.java[tags=user_guide,indent=0] ---- -- Kotlin:: + -- [source,kotlin] ---- include::example$kotlin/example/kotlin/PollingTimeoutDemo.kt[tags=user_guide,indent=0] ---- -- ==== NOTE: If you need more control over polling intervals and greater flexibility with asynchronous tests, consider using a dedicated library such as link:https://github.com/awaitility/awaitility[Awaitility]. [[debugging]] == Debugging Timeouts Registered xref:extensions/pre-interrupt-callback.adoc[] extensions are called prior to invoking `Thread.interrupt()` on the thread that is executing the timed out method. This allows to inspect the application state and output additional information that might be helpful for diagnosing the cause of a timeout. [[debugging-thread-dump]] === Thread Dump on Timeout JUnit registers a default implementation of the xref:extensions/pre-interrupt-callback.adoc[] extension point that dumps the stacks of all threads to `System.out` if enabled by setting the `junit.jupiter.execution.timeout.threaddump.enabled` xref:running-tests/configuration-parameters.adoc[configuration parameter] to `true`. [[mode]] == Disable @Timeout Globally When stepping through your code in a debug session, a fixed timeout limit may influence the result of the test, e.g. mark the test as failed although all assertions were met. JUnit Jupiter supports the `junit.jupiter.execution.timeout.mode` configuration parameter to configure when timeouts are applied. There are three modes: `enabled`, `disabled`, and `disabled_on_debug`. The default mode is `enabled`. A VM runtime is considered to run in debug mode when one of its input parameters starts with `-agentlib:jdwp` or `-Xrunjdwp`. This heuristic is queried by the `disabled_on_debug` mode. ================================================ FILE: documentation/modules/ROOT/partials/release-notes/release-notes-6.0.0.adoc ================================================ [[v6.0.0]] == 6.0.0 *Date of Release:* September 30, 2025 *Scope:* * Java 17 and Kotlin 2.1 baseline * Single version number for Platform, Jupiter, and Vintage * Use of JSpecify annotations to express nullability * Integration of JFR functionality in `junit-platform-launcher` * Removal of `junit-platform-runner` and `junit-platform-jfr` * Deterministic order of `@Nested` classes * `MethodOrderer.Default` and `ClassOrderer.Default` for `@Nested` classes * Inheritance of `@TestMethodOrder` by enclosed `@Nested` classes * Switch to FastCSV library for `@CsvSource` and `@CsvFileSource` * Support for using Kotlin `suspend` functions as test methods * New `--fail-fast` mode for ConsoleLauncher * Support for cancelling test execution via `CancellationToken` * Removal of various deprecated behaviors and APIs For complete details consult the https://docs.junit.org/6.0.0/release-notes.html[6.0.0 Release Notes] online. ================================================ FILE: documentation/modules/ROOT/partials/release-notes/release-notes-6.0.1.adoc ================================================ [[v6.0.1]] == 6.0.1 *Date of Release:* October 31, 2025 *Scope:* Bug fixes and enhancements since 6.0.0 For a complete list of all _closed_ issues and pull requests for this release, consult the link:{junit-framework-repo}+/milestone/110?closed=1+[6.0.1] milestone page in the JUnit repository on GitHub. [[v6.0.1-junit-platform]] === JUnit Platform [[v6.0.1-junit-platform-bug-fixes]] ==== Bug Fixes * The `jdk.jfr` package is now an optional import when using the `junit-platform-launcher` as an OSGi bundle. [[v6.0.1-junit-platform-new-features-and-improvements]] ==== New Features and Improvements * Legacy documentation regarding Java 8 compatibility has been removed from the User Guide. [[v6.0.1-junit-jupiter]] === JUnit Jupiter [[v6.0.1-junit-jupiter-bug-fixes]] ==== Bug Fixes * A regression introduced in version 6.0.0 caused an exception when using `@CsvSource` or `@CsvFileSource` if the `delimiter` or `delimiterString` attribute was set to `+++#+++`. This occurred because `+++#+++` was used as the default comment character without an 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. * Stop reporting discovery issues for synthetic methods, particularly in conjunction with Kotlin suspend functions. * Fix support for test methods with the same signature as package-private methods declared in super classes in different packages. [[v6.0.1-junit-jupiter-deprecations-and-breaking-changes]] ==== Deprecations and Breaking Changes * The `org.junit.jupiter.migrationsupport` module descriptor has been marked as deprecated for removal. [[v6.0.1-junit-jupiter-new-features-and-improvements]] ==== New Features and Improvements * The `@CsvSource` and `@CsvFileSource` annotations now allow specifying a custom comment character using the new `commentCharacter` attribute. * Improve error message when `@ParameterizedClass` is used with field injection without providing enough arguments. * Allow calling the `TypedArgumentConverter` constructor for `@Nullable T` target types without having to cast class literals to `Class<@Nullable T>`. [[v6.0.1-junit-vintage]] === JUnit Vintage [[v6.0.1-junit-vintage-new-features-and-improvements]] ==== New Features and Improvements * Allow disabling the reporting of discovery issues by the JUnit Vintage engine (including the one reported for its deprecation) by setting the new `junit.vintage.discovery.issue.reporting.enabled` configuration parameter to `false`. ================================================ FILE: documentation/modules/ROOT/partials/release-notes/release-notes-6.0.2.adoc ================================================ [[v6.0.2]] == 6.0.2 *Date of Release:* January 6, 2026 *Scope:* Bug fixes and enhancements since 6.0.1 For a complete list of all _closed_ issues and pull requests for this release, consult the link:{junit-framework-repo}+/milestone/113?closed=1+[6.0.2] milestone page in the JUnit repository on GitHub. [[v6.0.2-junit-platform]] === JUnit Platform [[v6.0.2-junit-platform-bug-fixes]] ==== Bug Fixes * Make `ConsoleLauncher` compatible with JDK 26 by avoiding final field mutations. * Enable recursive updates when using `NamespacedHierarchicalStore.computeIfAbsent(N, K, Function)`. This provides parity with the deprecated `NamespacedHierarchicalStore.getOrComputeIfAbsent(N, K, Function)` [[v6.0.2-junit-jupiter]] === JUnit Jupiter [[v6.0.2-junit-jupiter-bug-fixes]] ==== Bug Fixes * Allow using `@ResourceLock` on classes annotated with `@ClassTemplate` (or `@ParameterizedClass`). * Change API status of recommended method in `ArgumentsProvider` as well as `ParameterDeclaration(s)` as "maintained" rather than "experimental". [[v6.0.2-junit-vintage]] === JUnit Vintage No changes. ================================================ FILE: documentation/modules/ROOT/partials/release-notes/release-notes-6.0.3.adoc ================================================ [[v6.0.3]] == 6.0.3 *Date of Release:* February 15, 2026 *Scope:* Bug fixes and enhancements since 6.0.2 For a complete list of all _closed_ issues and pull requests for this release, consult the link:{junit-framework-repo}+/milestone/116?closed=1+[6.0.3] milestone page in the JUnit repository on GitHub. [[v6.0.3-junit-platform]] === JUnit Platform [[v6.0.3-junit-platform-bug-fixes]] ==== Bug Fixes * A deadlock issue in `NamespacedHierarchicalStore.computeIfAbsent(N, K, Function)` has been fixed. [[v6.0.3-junit-jupiter]] === JUnit Jupiter [[v6.0.3-junit-jupiter-bug-fixes]] ==== Bug Fixes * `@EnabledOnJre` and `@DisabledOnJre` once again work reliably when used with `JRE.OTHER` in a test running on a Java runtime whose version is higher than the version of the last `JAVA_*` constant in the `JRE` enum. [[v6.0.3-junit-vintage]] === JUnit Vintage No changes. ================================================ FILE: documentation/modules/ROOT/partials/release-notes/release-notes-6.1.0-M1.adoc ================================================ [[v6.1.0-M1]] == 6.1.0-M1 *Date of Release:* November 17, 2025 *Scope:* * New `org.junit.start` module for usage in compact source files * Execution mode configuration support for dynamic tests and containers * New parallel test executor implementation For a complete list of all _closed_ issues and pull requests for this release, consult the link:{junit-framework-repo}+/milestone/104?closed=1+[6.1.0-M1] milestone page in the JUnit repository on GitHub. [[v6.1.0-M1-junit-platform]] === JUnit Platform [[v6.1.0-M1-junit-platform-deprecations-and-breaking-changes]] ==== Deprecations and Breaking Changes * Deprecate constructors for `ForkJoinPoolHierarchicalTestExecutorService` in favor of the new `ParallelHierarchicalTestExecutorServiceFactory` that also supports `WorkerThreadPoolHierarchicalTestExecutorService`. [[v6.1.0-M1-junit-platform-new-features-and-improvements]] ==== New Features and Improvements * Support for creating a `ModuleSelector` from a `java.lang.Module` and using its classloader for test discovery. * New `WorkerThreadPoolHierarchicalTestExecutorService` implementation used for parallel test execution that is backed by a regular thread pool rather than a `ForkJoinPool`. Engine authors should switch to use `ParallelHierarchicalTestExecutorServiceFactory` rather than instantiating a concrete `HierarchicalTestExecutorService` implementation for parallel execution directly. * `OpenTestReportGeneratingListener` now supports redirecting XML events to a socket via the new `junit.platform.reporting.open.xml.socket` configuration parameter. When set to a port number, events are sent to `127.0.0.1:` instead of being written to a file. * Allow implementations of `HierarchicalTestEngine` to specify which nodes require the global read lock by overriding the `Node.isGlobalReadLockRequired()` method to return `false`. [[v6.1.0-M1-junit-jupiter]] === JUnit Jupiter [[v6.1.0-M1-junit-jupiter-new-features-and-improvements]] ==== New Features and Improvements * Introduce new module `org.junit.start` for writing and running tests. It simplifies using JUnit in compact source files together with a single `module import` statement. Find an example at the xref:running-tests/source-launcher.adoc[User Guide]. * Introduce new `dynamicTest(Consumer)` factory method for dynamic tests. It allows configuring the `ExecutionMode` of the dynamic test in addition to its display name, test source URI, and executable. * Introduce new `dynamicContainer(Consumer)` factory method for dynamic containers. It allows configuring the `ExecutionMode` of the dynamic container and/or its children in addition to its display name, test source URI, and children. * Enrich `assertInstanceOf` failure using the test subject `Throwable` as cause. It results in the stack trace of the test subject `Throwable` to get reported along with the failure. * Make implementation of `HierarchicalTestExecutorService` used for parallel test execution configurable via the new `junit.jupiter.execution.parallel.config.executor-service` configuration parameter to in order to add support for `WorkerThreadPoolHierarchicalTestExecutorService`. Please refer to the xref:writing-tests/parallel-execution.adoc#config-executor-service[User Guide] for details. [[v6.1.0-M1-junit-vintage]] === JUnit Vintage No changes. ================================================ FILE: documentation/modules/ROOT/partials/release-notes/release-notes-6.1.0-RC1.adoc ================================================ [[v6.1.0-RC1]] == 6.1.0-RC1 *Date of Release:* April 25, 2026 *Scope:* * New `@DefaultLocale` and `@DefaultTimeZone` built-in extensions * New built-in extension for clearing/setting/restoring system properties * Configurable deletion strategy for `@TempDir` that allows ignoring failures * Improved stack trace pruning for assertion failures * New `org.junit.jupiter.api.Constants` class for referencing configuration parameters * Improvements to legacy XML reports for parameterized test classes * New experimental memory cleanup mode for large test suites For a complete list of all _closed_ issues and pull requests for this release, consult the link:{junit-framework-repo}+/milestone/112?closed=1+[6.1.0-RC1] milestone page in the JUnit repository on GitHub. [[v6.1.0-RC1-junit-platform]] === JUnit Platform [[v6.1.0-RC1-junit-platform-bug-fixes]] ==== Bug Fixes * A deadlock issue in `NamespacedHierarchicalStore.computeIfAbsent(N, K, Function)` has been fixed. * `WorkerThreadPoolHierarchicalTestExecutorService` now rechecks the _done_ condition before rejecting an extraneous worker. * Missing precondition checks have been added to `Launcher` implementations. * Failures to resolve selectors are now propagated by the Suite Engine. [[v6.1.0-RC1-junit-platform-deprecations-and-breaking-changes]] ==== Deprecations and Breaking Changes * In the `EngineTestKit`, `Executions.started()` has been deprecated in favor of `Executions.finished()`, since started executions are always finished. [[v6.1.0-RC1-junit-platform-new-features-and-improvements]] ==== New Features and Improvements * `TestDescriptor` implementation requirements have now been clarified in the corresponding Javadoc. * The `UniqueId.uniqueIdFormat` field has been removed, reducing the size of `UniqueId` objects. * New `selectClasspathResources(String...)` and `selectClasspathResources(List)` ** `Arguments.argumentsFrom(Iterable)` (alias for `of(Iterable)`) ** `Arguments.argumentSetFrom(String, Iterable)` ** `Arguments.toList()` — returns a mutable `List<@Nullable Object>` * `@EmptySource` now supports `Iterable`, `Iterator`, and `ListIterator`. * `@Deprecated` factory methods are now excluded in the xref:writing-tests/parameterized-classes-and-tests.adoc#argument-conversion-implicit-fallback[fallback String-to-Object] conversion algorithm. * Internal stack frames are now removed from `AssertionFailedError` stack traces. * New `trimStacktrace(Class)` and `retainStackTraceElements(int)` methods in `AssertionFailureBuilder` which allow user-defined assertions to trim their stack traces. * Generic inline value classes (such as `kotlin.Result`) can now be used as parameters in `@ParameterizedTest` methods when `kotlin-reflect` is on the classpath. Note, however, that primitive-wrapper inline value classes (such as `UInt` or custom value classes wrapping primitives) are not yet supported. * Kotlin-specific assertions now include a variant of `assertThrowsExactly` with reified generics. * The new `org.junit.jupiter.api.Constants` class provides constants for xref:running-tests/configuration-parameters.adoc[] specific to the Jupiter engine. * The `{EmptySource}` annotation now provides a `type` attribute that allows configuring the type of the empty argument explicitly. This is intended to be used in conjunction with an `{ArgumentConverter}` that supports the specified type. [[v6.1.0-RC1-junit-vintage]] === JUnit Vintage No changes. ================================================ FILE: documentation/modules/ROOT/partials/release-notes/release-notes-6.1.0.adoc ================================================ [[v6.1.0]] == 6.1.0 *Date of Release:* ❓ *Scope:* ❓ For a complete list of all _closed_ issues and pull requests for this release, consult the link:{junit-framework-repo}+/milestone/119?closed=1+[6.1.0] milestone page in the JUnit repository on GitHub. [[v6.1.0-junit-platform]] === JUnit Platform [[v6.1.0-junit-platform-bug-fixes]] ==== Bug Fixes * `AbstractTestDescriptor.getChildren()` now returns immutable set of children rather than a merely unmodifiable set. [[v6.1.0-junit-platform-deprecations-and-breaking-changes]] ==== Deprecations and Breaking Changes * ❓ [[v6.1.0-junit-platform-new-features-and-improvements]] ==== New Features and Improvements * ❓ [[v6.1.0-junit-jupiter]] === JUnit Jupiter [[v6.1.0-junit-jupiter-bug-fixes]] ==== Bug Fixes * ❓ [[v6.1.0-junit-jupiter-deprecations-and-breaking-changes]] ==== Deprecations and Breaking Changes * ❓ [[v6.1.0-junit-jupiter-new-features-and-improvements]] ==== New Features and Improvements * ❓ [[v6.1.0-junit-vintage]] === JUnit Vintage [[v6.1.0-junit-vintage-bug-fixes]] ==== Bug Fixes * ❓ [[v6.1.0-junit-vintage-deprecations-and-breaking-changes]] ==== Deprecations and Breaking Changes * ❓ [[v6.1.0-junit-vintage-new-features-and-improvements]] ==== New Features and Improvements * ❓ ================================================ FILE: documentation/modules/ROOT/partials/release-notes/release-notes-TEMPLATE.adoc ================================================ // TODO: // // 1) Make a copy of this template file, replacing TEMPLATE in the file name with the // current version (for example, 5.10.0-M1, 5.10.0-RC1, 5.10.0). // 2) Open the new file for editing. // 3) Replace all occurrences of VERSION with the current version (for example, 5.10.0-M1, // 5.10.0-RC1, 5.10.0). The same version must be used in the file name and within the // file. // 4) Replace MILESTONE_NUMBER with the appropriate milestone number. This is an integer // which you can determine via https://github.com/junit-team/junit-framework/milestones/. // If a GitHub milestone does not yet exist for the given VERSION, you will need to // create a new GitHub milestone or ask a member of the JUnit team to create it for you. // 5) 'include:' this new file in ../../pages/release-notes.adoc. // 6) Delete this entire comment block. // [[vVERSION]] == VERSION *Date of Release:* ❓ *Scope:* ❓ For a complete list of all _closed_ issues and pull requests for this release, consult the link:{junit-framework-repo}+/milestone/MILESTONE_NUMBER?closed=1+[VERSION] milestone page in the JUnit repository on GitHub. [[vVERSION-junit-platform]] === JUnit Platform [[vVERSION-junit-platform-bug-fixes]] ==== Bug Fixes * ❓ [[vVERSION-junit-platform-deprecations-and-breaking-changes]] ==== Deprecations and Breaking Changes * ❓ [[vVERSION-junit-platform-new-features-and-improvements]] ==== New Features and Improvements * ❓ [[vVERSION-junit-jupiter]] === JUnit Jupiter [[vVERSION-junit-jupiter-bug-fixes]] ==== Bug Fixes * ❓ [[vVERSION-junit-jupiter-deprecations-and-breaking-changes]] ==== Deprecations and Breaking Changes * ❓ [[vVERSION-junit-jupiter-new-features-and-improvements]] ==== New Features and Improvements * ❓ [[vVERSION-junit-vintage]] === JUnit Vintage [[vVERSION-junit-vintage-bug-fixes]] ==== Bug Fixes * ❓ [[vVERSION-junit-vintage-deprecations-and-breaking-changes]] ==== Deprecations and Breaking Changes * ❓ [[vVERSION-junit-vintage-new-features-and-improvements]] ==== New Features and Improvements * ❓ ================================================ FILE: documentation/package.json ================================================ { "devDependencies": { "antora": "3.2.0-alpha.11" }, "dependencies": { "@antora/collector-extension": "1.0.3", "@antora/lunr-extension": "1.0.0-alpha.13", "@asciidoctor/tabs": "^1.0.0-beta.6", "@springio/antora-extensions": "1.14.11", "@springio/antora-xref-extension": "^1.0.0-alpha.5", "highlight.js": "11.11.1" } } ================================================ FILE: documentation/src/javadoc/junit-overview.html ================================================

This document consists of four sections:

Platform
The JUnit Platform serves as a foundation for launching testing frameworks on the JVM. It also defines the TestEngine API for developing a testing framework that runs on the platform. Furthermore, the platform provides a Console Launcher to launch the platform from the command line and the Suite Engine for running a custom test suite using one or more test engines on the platform
Jupiter
JUnit Jupiter is the combination of the programming model and extension model for writing JUnit tests and extensions. The Jupiter subproject provides a TestEngine for running Jupiter based tests on the platform.
Vintage
JUnit Vintage provides a TestEngine for running JUnit 3 and JUnit 4 based tests on the platform.
Other Modules
This section lists all modules that are not part of a dedicated section.

Already consulted the JUnit User Guide?

================================================ FILE: documentation/src/javadoc/junit-stylesheet.css ================================================ /* * CSS customizations for JUnit */ @import url('https://assets.junit.org/fonts/index.css'); :root { /* body, block and code fonts */ --body-font-family: "Inter Variable", sans-serif; --block-font-family: "Inter Variable", sans-serif; --code-font-family: "JetBrains Mono Variable", monospace; /* Text colors for body and block elements */ --body-text-color: #333; --block-text-color: #333; /* Colors for navigation bar and table captions */ --navbar-background-color: #25a162; /* Background color for subnavigation and various headers */ --subnav-background-color: #e8e8e8; --subnav-link-color: var(--link-color); /* Background and text colors for selected tabs and navigation items */ --selected-background-color: #dc524a; --selected-text-color: #fff; --selected-link-color: #651410; /* Background colors for generated tables */ --table-header-color: #eee; --even-row-color: #fff; --odd-row-color: #f6f6f6; /* Text color for page title */ --title-color: #444; /* Text colors for links */ --link-color: #dc524a; --link-color-active: #b62b23; /* Table of contents */ --toc-background-color: #f8f8f8; --toc-hover-color: #eee; /* Snippet colors */ --snippet-background-color: #ebecee; --snippet-text-color: var(--block-text-color); --snippet-highlight-color: #fcdbd9; /* Border colors for structural elements and user defined tables */ --border-color: #eee; --table-border-color: #eee; /* Highlight color for active search tag target */ --search-tag-highlight-color: #ffff00; /* Copy button colors and filters */ --button-border-color: #b0b8c8; } h1, h2 { font-family: "Blinker", sans-serif; font-style: normal !important; font-weight: 600; } dl.notes > dt { font-size: var(--body-font-size); } @media screen and (min-width: 1024px) { .title { font-size: 2rem; } } .top-nav a:hover, .bottom-nav a:hover { text-decoration:underline; color:inherit; } .nav-bar-cell1-rev { background-color:#fff; color:#dc524a; border-radius: 6px; font-weight: bold; } hr { color: transparent; border-top: 1px solid var(--border-color); } dt { font-weight: bold; } tt, code, pre, .module-signature, .package-signature, .type-signature, .member-signature { font-variant-ligatures: none; } ================================================ FILE: documentation/src/main/java/example/domain/Person.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.domain; import java.time.LocalDate; public final class Person { public enum Gender { F, M } private String firstName; private String lastName; private Gender gender; private LocalDate dateOfBirth; public Person(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } public Person(String firstName, String lastName, Gender gender, LocalDate dateOfBirth) { this(firstName, lastName); this.gender = gender; this.dateOfBirth = dateOfBirth; } public String getFirstName() { return firstName; } public String getLastName() { return lastName; } public Gender getGender() { return gender; } public LocalDate getDateOfBirth() { return dateOfBirth; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((firstName == null) ? 0 : firstName.hashCode()); result = prime * result + ((lastName == null) ? 0 : lastName.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } Person other = (Person) obj; if (firstName == null) { if (other.firstName != null) { return false; } } else if (!firstName.equals(other.firstName)) { return false; } if (lastName == null) { if (other.lastName != null) { return false; } } else if (!lastName.equals(other.lastName)) { return false; } return true; } @Override public String toString() { return "Person [firstName=" + firstName + ", lastName=" + lastName + "]"; } } ================================================ FILE: documentation/src/main/java/example/domain/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Demo domain model. */ package example.domain; ================================================ FILE: documentation/src/main/java/example/registration/WebClient.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.registration; public class WebClient implements AutoCloseable { public WebResponse get(String string) { return new WebResponse(); } @Override public void close() { /* no-op for demo */ } } ================================================ FILE: documentation/src/main/java/example/registration/WebResponse.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.registration; public class WebResponse { public int getResponseStatus() { return 200; } } ================================================ FILE: documentation/src/main/java/example/registration/WebServerExtension.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.registration; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; public class WebServerExtension implements BeforeAllCallback { @Override public void beforeAll(ExtensionContext context) { /* no-op for demo */ } public String getServerUrl() { return "https://example.org:8181"; } public static Builder builder() { return new Builder(); } public static class Builder { public Builder enableSecurity(boolean b) { return this; } public WebServerExtension build() { return new WebServerExtension(); } } } ================================================ FILE: documentation/src/main/java/example/registration/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Demo code for a WebServer extension. */ package example.registration; ================================================ FILE: documentation/src/main/java/example/util/Calculator.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.util; public class Calculator { public int add(int a, int b) { return a + b; } public int subtract(int a, int b) { return a - b; } public int multiply(int a, int b) { return a * b; } public int divide(int a, int b) { return a / b; } } ================================================ FILE: documentation/src/main/java/example/util/ListWriter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.util; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; public class ListWriter { private final Path file; public ListWriter(Path file) { this.file = file; } public void write(String... items) throws IOException { Files.write(file, List.of(String.join(",", items))); } } ================================================ FILE: documentation/src/main/java/example/util/StringUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.util; import static java.util.Objects.requireNonNull; import org.jspecify.annotations.Nullable; public class StringUtils { public static boolean isPalindrome(@Nullable String candidate) { int length = requireNonNull(candidate).length(); for (int i = 0; i < length / 2; i++) { if (candidate.charAt(i) != candidate.charAt(length - (i + 1))) { return false; } } return true; } private StringUtils() { } } ================================================ FILE: documentation/src/main/java/example/util/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Demo utilities. */ package example.util; ================================================ FILE: documentation/src/plantuml/component-diagram.puml ================================================ @startuml skinparam { defaultFontName sans-serif } package org.junit.jupiter { [junit-jupiter] as jupiter [junit-jupiter-api] as jupiter_api [junit-jupiter-engine] as jupiter_engine [junit-jupiter-params] as jupiter_params [junit-jupiter-migrationsupport] as jupiter_migration_support } package org.junit.vintage { [junit-vintage-engine] as vintage_engine } package org.junit.platform { [junit-platform-commons] as commons [junit-platform-console] as console [junit-platform-engine] as engine [junit-platform-launcher] as launcher [junit-platform-reporting] as reporting [junit-platform-suite] as suite [junit-platform-suite-api] as suite_api [junit-platform-suite-engine] as suite_engine [junit-platform-testkit] as testkit } package "JUnit 4" { [junit:junit] as junit4 } package org.opentest4j { [opentest4j] } package org.opentest4j.reporting { [open-test-reporting-tooling-spi] as otr_tooling_spi } package org.apiguardian { [apiguardian-api] as apiguardian note bottom of apiguardian #white All artifacts except opentest4j and junit:junit have a dependency on this artifact. The edges have been omitted from this diagram for the sake of readability. endnote } jupiter ..> jupiter_api jupiter ..> jupiter_params jupiter ..> jupiter_engine jupiter_api ....> opentest4j jupiter_api ...> commons jupiter_engine ...> engine jupiter_engine ..> jupiter_api jupiter_params ..> jupiter_api jupiter_migration_support ..> jupiter_api jupiter_migration_support ...> junit4 console ..> launcher console ..> reporting launcher ..> engine engine ....> opentest4j engine ..> commons reporting ..> launcher reporting ......> otr_tooling_spi suite ..> suite_api suite ..> suite_engine suite_engine ..> launcher suite_engine ..> suite_api testkit ....> opentest4j testkit ..> launcher vintage_engine ...> engine vintage_engine ..> junit4 @enduml ================================================ FILE: documentation/src/plantuml/junit-platform-suite-engine-diagram.puml ================================================ @startuml object "IDEs" object "Build Tools" object "Console Launcher" object "JUnit Platform" object "Suite Test Engine" object "@Suite annotated class A" object "@Suite annotated class B" object "JUnit Platform (A)" object "JUnit Platform (B)" object "Jupiter Test Engine (A)" object "Jupiter Test Engine (B)" object "Tests in package A" object "Tests in package B" "IDEs" --> "JUnit Platform" "Build Tools" --> "JUnit Platform" "Console Launcher" --> "JUnit Platform" : requests discovery and execution "JUnit Platform" --> "Suite Test Engine": forwards request "Suite Test Engine" --> "@Suite annotated class A" "@Suite annotated class A" --> "JUnit Platform (A)" "JUnit Platform (A)" --> "Jupiter Test Engine (A)" "Jupiter Test Engine (A)" --> "Tests in package A" "Suite Test Engine" --> "@Suite annotated class B" : discovers and executes "@Suite annotated class B" --> "JUnit Platform (B)" : requests discovery and execution "JUnit Platform (B)" --> "Jupiter Test Engine (B)" : forwards request "Jupiter Test Engine (B)" --> "Tests in package B" : discovers and executes @enduml ================================================ FILE: documentation/src/plantuml/junit-platform-suite-engine-duplicate-test-execution-diagram.puml ================================================ @startuml object "IDEs" object "Build Tools" object "Console Launcher" object "JUnit Platform" together { object "Suite Test Engine" object "Jupiter Test Engine" } object "@Suite annotated class" object "JUnit Platform (@Suite)" object "Jupiter Test Engine (@Suite)" together { object "Example Test A" object "Example Test A (@Suite)" } "IDEs" --> "JUnit Platform" "Build Tools" --> "JUnit Platform" "Console Launcher" --> "JUnit Platform" : requests discovery and execution "JUnit Platform" --> "Suite Test Engine" "Suite Test Engine" --> "@Suite annotated class" : discovers and executes "@Suite annotated class" --> "JUnit Platform (@Suite)" : requests discovery and execution "JUnit Platform (@Suite)" --> "Jupiter Test Engine (@Suite)" : forwards request "Jupiter Test Engine (@Suite)" --> "Example Test A (@Suite)" : discovers and executes "JUnit Platform" --> "Jupiter Test Engine": forwards request "Jupiter Test Engine" --> "Example Test A" #line:red;line.bold;text:red : also discovers and executes! @enduml ================================================ FILE: documentation/src/plantuml/launcher-api-diagram.puml ================================================ @startuml object "IDEs" object "Build Tools" object "Console Launcher" object "JUnit Platform" object "Jupiter Test Engine" object "Cucumber Test Engine" object "Spock Test Engine" object "Test Classes" object "Feature Files" object "Specifications" "IDEs" --> "JUnit Platform" "Build Tools" --> "JUnit Platform" "Console Launcher" --> "JUnit Platform" : requests discovery and execution "JUnit Platform" --> "Jupiter Test Engine" "Jupiter Test Engine" --> "Test Classes" "JUnit Platform" --> "Cucumber Test Engine" "Cucumber Test Engine" --> "Feature Files" "JUnit Platform" --> "Spock Test Engine": forwards request "Spock Test Engine" --> "Specifications" : discovers and executes @enduml ================================================ FILE: documentation/src/test/java/example/AssertJAssertionsDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; // tag::user_guide[] import static org.assertj.core.api.Assertions.assertThat; import example.util.Calculator; import org.junit.jupiter.api.Test; class AssertJAssertionsDemo { private final Calculator calculator = new Calculator(); @Test void assertWithAssertJ() { assertThat(calculator.subtract(4, 1)).isEqualTo(3); } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/AssertionsDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; // @formatter:off // tag::user_guide[] import static java.time.Duration.ofMillis; import static java.time.Duration.ofMinutes; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTimeout; import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.concurrent.CountDownLatch; import example.domain.Person; import example.util.Calculator; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; class AssertionsDemo { private final Calculator calculator = new Calculator(); private final Person person = new Person("Jane", "Doe"); @Test void standardAssertions() { assertEquals(2, calculator.add(1, 1)); assertEquals(4, calculator.multiply(2, 2), "The optional failure message is now the last parameter"); // Lazily evaluates generateFailureMessage('a','b'). assertTrue('a' < 'b', () -> generateFailureMessage('a','b')); } @Test void groupedAssertions() { // In a grouped assertion all assertions are executed, and all // failures will be reported together. assertAll("person", () -> assertEquals("Jane", person.getFirstName()), () -> assertEquals("Doe", person.getLastName()) ); } @Test void dependentAssertions() { // Within a code block, if an assertion fails the // subsequent code in the same block will be skipped. assertAll("properties", () -> { String firstName = person.getFirstName(); assertNotNull(firstName); // Executed only if the previous assertion is valid. assertAll("first name", () -> assertTrue(firstName.startsWith("J")), () -> assertTrue(firstName.endsWith("e")) ); }, () -> { // Grouped assertion, so processed independently // of results of first name assertions. String lastName = person.getLastName(); assertNotNull(lastName); // Executed only if the previous assertion is valid. assertAll("last name", () -> assertTrue(lastName.startsWith("D")), () -> assertTrue(lastName.endsWith("e")) ); } ); } // end::user_guide[] @extensions.DisabledOnOpenJ9 // tag::user_guide[] @Test void exceptionTesting() { Exception exception = assertThrows(ArithmeticException.class, () -> calculator.divide(1, 0)); assertEquals("/ by zero", exception.getMessage()); } // end::user_guide[] @Tag("timeout") // tag::user_guide[] @Test void timeoutNotExceeded() { // The following assertion succeeds. assertTimeout(ofMinutes(2), () -> { // Perform task that takes less than 2 minutes. }); } // end::user_guide[] @Tag("timeout") // tag::user_guide[] @Test void timeoutNotExceededWithResult() { // The following assertion succeeds, and returns the supplied object. String actualResult = assertTimeout(ofMinutes(2), () -> { return "a result"; }); assertEquals("a result", actualResult); } // end::user_guide[] @Tag("timeout") // tag::user_guide[] @Test void timeoutNotExceededWithMethod() { // The following assertion invokes a method reference and returns an object. String actualGreeting = assertTimeout(ofMinutes(2), AssertionsDemo::greeting); assertEquals("Hello, World!", actualGreeting); } // end::user_guide[] @Tag("timeout") @extensions.ExpectToFail // tag::user_guide[] @Test void timeoutExceeded() { // The following assertion fails with an error message similar to: // execution exceeded timeout of 10 ms by 91 ms assertTimeout(ofMillis(10), () -> { // Simulate task that takes more than 10 ms. Thread.sleep(100); }); } // end::user_guide[] @Tag("timeout") @extensions.ExpectToFail // tag::user_guide[] @Test void timeoutExceededWithPreemptiveTermination() { // The following assertion fails with an error message similar to: // execution timed out after 10 ms assertTimeoutPreemptively(ofMillis(10), () -> { // Simulate task that takes more than 10 ms. new CountDownLatch(1).await(); }); } private static String greeting() { return "Hello, World!"; } private static String generateFailureMessage(char a, char b) { return "Assertion messages can be lazily evaluated -- " + "to avoid constructing complex messages unnecessarily." + (a < b); } } // end::user_guide[] // @formatter:on ================================================ FILE: documentation/src/test/java/example/AssumptionsDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; // @formatter:off // tag::user_guide[] import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.junit.jupiter.api.Assumptions.assumingThat; import example.util.Calculator; import org.junit.jupiter.api.Test; class AssumptionsDemo { private final Calculator calculator = new Calculator(); @Test void testOnlyOnCiServer() { assumeTrue("CI".equals(System.getenv("ENV"))); // remainder of test } @Test void testOnlyOnDeveloperWorkstation() { assumeTrue("DEV".equals(System.getenv("ENV")), () -> "Aborting test: not on developer workstation"); // remainder of test } @Test void testInAllEnvironments() { assumingThat("CI".equals(System.getenv("ENV")), () -> { // perform these assertions only on the CI server assertEquals(2, calculator.divide(4, 2)); }); // perform these assertions in all environments assertEquals(42, calculator.multiply(6, 7)); } } // end::user_guide[] // @formatter:on ================================================ FILE: documentation/src/test/java/example/AutoCloseDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; import static org.junit.jupiter.api.Assertions.assertEquals; import example.registration.WebClient; import org.junit.jupiter.api.AutoClose; import org.junit.jupiter.api.Test; // tag::user_guide_example[] class AutoCloseDemo { @AutoClose // <1> WebClient webClient = new WebClient(); // <2> String serverUrl = // specify server URL ... // end::user_guide_example[] "https://localhost"; // tag::user_guide_example[] @Test void getProductList() { // Use WebClient to connect to web server and verify response assertEquals(200, webClient.get(serverUrl + "/products").getResponseStatus()); } } // end::user_guide_example[] ================================================ FILE: documentation/src/test/java/example/BeforeAndAfterSuiteDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; import org.junit.platform.suite.api.AfterSuite; import org.junit.platform.suite.api.BeforeSuite; import org.junit.platform.suite.api.SelectPackages; import org.junit.platform.suite.api.Suite; //tag::user_guide[] @Suite @SelectPackages("example") class BeforeAndAfterSuiteDemo { @BeforeSuite static void beforeSuite() { // executes before the test suite } @AfterSuite static void afterSuite() { // executes after the test suite } } //end::user_guide[] ================================================ FILE: documentation/src/test/java/example/ClassTemplateDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.List; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.ClassTemplate; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ClassTemplateInvocationContext; import org.junit.jupiter.api.extension.ClassTemplateInvocationContextProvider; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestInstancePostProcessor; // tag::user_guide[] @ClassTemplate @ExtendWith(ClassTemplateDemo.MyClassTemplateInvocationContextProvider.class) class ClassTemplateDemo { static final List WELL_KNOWN_FRUITS // tag::custom_line_break[] = List.of("apple", "banana", "lemon"); //end::user_guide[] @Nullable //tag::user_guide[] private String fruit; @Test void notNull() { assertNotNull(fruit); } @Test void wellKnown() { assertTrue(WELL_KNOWN_FRUITS.contains(fruit)); } // end::user_guide[] static // tag::user_guide[] public class MyClassTemplateInvocationContextProvider // tag::custom_line_break[] implements ClassTemplateInvocationContextProvider { @Override public boolean supportsClassTemplate(ExtensionContext context) { return true; } @Override public Stream // tag::custom_line_break[] provideClassTemplateInvocationContexts(ExtensionContext context) { return Stream.of(invocationContext("apple"), invocationContext("banana")); } private ClassTemplateInvocationContext invocationContext(String parameter) { return new ClassTemplateInvocationContext() { @Override public String getDisplayName(int invocationIndex) { return parameter; } // end::user_guide[] @SuppressWarnings("Convert2Lambda") // tag::user_guide[] @Override public List getAdditionalExtensions() { return List.of(new TestInstancePostProcessor() { @Override public void postProcessTestInstance( // tag::custom_line_break[] Object testInstance, ExtensionContext context) { ((ClassTemplateDemo) testInstance).fruit = parameter; } }); } }; } } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/ConditionalTestExecutionDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; import static org.junit.jupiter.api.condition.JRE.JAVA_17; import static org.junit.jupiter.api.condition.JRE.JAVA_18; import static org.junit.jupiter.api.condition.JRE.JAVA_19; import static org.junit.jupiter.api.condition.JRE.JAVA_21; import static org.junit.jupiter.api.condition.JRE.JAVA_25; import static org.junit.jupiter.api.condition.OS.LINUX; import static org.junit.jupiter.api.condition.OS.MAC; import static org.junit.jupiter.api.condition.OS.WINDOWS; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledForJreRange; import org.junit.jupiter.api.condition.DisabledIf; import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable; import org.junit.jupiter.api.condition.DisabledIfSystemProperty; import org.junit.jupiter.api.condition.DisabledInNativeImage; import org.junit.jupiter.api.condition.DisabledOnJre; import org.junit.jupiter.api.condition.DisabledOnOs; import org.junit.jupiter.api.condition.EnabledForJreRange; import org.junit.jupiter.api.condition.EnabledIf; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.junit.jupiter.api.condition.EnabledIfSystemProperty; import org.junit.jupiter.api.condition.EnabledInNativeImage; import org.junit.jupiter.api.condition.EnabledOnJre; import org.junit.jupiter.api.condition.EnabledOnOs; class ConditionalTestExecutionDemo { // tag::user_guide_os[] @Test @EnabledOnOs(MAC) void onlyOnMacOs() { // ... } @TestOnMac void testOnMac() { // ... } @Test @EnabledOnOs({ LINUX, MAC }) void onLinuxOrMac() { // ... } @Test @DisabledOnOs(WINDOWS) void notOnWindows() { // ... } @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Test @EnabledOnOs(MAC) @interface TestOnMac { } // end::user_guide_os[] // tag::user_guide_architecture[] @Test @EnabledOnOs(architectures = "aarch64") void onAarch64() { // ... } @Test @DisabledOnOs(architectures = "x86_64") void notOnX86_64() { // ... } @Test @EnabledOnOs(value = MAC, architectures = "aarch64") void onNewMacs() { // ... } @Test @DisabledOnOs(value = MAC, architectures = "aarch64") void notOnNewMacs() { // ... } // end::user_guide_architecture[] // tag::user_guide_jre[] @Test @EnabledOnJre(JAVA_17) void onlyOnJava17() { // ... } @Test @EnabledOnJre({ JAVA_17, JAVA_21 }) void onJava17And21() { // ... } @Test @EnabledForJreRange(min = JAVA_21, max = JAVA_25) void fromJava21To25() { // ... } @Test @EnabledForJreRange(min = JAVA_21) void onJava21ndHigher() { // ... } @Test @EnabledForJreRange(max = JAVA_18) void fromJava17To18() { // ... } @Test @DisabledOnJre(JAVA_19) void notOnJava19() { // ... } @Test @DisabledForJreRange(min = JAVA_17, max = JAVA_17) void notFromJava17To19() { // ... } @Test @DisabledForJreRange(min = JAVA_19) void notOnJava19AndHigher() { // ... } @Test @DisabledForJreRange(max = JAVA_18) void notFromJava17To18() { // ... } // end::user_guide_jre[] // tag::user_guide_jre_arbitrary_versions[] @Test @EnabledOnJre(versions = 26) void onlyOnJava26() { // ... } @Test @EnabledOnJre(versions = { 25, 26 }) // Can also be expressed as follows. // @EnabledOnJre(value = JAVA_25, versions = 26) void onJava25And26() { // ... } @Test @EnabledForJreRange(minVersion = 26) void onJava26AndHigher() { // ... } @Test @EnabledForJreRange(minVersion = 25, maxVersion = 27) // Can also be expressed as follows. // @EnabledForJreRange(min = JAVA_25, maxVersion = 27) void fromJava25To27() { // ... } @Test @DisabledOnJre(versions = 26) void notOnJava26() { // ... } @Test @DisabledOnJre(versions = { 25, 26 }) // Can also be expressed as follows. // @DisabledOnJre(value = JAVA_25, versions = 26) void notOnJava25And26() { // ... } @Test @DisabledForJreRange(minVersion = 26) void notOnJava26AndHigher() { // ... } @Test @DisabledForJreRange(minVersion = 25, maxVersion = 27) // Can also be expressed as follows. // @DisabledForJreRange(min = JAVA_25, maxVersion = 27) void notFromJava25To27() { // ... } // end::user_guide_jre_arbitrary_versions[] // tag::user_guide_native[] @Test @EnabledInNativeImage void onlyWithinNativeImage() { // ... } @Test @DisabledInNativeImage void neverWithinNativeImage() { // ... } // end::user_guide_native[] // tag::user_guide_system_property[] @Test @EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*") void onlyOn64BitArchitectures() { // ... } @Test @DisabledIfSystemProperty(named = "ci-server", matches = "true") void notOnCiServer() { // ... } // end::user_guide_system_property[] // tag::user_guide_environment_variable[] @Test @EnabledIfEnvironmentVariable(named = "ENV", matches = "staging-server") void onlyOnStagingServer() { // ... } @Test @DisabledIfEnvironmentVariable(named = "ENV", matches = ".*development.*") void notOnDeveloperWorkstation() { // ... } // end::user_guide_environment_variable[] // tag::user_guide_custom[] @Test @EnabledIf("customCondition") void enabled() { // ... } @Test @DisabledIf("customCondition") void disabled() { // ... } boolean customCondition() { return true; } // end::user_guide_custom[] } ================================================ FILE: documentation/src/test/java/example/CustomLauncherInterceptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; // tag::user_guide[] import java.io.IOException; import java.io.UncheckedIOException; import java.net.URI; import java.net.URL; import java.net.URLClassLoader; import org.junit.platform.launcher.LauncherInterceptor; public class CustomLauncherInterceptor implements LauncherInterceptor { private final URLClassLoader customClassLoader; public CustomLauncherInterceptor() throws Exception { ClassLoader parent = Thread.currentThread().getContextClassLoader(); customClassLoader = new URLClassLoader(new URL[] { URI.create("some.jar").toURL() }, parent); } @Override public T intercept(Invocation invocation) { Thread currentThread = Thread.currentThread(); ClassLoader originalClassLoader = currentThread.getContextClassLoader(); currentThread.setContextClassLoader(customClassLoader); try { return invocation.proceed(); } finally { currentThread.setContextClassLoader(originalClassLoader); } } @Override public void close() { try { customClassLoader.close(); } catch (IOException e) { throw new UncheckedIOException("Failed to close custom class loader", e); } } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/CustomTestEngine.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.EngineDescriptor; /** * This is a no-op {@link TestEngine} that is only * used to make examples compile. */ class CustomTestEngine implements TestEngine { @Override public String getId() { return "custom-test-engine"; } @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { return new EngineDescriptor(UniqueId.forEngine(getId()), "Custom Test Engine"); } @Override public void execute(ExecutionRequest request) { } } ================================================ FILE: documentation/src/test/java/example/DefaultLocaleTimezoneExtensionDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; import static org.assertj.core.api.Assertions.assertThat; import java.time.ZoneOffset; import java.util.Locale; import java.util.TimeZone; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.util.DefaultLocale; import org.junit.jupiter.api.util.DefaultTimeZone; import org.junit.jupiter.api.util.LocaleProvider; import org.junit.jupiter.api.util.TimeZoneProvider; public class DefaultLocaleTimezoneExtensionDemo { // tag::default_locale_language[] @Test @DefaultLocale("zh-Hant-TW") void test_with_language() { assertThat(Locale.getDefault()).isEqualTo(Locale.forLanguageTag("zh-Hant-TW")); } // end::default_locale_language[] // tag::default_locale_language_alternatives[] @Test @DefaultLocale(language = "en") void test_with_language_only() { assertThat(Locale.getDefault()).isEqualTo(new Locale.Builder().setLanguage("en").build()); } @Test @DefaultLocale(language = "en", country = "EN") void test_with_language_and_country() { assertThat(Locale.getDefault()).isEqualTo(new Locale.Builder().setLanguage("en").setRegion("EN").build()); } @Test @DefaultLocale(language = "ja", country = "JP", variant = "japanese") void test_with_language_and_country_and_vairant() { assertThat(Locale.getDefault()).isEqualTo( new Locale.Builder().setLanguage("ja").setRegion("JP").setVariant("japanese").build()); } // end::default_locale_language_alternatives[] @Nested // tag::default_locale_class_level[] @DefaultLocale(language = "fr") class MyLocaleTests { @Test void test_with_class_level_configuration() { assertThat(Locale.getDefault()).isEqualTo(new Locale.Builder().setLanguage("fr").build()); } @Test @DefaultLocale(language = "en") void test_with_method_level_configuration() { assertThat(Locale.getDefault()).isEqualTo(new Locale.Builder().setLanguage("en").build()); } } // end::default_locale_class_level[] // tag::default_locale_with_provider[] @Test @DefaultLocale(localeProvider = EnglishProvider.class) void test_with_locale_provider() { assertThat(Locale.getDefault()).isEqualTo(new Locale.Builder().setLanguage("en").build()); } static class EnglishProvider implements LocaleProvider { @Override public Locale get() { return Locale.ENGLISH; } } // end::default_locale_with_provider[] // tag::default_timezone_zone[] @Test @DefaultTimeZone("CET") void test_with_short_zone_id() { assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("CET")); } @Test @DefaultTimeZone("Africa/Juba") void test_with_long_zone_id() { assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("Africa/Juba")); } // end::default_timezone_zone[] @Nested // tag::default_timezone_class_level[] @DefaultTimeZone("CET") class MyTimeZoneTests { @Test void test_with_class_level_configuration() { assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("CET")); } @Test @DefaultTimeZone("Africa/Juba") void test_with_method_level_configuration() { assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("Africa/Juba")); } } // end::default_timezone_class_level[] // tag::default_time_zone_with_provider[] @Test @DefaultTimeZone(timeZoneProvider = UtcTimeZoneProvider.class) void test_with_time_zone_provider() { assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("UTC")); } static class UtcTimeZoneProvider implements TimeZoneProvider { @Override public TimeZone get() { return TimeZone.getTimeZone(ZoneOffset.UTC); } } // end::default_time_zone_with_provider[] } ================================================ FILE: documentation/src/test/java/example/DisabledClassDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; // tag::user_guide[] import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @Disabled("Disabled until bug #99 has been fixed") class DisabledClassDemo { @Test void testWillBeSkipped() { } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/DisabledTestsDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; // tag::user_guide[] import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; class DisabledTestsDemo { @Disabled("Disabled until bug #42 has been resolved") @Test void testWillBeSkipped() { } @Test void testWillBeExecuted() { } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/DisplayNameDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; // tag::user_guide[] import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @DisplayName("A special test case") class DisplayNameDemo { @Test @DisplayName("Custom test name containing spaces") void testWithDisplayNameContainingSpaces() { } @Test @DisplayName("╯°□°)╯") void testWithDisplayNameContainingSpecialCharacters() { } @Test @DisplayName("😱") void testWithDisplayNameContainingEmoji() { } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/DisplayNameGeneratorDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.DisplayNameGenerator.IndicativeSentences.SentenceFragment; import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; import org.junit.jupiter.api.IndicativeSentencesGeneration; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; class DisplayNameGeneratorDemo { @Nested // tag::user_guide_replace_underscores[] @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) class A_year_is_not_supported { @Test void if_it_is_zero() { } @DisplayName("A negative value for year is not supported by the leap year computation.") @ParameterizedTest(name = "For example, year {0} is not supported.") @ValueSource(ints = { -1, -4 }) void if_it_is_negative(int year) { } } // end::user_guide_replace_underscores[] @Nested // tag::user_guide_indicative_sentences[] @IndicativeSentencesGeneration(separator = " -> ", generator = ReplaceUnderscores.class) class A_year_is_a_leap_year { @Test void if_it_is_divisible_by_4_but_not_by_100() { } @ParameterizedTest(name = "Year {0} is a leap year.") @ValueSource(ints = { 2016, 2020, 2048 }) void if_it_is_one_of_the_following_years(int year) { } } // end::user_guide_indicative_sentences[] @Nested // tag::user_guide_custom_sentence_fragments[] @SentenceFragment("A year is a leap year") @IndicativeSentencesGeneration class LeapYearTests { @SentenceFragment("if it is divisible by 4 but not by 100") @Test void divisibleBy4ButNotBy100() { } @SentenceFragment("if it is one of the following years") @ParameterizedTest(name = "{0}") @ValueSource(ints = { 2016, 2020, 2048 }) void validLeapYear(int year) { } } // end::user_guide_custom_sentence_fragments[] } ================================================ FILE: documentation/src/test/java/example/DocumentationTestSuite.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; import org.junit.platform.suite.api.ExcludeTags; import org.junit.platform.suite.api.IncludeClassNamePatterns; import org.junit.platform.suite.api.SelectPackages; import org.junit.platform.suite.api.Suite; /** *

Logging Configuration

* *

In order for our log4j2 configuration to be used in an IDE, you must * set the following system property before running any tests — for * example, in Run Configurations in Eclipse. * *

 * -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager
 * 
* * @since 5.0 */ @Suite @SelectPackages("example") @IncludeClassNamePatterns(".+(Tests|Demo)$") @ExcludeTags("exclude") class DocumentationTestSuite { } ================================================ FILE: documentation/src/test/java/example/DynamicTestsDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; // tag::user_guide[] import static example.util.StringUtils.isPalindrome; 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.assertTrue; import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Random; import java.util.function.Function; import java.util.stream.IntStream; import java.util.stream.Stream; import example.util.Calculator; import org.junit.jupiter.api.DynamicNode; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.function.ThrowingConsumer; import org.junit.jupiter.api.parallel.Execution; // end::user_guide[] // @formatter:off // tag::user_guide[] class DynamicTestsDemo { private final Calculator calculator = new Calculator(); // This method will not be executed but produce a warning @TestFactory // end::user_guide[] @Tag("exclude") DynamicTest dummy() { return dynamicTest("dummy", () -> {}); } // tag::user_guide[] List dynamicTestsWithInvalidReturnType() { return Arrays.asList("Hello"); } @TestFactory Collection dynamicTestsFromCollection() { return Arrays.asList( dynamicTest("1st dynamic test", () -> assertTrue(isPalindrome("madam"))), dynamicTest("2nd dynamic test", () -> assertEquals(4, calculator.multiply(2, 2))) ); } @TestFactory Iterable dynamicTestsFromIterable() { return Arrays.asList( dynamicTest("3rd dynamic test", () -> assertTrue(isPalindrome("madam"))), dynamicTest("4th dynamic test", () -> assertEquals(4, calculator.multiply(2, 2))) ); } @TestFactory Iterator dynamicTestsFromIterator() { return Arrays.asList( dynamicTest("5th dynamic test", () -> assertTrue(isPalindrome("madam"))), dynamicTest("6th dynamic test", () -> assertEquals(4, calculator.multiply(2, 2))) ).iterator(); } @TestFactory DynamicTest[] dynamicTestsFromArray() { return new DynamicTest[] { dynamicTest("7th dynamic test", () -> assertTrue(isPalindrome("madam"))), dynamicTest("8th dynamic test", () -> assertEquals(4, calculator.multiply(2, 2))) }; } @TestFactory Stream dynamicTestsFromStream() { return Stream.of("racecar", "radar", "mom", "dad") .map(text -> dynamicTest(text, () -> assertTrue(isPalindrome(text)))); } @TestFactory Stream dynamicTestsFromIntStream() { // Generates tests for the first 10 even integers. return IntStream.iterate(0, n -> n + 2).limit(10) .mapToObj(n -> dynamicTest("test" + n, () -> assertEquals(0, n % 2))); } @TestFactory Stream generateRandomNumberOfTests() { // Generates random positive integers between 0 and 100 until // a number evenly divisible by 7 is encountered. Iterator inputGenerator = new Iterator<>() { Random random = new Random(); // end::user_guide[] { // Use fixed seed to always produce the same number of tests for execution on the CI server random = new Random(23); } // tag::user_guide[] int current; @Override public boolean hasNext() { current = random.nextInt(100); return current % 7 != 0; } @Override public Integer next() { return current; } }; // Generates display names like: input:5, input:37, input:85, etc. Function displayNameGenerator = (input) -> "input:" + input; // Executes tests based on the current input value. ThrowingConsumer testExecutor = (input) -> assertTrue(input % 7 != 0); // Returns a stream of dynamic tests. return DynamicTest.stream(inputGenerator, displayNameGenerator, testExecutor); } @TestFactory Stream dynamicTestsFromStreamFactoryMethod() { // Stream of palindromes to check Stream inputStream = Stream.of("racecar", "radar", "mom", "dad"); // Generates display names like: racecar is a palindrome Function displayNameGenerator = text -> text + " is a palindrome"; // Executes tests based on the current input value. ThrowingConsumer testExecutor = text -> assertTrue(isPalindrome(text)); // Returns a stream of dynamic tests. return DynamicTest.stream(inputStream, displayNameGenerator, testExecutor); } @TestFactory Stream dynamicTestsWithContainers() { return Stream.of("A", "B", "C") .map(input -> dynamicContainer("Container " + input, Stream.of( dynamicTest("not null", () -> assertNotNull(input)), dynamicContainer("properties", Stream.of( dynamicTest("length > 0", () -> assertTrue(input.length() > 0)), dynamicTest("not empty", () -> assertFalse(input.isEmpty())) )) ))); } // end::user_guide[] // tag::execution_mode[] @TestFactory @Execution(CONCURRENT) // <1> Stream dynamicTestsWithConfiguredExecutionMode() { return Stream.of("A", "B", "C") .map(input -> dynamicContainer(outer -> outer .displayName("Container " + input) .children( dynamicTest(config -> config .displayName("not null") .executionMode(SAME_THREAD) // <2> .executable(() -> assertNotNull(input)) ), dynamicContainer(inner -> inner .displayName("properties") .executionMode(CONCURRENT) // <3> .childExecutionMode(SAME_THREAD) // <4> .children( dynamicTest(config -> config .displayName("length > 0") .executionMode(CONCURRENT) // <5> .executable(() -> assertTrue(input.length() > 0)) ), dynamicTest(config -> config .displayName("not empty") .executable(() -> assertFalse(input.isEmpty())) ) ) ) ) ) ); } // end::execution_mode[] // tag::user_guide[] @TestFactory DynamicNode dynamicNodeSingleTest() { return dynamicTest("'pop' is a palindrome", () -> assertTrue(isPalindrome("pop"))); } @TestFactory DynamicNode dynamicNodeSingleContainer() { return dynamicContainer("palindromes", Stream.of("racecar", "radar", "mom", "dad") .map(text -> dynamicTest(text, () -> assertTrue(isPalindrome(text))) )); } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/DynamicTestsNamedDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; // tag::user_guide[] import static example.util.StringUtils.isPalindrome; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Named.named; import java.util.stream.Stream; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.NamedExecutable; import org.junit.jupiter.api.TestFactory; public class DynamicTestsNamedDemo { @TestFactory Stream dynamicTestsFromStreamFactoryMethodWithNames() { // Stream of palindromes to check // end::user_guide[] // @formatter:off // tag::user_guide[] var inputStream = Stream.of( named("racecar is a palindrome", "racecar"), named("radar is also a palindrome", "radar"), named("mom also seems to be a palindrome", "mom"), named("dad is yet another palindrome", "dad") ); // end::user_guide[] // @formatter:on // tag::user_guide[] // Returns a stream of dynamic tests. return DynamicTest.stream(inputStream, text -> assertTrue(isPalindrome(text))); } @TestFactory Stream dynamicTestsFromStreamFactoryMethodWithNamedExecutables() { // Stream of palindromes to check // end::user_guide[] // @formatter:off // tag::user_guide[] var inputStream = Stream.of("racecar", "radar", "mom", "dad") .map(PalindromeNamedExecutable::new); // end::user_guide[] // @formatter:on // tag::user_guide[] // Returns a stream of dynamic tests based on NamedExecutables. return DynamicTest.stream(inputStream); } record PalindromeNamedExecutable(String text) implements NamedExecutable { @Override public String getName() { return "'%s' is a palindrome".formatted(text); } @Override public void execute() { assertTrue(isPalindrome(text)); } } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/ExampleTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; // tag::user_guide[] import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assumptions.assumeTrue; import example.util.Calculator; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; @TestMethodOrder(OrderAnnotation.class) public class ExampleTestCase { private final Calculator calculator = new Calculator(); @Test @Disabled("for demonstration purposes") @Order(1) void skippedTest() { // skipped ... } @Test @Order(2) void succeedingTest() { assertEquals(42, calculator.multiply(6, 7)); } @Test @Order(3) void abortedTest() { assumeTrue("abc".contains("Z"), "abc does not contain Z"); // aborted ... } @Test @Order(4) void failingTest() { // The following throws an ArithmeticException: "/ by zero" calculator.divide(1, 0); } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/ExplicitExecutionModeDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ExecutionMode; //tag::user_guide[] @Execution(ExecutionMode.CONCURRENT) class ExplicitExecutionModeDemo { @Test void testA() { // concurrent } @Test @Execution(ExecutionMode.SAME_THREAD) void testB() { // overrides to same_thread } } //end::user_guide[] ================================================ FILE: documentation/src/test/java/example/ExternalCustomConditionDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; // tag::user_guide_external_custom_condition[] import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIf; class ExternalCustomConditionDemo { @Test @EnabledIf("example.ExternalCondition#customCondition") void enabled() { // ... } } class ExternalCondition { static boolean customCondition() { return true; } } // end::user_guide_external_custom_condition[] ================================================ FILE: documentation/src/test/java/example/ExternalFieldSourceDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; import java.util.List; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.FieldSource; class ExternalFieldSourceDemo { // tag::external_field_FieldSource_example[] @ParameterizedTest @FieldSource("example.FruitUtils#tropicalFruits") void testWithExternalFieldSource(String tropicalFruit) { // test with tropicalFruit } // end::external_field_FieldSource_example[] } class FruitUtils { public static final List tropicalFruits = List.of("pineapple", "kiwi"); } ================================================ FILE: documentation/src/test/java/example/ExternalMethodSourceDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; // tag::external_MethodSource_example[] import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; class ExternalMethodSourceDemo { @ParameterizedTest @MethodSource("example.StringsProviders#tinyStrings") void testWithExternalMethodSource(String tinyString) { // test with tiny string } } class StringsProviders { static Stream tinyStrings() { return Stream.of(".", "oo", "OOO"); } } // end::external_MethodSource_example[] ================================================ FILE: documentation/src/test/java/example/Fast.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; // tag::user_guide[] import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.junit.jupiter.api.Tag; @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Tag("fast") public @interface Fast { } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/FastTest.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; // tag::user_guide[] import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Tag("fast") @Test public @interface FastTest { } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/FirstCustomEngine.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; //tag::user_guide[] import static java.net.InetAddress.getLoopbackAddress; import static org.junit.platform.engine.TestExecutionResult.successful; import java.io.IOException; import java.io.UncheckedIOException; import java.net.ServerSocket; import org.jspecify.annotations.Nullable; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.EngineDescriptor; import org.junit.platform.engine.support.store.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; /** * First custom test engine implementation. */ public class FirstCustomEngine implements TestEngine { //end::user_guide[] @Nullable //tag::user_guide[] public ServerSocket socket; @Override public String getId() { return "first-custom-test-engine"; } //end::user_guide[] @Nullable //tag::user_guide[] public ServerSocket getSocket() { return this.socket; } @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { return new EngineDescriptor(uniqueId, "First Custom Test Engine"); } @Override public void execute(ExecutionRequest request) { request.getEngineExecutionListener() // tag::custom_line_break[] .executionStarted(request.getRootTestDescriptor()); NamespacedHierarchicalStore store = request.getStore(); socket = store.computeIfAbsent(Namespace.GLOBAL, "serverSocket", key -> { try { return new ServerSocket(0, 50, getLoopbackAddress()); } catch (IOException e) { throw new UncheckedIOException("Failed to start ServerSocket", e); } }, ServerSocket.class); request.getEngineExecutionListener() // tag::custom_line_break[] .executionFinished(request.getRootTestDescriptor(), successful()); } } //end::user_guide[] ================================================ FILE: documentation/src/test/java/example/HttpServerDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; import com.sun.net.httpserver.HttpServer; import example.extensions.HttpServerExtension; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; // tag::user_guide[] @ExtendWith(HttpServerExtension.class) public class HttpServerDemo { // end::user_guide[] @SuppressWarnings("HttpUrlsUsage") // tag::user_guide[] @Test void httpCall(HttpServer server) throws Exception { String hostName = server.getAddress().getHostName(); int port = server.getAddress().getPort(); String rawUrl = "http://%s:%d/example".formatted(hostName, port); URL requestUrl = URI.create(rawUrl).toURL(); String responseBody = sendRequest(requestUrl); assertEquals("This is a test", responseBody); } private static String sendRequest(URL url) throws IOException { HttpURLConnection connection = (HttpURLConnection) url.openConnection(); int contentLength = connection.getContentLength(); try (InputStream response = url.openStream()) { byte[] content = new byte[contentLength]; assertEquals(contentLength, response.read(content)); return new String(content, UTF_8); } } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/IgnoredTestsDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; // tag::user_guide[] import org.junit.Ignore; import org.junit.jupiter.api.Test; import org.junit.jupiter.migrationsupport.EnableJUnit4MigrationSupport; // @ExtendWith(IgnoreCondition.class) @SuppressWarnings("removal") @EnableJUnit4MigrationSupport class IgnoredTestsDemo { @Ignore @Test void testWillBeIgnored() { } @Test void testWillBeExecuted() { } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/JUnit4Tests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; import org.junit.Test; public class JUnit4Tests { @Test public void standardJUnit4Test() { // perform assertions } } ================================================ FILE: documentation/src/test/java/example/MethodSourceParameterResolutionDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.params.provider.Arguments.arguments; import java.util.stream.Stream; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; class MethodSourceParameterResolutionDemo { // @formatter:off // tag::parameter_resolution_MethodSource_example[] @RegisterExtension static final IntegerResolver integerResolver = new IntegerResolver(); @ParameterizedTest @MethodSource("factoryMethodWithArguments") void testWithFactoryMethodWithArguments(String argument) { assertTrue(argument.startsWith("2")); } static Stream factoryMethodWithArguments(int quantity) { return Stream.of( arguments(quantity + " apples"), arguments(quantity + " lemons") ); } static class IntegerResolver implements ParameterResolver { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return parameterContext.getParameter().getType() == int.class; } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return 2; } } // end::parameter_resolution_MethodSource_example[] // @formatter:on } ================================================ FILE: documentation/src/test/java/example/MyFirstJUnitJupiterRecordTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; // tag::user_guide[] import static org.junit.jupiter.api.Assertions.assertEquals; import example.util.Calculator; import org.junit.jupiter.api.Test; record MyFirstJUnitJupiterRecordTests() { @Test void addition() { assertEquals(2, new Calculator().add(1, 1)); } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/MyFirstJUnitJupiterTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; // tag::user_guide[] import static org.junit.jupiter.api.Assertions.assertEquals; import example.util.Calculator; import org.junit.jupiter.api.Test; class MyFirstJUnitJupiterTests { private final Calculator calculator = new Calculator(); @Test void addition() { assertEquals(2, calculator.add(1, 1)); } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/MyRandomParametersTest.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; import static org.junit.jupiter.api.Assertions.assertNotEquals; import example.extensions.Random; import org.junit.jupiter.api.Test; // tag::user_guide[] class MyRandomParametersTest { MyRandomParametersTest(@Random int randomNumber) { // Use randomNumber in constructor. } @Test void injectsInteger(@Random int i, @Random int j) { assertNotEquals(i, j); } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/OrderedNestedTestClassesDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; // tag::user_guide[] import org.junit.jupiter.api.ClassOrderer; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestClassOrder; @TestClassOrder(ClassOrderer.OrderAnnotation.class) class OrderedNestedTestClassesDemo { @Nested @Order(1) class PrimaryTests { @Test void test1() { } } @Nested @Order(2) class SecondaryTests { @Test void test2() { } } } //end::user_guide[] ================================================ FILE: documentation/src/test/java/example/OrderedTestsDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; // tag::user_guide[] import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; @TestMethodOrder(OrderAnnotation.class) class OrderedTestsDemo { @Test @Order(1) void nullValues() { // perform assertions against null values } @Test @Order(2) void emptyValues() { // perform assertions against empty values } @Test @Order(3) void validValues() { // perform assertions against valid values } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/ParameterizedClassDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import java.time.Duration; import java.util.Arrays; import example.util.StringUtils; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.params.Parameter; import org.junit.jupiter.params.ParameterizedClass; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; public class ParameterizedClassDemo { @Nested // tag::first_example[] @ParameterizedClass @ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" }) class PalindromeTests { @Parameter String candidate; @Test void palindrome() { assertTrue(StringUtils.isPalindrome(candidate)); } @Test void reversePalindrome() { String reverseCandidate = new StringBuilder(candidate).reverse().toString(); assertTrue(StringUtils.isPalindrome(reverseCandidate)); } } // end::first_example[] @Nested class ConstructorInjection { @Nested // tag::constructor_injection[] @ParameterizedClass @CsvSource({ "apple, 23", "banana, 42" }) class FruitTests { final String fruit; final int quantity; FruitTests(String fruit, int quantity) { this.fruit = fruit; this.quantity = quantity; } @Test void test() { assertFruit(fruit); assertQuantity(quantity); } @Test void anotherTest() { // ... } } // end::constructor_injection[] } @Nested class FieldInjection { @Nested // tag::field_injection[] @ParameterizedClass @CsvSource({ "apple, 23", "banana, 42" }) class FruitTests { @Parameter(0) String fruit; @Parameter(1) int quantity; @Test void test() { assertFruit(fruit); assertQuantity(quantity); } @Test void anotherTest() { // ... } } // end::field_injection[] } @Nested // tag::nested[] @Execution(SAME_THREAD) @ParameterizedClass @ValueSource(strings = { "apple", "banana" }) class FruitTests { @Parameter String fruit; @Nested @ParameterizedClass @ValueSource(ints = { 23, 42 }) class QuantityTests { @Parameter int quantity; @ParameterizedTest @ValueSource(strings = { "PT1H", "PT2H" }) void test(Duration duration) { assertFruit(fruit); assertQuantity(quantity); assertFalse(duration.isNegative()); } } } // end::nested[] static void assertFruit(String fruit) { assertTrue(Arrays.asList("apple", "banana", "cherry", "dewberry").contains(fruit), () -> "not a fruit: " + fruit); } static void assertQuantity(int quantity) { assertTrue(quantity > 0); } } ================================================ FILE: documentation/src/test/java/example/ParameterizedLifecycleDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.AfterParameterizedClassInvocation; import org.junit.jupiter.params.BeforeParameterizedClassInvocation; import org.junit.jupiter.params.Parameter; import org.junit.jupiter.params.ParameterizedClass; import org.junit.jupiter.params.provider.MethodSource; public class ParameterizedLifecycleDemo { @Nested // tag::example[] @ParameterizedClass @MethodSource("textFiles") class TextFileTests { static List textFiles() { return List.of( // tag::custom_line_break[] new TextFile("file1", "first content"), // tag::custom_line_break[] new TextFile("file2", "second content") // tag::custom_line_break[] ); } @Parameter TextFile textFile; @BeforeParameterizedClassInvocation static void beforeInvocation(TextFile textFile, @TempDir Path tempDir) throws Exception { var filePath = tempDir.resolve(textFile.fileName); // <1> textFile.path = Files.writeString(filePath, textFile.content); } //end::user_guide[] @SuppressWarnings("DataFlowIssue") //tag::user_guide[] @AfterParameterizedClassInvocation static void afterInvocation(TextFile textFile) throws Exception { var actualContent = Files.readString(textFile.path); // <3> assertEquals(textFile.content, actualContent, "Content must not have changed"); // Custom cleanup logic, if necessary // File will be deleted automatically by @TempDir support } //end::user_guide[] @SuppressWarnings("DataFlowIssue") //tag::user_guide[] @Test void test() { assertTrue(Files.exists(textFile.path)); // <2> } @Test void anotherTest() { // ... } static class TextFile { final String fileName; final String content; // end::example[] @Nullable // tag::example[] Path path; TextFile(String fileName, String content) { this.fileName = fileName; this.content = content; } @Override public String toString() { return fileName; } } } // end::example[] } ================================================ FILE: documentation/src/test/java/example/ParameterizedMigrationDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; import java.util.Arrays; import org.junit.jupiter.params.AfterParameterizedClassInvocation; import org.junit.jupiter.params.BeforeParameterizedClassInvocation; import org.junit.jupiter.params.ParameterizedClass; import org.junit.jupiter.params.provider.MethodSource; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; public class ParameterizedMigrationDemo { // tag::before[] @RunWith(Parameterized.class) // end::before[] static // tag::before[] public class JUnit4ParameterizedClassTests { @Parameterized.Parameters public static Iterable data() { return Arrays.asList(new Object[][] { { 1, "foo" }, { 2, "bar" } }); } // end::before[] @SuppressWarnings("DefaultAnnotationParam") // tag::before[] @Parameterized.Parameter(0) public int number; @Parameterized.Parameter(1) public String text; @Parameterized.BeforeParam public static void before(int number, String text) { } @Parameterized.AfterParam public static void after() { } @org.junit.Test public void someTest() { } @org.junit.Test public void anotherTest() { } } // end::before[] // tag::after[] @ParameterizedClass @MethodSource("data") // end::after[] static // tag::after[] class JupiterParameterizedClassTests { static Iterable data() { return Arrays.asList(new Object[][] { { 1, "foo" }, { 2, "bar" } }); } @org.junit.jupiter.params.Parameter(0) int number; @org.junit.jupiter.params.Parameter(1) String text; @BeforeParameterizedClassInvocation static void before(int number, String text) { } @AfterParameterizedClassInvocation static void after() { } @org.junit.jupiter.api.Test void someTest() { } @org.junit.jupiter.api.Test void anotherTest() { } } // end::after[] } ================================================ FILE: documentation/src/test/java/example/ParameterizedRecordDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Arrays; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedClass; import org.junit.jupiter.params.provider.CsvSource; public class ParameterizedRecordDemo { // tag::example[] @ParameterizedClass @CsvSource({ "apple, 23", "banana, 42" }) record FruitTests(String fruit, int quantity) { @Test void test() { assertFruit(fruit); assertQuantity(quantity); } @Test void anotherTest() { // ... } } // end::example[] static void assertFruit(String fruit) { assertTrue(Arrays.asList("apple", "banana", "cherry", "dewberry").contains(fruit)); } static void assertQuantity(int quantity) { assertTrue(quantity >= 0); } } ================================================ FILE: documentation/src/test/java/example/ParameterizedTestDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Named.named; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.junit.jupiter.params.provider.Arguments.argumentSet; import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.junit.jupiter.params.provider.EnumSource.Mode.EXCLUDE; import static org.junit.jupiter.params.provider.EnumSource.Mode.MATCH_ALL; import java.io.File; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.time.LocalDate; import java.time.temporal.ChronoUnit; import java.time.temporal.TemporalUnit; import java.util.Arrays; import java.util.Collection; import java.util.EnumSet; import java.util.List; import java.util.function.Supplier; import java.util.stream.IntStream; import java.util.stream.Stream; import example.domain.Person; import example.domain.Person.Gender; import example.util.StringUtils; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestReporter; import org.junit.jupiter.api.extension.AnnotatedElementContext; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.params.ArgumentCountValidationMode; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.aggregator.AggregateWith; import org.junit.jupiter.params.aggregator.ArgumentsAccessor; import org.junit.jupiter.params.aggregator.SimpleArgumentsAggregator; import org.junit.jupiter.params.converter.ConvertWith; import org.junit.jupiter.params.converter.JavaTimeConversionPattern; import org.junit.jupiter.params.converter.SimpleArgumentConverter; import org.junit.jupiter.params.converter.TypedArgumentConverter; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.ArgumentsProvider; import org.junit.jupiter.params.provider.ArgumentsSource; import org.junit.jupiter.params.provider.CsvFileSource; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.EmptySource; import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.FieldSource; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.NullAndEmptySource; import org.junit.jupiter.params.provider.NullSource; import org.junit.jupiter.params.provider.ValueSource; import org.junit.jupiter.params.support.ParameterDeclarations; @Execution(SAME_THREAD) class ParameterizedTestDemo { @BeforeEach void printDisplayName(TestInfo testInfo) { System.out.println(testInfo.getDisplayName()); } // tag::first_example[] @ParameterizedTest @ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" }) void palindromes(String candidate) { assertTrue(StringUtils.isPalindrome(candidate)); } // end::first_example[] // tag::ValueSource_example[] @ParameterizedTest @ValueSource(ints = { 1, 2, 3 }) void testWithValueSource(int argument) { assertTrue(argument > 0 && argument < 4); } // end::ValueSource_example[] @Nested class NullAndEmptySource_1 { // tag::NullAndEmptySource_example1[] @ParameterizedTest @NullSource @EmptySource @ValueSource(strings = { " ", " ", "\t", "\n" }) void nullEmptyAndBlankStrings(String text) { assertTrue(text == null || text.isBlank()); } // end::NullAndEmptySource_example1[] } @Nested class NullAndEmptySource_2 { // tag::NullAndEmptySource_example2[] @ParameterizedTest @NullAndEmptySource @ValueSource(strings = { " ", " ", "\t", "\n" }) void nullEmptyAndBlankStrings(String text) { assertTrue(text == null || text.isBlank()); } // end::NullAndEmptySource_example2[] } // tag::EnumSource_example[] @ParameterizedTest @EnumSource(ChronoUnit.class) void testWithEnumSource(TemporalUnit unit) { assertNotNull(unit); } // end::EnumSource_example[] // tag::EnumSource_example_autodetection[] @ParameterizedTest @EnumSource void testWithEnumSourceWithAutoDetection(ChronoUnit unit) { assertNotNull(unit); } // end::EnumSource_example_autodetection[] // tag::EnumSource_include_example[] @ParameterizedTest @EnumSource(names = { "DAYS", "HOURS" }) void testWithEnumSourceInclude(ChronoUnit unit) { assertTrue(EnumSet.of(ChronoUnit.DAYS, ChronoUnit.HOURS).contains(unit)); } // end::EnumSource_include_example[] // tag::EnumSource_range_example[] @ParameterizedTest @EnumSource(from = "HOURS", to = "DAYS") void testWithEnumSourceRange(ChronoUnit unit) { assertTrue(EnumSet.of(ChronoUnit.HOURS, ChronoUnit.HALF_DAYS, ChronoUnit.DAYS).contains(unit)); } // end::EnumSource_range_example[] // tag::EnumSource_exclude_example[] @ParameterizedTest @EnumSource(mode = EXCLUDE, names = { "ERAS", "FOREVER" }) void testWithEnumSourceExclude(ChronoUnit unit) { assertFalse(EnumSet.of(ChronoUnit.ERAS, ChronoUnit.FOREVER).contains(unit)); } // end::EnumSource_exclude_example[] // tag::EnumSource_regex_example[] @ParameterizedTest @EnumSource(mode = MATCH_ALL, names = "^.*DAYS$") void testWithEnumSourceRegex(ChronoUnit unit) { assertTrue(unit.name().endsWith("DAYS")); } // end::EnumSource_regex_example[] // tag::EnumSource_range_exclude_example[] @ParameterizedTest @EnumSource(from = "HOURS", to = "DAYS", mode = EXCLUDE, names = { "HALF_DAYS" }) void testWithEnumSourceRangeExclude(ChronoUnit unit) { assertTrue(EnumSet.of(ChronoUnit.HOURS, ChronoUnit.DAYS).contains(unit)); assertFalse(EnumSet.of(ChronoUnit.HALF_DAYS).contains(unit)); } // end::EnumSource_range_exclude_example[] // tag::simple_MethodSource_example[] @ParameterizedTest @MethodSource("stringProvider") void testWithExplicitLocalMethodSource(String argument) { assertNotNull(argument); } static Stream stringProvider() { return Stream.of("apple", "banana"); } // end::simple_MethodSource_example[] // tag::simple_MethodSource_without_value_example[] @ParameterizedTest @MethodSource void testWithDefaultLocalMethodSource(String argument) { assertNotNull(argument); } static Stream testWithDefaultLocalMethodSource() { return Stream.of("apple", "banana"); } // end::simple_MethodSource_without_value_example[] // tag::primitive_MethodSource_example[] @ParameterizedTest @MethodSource("range") void testWithRangeMethodSource(int argument) { assertNotEquals(9, argument); } static IntStream range() { return IntStream.range(0, 20).skip(10); } // end::primitive_MethodSource_example[] // @formatter:off // tag::multi_arg_MethodSource_example[] @ParameterizedTest @MethodSource("stringIntAndListProvider") void testWithMultiArgMethodSource(String str, int num, List list) { assertEquals(5, str.length()); assertTrue(num >=1 && num <=2); assertEquals(2, list.size()); } static Stream stringIntAndListProvider() { return Stream.of( arguments("apple", 1, Arrays.asList("a", "b")), arguments("lemon", 2, Arrays.asList("x", "y")) ); } // end::multi_arg_MethodSource_example[] // @formatter:on // @formatter:off // tag::default_field_FieldSource_example[] @ParameterizedTest @FieldSource void arrayOfFruits(String fruit) { assertFruit(fruit); } static final String[] arrayOfFruits = { "apple", "banana" }; // end::default_field_FieldSource_example[] // @formatter:on // @formatter:off // tag::explicit_field_FieldSource_example[] @ParameterizedTest @FieldSource("listOfFruits") void singleFieldSource(String fruit) { assertFruit(fruit); } static final List listOfFruits = Arrays.asList("apple", "banana"); // end::explicit_field_FieldSource_example[] // @formatter:on // @formatter:off // tag::multiple_fields_FieldSource_example[] @ParameterizedTest @FieldSource({ "listOfFruits", "additionalFruits" }) void multipleFieldSources(String fruit) { assertFruit(fruit); } static final Collection additionalFruits = Arrays.asList("cherry", "dewberry"); // end::multiple_fields_FieldSource_example[] // @formatter:on // @formatter:off // tag::named_arguments_FieldSource_example[] @ParameterizedTest @FieldSource void namedArgumentsSupplier(String fruit) { assertFruit(fruit); } static final Supplier> namedArgumentsSupplier = () -> Stream.of( arguments(named("Apple", "apple")), arguments(named("Banana", "banana")) ); // end::named_arguments_FieldSource_example[] // @formatter:on private static void assertFruit(String fruit) { assertTrue(Arrays.asList("apple", "banana", "cherry", "dewberry").contains(fruit)); } // @formatter:off // tag::multi_arg_FieldSource_example[] @ParameterizedTest @FieldSource("stringIntAndListArguments") void testWithMultiArgFieldSource(String str, int num, List list) { assertEquals(5, str.length()); assertTrue(num >=1 && num <=2); assertEquals(2, list.size()); } static List stringIntAndListArguments = Arrays.asList( arguments("apple", 1, Arrays.asList("a", "b")), arguments("lemon", 2, Arrays.asList("x", "y")) ); // end::multi_arg_FieldSource_example[] // @formatter:on // @formatter:off // tag::CsvSource_example[] @ParameterizedTest @CsvSource({ "apple, 1", "banana, 2", "'lemon, lime', 0xF1", "strawberry, 700_000" }) void testWithCsvSource(String fruit, int rank) { assertNotNull(fruit); assertNotEquals(0, rank); } // end::CsvSource_example[] // @formatter:on // tag::CsvFileSource_example[] @ParameterizedTest @CsvFileSource(resources = "/two-column.csv", numLinesToSkip = 1) void testWithCsvFileSourceFromClasspath(String country, int reference) { assertNotNull(country); assertNotEquals(0, reference); } @ParameterizedTest @CsvFileSource(files = "src/test/resources/two-column.csv", numLinesToSkip = 1) void testWithCsvFileSourceFromFile(String country, int reference) { assertNotNull(country); assertNotEquals(0, reference); } @ParameterizedTest @CsvFileSource(resources = "/two-column.csv", useHeadersInDisplayName = true) void testWithCsvFileSourceAndHeaders(String country, int reference) { assertNotNull(country); assertNotEquals(0, reference); } // end::CsvFileSource_example[] // tag::ArgumentsSource_example[] @ParameterizedTest @ArgumentsSource(MyArgumentsProvider.class) void testWithArgumentsSource(String argument) { assertNotNull(argument); } // end::ArgumentsSource_example[] static // tag::ArgumentsProvider_example[] public class MyArgumentsProvider implements ArgumentsProvider { @Override public Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context) { return Stream.of("apple", "banana").map(Arguments::of); } } // end::ArgumentsProvider_example[] @ParameterizedTest @ArgumentsSource(MyArgumentsProviderWithConstructorInjection.class) void testWithArgumentsSourceWithConstructorInjection(String argument) { assertNotNull(argument); } static // tag::ArgumentsProviderWithConstructorInjection_example[] public class MyArgumentsProviderWithConstructorInjection implements ArgumentsProvider { private final TestInfo testInfo; // end::ArgumentsProviderWithConstructorInjection_example[] @SuppressWarnings("RedundantModifier") // tag::ArgumentsProviderWithConstructorInjection_example[] public MyArgumentsProviderWithConstructorInjection(TestInfo testInfo) { this.testInfo = testInfo; } @Override public Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context) { return Stream.of(Arguments.of(testInfo.getDisplayName())); } } // end::ArgumentsProviderWithConstructorInjection_example[] // tag::ParameterResolver_example[] @BeforeEach void beforeEach(TestInfo testInfo) { // ... } @ParameterizedTest @ValueSource(strings = "apple") void testWithRegularParameterResolver(String argument, TestReporter testReporter) { testReporter.publishEntry("argument", argument); } @AfterEach void afterEach(TestInfo testInfo) { // ... } // end::ParameterResolver_example[] // tag::implicit_conversion_example[] @ParameterizedTest @ValueSource(strings = "SECONDS") void testWithImplicitArgumentConversion(ChronoUnit argument) { assertNotNull(argument.name()); } // end::implicit_conversion_example[] // tag::implicit_fallback_conversion_example[] @ParameterizedTest @ValueSource(strings = "42 Cats") void testWithImplicitFallbackArgumentConversion(Book book) { assertEquals("42 Cats", book.getTitle()); } // end::implicit_fallback_conversion_example[] static // tag::implicit_fallback_conversion_example_Book[] public class Book { private final String title; private Book(String title) { this.title = title; } public static Book fromTitle(String title) { return new Book(title); } public String getTitle() { return this.title; } } // end::implicit_fallback_conversion_example_Book[] // @formatter:off // tag::explicit_conversion_example[] @ParameterizedTest @EnumSource(ChronoUnit.class) void testWithExplicitArgumentConversion( @ConvertWith(ToStringArgumentConverter.class) String argument) { assertNotNull(ChronoUnit.valueOf(argument)); } // end::explicit_conversion_example[] static @SuppressWarnings({ "NullableProblems", "NullAway" }) // tag::explicit_conversion_example_ToStringArgumentConverter[] public class ToStringArgumentConverter extends SimpleArgumentConverter { @Override protected Object convert(Object source, Class targetType) { assertEquals(String.class, targetType, "Can only convert to String"); if (source instanceof Enum constant) { return constant.name(); } return String.valueOf(source); } } // end::explicit_conversion_example_ToStringArgumentConverter[] static @SuppressWarnings({ "NullableProblems", "NullAway", "ConstantValue" }) // tag::explicit_conversion_example_TypedArgumentConverter[] public class ToLengthArgumentConverter extends TypedArgumentConverter { protected ToLengthArgumentConverter() { super(String.class, Integer.class); } @Override protected Integer convert(String source) { return (source != null ? source.length() : 0); } } // end::explicit_conversion_example_TypedArgumentConverter[] // tag::explicit_java_time_converter[] @ParameterizedTest @ValueSource(strings = { "01.01.2017", "31.12.2017" }) void testWithExplicitJavaTimeConverter( @JavaTimeConversionPattern("dd.MM.yyyy") LocalDate argument) { assertEquals(2017, argument.getYear()); } // end::explicit_java_time_converter[] // @formatter:on // @formatter:off // tag::ArgumentsAccessor_example[] @ParameterizedTest @CsvSource({ "Jane, Doe, F, 1990-05-20", "John, Doe, M, 1990-10-22" }) void testWithArgumentsAccessor(ArgumentsAccessor arguments) { Person person = new Person( arguments.getString(0), arguments.getString(1), arguments.get(2, Gender.class), arguments.get(3, LocalDate.class)); if (person.getFirstName().equals("Jane")) { assertEquals(Gender.F, person.getGender()); } else { assertEquals(Gender.M, person.getGender()); } assertEquals("Doe", person.getLastName()); assertEquals(1990, person.getDateOfBirth().getYear()); } // end::ArgumentsAccessor_example[] // @formatter:on // @formatter:off // tag::ArgumentsAggregator_example[] @ParameterizedTest @CsvSource({ "Jane, Doe, F, 1990-05-20", "John, Doe, M, 1990-10-22" }) void testWithArgumentsAggregator(@AggregateWith(PersonAggregator.class) Person person) { // perform assertions against person } // end::ArgumentsAggregator_example[] static // tag::ArgumentsAggregator_example_PersonAggregator[] public class PersonAggregator extends SimpleArgumentsAggregator { @Override protected Person aggregateArguments(ArgumentsAccessor arguments, Class targetType, AnnotatedElementContext context, int parameterIndex) { return new Person( arguments.getString(0), arguments.getString(1), arguments.get(2, Gender.class), arguments.get(3, LocalDate.class)); } } // end::ArgumentsAggregator_example_PersonAggregator[] // @formatter:on // @formatter:off // tag::ArgumentsAggregator_with_custom_annotation_example[] @ParameterizedTest @CsvSource({ "Jane, Doe, F, 1990-05-20", "John, Doe, M, 1990-10-22" }) void testWithCustomAggregatorAnnotation(@CsvToPerson Person person) { // perform assertions against person } // end::ArgumentsAggregator_with_custom_annotation_example[] // tag::ArgumentsAggregator_with_custom_annotation_example_CsvToPerson[] @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) @AggregateWith(PersonAggregator.class) public @interface CsvToPerson { } // end::ArgumentsAggregator_with_custom_annotation_example_CsvToPerson[] // @formatter:on // tag::custom_display_names[] @DisplayName("Display name of container") @ParameterizedTest(name = "{index} ==> the rank of {0} is {1}") @CsvSource({ "apple, 1", "banana, 2", "'lemon, lime', 3" }) void testWithCustomDisplayNames(String fruit, int rank) { } // end::custom_display_names[] // @formatter:off // tag::named_arguments[] @DisplayName("A parameterized test with named arguments") @ParameterizedTest(name = "{index}: {0}") @MethodSource("namedArguments") void testWithNamedArguments(File file) { } static Stream namedArguments() { return Stream.of( arguments(named("An important file", new File("path1"))), arguments(named("Another file", new File("path2"))) ); } // end::named_arguments[] // @formatter:on // @formatter:off // tag::named_argument_set[] @DisplayName("A parameterized test with named argument sets") @ParameterizedTest @FieldSource("argumentSets") void testWithArgumentSets(File file1, File file2) { } static List argumentSets = Arrays.asList( argumentSet("Important files", new File("path1"), new File("path2")), argumentSet("Other files", new File("path3"), new File("path4")) ); // end::named_argument_set[] // @formatter:on // tag::repeatable_annotations[] @DisplayName("A parameterized test that makes use of repeatable annotations") @ParameterizedTest @MethodSource("someProvider") @MethodSource("otherProvider") void testWithRepeatedAnnotation(String argument) { assertNotNull(argument); } static Stream someProvider() { return Stream.of("foo"); } static Stream otherProvider() { return Stream.of("bar"); } // end::repeatable_annotations[] @Disabled("Fails prior to invoking the test method") // tag::argument_count_validation[] @ParameterizedTest(argumentCountValidation = ArgumentCountValidationMode.STRICT) @CsvSource({ "42, -666" }) void testWithArgumentCountValidation(int number) { assertTrue(number > 0); } // end::argument_count_validation[] } ================================================ FILE: documentation/src/test/java/example/PollingTimeoutDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; class PollingTimeoutDemo { // tag::user_guide[] @Test @Timeout(5) // Poll at most 5 seconds void pollUntil() throws InterruptedException { while (asynchronousResultNotAvailable()) { Thread.sleep(250); // custom poll interval } // Obtain the asynchronous result and perform assertions } // end::user_guide[] private boolean asynchronousResultNotAvailable() { return false; } } ================================================ FILE: documentation/src/test/java/example/RepeatedTestsDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; // tag::user_guide[] import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; import java.util.logging.Logger; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.RepetitionInfo; import org.junit.jupiter.api.TestInfo; // end::user_guide[] // Use fully qualified names to avoid having them show up in the imports. @org.junit.jupiter.api.parallel.Execution(org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD) // tag::user_guide[] class RepeatedTestsDemo { private Logger logger = // ... // end::user_guide[] Logger.getLogger(RepeatedTestsDemo.class.getName()); // tag::user_guide[] @BeforeEach void beforeEach(TestInfo testInfo, RepetitionInfo repetitionInfo) { int currentRepetition = repetitionInfo.getCurrentRepetition(); int totalRepetitions = repetitionInfo.getTotalRepetitions(); String methodName = testInfo.getTestMethod().get().getName(); logger.info("About to execute repetition %d of %d for %s".formatted( // currentRepetition, totalRepetitions, methodName)); } @RepeatedTest(10) void repeatedTest() { // ... } @RepeatedTest(5) void repeatedTestWithRepetitionInfo(RepetitionInfo repetitionInfo) { assertEquals(5, repetitionInfo.getTotalRepetitions()); } // end::user_guide[] // Use fully qualified name to avoid having it show up in the imports. @org.junit.jupiter.api.Disabled("intentional failures would break the build") // tag::user_guide[] @RepeatedTest(value = 8, failureThreshold = 2) void repeatedTestWithFailureThreshold(RepetitionInfo repetitionInfo) { // Simulate unexpected failure every second repetition if (repetitionInfo.getCurrentRepetition() % 2 == 0) { fail("Boom!"); } } @RepeatedTest(value = 1, name = "{displayName} {currentRepetition}/{totalRepetitions}") @DisplayName("Repeat!") void customDisplayName(TestInfo testInfo) { assertEquals("Repeat! 1/1", testInfo.getDisplayName()); } @RepeatedTest(value = 1, name = RepeatedTest.LONG_DISPLAY_NAME) @DisplayName("Details...") void customDisplayNameWithLongPattern(TestInfo testInfo) { assertEquals("Details... :: repetition 1 of 1", testInfo.getDisplayName()); } @RepeatedTest(value = 5, name = "Wiederholung {currentRepetition} von {totalRepetitions}") void repeatedTestInGerman() { // ... } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/SecondCustomEngine.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; import static java.net.InetAddress.getLoopbackAddress; import static org.junit.platform.engine.TestExecutionResult.successful; import java.io.IOException; import java.io.UncheckedIOException; import java.net.ServerSocket; import org.jspecify.annotations.Nullable; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.EngineDescriptor; import org.junit.platform.engine.support.store.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; //tag::user_guide[] /** * Second custom test engine implementation. */ public class SecondCustomEngine implements TestEngine { //end::user_guide[] @Nullable //tag::user_guide[] public ServerSocket socket; @Override public String getId() { return "second-custom-test-engine"; } //end::user_guide[] @Nullable //tag::user_guide[] public ServerSocket getSocket() { return this.socket; } @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { return new EngineDescriptor(uniqueId, "Second Custom Test Engine"); } @Override public void execute(ExecutionRequest request) { request.getEngineExecutionListener() // tag::custom_line_break[] .executionStarted(request.getRootTestDescriptor()); NamespacedHierarchicalStore store = request.getStore(); socket = store.computeIfAbsent(Namespace.GLOBAL, "serverSocket", key -> { try { return new ServerSocket(0, 50, getLoopbackAddress()); } catch (IOException e) { throw new UncheckedIOException("Failed to start ServerSocket", e); } }, ServerSocket.class); request.getEngineExecutionListener() // tag::custom_line_break[] .executionFinished(request.getRootTestDescriptor(), successful()); } } //end::user_guide[] ================================================ FILE: documentation/src/test/java/example/SlowTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import java.util.stream.IntStream; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.Execution; @Tag("exclude") @Disabled class SlowTests { @Execution(SAME_THREAD) @Test void a() { foo(); } @Test void b() { foo(); } @Test void c() { foo(); } @Test void d() { foo(); } @Test void e() { foo(); } @Test void f() { foo(); } @Test void g() { foo(); } @Test void h() { foo(); } @Test void i() { foo(); } @Test void j() { foo(); } @Test void k() { foo(); } @Test void l() { foo(); } @Test void m() { foo(); } @Test void n() { foo(); } @Test void o() { foo(); } @Test void p() { foo(); } @Execution(SAME_THREAD) @Test void q() { foo(); } @Test void r() { foo(); } @Test void s() { foo(); } private void foo() { IntStream.range(1, 100_000_000).mapToDouble(i -> Math.pow(i, i)).map(Math::sqrt).max(); } } ================================================ FILE: documentation/src/test/java/example/StandardTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; // tag::user_guide[] import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assumptions.assumeTrue; 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.Disabled; import org.junit.jupiter.api.Test; class StandardTests { @BeforeAll static void initAll() { } @BeforeEach void init() { } @Test void succeedingTest() { } // end::user_guide[] @extensions.ExpectToFail // tag::user_guide[] @Test void failingTest() { fail("a failing test"); } @Test @Disabled("for demonstration purposes") void skippedTest() { // not executed } @Test void abortedTest() { assumeTrue("abc".contains("Z")); fail("test should have been aborted"); } @AfterEach void tearDown() { } @AfterAll static void tearDownAll() { } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/SuiteDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; //tag::user_guide[] import org.junit.platform.suite.api.IncludeClassNamePatterns; import org.junit.platform.suite.api.SelectPackages; import org.junit.platform.suite.api.Suite; import org.junit.platform.suite.api.SuiteDisplayName; @Suite @SuiteDisplayName("JUnit Platform Suite Demo") @SelectPackages("example") @IncludeClassNamePatterns(".*Tests") //end::user_guide[] @org.junit.platform.suite.api.ExcludeTags("exclude") //tag::user_guide[] class SuiteDemo { } //end::user_guide[] ================================================ FILE: documentation/src/test/java/example/SystemPropertyExtensionDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; import static org.assertj.core.api.Assertions.assertThat; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.ClassOrderer; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestClassOrder; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.util.ClearSystemProperty; import org.junit.jupiter.api.util.ReadsSystemProperty; import org.junit.jupiter.api.util.RestoreSystemProperties; import org.junit.jupiter.api.util.SetSystemProperty; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; public class SystemPropertyExtensionDemo { // tag::systemproperty_clear_simple[] @Test @ClearSystemProperty(key = "some property") void testClearingProperty() { assertThat(System.getProperty("some property")).isNull(); } // end::systemproperty_clear_simple[] // tag::systemproperty_set_simple[] @Test @SetSystemProperty(key = "some property", value = "new value") void testSettingProperty() { assertThat(System.getProperty("some property")).isEqualTo("new value"); } // end::systemproperty_set_simple[] // tag::systemproperty_using_set_and_clear[] @Test @ClearSystemProperty(key = "1st property") @ClearSystemProperty(key = "2nd property") @SetSystemProperty(key = "3rd property", value = "new value") void testClearingAndSettingProperty() { assertThat(System.getProperty("1st property")).isNull(); assertThat(System.getProperty("2nd property")).isNull(); assertThat(System.getProperty("3rd property")).isEqualTo("new value"); } // end::systemproperty_using_set_and_clear[] @Nested // tag::systemproperty_using_at_class_level[] @ClearSystemProperty(key = "some property") class MySystemPropertyTest { @Test @SetSystemProperty(key = "some property", value = "new value") void clearedAtClasslevel() { assertThat(System.getProperty("some property")).isEqualTo("new value"); } } // end::systemproperty_using_at_class_level[] // tag::systemproperty_restore_test[] @ParameterizedTest @ValueSource(strings = { "foo", "bar" }) @RestoreSystemProperties void parameterizedTest(String value) { System.setProperty("some parameterized property", value); System.setProperty("some other dynamic property", "my code calculates somehow"); } // end::systemproperty_restore_test[] @Nested @TestClassOrder(ClassOrderer.OrderAnnotation.class) class SystemPropertyRestoreExample { @Nested @Order(1) // tag::systemproperty_class_restore_setup[] @TestInstance(TestInstance.Lifecycle.PER_CLASS) @RestoreSystemProperties class MySystemPropertyRestoreTest { @BeforeAll void beforeAll() { System.setProperty("A", "A value"); } @BeforeEach void beforeEach() { System.setProperty("B", "B value"); } @Test void isolatedTest1() { System.setProperty("C", "C value"); } @Test void isolatedTest2() { assertThat(System.getProperty("A")).isEqualTo("A value"); assertThat(System.getProperty("B")).isEqualTo("B value"); // Class-level @RestoreSystemProperties restores "C" to original state assertThat(System.getProperty("C")).isNull(); } } // end::systemproperty_class_restore_setup[] @Nested @Order(2) // tag::systemproperty_class_restore_isolated_class[] @ReadsSystemProperty class SomeOtherTestClass { @Test void isolatedTest() { assertThat(System.getProperty("A")).isNull(); assertThat(System.getProperty("B")).isNull(); assertThat(System.getProperty("C")).isNull(); } } // end::systemproperty_class_restore_isolated_class[] } // tag::systemproperty_method_combine_all_test[] @ParameterizedTest @ValueSource(ints = { 100, 500, 1000 }) @RestoreSystemProperties @SetSystemProperty(key = "DISABLE_CACHE", value = "TRUE") @ClearSystemProperty(key = "COPYWRITE_OVERLAY_TEXT") void imageGenerationTest(int imageSize) { System.setProperty("IMAGE_SIZE", String.valueOf(imageSize)); // Requires restore // Test your image generation utility with the current system properties } // end::systemproperty_method_combine_all_test[] } ================================================ FILE: documentation/src/test/java/example/TaggingDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; // tag::user_guide[] import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @Tag("fast") @Tag("model") class TaggingDemo { @Test @Tag("taxes") void testingTaxCalculation() { } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/TempDirectoryDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS; import java.io.IOException; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; import example.TempDirectoryDemo.InMemoryTempDirDemo.JimfsTempDirFactory; import example.util.ListWriter; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.AnnotatedElementContext; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDirDeletionStrategy; import org.junit.jupiter.api.io.TempDirFactory; @SuppressWarnings("NewClassNamingConvention") class TempDirectoryDemo { // tag::user_guide_parameter_injection[] @Test void writeItemsToFile(@TempDir Path tempDir) throws IOException { Path file = tempDir.resolve("test.txt"); new ListWriter(file).write("a", "b", "c"); assertEquals(List.of("a,b,c"), Files.readAllLines(file)); } // end::user_guide_parameter_injection[] // tag::user_guide_multiple_directories[] @Test void copyFileFromSourceToTarget(@TempDir Path source, @TempDir Path target) throws IOException { Path sourceFile = source.resolve("test.txt"); new ListWriter(sourceFile).write("a", "b", "c"); Path targetFile = Files.copy(sourceFile, target.resolve("test.txt")); assertNotEquals(sourceFile, targetFile); assertEquals(List.of("a,b,c"), Files.readAllLines(targetFile)); } // end::user_guide_multiple_directories[] static // tag::user_guide_field_injection[] class SharedTempDirectoryDemo { @TempDir static Path sharedTempDir; @Test void writeItemsToFile() throws IOException { Path file = sharedTempDir.resolve("test.txt"); new ListWriter(file).write("a", "b", "c"); assertEquals(List.of("a,b,c"), Files.readAllLines(file)); } @Test void anotherTestThatUsesTheSameTempDir() { // use sharedTempDir } } // end::user_guide_field_injection[] static // tag::user_guide_cleanup_mode[] class CleanupModeDemo { @Test void fileTest(@TempDir(cleanup = ON_SUCCESS) Path tempDir) { // perform test } } // end::user_guide_cleanup_mode[] static // tag::user_guide_factory_name_prefix[] class TempDirFactoryDemo { @Test void factoryTest(@TempDir(factory = Factory.class) Path tempDir) { assertTrue(tempDir.getFileName().toString().startsWith("factoryTest")); } static class Factory implements TempDirFactory { @Override public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) throws IOException { return Files.createTempDirectory(extensionContext.getRequiredTestMethod().getName()); } } } // end::user_guide_factory_name_prefix[] static // tag::user_guide_factory_jimfs[] class InMemoryTempDirDemo { @Test void test(@TempDir(factory = JimfsTempDirFactory.class) Path tempDir) { // perform test } static class JimfsTempDirFactory implements TempDirFactory { private final FileSystem fileSystem = Jimfs.newFileSystem(Configuration.unix()); @Override public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) throws IOException { return Files.createTempDirectory(fileSystem.getPath("/"), "junit-"); } @Override public void close() throws IOException { fileSystem.close(); } } } // end::user_guide_factory_jimfs[] static // tag::user_guide_deletion_strategy[] class DeletionStrategyDemo { @Test void test(@TempDir(deletionStrategy = TempDirDeletionStrategy.IgnoreFailures.class) Path tempDir) { // perform test } } // end::user_guide_deletion_strategy[] // tag::user_guide_composed_annotation[] @Target({ ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) @TempDir(factory = JimfsTempDirFactory.class) @interface JimfsTempDir { } // end::user_guide_composed_annotation[] static // tag::user_guide_composed_annotation_usage[] class JimfsTempDirAnnotationDemo { @Test void test(@JimfsTempDir Path tempDir) { // perform test } } // end::user_guide_composed_annotation_usage[] } ================================================ FILE: documentation/src/test/java/example/TestInfoDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; // tag::user_guide[] import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; @DisplayName("TestInfo Demo") class TestInfoDemo { @BeforeAll static void beforeAll(TestInfo testInfo) { assertEquals("TestInfo Demo", testInfo.getDisplayName()); } TestInfoDemo(TestInfo testInfo) { String displayName = testInfo.getDisplayName(); assertTrue(displayName.equals("TEST 1") || displayName.equals("test2()")); } @BeforeEach void init(TestInfo testInfo) { String displayName = testInfo.getDisplayName(); assertTrue(displayName.equals("TEST 1") || displayName.equals("test2()")); } @Test @DisplayName("TEST 1") @Tag("my-tag") void test1(TestInfo testInfo) { assertEquals("TEST 1", testInfo.getDisplayName()); assertTrue(testInfo.getTags().contains("my-tag")); } @Test void test2() { } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/TestReporterDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; import java.util.List; import java.util.Map; import org.junit.jupiter.api.MediaType; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestReporter; import org.junit.jupiter.api.io.TempDir; // tag::user_guide[] class TestReporterDemo { @Test void reportSingleValue(TestReporter testReporter) { testReporter.publishEntry("a status message"); } @Test void reportKeyValuePair(TestReporter testReporter) { testReporter.publishEntry("a key", "a value"); } @Test void reportMultipleKeyValuePairs(TestReporter testReporter) { Map values = new HashMap<>(); values.put("user name", "dk38"); values.put("award year", "1974"); testReporter.publishEntry(values); } @Test void reportFiles(TestReporter testReporter, @TempDir Path tempDir) throws Exception { testReporter.publishFile("test1.txt", MediaType.TEXT_PLAIN_UTF_8, file -> Files.write(file, List.of("Test 1"))); Path existingFile = Files.write(tempDir.resolve("test2.txt"), List.of("Test 2")); testReporter.publishFile(existingFile, MediaType.TEXT_PLAIN_UTF_8); testReporter.publishDirectory("test3", dir -> { Files.write(dir.resolve("nested1.txt"), List.of("Nested content 1")); Files.write(dir.resolve("nested2.txt"), List.of("Nested content 2")); }); Path existingDir = Files.createDirectory(tempDir.resolve("test4")); Files.write(existingDir.resolve("nested1.txt"), List.of("Nested content 1")); Files.write(existingDir.resolve("nested2.txt"), List.of("Nested content 2")); testReporter.publishDirectory(existingDir); } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/TestTemplateDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.jupiter.api.extension.TestTemplateInvocationContext; import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; class TestTemplateDemo { // tag::user_guide[] final List fruits = Arrays.asList("apple", "banana", "lemon"); @TestTemplate @ExtendWith(MyTestTemplateInvocationContextProvider.class) void testTemplate(String fruit) { assertTrue(fruits.contains(fruit)); } // end::user_guide[] static // @formatter:off // tag::user_guide[] public class MyTestTemplateInvocationContextProvider implements TestTemplateInvocationContextProvider { @Override public boolean supportsTestTemplate(ExtensionContext context) { return true; } @Override public Stream provideTestTemplateInvocationContexts( ExtensionContext context) { return Stream.of(invocationContext("apple"), invocationContext("banana")); } private TestTemplateInvocationContext invocationContext(String parameter) { return new TestTemplateInvocationContext() { @Override public String getDisplayName(int invocationIndex) { return parameter; } @Override public List getAdditionalExtensions() { return List.of(new ParameterResolver() { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return parameterContext.getParameter().getType().equals(String.class); } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return parameter; } }); } }; } } // end::user_guide[] // @formatter:on } ================================================ FILE: documentation/src/test/java/example/TestingAStackDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; // tag::user_guide[] import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.EmptyStackException; import java.util.Stack; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @DisplayName("A stack") class TestingAStackDemo { @Test @DisplayName("is instantiated with new Stack()") void isInstantiatedWithNew() { new Stack<>(); } @Nested @DisplayName("when new") class WhenNew { Stack stack; @BeforeEach void createNewStack() { stack = new Stack<>(); } @Test @DisplayName("is empty") void isEmpty() { assertTrue(stack.isEmpty()); } @Test @DisplayName("throws EmptyStackException when popped") void throwsExceptionWhenPopped() { assertThrows(EmptyStackException.class, stack::pop); } @Test @DisplayName("throws EmptyStackException when peeked") void throwsExceptionWhenPeeked() { assertThrows(EmptyStackException.class, stack::peek); } @Nested @DisplayName("after pushing an element") class AfterPushing { String anElement = "an element"; @BeforeEach void pushAnElement() { stack.push(anElement); } @Test @DisplayName("it is no longer empty") void isNotEmpty() { assertFalse(stack.isEmpty()); } @Test @DisplayName("returns the element when popped and is empty") void returnElementWhenPopped() { assertEquals(anElement, stack.pop()); assertTrue(stack.isEmpty()); } @Test @DisplayName("returns the element when peeked but remains not empty") void returnElementWhenPeeked() { assertEquals(anElement, stack.peek()); assertFalse(stack.isEmpty()); } } } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/TimeoutDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.Timeout.ThreadMode; // tag::user_guide[] @Tag("timeout") class TimeoutDemo { @BeforeEach @Timeout(5) void setUp() { // fails if execution time exceeds 5 seconds } @Test @Timeout(value = 500, unit = TimeUnit.MILLISECONDS) void failsIfExecutionTimeExceeds500Milliseconds() { // fails if execution time exceeds 500 milliseconds } @Test @Timeout(value = 500, unit = TimeUnit.MILLISECONDS, threadMode = ThreadMode.SEPARATE_THREAD) void failsIfExecutionTimeExceeds500MillisecondsInSeparateThread() { // fails if execution time exceeds 500 milliseconds, the test code is executed in a separate thread } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/UsingTheLauncherDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; import static org.junit.platform.engine.TestExecutionResult.Status.FAILED; import static org.junit.platform.engine.discovery.ClassNameFilter.includeClassNamePatterns; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; import java.io.PrintWriter; import java.nio.file.Path; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.FilterResult; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.LauncherExecutionRequest; import org.junit.platform.launcher.LauncherSession; import org.junit.platform.launcher.LauncherSessionListener; import org.junit.platform.launcher.PostDiscoveryFilter; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; import org.junit.platform.launcher.core.LauncherConfig; import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; import org.junit.platform.launcher.core.LauncherExecutionRequestBuilder; import org.junit.platform.launcher.core.LauncherFactory; import org.junit.platform.launcher.listeners.SummaryGeneratingListener; import org.junit.platform.launcher.listeners.TestExecutionSummary; import org.junit.platform.reporting.legacy.xml.LegacyXmlReportGeneratingListener; /** * @since 5.0 */ class UsingTheLauncherDemo { @Tag("exclude") @Test @SuppressWarnings("unused") void execution() { // @formatter:off // tag::execution[] LauncherDiscoveryRequest discoveryRequest = LauncherDiscoveryRequestBuilder.request() .selectors( selectPackage("com.example.mytests"), selectClass(MyTestClass.class) ) .filters( includeClassNamePatterns(".*Tests") ) // end::execution[] .configurationParameter("enableHttpServer", "false") // tag::execution[] .build(); SummaryGeneratingListener listener = new SummaryGeneratingListener(); try (LauncherSession session = LauncherFactory.openSession()) { Launcher launcher = session.getLauncher(); // Register one ore more listeners of your choice. launcher.registerTestExecutionListeners(listener); // Discover tests and build a test plan. TestPlan testPlan = launcher.discover(discoveryRequest); // Execute the test plan. launcher.execute(testPlan); // Alternatively, execute the discovery request directly. launcher.execute(discoveryRequest); } TestExecutionSummary summary = listener.getSummary(); // Do something with the summary... // end::execution[] // @formatter:on } @Test void launcherConfig() { Path reportsDir = Path.of("target", "xml-reports"); PrintWriter out = new PrintWriter(System.out); // @formatter:off // tag::launcherConfig[] LauncherConfig launcherConfig = LauncherConfig.builder() .enableTestEngineAutoRegistration(false) .enableLauncherSessionListenerAutoRegistration(false) .enableLauncherDiscoveryListenerAutoRegistration(false) .enablePostDiscoveryFilterAutoRegistration(false) .enableTestExecutionListenerAutoRegistration(false) .addTestEngines(new CustomTestEngine()) .addLauncherSessionListeners(new CustomLauncherSessionListener()) .addLauncherDiscoveryListeners(new CustomLauncherDiscoveryListener()) .addPostDiscoveryFilters(new CustomPostDiscoveryFilter()) .addTestExecutionListeners(new LegacyXmlReportGeneratingListener(reportsDir, out)) .addTestExecutionListeners(new CustomTestExecutionListener()) .build(); LauncherDiscoveryRequest discoveryRequest = LauncherDiscoveryRequestBuilder.request() .selectors(selectPackage("com.example.mytests")) .build(); try (LauncherSession session = LauncherFactory.openSession(launcherConfig)) { session.getLauncher().execute(discoveryRequest); } // end::launcherConfig[] // @formatter:on } @Test @SuppressWarnings("unused") void cancellationDirect() { // tag::cancellation-direct[] CancellationToken cancellationToken = CancellationToken.create(); // <1> TestExecutionListener failFastListener = new TestExecutionListener() { @Override public void executionFinished(TestIdentifier identifier, TestExecutionResult result) { if (result.getStatus() == FAILED) { cancellationToken.cancel(); // <2> } } }; // end::cancellation-direct[] // @formatter:off // tag::cancellation-direct[] LauncherExecutionRequest executionRequest = LauncherDiscoveryRequestBuilder.request() .selectors(selectClass(MyTestClass.class)) .forExecution() // end::cancellation-direct[] // @formatter:on // tag::cancellation-direct[] .cancellationToken(cancellationToken) // <3> .listeners(failFastListener) // <4> .build(); try (LauncherSession session = LauncherFactory.openSession()) { session.getLauncher().execute(executionRequest); // <5> } // end::cancellation-direct[] } @Test @SuppressWarnings("unused") void cancellationFromDiscoveryRequest() { CancellationToken cancellationToken = CancellationToken.create(); TestExecutionListener failFastListener = new TestExecutionListener() { @Override public void executionFinished(TestIdentifier identifier, TestExecutionResult result) { if (result.getStatus() == FAILED) { cancellationToken.cancel(); } } }; // @formatter:off // tag::cancellation-discovery-request[] LauncherDiscoveryRequest discoveryRequest = LauncherDiscoveryRequestBuilder.request() .selectors(selectClass(MyTestClass.class)) .build(); // <1> // end::cancellation-discovery-request[] // @formatter:on // tag::cancellation-discovery-request[] LauncherExecutionRequest executionRequest = LauncherExecutionRequestBuilder.request(discoveryRequest) // <2> .cancellationToken(cancellationToken) // <3> .listeners(failFastListener) // <4> .build(); try (LauncherSession session = LauncherFactory.openSession()) { session.getLauncher().execute(executionRequest); // <5> } // end::cancellation-discovery-request[] } @Test @SuppressWarnings("unused") void cancellationFromTestPlan() { CancellationToken cancellationToken = CancellationToken.create(); TestExecutionListener failFastListener = new TestExecutionListener() { @Override public void executionFinished(TestIdentifier identifier, TestExecutionResult result) { if (result.getStatus() == FAILED) { cancellationToken.cancel(); } } }; // @formatter:off // tag::cancellation-test-plan[] LauncherDiscoveryRequest discoveryRequest = LauncherDiscoveryRequestBuilder.request() .selectors(selectClass(MyTestClass.class)) .build(); // <1> // end::cancellation-test-plan[] // @formatter:on // tag::cancellation-test-plan[] try (LauncherSession session = LauncherFactory.openSession()) { var launcher = session.getLauncher(); TestPlan testPlan = launcher.discover(discoveryRequest); // <2> LauncherExecutionRequest executionRequest = LauncherExecutionRequestBuilder.request(testPlan) // <3> .cancellationToken(cancellationToken) // <4> .listeners(failFastListener) // <5> .build(); launcher.execute(executionRequest); // <6> } // end::cancellation-test-plan[] } } class MyTestClass { } class CustomTestExecutionListener implements TestExecutionListener { } class CustomLauncherSessionListener implements LauncherSessionListener { } class CustomLauncherDiscoveryListener implements LauncherDiscoveryListener { } class CustomPostDiscoveryFilter implements PostDiscoveryFilter { @Override public FilterResult apply(TestDescriptor object) { return FilterResult.included("includes everything"); } } ================================================ FILE: documentation/src/test/java/example/UsingTheLauncherForDiscoveryDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; // tag::imports[] import static org.junit.platform.engine.discovery.ClassNameFilter.includeClassNamePatterns; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.discoveryRequest; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.LauncherSession; import org.junit.platform.launcher.TestPlan; import org.junit.platform.launcher.core.LauncherFactory; // end::imports[] /** * @since 6.0 */ class UsingTheLauncherForDiscoveryDemo { @org.junit.jupiter.api.Test @SuppressWarnings("unused") void discovery() { // @formatter:off // tag::discovery[] LauncherDiscoveryRequest discoveryRequest = discoveryRequest() .selectors( selectPackage("com.example.mytests"), selectClass(MyTestClass.class) ) .filters( includeClassNamePatterns(".*Tests") ) .build(); try (LauncherSession session = LauncherFactory.openSession()) { TestPlan testPlan = session.getLauncher().discover(discoveryRequest); // ... discover additional test plans or execute tests } // end::discovery[] // @formatter:on } static class MyTestClass { } } ================================================ FILE: documentation/src/test/java/example/callbacks/AbstractDatabaseTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.callbacks; // tag::user_guide[] import static example.callbacks.Logger.afterAllMethod; import static example.callbacks.Logger.afterEachMethod; import static example.callbacks.Logger.beforeAllMethod; import static example.callbacks.Logger.beforeEachMethod; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; /** * Abstract base class for tests that use the database. */ abstract class AbstractDatabaseTests { @BeforeAll static void createDatabase() { beforeAllMethod(AbstractDatabaseTests.class.getSimpleName() + ".createDatabase()"); } @BeforeEach void connectToDatabase() { beforeEachMethod(AbstractDatabaseTests.class.getSimpleName() + ".connectToDatabase()"); } @AfterEach void disconnectFromDatabase() { afterEachMethod(AbstractDatabaseTests.class.getSimpleName() + ".disconnectFromDatabase()"); } @AfterAll static void destroyDatabase() { afterAllMethod(AbstractDatabaseTests.class.getSimpleName() + ".destroyDatabase()"); } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/callbacks/BrokenLifecycleMethodConfigDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.callbacks; // tag::user_guide[] import static example.callbacks.Logger.afterEachMethod; import static example.callbacks.Logger.beforeEachMethod; import static example.callbacks.Logger.testMethod; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; /** * Example of "broken" lifecycle method configuration. * *

Test data is inserted before the database connection has been opened. * *

Database connection is closed before deleting test data. */ @ExtendWith({ Extension1.class, Extension2.class }) class BrokenLifecycleMethodConfigDemo { @BeforeEach void connectToDatabase() { beforeEachMethod(getClass().getSimpleName() + ".connectToDatabase()"); } @BeforeEach void insertTestDataIntoDatabase() { beforeEachMethod(getClass().getSimpleName() + ".insertTestDataIntoDatabase()"); } @Test void testDatabaseFunctionality() { testMethod(getClass().getSimpleName() + ".testDatabaseFunctionality()"); } @AfterEach void deleteTestDataFromDatabase() { afterEachMethod(getClass().getSimpleName() + ".deleteTestDataFromDatabase()"); } @AfterEach void disconnectFromDatabase() { afterEachMethod(getClass().getSimpleName() + ".disconnectFromDatabase()"); } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/callbacks/DatabaseTestsDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.callbacks; // tag::user_guide[] import static example.callbacks.Logger.afterEachMethod; import static example.callbacks.Logger.beforeAllMethod; import static example.callbacks.Logger.beforeEachMethod; import static example.callbacks.Logger.testMethod; 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.Test; import org.junit.jupiter.api.extension.ExtendWith; /** * Extension of {@link AbstractDatabaseTests} that inserts test data * into the database (after the database connection has been opened) * and deletes test data (before the database connection is closed). */ @ExtendWith({ Extension1.class, Extension2.class }) class DatabaseTestsDemo extends AbstractDatabaseTests { @BeforeAll static void beforeAll() { beforeAllMethod(DatabaseTestsDemo.class.getSimpleName() + ".beforeAll()"); } @BeforeEach void insertTestDataIntoDatabase() { beforeEachMethod(getClass().getSimpleName() + ".insertTestDataIntoDatabase()"); } @Test void testDatabaseFunctionality() { testMethod(getClass().getSimpleName() + ".testDatabaseFunctionality()"); } @AfterEach void deleteTestDataFromDatabase() { afterEachMethod(getClass().getSimpleName() + ".deleteTestDataFromDatabase()"); } @AfterAll static void afterAll() { beforeAllMethod(DatabaseTestsDemo.class.getSimpleName() + ".afterAll()"); } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/callbacks/Extension1.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.callbacks; // tag::user_guide[] import static example.callbacks.Logger.afterEachCallback; import static example.callbacks.Logger.beforeEachCallback; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; public class Extension1 implements BeforeEachCallback, AfterEachCallback { @Override public void beforeEach(ExtensionContext context) { beforeEachCallback(this); } @Override public void afterEach(ExtensionContext context) { afterEachCallback(this); } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/callbacks/Extension2.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.callbacks; // tag::user_guide[] import static example.callbacks.Logger.afterEachCallback; import static example.callbacks.Logger.beforeEachCallback; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; public class Extension2 implements BeforeEachCallback, AfterEachCallback { @Override public void beforeEach(ExtensionContext context) { beforeEachCallback(this); } @Override public void afterEach(ExtensionContext context) { afterEachCallback(this); } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/callbacks/Logger.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.callbacks; import java.util.function.Supplier; import org.junit.jupiter.api.extension.Extension; class Logger { static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(Logger.class.getName()); static void beforeAllMethod(String text) { log(() -> "@BeforeAll " + text); } static void beforeEachCallback(Extension extension) { log(() -> " " + extension.getClass().getSimpleName() + ".beforeEach()"); } static void beforeEachMethod(String text) { log(() -> " @BeforeEach " + text); } static void testMethod(String text) { log(() -> " @Test " + text); } static void afterEachMethod(String text) { log(() -> " @AfterEach " + text); } static void afterEachCallback(Extension extension) { log(() -> " " + extension.getClass().getSimpleName() + ".afterEach()"); } static void afterAllMethod(String text) { log(() -> "@AfterAll " + text); } private static void log(Supplier supplier) { // System.err.println(supplier.get()); logger.info(supplier); } } ================================================ FILE: documentation/src/test/java/example/callbacks/package-info.java ================================================ @NullMarked package example.callbacks; import org.jspecify.annotations.NullMarked; ================================================ FILE: documentation/src/test/java/example/defaultmethods/ComparableContract.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.defaultmethods; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; // tag::user_guide[] public interface ComparableContract> extends Testable { T createSmallerValue(); @Test default void returnsZeroWhenComparedToItself() { T value = createValue(); assertEquals(0, value.compareTo(value)); } @Test default void returnsPositiveNumberWhenComparedToSmallerValue() { T value = createValue(); T smallerValue = createSmallerValue(); assertTrue(value.compareTo(smallerValue) > 0); } @Test default void returnsNegativeNumberWhenComparedToLargerValue() { T value = createValue(); T smallerValue = createSmallerValue(); assertTrue(smallerValue.compareTo(value) < 0); } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/defaultmethods/EqualsContract.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.defaultmethods; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import org.junit.jupiter.api.Test; // tag::user_guide[] public interface EqualsContract extends Testable { T createNotEqualValue(); @Test default void valueEqualsItself() { T value = createValue(); assertEquals(value, value); } @Test default void valueDoesNotEqualNull() { T value = createValue(); assertNotEquals(null, value); } @Test default void valueDoesNotEqualDifferentValue() { T value = createValue(); T differentValue = createNotEqualValue(); assertNotEquals(value, differentValue); assertNotEquals(differentValue, value); } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/defaultmethods/StringTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.defaultmethods; // tag::user_guide[] class StringTests implements ComparableContract, EqualsContract { @Override public String createValue() { return "banana"; } @Override public String createSmallerValue() { return "apple"; // 'a' < 'b' in "banana" } @Override public String createNotEqualValue() { return "cherry"; } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/defaultmethods/Testable.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.defaultmethods; // tag::user_guide[] public interface Testable { T createValue(); } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/defaultmethods/package-info.java ================================================ @NullMarked package example.defaultmethods; import org.jspecify.annotations.NullMarked; ================================================ FILE: documentation/src/test/java/example/exception/AssertDoesNotThrowExceptionDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.exception; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import org.junit.jupiter.api.Test; class AssertDoesNotThrowExceptionDemo { // tag::user_guide[] @Test void testExceptionIsNotThrown() { assertDoesNotThrow(() -> { shouldNotThrowException(); }); } void shouldNotThrowException() { } // end::user_guide[] } ================================================ FILE: documentation/src/test/java/example/exception/ExceptionAssertionDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.exception; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.Test; class ExceptionAssertionDemo { // @formatter:off // tag::user_guide[] @Test void testExpectedExceptionIsThrown() { // The following assertion succeeds because the code under assertion // throws the expected IllegalArgumentException. // The assertion also returns the thrown exception which can be used for // further assertions like asserting the exception message. IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { throw new IllegalArgumentException("expected message"); }); assertEquals("expected message", exception.getMessage()); // The following assertion also succeeds because the code under assertion // throws IllegalArgumentException which is a subclass of RuntimeException. assertThrows(RuntimeException.class, () -> { throw new IllegalArgumentException("expected message"); }); } // end::user_guide[] // @formatter:on } ================================================ FILE: documentation/src/test/java/example/exception/ExceptionAssertionExactDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.exception; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrowsExactly; import extensions.ExpectToFail; import org.junit.jupiter.api.Test; public class ExceptionAssertionExactDemo { @ExpectToFail // @formatter:off // tag::user_guide[] @Test void testExpectedExceptionIsThrown() { // The following assertion succeeds because the code under assertion throws // IllegalArgumentException which is exactly equal to the expected type. // The assertion also returns the thrown exception which can be used for // further assertions like asserting the exception message. IllegalArgumentException exception = assertThrowsExactly(IllegalArgumentException.class, () -> { throw new IllegalArgumentException("expected message"); }); assertEquals("expected message", exception.getMessage()); // The following assertion fails because the assertion expects exactly // RuntimeException to be thrown, not subclasses of RuntimeException. assertThrowsExactly(RuntimeException.class, () -> { throw new IllegalArgumentException("expected message"); }); } // end::user_guide[] // @formatter:on } ================================================ FILE: documentation/src/test/java/example/exception/FailedAssertionDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.exception; import static org.junit.jupiter.api.Assertions.assertEquals; import example.util.Calculator; import extensions.ExpectToFail; import org.junit.jupiter.api.Test; class FailedAssertionDemo { // tag::user_guide[] private final Calculator calculator = new Calculator(); // end::user_guide[] @ExpectToFail // tag::user_guide[] @Test void failsDueToUncaughtAssertionError() { // The following incorrect assertion will cause a test failure. // The expected value should be 2 instead of 99. assertEquals(99, calculator.add(1, 1)); } // end::user_guide[] } ================================================ FILE: documentation/src/test/java/example/exception/IgnoreIOExceptionExtension.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.exception; import java.io.IOException; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; // @formatter:off // tag::user_guide[] public class IgnoreIOExceptionExtension implements TestExecutionExceptionHandler { @Override public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { if (throwable instanceof IOException) { return; } throw throwable; } } // end::user_guide[] // @formatter:on ================================================ FILE: documentation/src/test/java/example/exception/IgnoreIOExceptionTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.exception; import java.io.IOException; import extensions.ExpectToFail; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(IgnoreIOExceptionExtension.class) class IgnoreIOExceptionTests { @Test void shouldSucceed() throws IOException { throw new IOException("any"); } @Test @ExpectToFail void shouldFail() { throw new RuntimeException("any"); } } ================================================ FILE: documentation/src/test/java/example/exception/MultipleHandlersTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.exception; import example.exception.MultipleHandlersTestCase.ThirdExecutedHandler; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler; import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; // @formatter:off // tag::user_guide[] // Register handlers for @Test, @BeforeEach, @AfterEach as well as @BeforeAll and @AfterAll @ExtendWith(ThirdExecutedHandler.class) class MultipleHandlersTestCase { // Register handlers for @Test, @BeforeEach, @AfterEach only @ExtendWith(SecondExecutedHandler.class) @ExtendWith(FirstExecutedHandler.class) @Test void testMethod() { } // end::user_guide[] static class FirstExecutedHandler implements TestExecutionExceptionHandler { @Override public void handleTestExecutionException(ExtensionContext context, Throwable ex) throws Throwable { throw ex; } } static class SecondExecutedHandler implements LifecycleMethodExecutionExceptionHandler { @Override public void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable ex) throws Throwable { throw ex; } } static class ThirdExecutedHandler implements LifecycleMethodExecutionExceptionHandler { @Override public void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable ex) throws Throwable { throw ex; } } // tag::user_guide[] } // end::user_guide[] // @formatter:on ================================================ FILE: documentation/src/test/java/example/exception/RecordStateOnErrorExtension.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.exception; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler; // @formatter:off // tag::user_guide[] class RecordStateOnErrorExtension implements LifecycleMethodExecutionExceptionHandler { @Override public void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable ex) throws Throwable { memoryDumpForFurtherInvestigation("Failure recorded during class setup"); throw ex; } @Override public void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable ex) throws Throwable { memoryDumpForFurtherInvestigation("Failure recorded during test setup"); throw ex; } @Override public void handleAfterEachMethodExecutionException(ExtensionContext context, Throwable ex) throws Throwable { memoryDumpForFurtherInvestigation("Failure recorded during test cleanup"); throw ex; } @Override public void handleAfterAllMethodExecutionException(ExtensionContext context, Throwable ex) throws Throwable { memoryDumpForFurtherInvestigation("Failure recorded during class cleanup"); throw ex; } // end::user_guide[] private void memoryDumpForFurtherInvestigation(String error) { } // tag::user_guide[] } // end::user_guide[] // @formatter:on ================================================ FILE: documentation/src/test/java/example/exception/UncaughtExceptionHandlingDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.exception; import example.util.Calculator; import extensions.ExpectToFail; import org.junit.jupiter.api.Test; class UncaughtExceptionHandlingDemo { // tag::user_guide[] private final Calculator calculator = new Calculator(); // end::user_guide[] @ExpectToFail // tag::user_guide[] @Test void failsDueToUncaughtException() { // The following throws an ArithmeticException due to division by // zero, which causes a test failure. calculator.divide(1, 0); } // end::user_guide[] } ================================================ FILE: documentation/src/test/java/example/exception/package-info.java ================================================ @NullMarked package example.exception; import org.jspecify.annotations.NullMarked; ================================================ FILE: documentation/src/test/java/example/extensions/HttpServerExtension.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.extensions; import java.io.IOException; import java.io.UncheckedIOException; import com.sun.net.httpserver.HttpServer; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Namespace; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; // tag::user_guide[] public class HttpServerExtension implements ParameterResolver { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return HttpServer.class.equals(parameterContext.getParameter().getType()); } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { ExtensionContext rootContext = extensionContext.getRoot(); ExtensionContext.Store store = rootContext.getStore(Namespace.GLOBAL); Class key = HttpServerResource.class; HttpServerResource resource = store.computeIfAbsent(key, __ -> { try { HttpServerResource serverResource = new HttpServerResource(0); serverResource.start(); return serverResource; } catch (IOException e) { throw new UncheckedIOException("Failed to create HttpServerResource", e); } }, HttpServerResource.class); return resource.getHttpServer(); } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/extensions/HttpServerResource.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.extensions; import static java.nio.charset.StandardCharsets.UTF_8; import java.io.IOException; import java.io.OutputStream; import java.net.InetAddress; import java.net.InetSocketAddress; import com.sun.net.httpserver.HttpServer; /** * Demonstrates an implementation of {@link AutoCloseable} using an {@link HttpServer}. */ // tag::user_guide[] class HttpServerResource implements AutoCloseable { private final HttpServer httpServer; // end::user_guide[] /** * Initializes the Http server resource, using the given port. * * @param port (int) The port number for the server, must be in the range 0-65535. * @throws IOException if an IOException occurs during initialization. */ // tag::user_guide[] HttpServerResource(int port) throws IOException { InetAddress loopbackAddress = InetAddress.getLoopbackAddress(); this.httpServer = HttpServer.create(new InetSocketAddress(loopbackAddress, port), 0); } HttpServer getHttpServer() { return httpServer; } // end::user_guide[] /** * Starts the Http server with an example handler. */ // tag::user_guide[] void start() { // Example handler httpServer.createContext("/example", exchange -> { String body = "This is a test"; exchange.sendResponseHeaders(200, body.length()); try (OutputStream os = exchange.getResponseBody()) { os.write(body.getBytes(UTF_8)); } }); httpServer.setExecutor(null); httpServer.start(); } @Override public void close() { httpServer.stop(0); } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/extensions/ParameterResolverConflictDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.extensions; import static org.junit.jupiter.api.Assertions.assertEquals; import extensions.ExpectToFail; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; // tag::user_guide[] public class ParameterResolverConflictDemo { // end::user_guide[] @ExpectToFail // tag::user_guide[] @Test @ExtendWith({ FirstIntegerResolver.class, SecondIntegerResolver.class }) void testInt(int i) { // Test will not run due to ParameterResolutionException assertEquals(1, i); } static class FirstIntegerResolver implements ParameterResolver { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return parameterContext.getParameter().getType() == int.class; } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return 1; } } static class SecondIntegerResolver implements ParameterResolver { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return parameterContext.getParameter().getType() == int.class; } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return 2; } } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/extensions/ParameterResolverCustomAnnotationDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.extensions; import static org.junit.jupiter.api.Assertions.assertEquals; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; // tag::user_guide[] public class ParameterResolverCustomAnnotationDemo { @Test void testInt(@FirstInteger Integer first, @SecondInteger Integer second) { assertEquals(1, first); assertEquals(2, second); } @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @ExtendWith(FirstInteger.Extension.class) public @interface FirstInteger { class Extension implements ParameterResolver { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return parameterContext.getParameter().getType().equals(Integer.class) && !parameterContext.isAnnotated(SecondInteger.class); } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return 1; } } } @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @ExtendWith(SecondInteger.Extension.class) public @interface SecondInteger { class Extension implements ParameterResolver { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return parameterContext.isAnnotated(SecondInteger.class); } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return 2; } } } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/extensions/ParameterResolverCustomTypeDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.extensions; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; // tag::user_guide[] public class ParameterResolverCustomTypeDemo { @Test @ExtendWith({ FirstIntegerResolver.class, SecondIntegerResolver.class }) void testInt(Integer i, WrappedInteger wrappedInteger) { assertEquals(1, i); assertEquals(2, wrappedInteger.value); } static class FirstIntegerResolver implements ParameterResolver { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return parameterContext.getParameter().getType().equals(Integer.class); } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return 1; } } static class SecondIntegerResolver implements ParameterResolver { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return parameterContext.getParameter().getType().equals(WrappedInteger.class); } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return new WrappedInteger(2); } } static class WrappedInteger { private final int value; WrappedInteger(int value) { this.value = value; } } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/extensions/ParameterResolverNoConflictDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.extensions; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; // tag::user_guide[] public class ParameterResolverNoConflictDemo { @Test @ExtendWith(FirstIntegerResolver.class) void firstResolution(int i) { assertEquals(1, i); } @Test @ExtendWith(SecondIntegerResolver.class) void secondResolution(int i) { assertEquals(2, i); } static class FirstIntegerResolver implements ParameterResolver { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return parameterContext.getParameter().getType() == int.class; } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return 1; } } static class SecondIntegerResolver implements ParameterResolver { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return parameterContext.getParameter().getType() == int.class; } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return 2; } } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/extensions/Random.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.extensions; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.junit.jupiter.api.extension.ExtendWith; //tag::user_guide[] @Target({ ElementType.FIELD, ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) @ExtendWith(RandomNumberExtension.class) public @interface Random { } //end::user_guide[] ================================================ FILE: documentation/src/test/java/example/extensions/RandomNumberDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.extensions; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; // tag::user_guide[] class RandomNumberDemo { // Use static randomNumber0 field anywhere in the test class, // including @BeforeAll or @AfterEach lifecycle methods. @Random // end::user_guide[] @Nullable // tag::user_guide[] private static Integer randomNumber0; // Use randomNumber1 field in test methods and @BeforeEach // or @AfterEach lifecycle methods. @Random private int randomNumber1; RandomNumberDemo(@Random int randomNumber2) { // Use randomNumber2 in constructor. } @BeforeEach void beforeEach(@Random int randomNumber3) { // Use randomNumber3 in @BeforeEach method. } @Test void test(@Random int randomNumber4) { // Use randomNumber4 in test method. } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/extensions/RandomNumberExtension.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.extensions; // tag::user_guide[] import static org.junit.platform.commons.support.AnnotationSupport.findAnnotatedFields; import java.lang.reflect.Field; import java.util.function.Predicate; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.jupiter.api.extension.TestInstancePostProcessor; import org.junit.platform.commons.support.ModifierSupport; // end::user_guide[] // @formatter:off // tag::user_guide[] class RandomNumberExtension implements BeforeAllCallback, TestInstancePostProcessor, ParameterResolver { private final java.util.Random random = new java.util.Random(System.nanoTime()); /** * Inject a random integer into static fields that are annotated with * {@code @Random} and can be assigned an integer value. */ @Override public void beforeAll(ExtensionContext context) { Class testClass = context.getRequiredTestClass(); injectFields(testClass, null, ModifierSupport::isStatic); } /** * Inject a random integer into non-static fields that are annotated with * {@code @Random} and can be assigned an integer value. */ @Override public void postProcessTestInstance(Object testInstance, ExtensionContext context) { Class testClass = context.getRequiredTestClass(); injectFields(testClass, testInstance, ModifierSupport::isNotStatic); } /** * Determine if the parameter is annotated with {@code @Random} and can be * assigned an integer value. */ @Override public boolean supportsParameter(ParameterContext pc, ExtensionContext ec) { return pc.isAnnotated(Random.class) && isInteger(pc.getParameter().getType()); } /** * Resolve a random integer. */ @Override public Integer resolveParameter(ParameterContext pc, ExtensionContext ec) { return this.random.nextInt(); } private void injectFields(Class testClass, @Nullable Object testInstance, Predicate predicate) { predicate = predicate.and(field -> isInteger(field.getType())); findAnnotatedFields(testClass, Random.class, predicate) .forEach(field -> { try { field.setAccessible(true); field.set(testInstance, this.random.nextInt()); } catch (Exception ex) { throw new RuntimeException(ex); } }); } private static boolean isInteger(Class type) { return type == Integer.class || type == int.class; } } // end::user_guide[] // @formatter:on ================================================ FILE: documentation/src/test/java/example/extensions/package-info.java ================================================ @NullMarked package example.extensions; import org.jspecify.annotations.NullMarked; ================================================ FILE: documentation/src/test/java/example/interceptor/SwingEdtInterceptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.interceptor; import java.lang.reflect.Method; import java.util.concurrent.atomic.AtomicReference; import javax.swing.SwingUtilities; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.InvocationInterceptor; import org.junit.jupiter.api.extension.ReflectiveInvocationContext; // @formatter:off // tag::user_guide[] public class SwingEdtInterceptor implements InvocationInterceptor { //end::user_guide[] @SuppressWarnings("NullAway") //tag::user_guide[] @Override public void interceptTestMethod(Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { AtomicReference throwable = new AtomicReference<>(); SwingUtilities.invokeAndWait(() -> { try { invocation.proceed(); } catch (Throwable t) { throwable.set(t); } }); Throwable t = throwable.get(); if (t != null) { throw t; } } } // end::user_guide[] // @formatter:on ================================================ FILE: documentation/src/test/java/example/interceptor/package-info.java ================================================ @NullMarked package example.interceptor; import org.jspecify.annotations.NullMarked; ================================================ FILE: documentation/src/test/java/example/package-info.java ================================================ @NullMarked package example; import org.jspecify.annotations.NullMarked; ================================================ FILE: documentation/src/test/java/example/registration/DocumentationDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.registration; import java.nio.file.Path; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.RegisterExtension; //tag::user_guide[] class DocumentationDemo { //end::user_guide[] @Nullable //tag::user_guide[] static Path lookUpDocsDir() { // return path to docs dir // end::user_guide[] return null; // tag::user_guide[] } @RegisterExtension DocumentationExtension docs = DocumentationExtension.forPath(lookUpDocsDir()); @Test void generateDocumentation() { // use this.docs ... } } //end::user_guide[] @NullMarked class DocumentationExtension implements AfterEachCallback { @SuppressWarnings("unused") private final @Nullable Path path; private DocumentationExtension(@Nullable Path path) { this.path = path; } static DocumentationExtension forPath(@Nullable Path path) { return new DocumentationExtension(path); } @Override public void afterEach(ExtensionContext context) { /* no-op for demo */ } } ================================================ FILE: documentation/src/test/java/example/registration/WebServerDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.registration; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; // tag::user_guide[] class WebServerDemo { // end::user_guide[] // @formatter:off // tag::user_guide[] @RegisterExtension static WebServerExtension server = WebServerExtension.builder() .enableSecurity(false) .build(); // end::user_guide[] // @formatter:on // tag::user_guide[] @Test void getProductList() { // end::user_guide[] @SuppressWarnings("resource") // tag::user_guide[] WebClient webClient = new WebClient(); String serverUrl = server.getServerUrl(); // Use WebClient to connect to web server using serverUrl and verify response assertEquals(200, webClient.get(serverUrl + "/products").getResponseStatus()); } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/session/CloseableHttpServer.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.session; //tag::user_guide[] import java.util.concurrent.ExecutorService; import com.sun.net.httpserver.HttpServer; public class CloseableHttpServer implements AutoCloseable { private final HttpServer server; private final ExecutorService executorService; CloseableHttpServer(HttpServer server, ExecutorService executorService) { this.server = server; this.executorService = executorService; } public HttpServer getServer() { return server; } @Override public void close() { // <1> server.stop(0); // <2> executorService.shutdownNow(); } } //end::user_guide[] ================================================ FILE: documentation/src/test/java/example/session/GlobalSetupTeardownListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.session; //tag::user_guide[] import static java.net.InetAddress.getLoopbackAddress; import java.io.IOException; import java.io.UncheckedIOException; import java.net.InetSocketAddress; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import com.sun.net.httpserver.HttpServer; import org.junit.platform.engine.support.store.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.LauncherSession; import org.junit.platform.launcher.LauncherSessionListener; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestPlan; public class GlobalSetupTeardownListener implements LauncherSessionListener { @Override public void launcherSessionOpened(LauncherSession session) { // Avoid setup for test discovery by delaying it until tests are about to be executed session.getLauncher().registerTestExecutionListeners(new TestExecutionListener() { @Override public void testPlanExecutionStarted(TestPlan testPlan) { //end::user_guide[] if (!testPlan.getConfigurationParameters().getBoolean("enableHttpServer").orElse(true)) { // avoid starting multiple HTTP servers unnecessarily from UsingTheLauncherDemo return; } //tag::user_guide[] NamespacedHierarchicalStore store = session.getStore(); // <1> store.computeIfAbsent(Namespace.GLOBAL, "httpServer", key -> { // <2> InetSocketAddress address = new InetSocketAddress(getLoopbackAddress(), 0); HttpServer server; try { server = HttpServer.create(address, 0); } catch (IOException e) { throw new UncheckedIOException("Failed to start HTTP server", e); } server.createContext("/test", exchange -> { exchange.sendResponseHeaders(204, -1); exchange.close(); }); ExecutorService executorService = Executors.newCachedThreadPool(); server.setExecutor(executorService); server.start(); // <3> return new CloseableHttpServer(server, executorService); }); } }); } } //end::user_guide[] ================================================ FILE: documentation/src/test/java/example/session/HttpTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.session; //tag::user_guide[] import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; import com.sun.net.httpserver.HttpServer; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; @ExtendWith(HttpServerParameterResolver.class) class HttpTests { @Test void respondsWith204(HttpServer server) throws IOException { String host = server.getAddress().getHostString(); // <2> int port = server.getAddress().getPort(); // <3> URL url = URI.create("http://" + host + ":" + port + "/test").toURL(); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); int responseCode = connection.getResponseCode(); // <4> assertEquals(204, responseCode); // <5> } } class HttpServerParameterResolver implements ParameterResolver { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return HttpServer.class.equals(parameterContext.getParameter().getType()); } //end::user_guide[] @SuppressWarnings("DataFlowIssue") //tag::user_guide[] @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return extensionContext // tag::custom_line_break[] .getStore(ExtensionContext.Namespace.GLOBAL) // tag::custom_line_break[] .get("httpServer", CloseableHttpServer.class) // <1> .getServer(); } } //end::user_guide[] ================================================ FILE: documentation/src/test/java/example/session/package-info.java ================================================ @NullMarked package example.session; import org.jspecify.annotations.NullMarked; ================================================ FILE: documentation/src/test/java/example/sharedresources/ChildrenSharedResourcesDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.sharedresources; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ; import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ_WRITE; import static org.junit.jupiter.api.parallel.ResourceLockTarget.CHILDREN; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ResourceLock; // tag::user_guide[] @Execution(CONCURRENT) @ResourceLock(value = "a", mode = READ, target = CHILDREN) public class ChildrenSharedResourcesDemo { @ResourceLock(value = "a", mode = READ_WRITE) @Test void test1() throws InterruptedException { Thread.sleep(2000L); } @Test void test2() throws InterruptedException { Thread.sleep(2000L); } @Test void test3() throws InterruptedException { Thread.sleep(2000L); } @Test void test4() throws InterruptedException { Thread.sleep(2000L); } @Test void test5() throws InterruptedException { Thread.sleep(2000L); } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/sharedresources/DynamicSharedResourcesDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.sharedresources; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ; import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ_WRITE; import static org.junit.jupiter.api.parallel.Resources.SYSTEM_PROPERTIES; import java.lang.reflect.Method; import java.util.List; import java.util.Properties; import java.util.Set; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ResourceAccessMode; import org.junit.jupiter.api.parallel.ResourceLock; import org.junit.jupiter.api.parallel.ResourceLocksProvider; // tag::user_guide[] @Execution(CONCURRENT) @ResourceLock(providers = DynamicSharedResourcesDemo.Provider.class) class DynamicSharedResourcesDemo { private Properties backup; @BeforeEach void backup() { backup = new Properties(); backup.putAll(System.getProperties()); } @AfterEach void restore() { System.setProperties(backup); } @Test void customPropertyIsNotSetByDefault() { assertNull(System.getProperty("my.prop")); } @Test void canSetCustomPropertyToApple() { System.setProperty("my.prop", "apple"); assertEquals("apple", System.getProperty("my.prop")); } @Test void canSetCustomPropertyToBanana() { System.setProperty("my.prop", "banana"); assertEquals("banana", System.getProperty("my.prop")); } static class Provider implements ResourceLocksProvider { @Override public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, Method testMethod) { ResourceAccessMode mode = testMethod.getName().startsWith("canSet") ? READ_WRITE : READ; return Set.of(new Lock(SYSTEM_PROPERTIES, mode)); } } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/sharedresources/SharedResourceDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.sharedresources; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.discoveryRequest; import example.FirstCustomEngine; import example.SecondCustomEngine; import org.junit.jupiter.api.Test; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.core.LauncherConfig; import org.junit.platform.launcher.core.LauncherFactory; class SharedResourceDemo { @SuppressWarnings("DataFlowIssue") //tag::user_guide[] @Test void runBothCustomEnginesTest() { FirstCustomEngine firstCustomEngine = new FirstCustomEngine(); SecondCustomEngine secondCustomEngine = new SecondCustomEngine(); LauncherConfig launcherConfig = LauncherConfig.builder() // tag::custom_line_break[] .addTestEngines(firstCustomEngine, secondCustomEngine) // tag::custom_line_break[] .enableTestEngineAutoRegistration(false) // tag::custom_line_break[] .build(); LauncherDiscoveryRequest discoveryRequest = discoveryRequest() // tag::custom_line_break[] .selectors(selectPackage("com.example.mytests")) // tag::custom_line_break[] .build(); Launcher launcher = LauncherFactory.create(launcherConfig); launcher.execute(discoveryRequest); assertSame(firstCustomEngine.getSocket(), secondCustomEngine.getSocket()); assertTrue(firstCustomEngine.getSocket().isClosed(), "socket should be closed"); } //end::user_guide[] } ================================================ FILE: documentation/src/test/java/example/sharedresources/StaticSharedResourcesDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.sharedresources; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ; import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ_WRITE; import static org.junit.jupiter.api.parallel.Resources.SYSTEM_PROPERTIES; import java.util.Properties; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ResourceLock; // tag::user_guide[] @Execution(CONCURRENT) class StaticSharedResourcesDemo { private Properties backup; @BeforeEach void backup() { backup = new Properties(); backup.putAll(System.getProperties()); } @AfterEach void restore() { System.setProperties(backup); } @Test @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ) void customPropertyIsNotSetByDefault() { assertNull(System.getProperty("my.prop")); } @Test @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE) void canSetCustomPropertyToApple() { System.setProperty("my.prop", "apple"); assertEquals("apple", System.getProperty("my.prop")); } @Test @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE) void canSetCustomPropertyToBanana() { System.setProperty("my.prop", "banana"); assertEquals("banana", System.getProperty("my.prop")); } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/sharedresources/package-info.java ================================================ @NullMarked package example.sharedresources; import org.jspecify.annotations.NullMarked; ================================================ FILE: documentation/src/test/java/example/testinterface/TestInterfaceDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.testinterface; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; // @formatter:off // tag::user_guide[] class TestInterfaceDemo implements TestLifecycleLogger, TimeExecutionLogger, TestInterfaceDynamicTestsDemo { @Test void isEqualValue() { assertEquals(1, "a".length(), "is always equal"); } } // end::user_guide[] // @formatter:on ================================================ FILE: documentation/src/test/java/example/testinterface/TestInterfaceDynamicTestsDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.testinterface; import static example.util.StringUtils.isPalindrome; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import java.util.stream.Stream; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.TestFactory; // @formatter:off // tag::user_guide[] interface TestInterfaceDynamicTestsDemo { @TestFactory default Stream dynamicTestsForPalindromes() { return Stream.of("racecar", "radar", "mom", "dad") .map(text -> dynamicTest(text, () -> assertTrue(isPalindrome(text)))); } } // end::user_guide[] // @formatter:on ================================================ FILE: documentation/src/test/java/example/testinterface/TestLifecycleLogger.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.testinterface; import java.util.logging.Logger; 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.TestInfo; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; // @formatter:off // tag::user_guide[] @TestInstance(Lifecycle.PER_CLASS) interface TestLifecycleLogger { Logger logger = Logger.getLogger(TestLifecycleLogger.class.getName()); @BeforeAll default void beforeAllTests() { logger.info("Before all tests"); } @AfterAll default void afterAllTests() { logger.info("After all tests"); } @BeforeEach default void beforeEachTest(TestInfo testInfo) { logger.info(() -> "About to execute [%s]".formatted( testInfo.getDisplayName())); } @AfterEach default void afterEachTest(TestInfo testInfo) { logger.info(() -> "Finished executing [%s]".formatted( testInfo.getDisplayName())); } } // end::user_guide[] // @formatter:on ================================================ FILE: documentation/src/test/java/example/testinterface/TimeExecutionLogger.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.testinterface; import example.timing.TimingExtension; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.extension.ExtendWith; //tag::user_guide[] @Tag("timed") @ExtendWith(TimingExtension.class) interface TimeExecutionLogger { } //end::user_guide[] ================================================ FILE: documentation/src/test/java/example/testinterface/package-info.java ================================================ @NullMarked package example.testinterface; import org.jspecify.annotations.NullMarked; ================================================ FILE: documentation/src/test/java/example/testkit/EngineTestKitAllEventsDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.testkit; // @formatter:off // tag::user_guide[] import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.testkit.engine.EventConditions.abortedWithReason; import static org.junit.platform.testkit.engine.EventConditions.container; import static org.junit.platform.testkit.engine.EventConditions.engine; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.EventConditions.skippedWithReason; import static org.junit.platform.testkit.engine.EventConditions.started; import static org.junit.platform.testkit.engine.EventConditions.test; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import java.io.StringWriter; import java.io.Writer; import example.ExampleTestCase; import org.junit.jupiter.api.Test; import org.junit.platform.testkit.engine.EngineTestKit; import org.opentest4j.TestAbortedException; class EngineTestKitAllEventsDemo { @Test void verifyAllJupiterEvents() { Writer writer = // create a java.io.Writer for debug output // end::user_guide[] // For the demo, we are swallowing the debug output. new StringWriter(); // tag::user_guide[] EngineTestKit.engine("junit-jupiter") // <1> .selectors(selectClass(ExampleTestCase.class)) // <2> .execute() // <3> .allEvents() // <4> .debug(writer) // <5> .assertEventsMatchExactly( // <6> event(engine(), started()), event(container(ExampleTestCase.class), started()), event(test("skippedTest"), skippedWithReason("for demonstration purposes")), event(test("succeedingTest"), started()), event(test("succeedingTest"), finishedSuccessfully()), event(test("abortedTest"), started()), event(test("abortedTest"), abortedWithReason(instanceOf(TestAbortedException.class), message(m -> m.contains("abc does not contain Z")))), event(test("failingTest"), started()), event(test("failingTest"), finishedWithFailure( instanceOf(ArithmeticException.class), message(it -> it.endsWith("by zero")))), event(container(ExampleTestCase.class), finishedSuccessfully()), event(engine(), finishedSuccessfully())); } } // end::user_guide[] // @formatter:on ================================================ FILE: documentation/src/test/java/example/testkit/EngineTestKitDiscoveryDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.testkit; // tag::user_guide[] import static java.util.Collections.emptyList; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import example.ExampleTestCase; import org.junit.jupiter.api.Test; import org.junit.platform.testkit.engine.EngineDiscoveryResults; import org.junit.platform.testkit.engine.EngineTestKit; class EngineTestKitDiscoveryDemo { @Test void verifyJupiterDiscovery() { EngineDiscoveryResults results = EngineTestKit.engine("junit-jupiter") // <1> .selectors(selectClass(ExampleTestCase.class)) // <2> .discover(); // <3> assertEquals("JUnit Jupiter", results.getEngineDescriptor().getDisplayName()); // <4> assertEquals(emptyList(), results.getDiscoveryIssues()); // <5> } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/testkit/EngineTestKitFailedMethodDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.testkit; // @formatter:off // tag::user_guide[] import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.EventConditions.test; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import example.ExampleTestCase; import org.junit.jupiter.api.Test; import org.junit.platform.testkit.engine.EngineTestKit; class EngineTestKitFailedMethodDemo { @Test void verifyJupiterMethodFailed() { EngineTestKit.engine("junit-jupiter") // <1> .selectors(selectClass(ExampleTestCase.class)) // <2> .execute() // <3> .testEvents() // <4> .assertThatEvents().haveExactly(1, // <5> event(test("failingTest"), finishedWithFailure( instanceOf(ArithmeticException.class), message(it -> it.endsWith("by zero"))))); } } // end::user_guide[] // @formatter:on ================================================ FILE: documentation/src/test/java/example/testkit/EngineTestKitSkippedMethodDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.testkit; // @formatter:off // tag::user_guide[] import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.skippedWithReason; import static org.junit.platform.testkit.engine.EventConditions.test; import example.ExampleTestCase; import org.junit.jupiter.api.Test; import org.junit.platform.testkit.engine.EngineTestKit; import org.junit.platform.testkit.engine.Events; class EngineTestKitSkippedMethodDemo { @Test void verifyJupiterMethodWasSkipped() { String methodName = "skippedTest"; Events testEvents = EngineTestKit // <5> .engine("junit-jupiter") // <1> .selectors(selectMethod(ExampleTestCase.class, methodName)) // <2> .execute() // <3> .testEvents(); // <4> testEvents.assertStatistics(stats -> stats.skipped(1)); // <6> testEvents.assertThatEvents() // <7> .haveExactly(1, event(test(methodName), skippedWithReason("for demonstration purposes"))); } } // end::user_guide[] // @formatter:on ================================================ FILE: documentation/src/test/java/example/testkit/EngineTestKitStatisticsDemo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.testkit; // @formatter:off // tag::user_guide[] import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import example.ExampleTestCase; import org.junit.jupiter.api.Test; import org.junit.platform.testkit.engine.EngineTestKit; class EngineTestKitStatisticsDemo { @Test void verifyJupiterContainerStats() { EngineTestKit .engine("junit-jupiter") // <1> .selectors(selectClass(ExampleTestCase.class)) // <2> .execute() // <3> .containerEvents() // <4> .assertStatistics(stats -> stats.started(2).succeeded(2)); // <5> } @Test void verifyJupiterTestStats() { EngineTestKit .engine("junit-jupiter") // <1> .selectors(selectClass(ExampleTestCase.class)) // <2> .execute() // <3> .testEvents() // <6> .assertStatistics(stats -> stats.skipped(1).started(3).succeeded(1).aborted(1).failed(1)); // <7> } } // end::user_guide[] // @formatter:on ================================================ FILE: documentation/src/test/java/example/testkit/package-info.java ================================================ @NullMarked package example.testkit; import org.jspecify.annotations.NullMarked; ================================================ FILE: documentation/src/test/java/example/timing/TimingExtension.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.timing; // tag::user_guide[] import java.lang.reflect.Method; import java.util.logging.Logger; import org.junit.jupiter.api.extension.AfterTestExecutionCallback; import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Namespace; import org.junit.jupiter.api.extension.ExtensionContext.Store; // end::user_guide[] /** * Simple extension that times the execution of test methods and * logs the results at {@code INFO} level. * * @since 5.0 */ // @formatter:off // tag::user_guide[] public class TimingExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback { private static final Logger logger = Logger.getLogger(TimingExtension.class.getName()); private static final String START_TIME = "start time"; @Override public void beforeTestExecution(ExtensionContext context) { getStore(context).put(START_TIME, System.currentTimeMillis()); } //end::user_guide[] @SuppressWarnings("DataFlowIssue") //tag::user_guide[] @Override public void afterTestExecution(ExtensionContext context) { Method testMethod = context.getRequiredTestMethod(); long startTime = getStore(context).remove(START_TIME, long.class); long duration = System.currentTimeMillis() - startTime; logger.info(() -> "Method [%s] took %s ms.".formatted(testMethod.getName(), duration)); } private Store getStore(ExtensionContext context) { return context.getStore(Namespace.create(getClass(), context.getRequiredTestMethod())); } } // end::user_guide[] // @formatter:on ================================================ FILE: documentation/src/test/java/example/timing/TimingExtensionTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.timing; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; /** * Tests that demonstrate the example {@link TimingExtension}. * * @since 5.0 */ // tag::user_guide[] @ExtendWith(TimingExtension.class) class TimingExtensionTests { @Test void sleep20ms() throws Exception { Thread.sleep(20); } @Test void sleep50ms() throws Exception { Thread.sleep(50); } } // end::user_guide[] ================================================ FILE: documentation/src/test/java/example/timing/package-info.java ================================================ @NullMarked package example.timing; import org.jspecify.annotations.NullMarked; ================================================ FILE: documentation/src/test/java/extensions/DisabledOnOpenJ9.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package extensions; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.junit.jupiter.api.condition.DisabledIfSystemProperty; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @DisabledIfSystemProperty(named = "java.vm.vendor", matches = ".*OpenJ9.*") public @interface DisabledOnOpenJ9 { } ================================================ FILE: documentation/src/test/java/extensions/ExpectToFail.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package extensions; import static org.junit.jupiter.api.Assertions.assertNotNull; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Namespace; import org.junit.jupiter.api.extension.ExtensionContext.Store; import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @ExtendWith(ExpectToFail.Extension.class) public @interface ExpectToFail { class Extension implements TestExecutionExceptionHandler, AfterEachCallback { private static final String KEY = "exception"; @Override public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { getExceptionStore(context).put(KEY, throwable); } @Override public void afterEach(ExtensionContext context) throws Exception { assertNotNull(getExceptionStore(context).get(KEY), "Test should have failed"); } private Store getExceptionStore(ExtensionContext context) { return context.getStore(Namespace.create(getClass(), context.getRequiredTestMethod())); } } } ================================================ FILE: documentation/src/test/java/extensions/package-info.java ================================================ @NullMarked package extensions; import org.jspecify.annotations.NullMarked; ================================================ FILE: documentation/src/test/kotlin/example/FibonacciCalculator.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example class FibonacciCalculator { private val fibonacci = sequence { yield(0) // 0th Fibonacci number yield(1) // first Fibonacci number var cur = 1 var next = 1 while (true) { yield(next) // next Fibonacci number val tmp = cur + next cur = next next = tmp } } fun fib(fibonacciNumber: Int) = fibonacci.elementAt(fibonacciNumber) } ================================================ FILE: documentation/src/test/kotlin/example/KotlinAssertionsDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example // tag::user_guide[] import example.domain.Person import example.util.Calculator import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertAll import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.assertInstanceOf import org.junit.jupiter.api.assertNotNull import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.assertTimeout import org.junit.jupiter.api.assertTimeoutPreemptively import java.time.Duration class KotlinAssertionsDemo { private val person = Person("Jane", "Doe") private val people = setOf(person, Person("John", "Doe")) @Test fun `exception absence testing`() { val calculator = Calculator() val result = assertDoesNotThrow("Should not throw an exception") { calculator.divide(0, 1) } assertEquals(0, result) } // end::user_guide[] @extensions.DisabledOnOpenJ9 // tag::user_guide[] @Test fun `expected exception testing`() { val calculator = Calculator() val exception = assertThrows ("Should throw an exception") { calculator.divide(1, 0) } assertEquals("/ by zero", exception.message) } @Test fun `grouped assertions`() { assertAll( "Person properties", { assertEquals("Jane", person.firstName) }, { assertEquals("Doe", person.lastName) } ) } @Test fun `grouped assertions from a stream`() { assertAll( "People with first name starting with J", people .stream() .map { // This mapping returns Stream<() -> Unit> { assertTrue(it.firstName.startsWith("J")) } } ) } @Test fun `grouped assertions from a collection`() { assertAll( "People with last name of Doe", people.map { { assertEquals("Doe", it.lastName) } } ) } // end::user_guide[] @Tag("timeout") // tag::user_guide[] @Test fun `timeout not exceeded testing`() { val fibonacciCalculator = FibonacciCalculator() val result = assertTimeout(Duration.ofMillis(1000)) { fibonacciCalculator.fib(14) } assertEquals(377, result) } // end::user_guide[] @Tag("timeout") @extensions.ExpectToFail // tag::user_guide[] @Test fun `timeout exceeded with preemptive termination`() { // The following assertion fails with an error message similar to: // execution timed out after 10 ms assertTimeoutPreemptively(Duration.ofMillis(10)) { // Simulate task that takes more than 10 ms. Thread.sleep(100) } } @Test fun `assertNotNull with a smart cast`() { val nullablePerson: Person? = person assertNotNull(nullablePerson) // The compiler smart casts nullablePerson to a non-nullable object. // The safe call operator (?.) isn't required. assertEquals(person.firstName, nullablePerson.firstName) assertEquals(person.lastName, nullablePerson.lastName) } @Test fun `assertInstanceOf with a smart cast`() { val maybePerson: Any = person assertInstanceOf(maybePerson) // The compiler smart casts maybePerson to a Person object, // allowing to access the Person properties. assertEquals(person.firstName, maybePerson.firstName) assertEquals(person.lastName, maybePerson.lastName) } } // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/KotlinCoroutinesDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example // tag::user_guide[] import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test // end::user_guide[] // @formatter:off @Suppress("JUnitMalformedDeclaration") // tag::user_guide[] class KotlinCoroutinesDemo { // end::user_guide[] // @formatter:on // tag::user_guide[] @BeforeEach fun regularSetUp() { } @BeforeEach suspend fun coroutineSetUp() { } @Test fun regularTest() { } @Test suspend fun coroutineTest() { } } // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/KotlinCoroutinesRunTestDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example // tag::user_guide[] import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Test class KotlinCoroutinesRunTestDemo { // end::user_guide[] // @formatter:off // tag::user_guide[] @Test fun coroutineTestUsingRunTest() = runTest { // ... } // end::user_guide[] // @formatter:on // tag::user_guide[] } // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/AssumptionsDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin // tag::user_guide[] import example.util.Calculator import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assumptions.assumeTrue import org.junit.jupiter.api.Assumptions.assumingThat import org.junit.jupiter.api.Test class AssumptionsDemo { private val calculator = Calculator() @Test fun testOnlyOnCiServer() { assumeTrue(System.getenv("ENV") == "CI") // remainder of test } @Test fun testOnlyOnDeveloperWorkstation() { assumeTrue(System.getenv("ENV") == "DEV") { "Aborting test: not on developer workstation" } // remainder of test } @Test fun testInAllEnvironments() { assumingThat(System.getenv("ENV") == "CI") { // perform these assertions only on the CI server assertEquals(2, calculator.divide(4, 2)) } // perform these assertions in all environments assertEquals(42, calculator.multiply(6, 7)) } } // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/AutoCloseDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin import example.registration.WebClient import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.AutoClose import org.junit.jupiter.api.Test // tag::user_guide_example[] class AutoCloseDemo { @AutoClose // <1> val webClient = WebClient() // <2> val serverUrl = // specify server URL ... // end::user_guide_example[] "https://localhost" // tag::user_guide_example[] @Test fun getProductList() { // Use WebClient to connect to web server and verify response assertEquals(200, webClient.get("$serverUrl/products").responseStatus) } } // end::user_guide_example[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/ConditionalTestExecutionDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin import org.junit.jupiter.api.Test import org.junit.jupiter.api.condition.DisabledForJreRange import org.junit.jupiter.api.condition.DisabledIf import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable import org.junit.jupiter.api.condition.DisabledIfSystemProperty import org.junit.jupiter.api.condition.DisabledInNativeImage import org.junit.jupiter.api.condition.DisabledOnJre import org.junit.jupiter.api.condition.DisabledOnOs import org.junit.jupiter.api.condition.EnabledForJreRange import org.junit.jupiter.api.condition.EnabledIf import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable import org.junit.jupiter.api.condition.EnabledIfSystemProperty import org.junit.jupiter.api.condition.EnabledInNativeImage import org.junit.jupiter.api.condition.EnabledOnJre import org.junit.jupiter.api.condition.EnabledOnOs import org.junit.jupiter.api.condition.JRE.JAVA_17 import org.junit.jupiter.api.condition.JRE.JAVA_18 import org.junit.jupiter.api.condition.JRE.JAVA_19 import org.junit.jupiter.api.condition.JRE.JAVA_21 import org.junit.jupiter.api.condition.JRE.JAVA_25 import org.junit.jupiter.api.condition.OS.LINUX import org.junit.jupiter.api.condition.OS.MAC import org.junit.jupiter.api.condition.OS.WINDOWS class ConditionalTestExecutionDemo { // tag::user_guide_os[] @Test @EnabledOnOs(MAC) fun onlyOnMacOs() { // ... } @TestOnMac fun testOnMac() { // ... } @Test @EnabledOnOs(LINUX, MAC) fun onLinuxOrMac() { // ... } @Test @DisabledOnOs(WINDOWS) fun notOnWindows() { // ... } @Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) @Test @EnabledOnOs(MAC) annotation class TestOnMac // end::user_guide_os[] // tag::user_guide_architecture[] @Test @EnabledOnOs(architectures = ["aarch64"]) fun onAarch64() { // ... } @Test @DisabledOnOs(architectures = ["x86_64"]) fun notOnX86_64() { // ... } @Test @EnabledOnOs(value = [MAC], architectures = ["aarch64"]) fun onNewMacs() { // ... } @Test @DisabledOnOs(value = [MAC], architectures = ["aarch64"]) fun notOnNewMacs() { // ... } // end::user_guide_architecture[] // tag::user_guide_jre[] @Test @EnabledOnJre(JAVA_17) fun onlyOnJava17() { // ... } @Test @EnabledOnJre(JAVA_17, JAVA_21) fun onJava17And21() { // ... } @Test @EnabledForJreRange(min = JAVA_21, max = JAVA_25) fun fromJava21To25() { // ... } @Test @EnabledForJreRange(min = JAVA_21) fun onJava21ndHigher() { // ... } @Test @EnabledForJreRange(max = JAVA_18) fun fromJava17To18() { // ... } @Test @DisabledOnJre(JAVA_19) fun notOnJava19() { // ... } @Test @DisabledForJreRange(min = JAVA_17, max = JAVA_17) fun notFromJava17To19() { // ... } @Test @DisabledForJreRange(min = JAVA_19) fun notOnJava19AndHigher() { // ... } @Test @DisabledForJreRange(max = JAVA_18) fun notFromJava17To18() { // ... } // end::user_guide_jre[] // tag::user_guide_jre_arbitrary_versions[] @Test @EnabledOnJre(versions = [26]) fun onlyOnJava26() { // ... } // Can also be expressed as follows. // @EnabledOnJre(value = [JAVA_25], versions = [26]) @Test @EnabledOnJre(versions = [25, 26]) fun onJava25And26() { // ... } @Test @EnabledForJreRange(minVersion = 26) fun onJava26AndHigher() { // ... } // Can also be expressed as follows. // @EnabledForJreRange(min = JAVA_25, maxVersion = 27) @Test @EnabledForJreRange(minVersion = 25, maxVersion = 27) fun fromJava25To27() { // ... } @Test @DisabledOnJre(versions = [26]) fun notOnJava26() { // ... } // Can also be expressed as follows. // @DisabledOnJre(value = [JAVA_25], versions = [26]) @Test @DisabledOnJre(versions = [25, 26]) fun notOnJava25And26() { // ... } @Test @DisabledForJreRange(minVersion = 26) fun notOnJava26AndHigher() { // ... } // Can also be expressed as follows. // @DisabledForJreRange(min = JAVA_25, maxVersion = 27) @Test @DisabledForJreRange(minVersion = 25, maxVersion = 27) fun notFromJava25To27() { // ... } // end::user_guide_jre_arbitrary_versions[] // tag::user_guide_native[] @Test @EnabledInNativeImage fun onlyWithinNativeImage() { // ... } @Test @DisabledInNativeImage fun neverWithinNativeImage() { // ... } // end::user_guide_native[] // tag::user_guide_system_property[] @Test @EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*") fun onlyOn64BitArchitectures() { // ... } @Test @DisabledIfSystemProperty(named = "ci-server", matches = "true") fun notOnCiServer() { // ... } // end::user_guide_system_property[] // tag::user_guide_environment_variable[] @Test @EnabledIfEnvironmentVariable(named = "ENV", matches = "staging-server") fun onlyOnStagingServer() { // ... } @Test @DisabledIfEnvironmentVariable(named = "ENV", matches = ".*development.*") fun notOnDeveloperWorkstation() { // ... } // end::user_guide_environment_variable[] // tag::user_guide_custom[] @Test @EnabledIf("customCondition") fun enabled() { // ... } @Test @DisabledIf("customCondition") fun disabled() { // ... } fun customCondition(): Boolean = true // end::user_guide_custom[] } ================================================ FILE: documentation/src/test/kotlin/example/kotlin/DefaultLocaleTimezoneExtensionDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.junit.jupiter.api.util.DefaultLocale import org.junit.jupiter.api.util.DefaultTimeZone import org.junit.jupiter.api.util.LocaleProvider import org.junit.jupiter.api.util.TimeZoneProvider import java.time.ZoneOffset import java.util.Locale import java.util.TimeZone class DefaultLocaleTimezoneExtensionDemo { // tag::default_locale_language[] @Test @DefaultLocale("zh-Hant-TW") fun test_with_language() { assertThat(Locale.getDefault()).isEqualTo(Locale.forLanguageTag("zh-Hant-TW")) } // end::default_locale_language[] // tag::default_locale_language_alternatives[] @Test @DefaultLocale(language = "en") fun test_with_language_only() { assertThat(Locale.getDefault()).isEqualTo(Locale.Builder().setLanguage("en").build()) } @Test @DefaultLocale(language = "en", country = "EN") fun test_with_language_and_country() { assertThat(Locale.getDefault()).isEqualTo( Locale .Builder() .setLanguage("en") .setRegion("EN") .build() ) } @Test @DefaultLocale(language = "ja", country = "JP", variant = "japanese") fun test_with_language_and_country_and_vairant() { assertThat(Locale.getDefault()).isEqualTo( Locale .Builder() .setLanguage("ja") .setRegion("JP") .setVariant("japanese") .build() ) } // end::default_locale_language_alternatives[] // @formatter:off @Nested // tag::default_locale_class_level[] @DefaultLocale(language = "fr") // end::default_locale_class_level[] inner // tag::default_locale_class_level[] class MyLocaleTests { // end::default_locale_class_level[] // @formatter:on // tag::default_locale_class_level[] @Test fun test_with_class_level_configuration() { assertThat(Locale.getDefault()).isEqualTo(Locale.Builder().setLanguage("fr").build()) } @Test @DefaultLocale(language = "en") fun test_with_method_level_configuration() { assertThat(Locale.getDefault()).isEqualTo(Locale.Builder().setLanguage("en").build()) } } // end::default_locale_class_level[] // tag::default_locale_with_provider[] @Test @DefaultLocale(localeProvider = EnglishProvider::class) fun test_with_locale_provider() { assertThat(Locale.getDefault()).isEqualTo(Locale.Builder().setLanguage("en").build()) } class EnglishProvider : LocaleProvider { override fun get(): Locale = Locale.ENGLISH } // end::default_locale_with_provider[] // tag::default_timezone_zone[] @Test @DefaultTimeZone("CET") fun test_with_short_zone_id() { assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("CET")) } @Test @DefaultTimeZone("Africa/Juba") fun test_with_long_zone_id() { assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("Africa/Juba")) } // end::default_timezone_zone[] // @formatter:off @Nested // tag::default_timezone_class_level[] @DefaultTimeZone("CET") // end::default_timezone_class_level[] inner // tag::default_timezone_class_level[] class MyTimeZoneTests { // end::default_timezone_class_level[] // @formatter:on // tag::default_timezone_class_level[] @Test fun test_with_class_level_configuration() { assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("CET")) } @Test @DefaultTimeZone("Africa/Juba") fun test_with_method_level_configuration() { assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("Africa/Juba")) } } // end::default_timezone_class_level[] // tag::default_time_zone_with_provider[] @Test @DefaultTimeZone(timeZoneProvider = UtcTimeZoneProvider::class) fun test_with_time_zone_provider() { assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("UTC")) } class UtcTimeZoneProvider : TimeZoneProvider { override fun get(): TimeZone = TimeZone.getTimeZone(ZoneOffset.UTC) } // end::default_time_zone_with_provider[] } ================================================ FILE: documentation/src/test/kotlin/example/kotlin/DisplayNameDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin // tag::user_guide[] import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test @DisplayName("A special test case") class DisplayNameDemo { @Test @DisplayName("Custom test name containing spaces") fun testWithDisplayNameContainingSpaces() { } @Test @DisplayName("╯°□°)╯") fun testWithDisplayNameContainingSpecialCharacters() { } @Test @DisplayName("😱") fun testWithDisplayNameContainingEmoji() { } } // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/DisplayNameGeneratorDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.DisplayNameGeneration import org.junit.jupiter.api.DisplayNameGenerator import org.junit.jupiter.api.DisplayNameGenerator.IndicativeSentences.SentenceFragment import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores import org.junit.jupiter.api.IndicativeSentencesGeneration import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.ValueSource @Suppress("ClassName") class DisplayNameGeneratorDemo { // @formatter:off @Nested // tag::user_guide_replace_underscores[] @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores::class) // end::user_guide_replace_underscores[] inner // tag::user_guide_replace_underscores[] class A_year_is_not_supported { // end::user_guide_replace_underscores[] // @formatter:on // tag::user_guide_replace_underscores[] @Test fun if_it_is_zero() { } @DisplayName("A negative value for year is not supported by the leap year computation.") @ParameterizedTest(name = "For example, year {0} is not supported.") @ValueSource(ints = [-1, -4]) fun if_it_is_negative(year: Int) { } } // end::user_guide_replace_underscores[] // @formatter:off @Nested // tag::user_guide_indicative_sentences[] @IndicativeSentencesGeneration(separator = " -> ", generator = ReplaceUnderscores::class) // end::user_guide_indicative_sentences[] inner // tag::user_guide_indicative_sentences[] class A_year_is_a_leap_year { // end::user_guide_indicative_sentences[] // @formatter:on // tag::user_guide_indicative_sentences[] @Test fun if_it_is_divisible_by_4_but_not_by_100() { } @ParameterizedTest(name = "Year {0} is a leap year.") @ValueSource(ints = [2016, 2020, 2048]) fun if_it_is_one_of_the_following_years(year: Int) { } } // end::user_guide_indicative_sentences[] // @formatter:off @Nested // tag::user_guide_custom_sentence_fragments[] @SentenceFragment("A year is a leap year") @IndicativeSentencesGeneration // end::user_guide_custom_sentence_fragments[] inner // tag::user_guide_custom_sentence_fragments[] class LeapYearTests { // end::user_guide_custom_sentence_fragments[] // @formatter:on // tag::user_guide_custom_sentence_fragments[] @SentenceFragment("if it is divisible by 4 but not by 100") @Test fun divisibleBy4ButNotBy100() { } @SentenceFragment("if it is one of the following years") @ParameterizedTest(name = "{0}") @ValueSource(ints = [2016, 2020, 2048]) fun validLeapYear(year: Int) { } } // end::user_guide_custom_sentence_fragments[] } ================================================ FILE: documentation/src/test/kotlin/example/kotlin/DynamicTestsDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin // tag::user_guide[] import example.util.Calculator import example.util.StringUtils.isPalindrome import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertNotNull 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 import org.junit.jupiter.api.DynamicTest.dynamicTest import org.junit.jupiter.api.Tag import org.junit.jupiter.api.TestFactory import org.junit.jupiter.api.parallel.Execution import org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT import org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD import java.util.Random import java.util.stream.IntStream import java.util.stream.Stream // end::user_guide[] // tag::user_guide[] class DynamicTestsDemo { private val calculator = Calculator() // This method will not be executed but produce a warning @TestFactory // end::user_guide[] @Tag("exclude") fun dummy(): DynamicTest = dynamicTest("dummy") {} // tag::user_guide[] fun dynamicTestsWithInvalidReturnType(): List = listOf("Hello") @TestFactory fun dynamicTestsFromCollection(): Collection = listOf( dynamicTest("1st dynamic test") { assertTrue(isPalindrome("madam")) }, dynamicTest("2nd dynamic test") { assertEquals(4, calculator.multiply(2, 2)) } ) @TestFactory fun dynamicTestsFromIterable(): Iterable = listOf( dynamicTest("3rd dynamic test") { assertTrue(isPalindrome("madam")) }, dynamicTest("4th dynamic test") { assertEquals(4, calculator.multiply(2, 2)) } ) @TestFactory fun dynamicTestsFromIterator(): Iterator = listOf( dynamicTest("5th dynamic test") { assertTrue(isPalindrome("madam")) }, dynamicTest("6th dynamic test") { assertEquals(4, calculator.multiply(2, 2)) } ).iterator() @TestFactory fun dynamicTestsFromArray(): Array = arrayOf( dynamicTest("7th dynamic test") { assertTrue(isPalindrome("madam")) }, dynamicTest("8th dynamic test") { assertEquals(4, calculator.multiply(2, 2)) } ) @TestFactory fun dynamicTestsFromStream(): Stream = Stream .of("racecar", "radar", "mom", "dad") .map { text -> dynamicTest(text) { assertTrue(isPalindrome(text)) } } @TestFactory fun dynamicTestsFromSequence(): Sequence = sequenceOf("racecar", "radar", "mom", "dad") .map { text -> dynamicTest(text) { assertTrue(isPalindrome(text)) } } @TestFactory fun dynamicTestsFromIntStream(): Stream { // Generates tests for the first 10 even integers. return IntStream .iterate(0) { n -> n + 2 } .limit(10) .mapToObj { n -> dynamicTest("test$n") { assertEquals(0, n % 2) } } } @TestFactory fun generateRandomNumberOfTests(): Stream { // Generates random positive integers between 0 and 100 until // a number evenly divisible by 7 is encountered. val inputGenerator = object : Iterator { var random = Random() // end::user_guide[] init { // Use fixed seed to always produce the same number of tests for execution on the CI server random = Random(23) } // tag::user_guide[] var current = 0 override fun hasNext(): Boolean { current = random.nextInt(100) return current % 7 != 0 } override fun next(): Int = current } // Generates display names like: input:5, input:37, input:85, etc. val displayNameGenerator = { input: Int -> "input:$input" } // Executes tests based on the current input value. val testExecutor = { input: Int -> assertTrue(input % 7 != 0) } // Returns a stream of dynamic tests. return DynamicTest.stream(inputGenerator, displayNameGenerator, testExecutor) } @TestFactory fun dynamicTestsFromStreamFactoryMethod(): Stream { // Stream of palindromes to check val inputStream = Stream.of("racecar", "radar", "mom", "dad") // Generates display names like: racecar is a palindrome val displayNameGenerator = { text: String -> "$text is a palindrome" } // Executes tests based on the current input value. val testExecutor = { text: String -> assertTrue(isPalindrome(text)) } // Returns a stream of dynamic tests. return DynamicTest.stream(inputStream, displayNameGenerator, testExecutor) } @TestFactory fun dynamicTestsWithContainers(): Stream = Stream .of("A", "B", "C") .map { input -> dynamicContainer( "Container $input", Stream.of( dynamicTest("not null") { assertNotNull(input) }, dynamicContainer( "properties", Stream.of( dynamicTest("length > 0") { assertTrue(input.length > 0) }, dynamicTest("not empty") { assertFalse(input.isEmpty()) } ) ) ) ) } // end::user_guide[] // tag::execution_mode[] @TestFactory @Execution(CONCURRENT) // <1> fun dynamicTestsWithConfiguredExecutionMode(): Stream = Stream .of("A", "B", "C") .map { input -> dynamicContainer { outer -> outer .displayName("Container $input") .children( dynamicTest { config -> config .displayName("not null") .executionMode(SAME_THREAD) // <2> .executable { assertNotNull(input) } }, dynamicContainer { inner -> inner .displayName("properties") .executionMode(CONCURRENT) // <3> .childExecutionMode(SAME_THREAD) // <4> .children( dynamicTest { config -> config .displayName("length > 0") .executionMode(CONCURRENT) // <5> .executable { assertTrue(input.length > 0) } }, dynamicTest { config -> config .displayName("not empty") .executable { assertFalse(input.isEmpty()) } } ) } ) } } // end::execution_mode[] // tag::user_guide[] @TestFactory fun dynamicNodeSingleTest(): DynamicNode = dynamicTest("'pop' is a palindrome") { assertTrue(isPalindrome("pop")) } @TestFactory fun dynamicNodeSingleContainer(): DynamicNode = dynamicContainer( "palindromes", Stream .of("racecar", "radar", "mom", "dad") .map { text -> dynamicTest(text) { assertTrue(isPalindrome(text)) } } ) } // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/DynamicTestsNamedDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin // tag::user_guide[] import example.util.StringUtils.isPalindrome import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.DynamicTest import org.junit.jupiter.api.Named.named import org.junit.jupiter.api.NamedExecutable import org.junit.jupiter.api.TestFactory import java.util.stream.Stream class DynamicTestsNamedDemo { @TestFactory fun dynamicTestsFromStreamFactoryMethodWithNames(): Stream { // Stream of palindromes to check // end::user_guide[] // tag::user_guide[] val inputStream = Stream.of( named("racecar is a palindrome", "racecar"), named("radar is also a palindrome", "radar"), named("mom also seems to be a palindrome", "mom"), named("dad is yet another palindrome", "dad") ) // end::user_guide[] // tag::user_guide[] // Returns a stream of dynamic tests. return DynamicTest.stream(inputStream) { text -> assertTrue(isPalindrome(text)) } } @TestFactory fun dynamicTestsFromStreamFactoryMethodWithNamedExecutables(): Stream { // Stream of palindromes to check // end::user_guide[] // tag::user_guide[] val inputStream = Stream .of("racecar", "radar", "mom", "dad") .map { PalindromeNamedExecutable(it) } // end::user_guide[] // tag::user_guide[] // Returns a stream of dynamic tests based on NamedExecutables. return DynamicTest.stream(inputStream) } class PalindromeNamedExecutable( private val text: String ) : NamedExecutable { override fun getName(): String = "'$text' is a palindrome" override fun execute() { assertTrue(isPalindrome(text)) } } } // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/ExternalCustomConditionDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin // tag::user_guide_external_custom_condition[] import org.junit.jupiter.api.Test import org.junit.jupiter.api.condition.EnabledIf class ExternalCustomConditionDemo { @Test @EnabledIf("example.kotlin.ExternalCondition#customCondition") fun enabled() { // ... } } class ExternalCondition { companion object { @JvmStatic fun customCondition(): Boolean = true } } // end::user_guide_external_custom_condition[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/Fast.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin // tag::user_guide[] import org.junit.jupiter.api.Tag @Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) @Tag("fast") annotation class Fast // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/FastTest.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin // tag::user_guide[] import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test @Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) @Tag("fast") @Test annotation class FastTest // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/HttpServerDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin import com.sun.net.httpserver.HttpServer import example.kotlin.extensions.HttpServerExtension import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import java.net.HttpURLConnection import java.net.URI import java.net.URL // tag::user_guide[] @ExtendWith(HttpServerExtension::class) class HttpServerDemo { // end::user_guide[] @Suppress("HttpUrlsUsage") // tag::user_guide[] @Test fun httpCall(server: HttpServer) { val address = server.address val requestUrl = URI("http://${address.hostName}:${address.port}/example").toURL() val responseBody = sendRequest(requestUrl) assertEquals("This is a test", responseBody) } private fun sendRequest(url: URL): String { val connection = url.openConnection() as HttpURLConnection val contentLength = connection.contentLength url.openStream().use { response -> val content = ByteArray(contentLength) assertEquals(contentLength, response.read(content)) return String(content, Charsets.UTF_8) } } } // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/MyFirstJUnitJupiterTests.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin // tag::user_guide[] import example.util.Calculator import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test class MyFirstJUnitJupiterTests { private val calculator = Calculator() @Test fun addition() { assertEquals(2, calculator.add(1, 1)) } } // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/MyRandomParametersTest.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin import example.extensions.Random import org.junit.jupiter.api.Assertions.assertNotEquals import org.junit.jupiter.api.Test // tag::user_guide[] class MyRandomParametersTest( @Random randomNumber: Int ) { @Test fun injectsInteger( @Random i: Int, @Random j: Int ) { assertNotEquals(i, j) } } // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/ParameterizedClassDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Nested import org.junit.jupiter.api.parallel.Execution import org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD import org.junit.jupiter.params.Parameter import org.junit.jupiter.params.ParameterizedClass import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.ValueSource import java.time.Duration class ParameterizedClassDemo { // @formatter:off @Nested // tag::nested[] @Execution(SAME_THREAD) @ParameterizedClass @ValueSource(strings = ["apple", "banana"]) // end::nested[] inner // tag::nested[] class FruitTests { // end::nested[] // @formatter:on // tag::nested[] @Parameter lateinit var fruit: String @Nested @ParameterizedClass @ValueSource(ints = [23, 42]) inner class QuantityTests { @Parameter var quantity: Int = 0 @ParameterizedTest @ValueSource(strings = ["PT1H", "PT2H"]) fun test(duration: Duration) { assertFruit(fruit) assertQuantity(quantity) assertFalse(duration.isNegative) } } } // end::nested[] private fun assertFruit(fruit: String) { assertTrue( listOf("apple", "banana", "cherry", "dewberry").contains(fruit) ) { "not a fruit: $fruit" } } private fun assertQuantity(quantity: Int) { assertTrue(quantity > 0) } } ================================================ FILE: documentation/src/test/kotlin/example/kotlin/PollingTimeoutDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin import org.junit.jupiter.api.Test import org.junit.jupiter.api.Timeout class PollingTimeoutDemo { // tag::user_guide[] @Test @Timeout(5) // Poll at most 5 seconds fun pollUntil() { while (asynchronousResultNotAvailable()) { Thread.sleep(250) // custom poll interval } // Obtain the asynchronous result and perform assertions } // end::user_guide[] private fun asynchronousResultNotAvailable(): Boolean = false } ================================================ FILE: documentation/src/test/kotlin/example/kotlin/RepeatedTestsDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin // tag::user_guide[] import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.RepeatedTest import org.junit.jupiter.api.RepetitionInfo import org.junit.jupiter.api.TestInfo import org.junit.jupiter.api.fail import org.junit.jupiter.api.parallel.Execution import org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD import java.util.logging.Logger @Execution(SAME_THREAD) class RepeatedTestsDemo { private val logger = // ... // end::user_guide[] Logger.getLogger(RepeatedTestsDemo::class.java.name) // tag::user_guide[] @BeforeEach fun beforeEach( testInfo: TestInfo, repetitionInfo: RepetitionInfo ) { val currentRepetition = repetitionInfo.currentRepetition val totalRepetitions = repetitionInfo.totalRepetitions val methodName = testInfo.testMethod.get().name logger.info("About to execute repetition $currentRepetition of $totalRepetitions for $methodName") } @RepeatedTest(10) fun repeatedTest() { // ... } @RepeatedTest(5) fun repeatedTestWithRepetitionInfo(repetitionInfo: RepetitionInfo) { assertEquals(5, repetitionInfo.totalRepetitions) } // end::user_guide[] // Use fully qualified name to avoid having it show up in the imports. @org.junit.jupiter.api.Disabled("intentional failures would break the build") // tag::user_guide[] @RepeatedTest(value = 8, failureThreshold = 2) fun repeatedTestWithFailureThreshold(repetitionInfo: RepetitionInfo) { // Simulate unexpected failure every second repetition if (repetitionInfo.currentRepetition % 2 == 0) { fail("Boom!") } } @RepeatedTest(value = 1, name = "{displayName} {currentRepetition}/{totalRepetitions}") @DisplayName("Repeat!") fun customDisplayName(testInfo: TestInfo) { assertEquals("Repeat! 1/1", testInfo.displayName) } @RepeatedTest(value = 1, name = RepeatedTest.LONG_DISPLAY_NAME) @DisplayName("Details...") fun customDisplayNameWithLongPattern(testInfo: TestInfo) { assertEquals("Details... :: repetition 1 of 1", testInfo.displayName) } @RepeatedTest(value = 5, name = "Wiederholung {currentRepetition} von {totalRepetitions}") fun repeatedTestInGerman() { // ... } } // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/StandardTests.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin // tag::user_guide[] import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assumptions.assumeTrue import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import org.junit.jupiter.api.fail class StandardTests { @BeforeEach fun init() { } @Test fun succeedingTest() { } // end::user_guide[] @extensions.ExpectToFail // tag::user_guide[] @Test fun failingTest() { fail("a failing test") } @Test @Disabled("for demonstration purposes") fun skippedTest() { // not executed } @Test fun abortedTest() { assumeTrue("abc".contains("Z")) fail("test should have been aborted") } @AfterEach fun tearDown() { } companion object { @JvmStatic @BeforeAll fun initAll() { } @JvmStatic @AfterAll fun tearDownAll() { } } } // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/SystemPropertyExtensionDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.ClassOrderer import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Order import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestClassOrder import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.util.ClearSystemProperty import org.junit.jupiter.api.util.ReadsSystemProperty import org.junit.jupiter.api.util.RestoreSystemProperties import org.junit.jupiter.api.util.SetSystemProperty import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.ValueSource class SystemPropertyExtensionDemo { // tag::systemproperty_clear_simple[] @Test @ClearSystemProperty(key = "some property") fun testClearingProperty() { assertThat(System.getProperty("some property")).isNull() } // end::systemproperty_clear_simple[] // tag::systemproperty_set_simple[] @Test @SetSystemProperty(key = "some property", value = "new value") fun testSettingProperty() { assertThat(System.getProperty("some property")).isEqualTo("new value") } // end::systemproperty_set_simple[] // tag::systemproperty_using_set_and_clear[] @Test @ClearSystemProperty(key = "1st property") @ClearSystemProperty(key = "2nd property") @SetSystemProperty(key = "3rd property", value = "new value") fun testClearingAndSettingProperty() { assertThat(System.getProperty("1st property")).isNull() assertThat(System.getProperty("2nd property")).isNull() assertThat(System.getProperty("3rd property")).isEqualTo("new value") } // end::systemproperty_using_set_and_clear[] // @formatter:off @Nested // tag::systemproperty_using_at_class_level[] @ClearSystemProperty(key = "some property") // end::systemproperty_using_at_class_level[] inner // tag::systemproperty_using_at_class_level[] class MySystemPropertyTest { // end::systemproperty_using_at_class_level[] // @formatter:on // tag::systemproperty_using_at_class_level[] @Test @SetSystemProperty(key = "some property", value = "new value") fun clearedAtClasslevel() { assertThat(System.getProperty("some property")).isEqualTo("new value") } } // end::systemproperty_using_at_class_level[] // tag::systemproperty_restore_test[] @ParameterizedTest @ValueSource(strings = ["foo", "bar"]) @RestoreSystemProperties fun parameterizedTest(value: String) { System.setProperty("some parameterized property", value) System.setProperty("some other dynamic property", "my code calculates somehow") } // end::systemproperty_restore_test[] @Nested @TestClassOrder(ClassOrderer.OrderAnnotation::class) inner class SystemPropertyRestoreExample { // @formatter:off @Nested @Order(1) // tag::systemproperty_class_restore_setup[] @TestInstance(TestInstance.Lifecycle.PER_CLASS) @RestoreSystemProperties // end::systemproperty_class_restore_setup[] inner // tag::systemproperty_class_restore_setup[] class MySystemPropertyRestoreTest { // end::systemproperty_class_restore_setup[] // @formatter:on // tag::systemproperty_class_restore_setup[] @BeforeAll fun beforeAll() { System.setProperty("A", "A value") } @BeforeEach fun beforeEach() { System.setProperty("B", "B value") } @Test fun isolatedTest1() { System.setProperty("C", "C value") } @Test fun isolatedTest2() { assertThat(System.getProperty("A")).isEqualTo("A value") assertThat(System.getProperty("B")).isEqualTo("B value") // Class-level @RestoreSystemProperties restores "C" to original state assertThat(System.getProperty("C")).isNull() } } // end::systemproperty_class_restore_setup[] // @formatter:off @Nested @Order(2) // tag::systemproperty_class_restore_isolated_class[] @ReadsSystemProperty // end::systemproperty_class_restore_isolated_class[] inner // tag::systemproperty_class_restore_isolated_class[] class SomeOtherTestClass { // end::systemproperty_class_restore_isolated_class[] // @formatter:on // tag::systemproperty_class_restore_isolated_class[] @Test fun isolatedTest() { assertThat(System.getProperty("A")).isNull() assertThat(System.getProperty("B")).isNull() assertThat(System.getProperty("C")).isNull() } } // end::systemproperty_class_restore_isolated_class[] } // tag::systemproperty_method_combine_all_test[] @ParameterizedTest @ValueSource(ints = [100, 500, 1000]) @RestoreSystemProperties @SetSystemProperty(key = "DISABLE_CACHE", value = "TRUE") @ClearSystemProperty(key = "COPYWRITE_OVERLAY_TEXT") fun imageGenerationTest(imageSize: Int) { System.setProperty("IMAGE_SIZE", "$imageSize") // Requires restore // Test your image generation utility with the current system properties } // end::systemproperty_method_combine_all_test[] } ================================================ FILE: documentation/src/test/kotlin/example/kotlin/TempDirectoryDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin import com.google.common.jimfs.Configuration import com.google.common.jimfs.Jimfs import example.kotlin.TempDirectoryDemo.InMemoryTempDirDemo.JimfsTempDirFactory import example.util.ListWriter import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotEquals import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.AnnotatedElementContext import org.junit.jupiter.api.extension.ExtensionContext import org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS import org.junit.jupiter.api.io.TempDir import org.junit.jupiter.api.io.TempDirDeletionStrategy import org.junit.jupiter.api.io.TempDirFactory import java.nio.file.FileSystem import java.nio.file.Files import java.nio.file.Path @Suppress("ClassName") class TempDirectoryDemo { // tag::user_guide_parameter_injection[] @Test fun writeItemsToFile( @TempDir tempDir: Path ) { val file = tempDir.resolve("test.txt") ListWriter(file).write("a", "b", "c") assertEquals(listOf("a,b,c"), Files.readAllLines(file)) } // end::user_guide_parameter_injection[] // tag::user_guide_multiple_directories[] @Test fun copyFileFromSourceToTarget( @TempDir source: Path, @TempDir target: Path ) { val sourceFile = source.resolve("test.txt") ListWriter(sourceFile).write("a", "b", "c") val targetFile = Files.copy(sourceFile, target.resolve("test.txt")) assertNotEquals(sourceFile, targetFile) assertEquals(listOf("a,b,c"), Files.readAllLines(targetFile)) } // end::user_guide_multiple_directories[] // tag::user_guide_field_injection[] class SharedTempDirectoryDemo { @Test fun writeItemsToFile() { val file = sharedTempDir.resolve("test.txt") ListWriter(file).write("a", "b", "c") assertEquals(listOf("a,b,c"), Files.readAllLines(file)) } @Test fun anotherTestThatUsesTheSameTempDir() { // use sharedTempDir } companion object { @TempDir @JvmStatic lateinit var sharedTempDir: Path } } // end::user_guide_field_injection[] // tag::user_guide_cleanup_mode[] class CleanupModeDemo { @Test fun fileTest( @TempDir(cleanup = ON_SUCCESS) tempDir: Path ) { // perform test } } // end::user_guide_cleanup_mode[] // tag::user_guide_factory_name_prefix[] class TempDirFactoryDemo { @Test fun factoryTest( @TempDir(factory = Factory::class) tempDir: Path ) { assertTrue(tempDir.fileName.toString().startsWith("factoryTest")) } class Factory : TempDirFactory { override fun createTempDirectory( elementContext: AnnotatedElementContext, extensionContext: ExtensionContext ): Path = Files.createTempDirectory(extensionContext.requiredTestMethod.name) } } // end::user_guide_factory_name_prefix[] // tag::user_guide_factory_jimfs[] class InMemoryTempDirDemo { @Test fun test( @TempDir(factory = JimfsTempDirFactory::class) tempDir: Path ) { // perform test } class JimfsTempDirFactory : TempDirFactory { private val fileSystem: FileSystem = Jimfs.newFileSystem(Configuration.unix()) override fun createTempDirectory( elementContext: AnnotatedElementContext, extensionContext: ExtensionContext ): Path = Files.createTempDirectory(fileSystem.getPath("/"), "junit-") override fun close() { fileSystem.close() } } } // end::user_guide_factory_jimfs[] // tag::user_guide_deletion_strategy[] class DeletionStrategyDemo { @Test fun test( @TempDir(deletionStrategy = TempDirDeletionStrategy.IgnoreFailures::class) tempDir: Path ) { // perform test } } // end::user_guide_deletion_strategy[] // tag::user_guide_composed_annotation[] @Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER) @Retention(AnnotationRetention.RUNTIME) @TempDir(factory = JimfsTempDirFactory::class) annotation class JimfsTempDir // end::user_guide_composed_annotation[] // tag::user_guide_composed_annotation_usage[] class JimfsTempDirAnnotationDemo { @Test fun test( @JimfsTempDir tempDir: Path ) { // perform test } } // end::user_guide_composed_annotation_usage[] } ================================================ FILE: documentation/src/test/kotlin/example/kotlin/TestInfoDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin // tag::user_guide[] import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInfo @DisplayName("TestInfo Demo") class TestInfoDemo( testInfo: TestInfo ) { companion object { @JvmStatic @BeforeAll fun beforeAll(testInfo: TestInfo) { assertEquals("TestInfo Demo", testInfo.displayName) } } init { assertTrue(testInfo.displayName in listOf("TEST 1", "test2()")) } @BeforeEach fun init(testInfo: TestInfo) { assertTrue(testInfo.displayName in listOf("TEST 1", "test2()")) } @Test @DisplayName("TEST 1") @Tag("my-tag") fun test1(testInfo: TestInfo) { assertEquals("TEST 1", testInfo.displayName) assertTrue("my-tag" in testInfo.tags) } @Test fun test2() { } } // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/TestReporterDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin import org.junit.jupiter.api.MediaType import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestReporter import org.junit.jupiter.api.io.TempDir import java.nio.file.Files import java.nio.file.Path // tag::user_guide[] class TestReporterDemo { @Test fun reportSingleValue(testReporter: TestReporter) { testReporter.publishEntry("a status message") } @Test fun reportKeyValuePair(testReporter: TestReporter) { testReporter.publishEntry("a key", "a value") } @Test fun reportMultipleKeyValuePairs(testReporter: TestReporter) { testReporter.publishEntry( mapOf( "user name" to "dk38", "award year" to "1974" ) ) } @Test fun reportFiles( testReporter: TestReporter, @TempDir tempDir: Path ) { testReporter.publishFile("test1.txt", MediaType.TEXT_PLAIN_UTF_8) { file -> Files.write(file, listOf("Test 1")) } val existingFile = Files.write(tempDir.resolve("test2.txt"), listOf("Test 2")) testReporter.publishFile(existingFile, MediaType.TEXT_PLAIN_UTF_8) testReporter.publishDirectory("test3") { dir -> Files.write(dir.resolve("nested1.txt"), listOf("Nested content 1")) Files.write(dir.resolve("nested2.txt"), listOf("Nested content 2")) } val existingDir = Files.createDirectory(tempDir.resolve("test4")) Files.write(existingDir.resolve("nested1.txt"), listOf("Nested content 1")) Files.write(existingDir.resolve("nested2.txt"), listOf("Nested content 2")) testReporter.publishDirectory(existingDir) } } // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/TestingAStackDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin // tag::user_guide[] 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.BeforeEach import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import java.util.EmptyStackException import java.util.Stack @DisplayName("A stack") class TestingAStackDemo { @Test @DisplayName("is instantiated with new Stack()") fun isInstantiatedWithNew() { Stack() } @Nested @DisplayName("when new") inner class WhenNew { lateinit var stack: Stack @BeforeEach fun createNewStack() { stack = Stack() } @Test @DisplayName("is empty") fun isEmpty() { assertTrue(stack.isEmpty()) } @Test @DisplayName("throws EmptyStackException when popped") fun throwsExceptionWhenPopped() { assertThrows { stack.pop() } } @Test @DisplayName("throws EmptyStackException when peeked") fun throwsExceptionWhenPeeked() { assertThrows { stack.peek() } } @Nested @DisplayName("after pushing an element") inner class AfterPushing { val anElement = "an element" @BeforeEach fun pushAnElement() { stack.push(anElement) } @Test @DisplayName("it is no longer empty") fun isNotEmpty() { assertFalse(stack.isEmpty()) } @Test @DisplayName("returns the element when popped and is empty") fun returnElementWhenPopped() { assertEquals(anElement, stack.pop()) assertTrue(stack.isEmpty()) } @Test @DisplayName("returns the element when peeked but remains not empty") fun returnElementWhenPeeked() { assertEquals(anElement, stack.peek()) assertFalse(stack.isEmpty()) } } } } // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/TimeoutDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test import org.junit.jupiter.api.Timeout import org.junit.jupiter.api.Timeout.ThreadMode import java.util.concurrent.TimeUnit // tag::user_guide[] @Tag("timeout") class TimeoutDemo { @BeforeEach @Timeout(5) fun setUp() { // fails if execution time exceeds 5 seconds } @Test @Timeout(value = 500, unit = TimeUnit.MILLISECONDS) fun failsIfExecutionTimeExceeds500Milliseconds() { // fails if execution time exceeds 500 milliseconds } @Test @Timeout(value = 500, unit = TimeUnit.MILLISECONDS, threadMode = ThreadMode.SEPARATE_THREAD) fun failsIfExecutionTimeExceeds500MillisecondsInSeparateThread() { // fails if execution time exceeds 500 milliseconds, the test code is executed in a separate thread } } // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/defaultmethods/ComparableContract.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin.defaultmethods import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test // tag::user_guide[] interface ComparableContract> : Testable { fun createSmallerValue(): T @Test fun returnsZeroWhenComparedToItself() { val value = createValue() assertEquals(0, value.compareTo(value)) } @Test fun returnsPositiveNumberWhenComparedToSmallerValue() { val value = createValue() val smallerValue = createSmallerValue() assertTrue(value > smallerValue) } @Test fun returnsNegativeNumberWhenComparedToLargerValue() { val value = createValue() val smallerValue = createSmallerValue() assertTrue(smallerValue < value) } } // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/defaultmethods/EqualsContract.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin.defaultmethods import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotEquals import org.junit.jupiter.api.Test // tag::user_guide[] interface EqualsContract : Testable { fun createNotEqualValue(): T @Test fun valueEqualsItself() { val value = createValue() assertEquals(value, value) } @Test fun valueDoesNotEqualNull() { val value = createValue() assertNotEquals(null, value) } @Test fun valueDoesNotEqualDifferentValue() { val value = createValue() val differentValue = createNotEqualValue() assertNotEquals(value, differentValue) assertNotEquals(differentValue, value) } } // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/defaultmethods/StringTests.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin.defaultmethods // tag::user_guide[] class StringTests : ComparableContract, EqualsContract { override fun createValue() = "banana" override fun createSmallerValue() = "apple" // 'a' < 'b' in "banana" override fun createNotEqualValue() = "cherry" } // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/defaultmethods/Testable.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin.defaultmethods // tag::user_guide[] interface Testable { fun createValue(): T } // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/exception/AssertDoesNotThrowExceptionDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin.exception import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertDoesNotThrow class AssertDoesNotThrowExceptionDemo { // tag::user_guide[] @Test fun testExceptionIsNotThrown() { assertDoesNotThrow { shouldNotThrowException() } } fun shouldNotThrowException() { } // end::user_guide[] } ================================================ FILE: documentation/src/test/kotlin/example/kotlin/exception/ExceptionAssertionDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin.exception import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows class ExceptionAssertionDemo { // tag::user_guide[] @Test fun testExpectedExceptionIsThrown() { // The following assertion succeeds because the code under assertion // throws the expected IllegalArgumentException. // The assertion also returns the thrown exception which can be used for // further assertions like asserting the exception message. val exception = assertThrows { throw IllegalArgumentException("expected message") } assertEquals("expected message", exception.message) // The following assertion also succeeds because the code under assertion // throws IllegalArgumentException which is a subclass of RuntimeException. assertThrows { throw IllegalArgumentException("expected message") } } // end::user_guide[] } ================================================ FILE: documentation/src/test/kotlin/example/kotlin/exception/ExceptionAssertionExactDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin.exception import extensions.ExpectToFail import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrowsExactly class ExceptionAssertionExactDemo { @ExpectToFail // tag::user_guide[] @Test fun testExpectedExceptionIsThrown() { // The following assertion succeeds because the code under assertion throws // IllegalArgumentException which is exactly equal to the expected type. // The assertion also returns the thrown exception which can be used for // further assertions like asserting the exception message. val exception = assertThrowsExactly { throw IllegalArgumentException("expected message") } assertEquals("expected message", exception.message) // The following assertion fails because the assertion expects exactly // RuntimeException to be thrown, not subclasses of RuntimeException. assertThrowsExactly { throw IllegalArgumentException("expected message") } } // end::user_guide[] } ================================================ FILE: documentation/src/test/kotlin/example/kotlin/exception/FailedAssertionDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin.exception import example.util.Calculator import extensions.ExpectToFail import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test class FailedAssertionDemo { // tag::user_guide[] private val calculator = Calculator() // end::user_guide[] @ExpectToFail // tag::user_guide[] @Test fun failsDueToUncaughtAssertionError() { // The following incorrect assertion will cause a test failure. // The expected value should be 2 instead of 99. assertEquals(99, calculator.add(1, 1)) } // end::user_guide[] } ================================================ FILE: documentation/src/test/kotlin/example/kotlin/exception/UncaughtExceptionHandlingDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin.exception import example.util.Calculator import extensions.ExpectToFail import org.junit.jupiter.api.Test class UncaughtExceptionHandlingDemo { // tag::user_guide[] private val calculator = Calculator() // end::user_guide[] @ExpectToFail // tag::user_guide[] @Test fun failsDueToUncaughtException() { // The following throws an ArithmeticException due to division by // zero, which causes a test failure. calculator.divide(1, 0) } // end::user_guide[] } ================================================ FILE: documentation/src/test/kotlin/example/kotlin/extensions/HttpServerExtension.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin.extensions import com.sun.net.httpserver.HttpServer import org.junit.jupiter.api.extension.ExtensionContext import org.junit.jupiter.api.extension.ExtensionContext.Namespace import org.junit.jupiter.api.extension.ParameterContext import org.junit.jupiter.api.extension.ParameterResolver import java.io.IOException import java.io.UncheckedIOException // tag::user_guide[] class HttpServerExtension : ParameterResolver { override fun supportsParameter( parameterContext: ParameterContext, extensionContext: ExtensionContext ): Boolean = HttpServer::class.java == parameterContext.parameter.type override fun resolveParameter( parameterContext: ParameterContext, extensionContext: ExtensionContext ): Any { val rootContext = extensionContext.root val store = rootContext.getStore(Namespace.GLOBAL) val key = HttpServerResource::class.java val resource = store.computeIfAbsent(key, { try { HttpServerResource(0).apply { start() } } catch (e: IOException) { throw UncheckedIOException("Failed to create HttpServerResource", e) } }, HttpServerResource::class.java) return resource.httpServer } } // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/extensions/HttpServerResource.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin.extensions import com.sun.net.httpserver.HttpServer import java.net.InetAddress import java.net.InetSocketAddress // tag::user_guide[] class HttpServerResource( port: Int ) : AutoCloseable { val httpServer: HttpServer init { val loopbackAddress = InetAddress.getLoopbackAddress() httpServer = HttpServer.create(InetSocketAddress(loopbackAddress, port), 0) } // end::user_guide[] // tag::user_guide[] fun start() { // Example handler httpServer.createContext("/example") { exchange -> val body = "This is a test" exchange.sendResponseHeaders(200, body.length.toLong()) exchange.responseBody.use { os -> os.write(body.toByteArray(Charsets.UTF_8)) } } httpServer.executor = null httpServer.start() } override fun close() { httpServer.stop(0) } } // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/extensions/ParameterResolverConflictDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin.extensions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.ExtensionContext import org.junit.jupiter.api.extension.ParameterContext import org.junit.jupiter.api.extension.ParameterResolver // tag::user_guide[] class ParameterResolverConflictDemo { // end::user_guide[] @extensions.ExpectToFail // tag::user_guide[] @Test @ExtendWith(FirstIntegerResolver::class, SecondIntegerResolver::class) fun testInt(i: Int) { // Test will not run due to ParameterResolutionException assertEquals(1, i) } class FirstIntegerResolver : ParameterResolver { override fun supportsParameter( parameterContext: ParameterContext, extensionContext: ExtensionContext ): Boolean = parameterContext.parameter.type == Int::class.javaPrimitiveType override fun resolveParameter( parameterContext: ParameterContext, extensionContext: ExtensionContext ): Any = 1 } class SecondIntegerResolver : ParameterResolver { override fun supportsParameter( parameterContext: ParameterContext, extensionContext: ExtensionContext ): Boolean = parameterContext.parameter.type == Int::class.javaPrimitiveType override fun resolveParameter( parameterContext: ParameterContext, extensionContext: ExtensionContext ): Any = 2 } } // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/extensions/ParameterResolverCustomAnnotationDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin.extensions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.ExtensionContext import org.junit.jupiter.api.extension.ParameterContext import org.junit.jupiter.api.extension.ParameterResolver // tag::user_guide[] class ParameterResolverCustomAnnotationDemo { @Test fun testInt( @FirstInteger first: Int, @SecondInteger second: Int ) { assertEquals(1, first) assertEquals(2, second) } @Target(AnnotationTarget.VALUE_PARAMETER) @Retention(AnnotationRetention.RUNTIME) @ExtendWith(FirstIntegerExtension::class) annotation class FirstInteger class FirstIntegerExtension : ParameterResolver { override fun supportsParameter( parameterContext: ParameterContext, extensionContext: ExtensionContext ): Boolean = parameterContext.parameter.type == Int::class.javaPrimitiveType && !parameterContext.isAnnotated(SecondInteger::class.java) override fun resolveParameter( parameterContext: ParameterContext, extensionContext: ExtensionContext ): Any = 1 } @Target(AnnotationTarget.VALUE_PARAMETER) @Retention(AnnotationRetention.RUNTIME) @ExtendWith(SecondIntegerExtension::class) annotation class SecondInteger class SecondIntegerExtension : ParameterResolver { override fun supportsParameter( parameterContext: ParameterContext, extensionContext: ExtensionContext ): Boolean = parameterContext.isAnnotated(SecondInteger::class.java) override fun resolveParameter( parameterContext: ParameterContext, extensionContext: ExtensionContext ): Any = 2 } } // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/extensions/ParameterResolverCustomTypeDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin.extensions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.ExtensionContext import org.junit.jupiter.api.extension.ParameterContext import org.junit.jupiter.api.extension.ParameterResolver // tag::user_guide[] class ParameterResolverCustomTypeDemo { @Test @ExtendWith(FirstIntegerResolver::class, SecondIntegerResolver::class) fun testInt( i: Int, wrappedInteger: WrappedInteger ) { assertEquals(1, i) assertEquals(2, wrappedInteger.value) } class FirstIntegerResolver : ParameterResolver { override fun supportsParameter( parameterContext: ParameterContext, extensionContext: ExtensionContext ): Boolean = parameterContext.parameter.type == Int::class.javaPrimitiveType override fun resolveParameter( parameterContext: ParameterContext, extensionContext: ExtensionContext ): Any = 1 } class SecondIntegerResolver : ParameterResolver { override fun supportsParameter( parameterContext: ParameterContext, extensionContext: ExtensionContext ): Boolean = parameterContext.parameter.type == WrappedInteger::class.java override fun resolveParameter( parameterContext: ParameterContext, extensionContext: ExtensionContext ): Any = WrappedInteger(2) } data class WrappedInteger( val value: Int ) } // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/extensions/ParameterResolverNoConflictDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin.extensions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.ExtensionContext import org.junit.jupiter.api.extension.ParameterContext import org.junit.jupiter.api.extension.ParameterResolver // tag::user_guide[] class ParameterResolverNoConflictDemo { @Test @ExtendWith(FirstIntegerResolver::class) fun firstResolution(i: Int) { assertEquals(1, i) } @Test @ExtendWith(SecondIntegerResolver::class) fun secondResolution(i: Int) { assertEquals(2, i) } class FirstIntegerResolver : ParameterResolver { override fun supportsParameter( parameterContext: ParameterContext, extensionContext: ExtensionContext ): Boolean = parameterContext.parameter.type == Int::class.javaPrimitiveType override fun resolveParameter( parameterContext: ParameterContext, extensionContext: ExtensionContext ): Any = 1 } class SecondIntegerResolver : ParameterResolver { override fun supportsParameter( parameterContext: ParameterContext, extensionContext: ExtensionContext ): Boolean = parameterContext.parameter.type == Int::class.javaPrimitiveType override fun resolveParameter( parameterContext: ParameterContext, extensionContext: ExtensionContext ): Any = 2 } } // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/extensions/Random.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin.extensions import org.junit.jupiter.api.extension.ExtendWith // tag::user_guide[] @Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER) @Retention(AnnotationRetention.RUNTIME) @ExtendWith(RandomNumberExtension::class) annotation class Random // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/extensions/RandomNumberDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin.extensions import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test // tag::user_guide[] class RandomNumberDemo( @Random randomNumber2: Int ) { // Use randomNumber1 field in test methods and @BeforeEach // or @AfterEach lifecycle methods. @Random var randomNumber1: Int = 0 // randomNumber2 is available in the constructor via primary constructor above. @BeforeEach fun beforeEach( @Random randomNumber3: Int ) { // Use randomNumber3 in @BeforeEach method. } @Test fun test( @Random randomNumber4: Int ) { // Use randomNumber4 in test method. } companion object { // Use static randomNumber0 field anywhere in the test class, // including @BeforeAll or @AfterAll lifecycle methods. @Random @JvmStatic var randomNumber0: Int = 0 } } // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/extensions/RandomNumberExtension.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin.extensions // tag::user_guide[] import org.junit.jupiter.api.extension.BeforeAllCallback import org.junit.jupiter.api.extension.ExtensionContext import org.junit.jupiter.api.extension.ParameterContext import org.junit.jupiter.api.extension.ParameterResolver import org.junit.jupiter.api.extension.TestInstancePostProcessor import org.junit.platform.commons.support.AnnotationSupport.findAnnotatedFields import org.junit.platform.commons.support.ModifierSupport import java.lang.reflect.Field import java.util.function.Predicate class RandomNumberExtension : BeforeAllCallback, TestInstancePostProcessor, ParameterResolver { private val random = java.util.Random(System.nanoTime()) /** * Inject a random integer into static fields that are annotated with * `@Random` and can be assigned an integer value. */ override fun beforeAll(context: ExtensionContext) { val testClass = context.requiredTestClass injectFields(testClass, null, ModifierSupport::isStatic) } /** * Inject a random integer into non-static fields that are annotated with * `@Random` and can be assigned an integer value. */ override fun postProcessTestInstance( testInstance: Any, context: ExtensionContext ) { val testClass = context.requiredTestClass injectFields(testClass, testInstance, ModifierSupport::isNotStatic) } /** * Determine if the parameter is annotated with `@Random` and can be * assigned an integer value. */ override fun supportsParameter( pc: ParameterContext, ec: ExtensionContext ): Boolean = pc.isAnnotated(Random::class.java) && isInteger(pc.parameter.type) /** * Resolve a random integer. */ override fun resolveParameter( pc: ParameterContext, ec: ExtensionContext ): Int = random.nextInt() private fun injectFields( testClass: Class<*>, testInstance: Any?, predicate: Predicate ) { val combined = predicate.and { field -> isInteger(field.type) } findAnnotatedFields(testClass, Random::class.java, combined) .forEach { field -> field.isAccessible = true field.set(testInstance, random.nextInt()) } } private fun isInteger(type: Class<*>): Boolean = type == Int::class.javaObjectType || type == Int::class.javaPrimitiveType } // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/registration/DocumentationDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin.registration import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.RegisterExtension import java.nio.file.Path // tag::user_guide[] class DocumentationDemo { @JvmField @RegisterExtension val docs: DocumentationExtension = DocumentationExtension.forPath(lookUpDocsDir()) @Test fun generateDocumentation() { // use this.docs ... } companion object { fun lookUpDocsDir(): Path? { // return path to docs dir return null } } } // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/registration/DocumentationExtension.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin.registration import org.junit.jupiter.api.extension.AfterEachCallback import org.junit.jupiter.api.extension.ExtensionContext import java.nio.file.Path class DocumentationExtension private constructor( private val path: Path? ) : AfterEachCallback { override fun afterEach(context: ExtensionContext) { // no-op for demo } companion object { @JvmStatic fun forPath(path: Path?): DocumentationExtension = DocumentationExtension(path) } } ================================================ FILE: documentation/src/test/kotlin/example/kotlin/testinterface/TestInterfaceDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin.testinterface import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test // tag::user_guide[] class TestInterfaceDemo : TestLifecycleLogger, TimeExecutionLogger, TestInterfaceDynamicTestsDemo { @Test fun isEqualValue() { assertEquals(1, "a".length, "is always equal") } } // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/testinterface/TestInterfaceDynamicTestsDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin.testinterface import example.util.StringUtils.isPalindrome import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.DynamicTest import org.junit.jupiter.api.DynamicTest.dynamicTest import org.junit.jupiter.api.TestFactory // tag::user_guide[] interface TestInterfaceDynamicTestsDemo { @TestFactory fun dynamicTestsForPalindromes(): Sequence = sequenceOf("racecar", "radar", "mom", "dad") .map { text -> dynamicTest(text) { assertTrue(isPalindrome(text)) } } } // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/testinterface/TestLifecycleLogger.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin.testinterface 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.TestInfo import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance.Lifecycle import java.util.logging.Logger // tag::user_guide[] @TestInstance(Lifecycle.PER_CLASS) interface TestLifecycleLogger { @BeforeAll fun beforeAllTests() { logger.info("Before all tests") } @AfterAll fun afterAllTests() { logger.info("After all tests") } @BeforeEach fun beforeEachTest(testInfo: TestInfo) { logger.info { "About to execute [${testInfo.displayName}]" } } @AfterEach fun afterEachTest(testInfo: TestInfo) { logger.info { "Finished executing [${testInfo.displayName}]" } } companion object { private val logger: Logger = Logger.getLogger(TestLifecycleLogger::class.java.name) } } // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/testinterface/TimeExecutionLogger.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin.testinterface import example.timing.TimingExtension import org.junit.jupiter.api.Tag import org.junit.jupiter.api.extension.ExtendWith // tag::user_guide[] @Tag("timed") @ExtendWith(TimingExtension::class) interface TimeExecutionLogger // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/timing/TimingExtension.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin.timing // tag::user_guide[] import org.junit.jupiter.api.extension.AfterTestExecutionCallback import org.junit.jupiter.api.extension.BeforeTestExecutionCallback import org.junit.jupiter.api.extension.ExtensionContext import org.junit.jupiter.api.extension.ExtensionContext.Namespace import org.junit.jupiter.api.extension.ExtensionContext.Store import java.util.logging.Logger // end::user_guide[] // tag::user_guide[] class TimingExtension : BeforeTestExecutionCallback, AfterTestExecutionCallback { override fun beforeTestExecution(context: ExtensionContext) { getStore(context).put(START_TIME, System.currentTimeMillis()) } override fun afterTestExecution(context: ExtensionContext) { val testMethod = context.requiredTestMethod val startTime = getStore(context).remove(START_TIME, Long::class.javaObjectType)!! val duration = System.currentTimeMillis() - startTime logger.info { "Method [${testMethod.name}] took $duration ms." } } private fun getStore(context: ExtensionContext): Store = context.getStore(Namespace.create(javaClass, context.requiredTestMethod)) companion object { private val logger: Logger = Logger.getLogger(TimingExtension::class.java.name) private const val START_TIME = "start time" } } // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/kotlin/timing/TimingExtensionTests.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.kotlin.timing import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith // tag::user_guide[] @ExtendWith(TimingExtension::class) class TimingExtensionTests { @Test fun sleep20ms() { Thread.sleep(20) } @Test fun sleep50ms() { Thread.sleep(50) } } // end::user_guide[] ================================================ FILE: documentation/src/test/kotlin/example/registration/KotlinWebServerDemo.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example.registration import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.RegisterExtension // tag::user_guide[] class KotlinWebServerDemo { companion object { @JvmField @RegisterExtension val server = WebServerExtension .builder() .enableSecurity(false) .build()!! } @Test fun getProductList() { // Use WebClient to connect to web server using serverUrl and verify response val webClient = WebClient() val serverUrl = server.serverUrl assertEquals(200, webClient.get("$serverUrl/products").responseStatus) } } // end::user_guide[] ================================================ FILE: documentation/src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener ================================================ example.session.GlobalSetupTeardownListener ================================================ FILE: documentation/src/test/resources/junit-platform.properties ================================================ junit.jupiter.execution.parallel.enabled=true junit.jupiter.execution.parallel.mode.default=concurrent junit.jupiter.execution.parallel.config.executor-service=worker_thread_pool junit.jupiter.execution.parallel.config.strategy=fixed junit.jupiter.execution.parallel.config.fixed.parallelism=6 junit.platform.stacktrace.pruning.enabled=false ================================================ FILE: documentation/src/test/resources/log4j2-test.xml ================================================ ================================================ FILE: documentation/src/test/resources/two-column.csv ================================================ COUNTRY, REFERENCE Sweden, 1 Poland, 2 "United States of America", 3 France, 700_000 ================================================ FILE: documentation/src/tools/java/org/junit/api/tools/AbstractApiReportWriter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.api.tools; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.toList; import java.io.PrintWriter; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import org.apiguardian.api.API.Status; /** * @since 1.0 */ abstract class AbstractApiReportWriter implements ApiReportWriter { protected static final int NAME_COLUMN_WIDTH = 128; private final ApiReport apiReport; AbstractApiReportWriter(ApiReport apiReport) { this.apiReport = apiReport; } @Override public void printReportHeader(PrintWriter out) { out.println(h1("@API Declarations")); out.println(); out.println(paragraph( "Discovered %d types with %s declarations.".formatted(this.apiReport.types().size(), code("@API")))); out.println(); } @Override public void printDeclarationInfo(PrintWriter out, Set statuses) { statuses.forEach( status -> printDeclarationSection(statuses, status, this.apiReport.declarations().get(status), out)); } protected void printDeclarationSection(Set statuses, Status status, List declarations, PrintWriter out) { printDeclarationSectionHeader(statuses, status, declarations, out); Map> declarationsByModule = declarations.stream() // .collect(groupingBy(Declaration::moduleName, TreeMap::new, toList())); if (declarationsByModule.isEmpty()) { out.println(paragraph("NOTE: There are currently no APIs annotated with %s.".formatted( code("@API(status = %s)".formatted(status.name()))))); return; } declarationsByModule.forEach((moduleName, moduleDeclarations) -> { out.println(h3("Module " + moduleName)); out.println(); moduleDeclarations.stream() // .collect(groupingBy(Declaration::packageName, TreeMap::new, toList())) // .forEach((packageName, packageDeclarations) -> { out.println(h4("Package " + packageName)); out.println(); printDeclarationTableHeader(out); packageDeclarations.forEach(it -> printDeclarationTableRow(it, out)); printDeclarationTableFooter(out); out.println(); }); }); } protected void printDeclarationSectionHeader(Set statuses, Status status, List declarations, PrintWriter out) { if (statuses.size() < 2) { // omit section header when only a single status is printed return; } out.println(h2("@API(%s)".formatted(status))); out.println(); out.println(paragraph( "Discovered %d %s declarations.".formatted(declarations.size(), code("@API(%s)".formatted(status))))); out.println(); } protected abstract String h1(String header); protected abstract String h2(String header); protected abstract String h3(String header); protected abstract String h4(String header); protected abstract String code(String element); protected abstract String italic(String element); protected String paragraph(String element) { return element; } protected abstract void printDeclarationTableHeader(PrintWriter out); protected abstract void printDeclarationTableRow(Declaration declaration, PrintWriter out); protected abstract void printDeclarationTableFooter(PrintWriter out); } ================================================ FILE: documentation/src/tools/java/org/junit/api/tools/ApiReport.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.api.tools; import java.util.List; import java.util.Map; import java.util.SortedSet; import io.github.classgraph.ClassInfo; import org.apiguardian.api.API.Status; /** * @since 1.0 */ record ApiReport(SortedSet types, Map> declarations) { } ================================================ FILE: documentation/src/tools/java/org/junit/api/tools/ApiReportGenerator.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.api.tools; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.stream.Collectors.toCollection; import static java.util.stream.Collectors.toUnmodifiableSet; import java.io.BufferedOutputStream; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import java.io.UncheckedIOException; import java.lang.module.ModuleFinder; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.EnumMap; import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; import java.util.stream.Stream; import io.github.classgraph.ClassGraph; import io.github.classgraph.ClassInfo; import io.github.classgraph.MethodInfo; import io.github.classgraph.ScanResult; import org.apiguardian.api.API; import org.apiguardian.api.API.Status; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; /** * @since 1.0 */ class ApiReportGenerator { private static final Logger logger = LoggerFactory.getLogger(ApiReportGenerator.class); private static final String EOL = System.lineSeparator(); public static void main(String... args) { // CAUTION: The output produced by this method is used to // generate a table in the User Guide. try (var scanResult = scanClasspath()) { var apiReport = generateReport(scanResult); // ApiReportWriter reportWriter = new MarkdownApiReportWriter(apiReport); ApiReportWriter reportWriter = new AsciidocApiReportWriter(apiReport); // ApiReportWriter reportWriter = new HtmlApiReportWriter(apiReport); // reportWriter.printReportHeader(new PrintWriter(System.out, true)); // Print report for all Usage enum constants // reportWriter.printDeclarationInfo(new PrintWriter(System.out, true), EnumSet.allOf(Status.class)); // Print report only for specific Status constants, defaults to only EXPERIMENTAL parseArgs(args).forEach((status, opener) -> { try (var stream = opener.openStream()) { var writer = new PrintWriter(stream == null ? System.out : stream, true, UTF_8); reportWriter.printDeclarationInfo(writer, EnumSet.of(status)); } catch (IOException e) { throw new UncheckedIOException("Failed to write report", e); } }); } } // ------------------------------------------------------------------------- private static Map parseArgs(String[] args) { Map outputByStatus = new EnumMap<>(Status.class); if (args.length == 0) { outputByStatus.put(Status.EXPERIMENTAL, () -> null); } else { Arrays.stream(args) // .map(arg -> arg.split("=", 2)) // .forEach(parts -> outputByStatus.put(// Status.valueOf(parts[0]), // () -> parts.length < 2 // ? null // : new BufferedOutputStream(Files.newOutputStream(Path.of(parts[1]))) // )); } return outputByStatus; } private interface StreamOpener { OutputStream openStream() throws IOException; } private static ApiReport generateReport(ScanResult scanResult) { Map> declarations = new EnumMap<>(Status.class); for (var status : Status.values()) { declarations.put(status, new ArrayList<>()); } var types = collectTypes(scanResult); types.stream() // .map(Declaration.Type::new) // .forEach(type -> declarations.get(type.status()).add(type)); collectMethods(scanResult) // .map(Declaration.Method::new) // .filter(method -> !declarations.get(method.status()) // .contains(new Declaration.Type(method.classInfo()))) // .forEach(method -> { types.add(method.classInfo()); declarations.get(method.status()).add(method); }); declarations.values().forEach(list -> list.sort(null)); return new ApiReport(types, declarations); } private static ScanResult scanClasspath() { // scan all types below "org.junit" package var classGraph = new ClassGraph() // .acceptPackages("org.junit") // .rejectPackages("*.shadow.*", "org.opentest4j.*", "org.junit.platform.commons.logging", "org.junit.platform.commons.util") // .disableNestedJarScanning() // .enableClassInfo() // .enableMethodInfo() // .enableAnnotationInfo(); // var apiClasspath = System.getProperty("api.modulePath"); var apiModules = System.getProperty("api.moduleNames"); if (apiClasspath != null && apiModules != null) { var paths = Arrays.stream(apiClasspath.split(File.pathSeparator)).map(Path::of).toArray(Path[]::new); var bootLayer = ModuleLayer.boot(); var roots = Arrays.stream(apiModules.split(",")).collect(toUnmodifiableSet()); var configuration = bootLayer.configuration().resolveAndBind(ModuleFinder.of(), ModuleFinder.of(paths), roots); var layer = bootLayer.defineModulesWithOneLoader(configuration, ClassLoader.getPlatformClassLoader()); classGraph = classGraph.overrideModuleLayers(layer); } return classGraph.scan(); } private static SortedSet collectTypes(ScanResult scanResult) { var types = scanResult.getClassesWithAnnotation(API.class).stream() // .filter(it -> !it.getAnnotationInfo(API.class).isInherited()) // .collect(toCollection(TreeSet::new)); logger.debug(() -> { var builder = new StringBuilder("Listing of all " + types.size() + " annotated types:"); builder.append(EOL); types.forEach(e -> builder.append(e.getName()).append(EOL)); return builder.toString(); }); return types; } private static Stream collectMethods(ScanResult scanResult) { return scanResult.getClassesWithMethodAnnotation(API.class).stream() // .flatMap(type -> type.getDeclaredMethodAndConstructorInfo().stream()) // .filter(m -> m.getAnnotationInfo(API.class) != null); } private ApiReportGenerator() { } } ================================================ FILE: documentation/src/tools/java/org/junit/api/tools/ApiReportWriter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.api.tools; import java.io.PrintWriter; import java.util.Set; import org.apiguardian.api.API.Status; /** * @since 1.0 */ interface ApiReportWriter { void printReportHeader(PrintWriter out); void printDeclarationInfo(PrintWriter out, Set statuses); } ================================================ FILE: documentation/src/tools/java/org/junit/api/tools/AsciidocApiReportWriter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.api.tools; import java.io.PrintWriter; /** * @since 1.0 */ class AsciidocApiReportWriter extends AbstractApiReportWriter { private static final String ASCIIDOC_FORMAT = "|%-" + NAME_COLUMN_WIDTH + "s | %-12s%n"; AsciidocApiReportWriter(ApiReport apiReport) { super(apiReport); } @Override protected String h1(String header) { return "= " + header; } @Override protected String h2(String header) { return "== " + header; } @Override protected String h3(String header) { return "%n=== %s".formatted(header); } @Override protected String h4(String header) { return "%n==== %s".formatted(header); } @Override protected String code(String element) { return "`" + element + "`"; } @Override protected String italic(String element) { return "_" + element + "_"; } @Override protected void printDeclarationTableHeader(PrintWriter out) { out.println("[cols=\"99,1\"]"); out.println("|==="); out.printf(ASCIIDOC_FORMAT, "Name", "Since"); out.println(); } @Override protected void printDeclarationTableRow(Declaration declaration, PrintWriter out) { out.printf(ASCIIDOC_FORMAT, // code(declaration.name().replace(".", ".​")) + " " + italic("(" + declaration.kind() + ")"), // code(declaration.since()) // ); } @Override protected void printDeclarationTableFooter(PrintWriter out) { out.println("|==="); } } ================================================ FILE: documentation/src/tools/java/org/junit/api/tools/Declaration.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.api.tools; import static java.util.stream.Collectors.joining; import java.util.Arrays; import io.github.classgraph.AnnotationEnumValue; import io.github.classgraph.AnnotationParameterValueList; import io.github.classgraph.ClassInfo; import io.github.classgraph.MethodInfo; import org.apiguardian.api.API; import org.apiguardian.api.API.Status; sealed interface Declaration extends Comparable { String moduleName(); String packageName(); String fullName(); String name(); String kind(); Status status(); String since(); @Override default int compareTo(Declaration o) { return fullName().compareTo(o.fullName()); } record Type(ClassInfo classInfo) implements Declaration { @Override public String moduleName() { return classInfo.getModuleRef().getName(); } @Override public String packageName() { return classInfo.getPackageName(); } @Override public String fullName() { return classInfo.getName(); } @Override public String name() { var shortClassName = getShortClassName(classInfo); return classInfo.isAnnotation() ? "@" + shortClassName : shortClassName; } @Override public String kind() { return switch (classInfo) { case ClassInfo ignored when classInfo.isRecord() -> "record"; case ClassInfo ignored when classInfo.isAnnotation() -> "annotation"; case ClassInfo ignored when classInfo.isEnum() -> "enum"; case ClassInfo ignored when classInfo.isInterface() -> "interface"; default -> "class"; }; } @Override public Status status() { return readStatus(getParameterValues()); } @Override public String since() { return readSince(getParameterValues()); } private AnnotationParameterValueList getParameterValues() { return classInfo.getAnnotationInfo(API.class).getParameterValues(); } } record Method(MethodInfo methodInfo) implements Declaration { @Override public String moduleName() { return classInfo().getModuleRef().getName(); } @Override public String packageName() { return classInfo().getPackageName(); } @Override public String fullName() { return "%s.%s".formatted(classInfo().getName(), methodSignature()); } @Override public String name() { if (classInfo().isAnnotation()) { return "@%s(%s=...)".formatted(getShortClassName(classInfo()), methodInfo.getName()); } if (methodInfo.isConstructor()) { return "%s%s".formatted(getShortClassName(classInfo()), methodParameters()); } return "%s.%s".formatted(getShortClassName(classInfo()), methodSignature()); } private String methodSignature() { return methodInfo.getName() + methodParameters(); } private String methodParameters() { return Arrays.stream(methodInfo.getParameterInfo()) // .map(parameterInfo -> parameterInfo.getTypeSignatureOrTypeDescriptor().toStringWithSimpleNames()) // .collect(joining(", ", "(", ")")); } @Override public String kind() { if (methodInfo.isConstructor()) { return "constructor"; } if (classInfo().isAnnotation()) { return "annotation attribute"; } return "method"; } @Override public Status status() { return readStatus(getParameterValues()); } @Override public String since() { return readSince(getParameterValues()); } private AnnotationParameterValueList getParameterValues() { return methodInfo.getAnnotationInfo(API.class).getParameterValues(); } public ClassInfo classInfo() { return methodInfo.getClassInfo(); } } private static Status readStatus(AnnotationParameterValueList parameterValues) { return Status.valueOf(((AnnotationEnumValue) parameterValues.getValue("status")).getValueName()); } private static String readSince(AnnotationParameterValueList parameterValues) { return (String) parameterValues.getValue("since"); } private static String getShortClassName(ClassInfo classInfo) { var typeName = classInfo.getName(); var packageName = classInfo.getPackageName(); if (typeName.startsWith(packageName + '.')) { typeName = typeName.substring(packageName.length() + 1); } return typeName.replace('$', '.'); } } ================================================ FILE: documentation/src/tools/java/org/junit/api/tools/HtmlApiReportWriter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.api.tools; import java.io.PrintWriter; /** * @since 1.0 */ class HtmlApiReportWriter extends AbstractApiReportWriter { private static final String HTML_HEADER_FORMAT = "\t%s%s%n"; private static final String HTML_ROW_FORMAT = "\t%s%s%n"; HtmlApiReportWriter(ApiReport apiReport) { super(apiReport); } @Override protected String h1(String header) { return "

" + header + "

"; } @Override protected String h2(String header) { return "

" + header + "

"; } @Override protected String h3(String header) { return "

" + header + "

"; } @Override protected String h4(String header) { return "

" + header + "

"; } @Override protected String code(String element) { return "" + element + ""; } @Override protected String italic(String element) { return "" + element + ""; } @Override protected String paragraph(String element) { return "

" + element + "

"; } @Override protected void printDeclarationTableHeader(PrintWriter out) { out.println(""); out.printf(HTML_HEADER_FORMAT, "Name", "Since"); } @Override protected void printDeclarationTableRow(Declaration declaration, PrintWriter out) { out.printf(HTML_ROW_FORMAT, // code(declaration.name()) + " " + italic("(" + declaration.kind() + ")"), // code(declaration.since()) // ); } @Override protected void printDeclarationTableFooter(PrintWriter out) { out.println("
"); } } ================================================ FILE: documentation/src/tools/java/org/junit/api/tools/MarkdownApiReportWriter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.api.tools; import java.io.PrintWriter; import java.nio.CharBuffer; /** * @since 1.0 */ class MarkdownApiReportWriter extends AbstractApiReportWriter { private static final String MARKDOWN_FORMAT = "%-" + NAME_COLUMN_WIDTH + "s | %-12s%n"; MarkdownApiReportWriter(ApiReport apiReport) { super(apiReport); } @Override protected String h1(String header) { return "# " + header; } @Override protected String h2(String header) { return "## " + header; } @Override protected String h3(String header) { return "### " + header; } @Override protected String h4(String header) { return "#### " + header; } @Override protected String code(String element) { return "`" + element + "`"; } @Override protected String italic(String element) { return "_" + element + "_"; } @Override protected void printDeclarationTableHeader(PrintWriter out) { out.printf(MARKDOWN_FORMAT, "Name", "Since"); out.printf(MARKDOWN_FORMAT, dashes(NAME_COLUMN_WIDTH), dashes(12)); } private String dashes(int length) { return CharBuffer.allocate(length).toString().replace('\0', '-'); } @Override protected void printDeclarationTableRow(Declaration declaration, PrintWriter out) { out.printf(MARKDOWN_FORMAT, // code(declaration.name()) + " " + italic("(" + declaration.kind() + ")"), // code(declaration.since()) // ); } @Override protected void printDeclarationTableFooter(PrintWriter out) { /* no-op */ } } ================================================ FILE: documentation/src/tools/java/org/junit/api/tools/package-info.java ================================================ /** * Tools to generate reports based on {@link org.apiguardian.api.API} annotations. */ package org.junit.api.tools; ================================================ FILE: gradle/base/code-generator-model/.gitignore ================================================ /bin/ ================================================ FILE: gradle/base/code-generator-model/build.gradle.kts ================================================ plugins { `kotlin-dsl` } ================================================ FILE: gradle/base/code-generator-model/src/main/kotlin/junitbuild/generator/model/JRE.kt ================================================ package junitbuild.generator.model data class JRE(val version: Int, val since: String?) ================================================ FILE: gradle/base/code-generator-model/src/main/resources/jre.yaml ================================================ - version: 8 - version: 9 - version: 10 - version: 11 - version: 12 since: '5.4' - version: 13 since: '5.4' - version: 14 since: '5.5' - version: 15 since: '5.6' - version: 16 since: '5.7' - version: 17 since: '5.7.1' - version: 18 since: '5.8.1' - version: 19 since: '5.9' - version: 20 since: '5.9' - version: 21 since: '5.9.2' - version: 22 since: '5.10' - version: 23 since: '5.11' - version: 24 since: '5.11' - version: 25 since: '5.11.4' - version: 26 since: '5.13.2' - version: 27 since: '6.1' ================================================ FILE: gradle/base/dsl-extensions/.gitignore ================================================ /bin/ ================================================ FILE: gradle/base/dsl-extensions/build.gradle.kts ================================================ plugins { `kotlin-dsl` } ================================================ FILE: gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/DependencyExtensions.kt ================================================ package junitbuild.extensions import org.gradle.api.provider.Provider import org.gradle.plugin.use.PluginDependency // see https://docs.gradle.org/current/userguide/plugins.html#sec:plugin_markers val Provider.markerCoordinates: Provider get() = map { "${it.pluginId}:${it.pluginId}.gradle.plugin:${it.version}" } ================================================ FILE: gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/ProjectExtensions.kt ================================================ package junitbuild.extensions import org.gradle.api.Project import org.gradle.api.artifacts.ProjectDependency import org.gradle.api.artifacts.VersionCatalog import org.gradle.api.artifacts.VersionCatalogsExtension import org.gradle.kotlin.dsl.the val Project.javaModuleName: String get() = toModuleName(name) val ProjectDependency.javaModuleName: String get() = toModuleName(name) private fun toModuleName(name: String) = "org.${name.replace('-', '.')}" fun Project.dependencyProject(dependency: ProjectDependency) = project(dependency.path) fun Project.requiredVersionFromLibs(name: String) = libsVersionCatalog.findVersion(name).get().requiredVersion fun Project.dependencyFromLibs(name: String) = libsVersionCatalog.findLibrary(name).get() fun Project.bundleFromLibs(name: String) = libsVersionCatalog.findBundle(name).get() private val Project.libsVersionCatalog: VersionCatalog get() = the().named("libs") ================================================ FILE: gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/StringExtensions.kt ================================================ package junitbuild.extensions import java.util.Locale fun String.capitalized() = replaceFirstChar { it.uppercase(Locale.US) } ================================================ FILE: gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/TaskExtensions.kt ================================================ package junitbuild.extensions import org.gradle.api.Task import org.gradle.api.file.ArchiveOperations import org.gradle.internal.os.OperatingSystem import org.gradle.kotlin.dsl.newInstance import javax.inject.Inject fun Task.trackOperationSystemAsInput() = inputs.property("os", OperatingSystem.current().familyName) fun Task.withArchiveOperations(action: (ArchiveOperations) -> T): T = archiveOperations.run { action(this) } private val Task.archiveOperations: ArchiveOperations get() = project.objects.newInstance(DummyObject::class).archiveOperations private abstract class DummyObject { @get:Inject abstract val archiveOperations: ArchiveOperations } ================================================ FILE: gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/VersionExtensions.kt ================================================ package junitbuild.extensions fun Any.isSnapshot(): Boolean = toString().contains("SNAPSHOT") ================================================ FILE: gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/junitbuild.dsl-extensions.gradle.kts ================================================ // Just a dummy plugin to get the extensions on the classpath of downstream builds ================================================ FILE: gradle/base/gradle.properties ================================================ group = junitbuild.base ================================================ FILE: gradle/base/settings.gradle.kts ================================================ rootProject.name = "base" dependencyResolutionManagement { repositories { gradlePluginPortal() } } include("code-generator-model") include("dsl-extensions") ================================================ FILE: gradle/config/checkstyle/checkstyleMain.xml ================================================ ================================================ FILE: gradle/config/checkstyle/checkstyleNohttp.xml ================================================ ================================================ FILE: gradle/config/checkstyle/checkstyleTest.xml ================================================ ================================================ FILE: gradle/config/checkstyle/suppressions.xml ================================================ ================================================ FILE: gradle/config/eclipse/junit-eclipse-formatter-settings.xml ================================================ ================================================ FILE: gradle/config/eclipse/junit-eclipse.importorder ================================================ #Organize Import Order 0=java 1=javax 2=jdk 3=aQute 4=junit 5=de 6=com 7=example 8=extensions 9=io 10=org ================================================ FILE: gradle/config/roseau/config.yaml ================================================ common: excludes: annotations: - name: org.apiguardian.api.API args: { status: org.apiguardian.api.API$Status.INTERNAL } ================================================ FILE: gradle/config/spotless/eclipse-public-license-2.0.java ================================================ /* * Copyright $YEAR the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ ================================================ FILE: gradle/gradle-daemon-jvm.properties ================================================ #This file is generated by updateDaemonJvm toolchainVersion=25 ================================================ FILE: gradle/libs.versions.toml ================================================ [versions] ant = "1.10.17" apiguardian = "1.1.2" assertj = "3.27.7" bnd = "7.2.3" checkstyle = "13.4.2" eclipse = "4.39.0" jackson = "3.1.3" jacoco = "0.8.14" jmh = "1.37" junit4 = "4.13.2" junit4Min = "4.12" ktlint = "1.8.0" log4j = "2.26.0" opentest4j = "1.3.0" openTestReporting = "0.2.5" snapshotTests = "1.11.0" surefire = "3.5.5" xmlunit = "2.11.0" [libraries] ant = { module = "org.apache.ant:ant", version.ref = "ant" } ant-junit = { module = "org.apache.ant:ant-junit", version.ref = "ant" } ant-junitlauncher = { module = "org.apache.ant:ant-junitlauncher", version.ref = "ant" } apiguardian = { module = "org.apiguardian:apiguardian-api", version.ref = "apiguardian" } archunit = { module = "com.tngtech.archunit:archunit-junit5", version = "1.4.2" } assertj = { module = "org.assertj:assertj-core", version.ref = "assertj" } bndlib = { module = "biz.aQute.bnd:biz.aQute.bndlib", version.ref = "bnd" } byteBuddy = { module = "net.bytebuddy:byte-buddy", version = "1.18.8" } checkstyle = { module = "com.puppycrawl.tools:checkstyle", version.ref = "checkstyle" } classgraph = { module = "io.github.classgraph:classgraph", version = "4.8.184" } commons-io = { module = "commons-io:commons-io", version = "2.22.0" } error-prone-contrib = { module = "tech.picnic.error-prone-support:error-prone-contrib", version = "0.29.0" } error-prone-core = { module = "com.google.errorprone:error_prone_core", version = "2.49.0" } fastcsv = { module = "de.siegmar:fastcsv", version = "4.2.0" } groovy = { module = "org.apache.groovy:groovy", version = "5.0.5" } groovy2-bom = { module = "org.codehaus.groovy:groovy-bom", version = "2.5.23" } hamcrest = { module = "org.hamcrest:hamcrest", version = "3.0" } jackson-dataformat-yaml = { module = "tools.jackson.dataformat:jackson-dataformat-yaml", version.ref = "jackson" } jackson-module-kotlin = { module = "tools.jackson.module:jackson-module-kotlin", version.ref = "jackson" } jaxb-api = { module = "jakarta.xml.bind:jakarta.xml.bind-api", version = "4.0.5" } jaxb-runtime = { module = "org.glassfish.jaxb:jaxb-runtime", version = "4.0.8" } jfrunit = { module = "org.moditect.jfrunit:jfrunit-core", version = "1.0.0.Alpha2" } jimfs = { module = "com.google.jimfs:jimfs", version = "1.3.1" } jmh-core = { module = "org.openjdk.jmh:jmh-core", version.ref = "jmh" } jmh-generator-annprocess = { module = "org.openjdk.jmh:jmh-generator-annprocess", version.ref = "jmh" } joox = { module = "org.jooq:joox", version = "2.0.1" } jspecify = { module = "org.jspecify:jspecify", version = "1.0.0" } jte = { module = "gg.jte:jte", version = "3.2.4" } junit4 = { module = "junit:junit", version = { require = "[4.12,)", prefer = "4.13.2" } } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.11.0" } kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version = "1.11.0" } log4j-bom = { module = "org.apache.logging.log4j:log4j-bom", version.ref = "log4j" } log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" } log4j-jul = { module = "org.apache.logging.log4j:log4j-jul", version.ref = "log4j" } maven = { module = "org.apache.maven:apache-maven", version = "3.9.15" } mavenSurefirePlugin = { module = "org.apache.maven.plugins:maven-surefire-plugin", version.ref = "surefire" } memoryfilesystem = { module = "com.github.marschall:memoryfilesystem", version = "2.8.2" } mockito-bom = { module = "org.mockito:mockito-bom", version = "5.23.0" } mockito-core = { module = "org.mockito:mockito-core" } mockito-junit-jupiter = { module = "org.mockito:mockito-junit-jupiter" } nohttp-checkstyle = { module = "io.spring.nohttp:nohttp-checkstyle", version = "0.0.11" } nullaway = { module = "com.uber.nullaway:nullaway", version = "0.13.4" } opentest4j = { module = "org.opentest4j:opentest4j", version.ref = "opentest4j" } openTestReporting-cli = { module = "org.opentest4j.reporting:open-test-reporting-cli", version.ref = "openTestReporting" } openTestReporting-events = { module = "org.opentest4j.reporting:open-test-reporting-events", version.ref = "openTestReporting" } openTestReporting-tooling-core = { module = "org.opentest4j.reporting:open-test-reporting-tooling-core", version.ref = "openTestReporting" } openTestReporting-tooling-spi = { module = "org.opentest4j.reporting:open-test-reporting-tooling-spi", version.ref = "openTestReporting" } picocli = { module = "info.picocli:picocli", version = "4.7.7" } roseau-cli = { module = "io.github.alien-tools:roseau-cli", version = "0.5.0" } slf4j-julBinding = { module = "org.slf4j:slf4j-jdk14", version = "2.0.17" } snapshotTests-junit5 = { module = "de.skuzzle.test:snapshot-tests-junit5", version.ref = "snapshotTests" } snapshotTests-xml = { module = "de.skuzzle.test:snapshot-tests-xml", version.ref = "snapshotTests" } spock1 = { module = "org.spockframework:spock-core", version = "1.3-groovy-2.5" } xmlunit-assertj = { module = "org.xmlunit:xmlunit-assertj3", version.ref = "xmlunit" } xmlunit-placeholders = { module = "org.xmlunit:xmlunit-placeholders", version.ref = "xmlunit" } xmlunit-jakarta-jaxb-impl = { module = "org.xmlunit:xmlunit-jakarta-jaxb-impl", version.ref = "xmlunit" } testingAnnotations = { module = "com.gradle:develocity-testing-annotations", version = "2.0.1" } woodstox = { module = "com.fasterxml.woodstox:woodstox-core", version = "7.1.1" } # Only declared here so Dependabot knows when to update the referenced versions eclipse-platform = { module = "org.eclipse.platform:org.eclipse.platform", version.ref = "eclipse" } jacoco = { module = "org.jacoco:jacoco", version.ref = "jacoco" } junit4-latest = { module = "junit:junit", version.ref = "junit4" } junit4-bundle = { module = "org.apache.servicemix.bundles:org.apache.servicemix.bundles.junit", version = "4.13.2_1" } ktlint-cli = { module = "com.pinterest.ktlint:ktlint-cli", version.ref = "ktlint" } [bundles] ant = ["ant", "ant-junit", "ant-junitlauncher"] log4j = ["log4j-core", "log4j-jul"] xmlunit = ["xmlunit-assertj", "xmlunit-placeholders", "xmlunit-jakarta-jaxb-impl", "jaxb-api", "jaxb-runtime"] [plugins] bnd = { id = "biz.aQute.bnd", version.ref = "bnd" } buildParameters = { id = "org.gradlex.build-parameters", version = "1.4.5" } commonCustomUserData = { id = "com.gradle.common-custom-user-data-gradle-plugin", version = "2.6.0" } develocity = { id = "com.gradle.develocity", version = "4.4.1" } download = { id = "de.undercouch.download", version = "5.7.0" } errorProne = { id = "net.ltgt.errorprone", version = "5.1.0" } foojayResolver = { id = "org.gradle.toolchains.foojay-resolver", version = "1.0.0" } jmh = { id = "me.champeau.jmh", version = "0.7.3" } # check if workaround in gradle.properties can be removed when updating kotlin = { id = "org.jetbrains.kotlin.jvm", version = "2.3.21" } nmcp-settings = { id = "com.gradleup.nmcp.settings", version = "1.5.0" } node = { id = "com.github.node-gradle.node", version = "7.1.0" } nullaway = { id = "net.ltgt.nullaway", version = "3.0.0" } plantuml = { id = "io.freefair.plantuml", version = "9.5.0" } shadow = { id = "com.gradleup.shadow", version = "9.4.1" } spotless = { id = "com.diffplug.spotless", version = "8.4.0" } spring-antora = { id = "io.spring.antora.generate-antora-yml", version = "0.0.1" } ================================================ FILE: gradle/plugins/antora/build.gradle.kts ================================================ import junitbuild.extensions.markerCoordinates plugins { `kotlin-dsl` } dependencies { implementation(projects.buildParameters) implementation(libs.plugins.node.markerCoordinates) constraints { implementation("com.fasterxml.jackson.core:jackson-core") { version { require("2.21.1") } because("Workaround for GHSA-72hv-8253-57qq") } } implementation(libs.plugins.spring.antora.markerCoordinates) constraints { implementation("org.yaml:snakeyaml") { version { require("2.0") } because("Workaround for CVE-2022-1471") } } } ================================================ FILE: gradle/plugins/antora/src/main/kotlin/junitbuild/antora/AntoraConfiguration.kt ================================================ package junitbuild.antora import org.gradle.api.file.DirectoryProperty interface AntoraConfiguration { val siteDir: DirectoryProperty } ================================================ FILE: gradle/plugins/antora/src/main/kotlin/junitbuild.antora-conventions.gradle.kts ================================================ import com.github.gradle.node.npm.task.NpxTask import junitbuild.antora.AntoraConfiguration plugins { id("com.github.node-gradle.node") id("io.spring.antora.generate-antora-yml") id("junitbuild.build-parameters") } val configuration = extensions.create("antora") val siteDir = layout.buildDirectory.dir("antora-site") configuration.siteDir.value(siteDir).finalizeValue() repositories { // Redefined here because the Node.js plugin adds a repo mavenCentral() } tasks.register("generateAntoraResources") { dependsOn("generateAntoraYml") } val generateAntoraPlaybook by tasks.registering(Copy::class) { val gitRepoRoot = providers.exec { commandLine("git", "worktree", "list", "--porcelain", "-z") }.standardOutput.asText.map { it.substringBefore('\u0000').substringAfter(' ') } inputs.property("gitRepoRoot", gitRepoRoot) val gitBranchName = providers.exec { commandLine("git", "rev-parse", "--abbrev-ref", "HEAD") }.standardOutput.asText.map { it.trim() } inputs.property("gitBranchName", gitBranchName) from(layout.projectDirectory.file("antora-playbook.yml").asFile) filter { line -> var result = line if (line.contains("@GIT_REPO_ROOT@")) { result = result.replace("@GIT_REPO_ROOT@", gitRepoRoot.get()) } if (line.contains("@GIT_BRANCH_NAME@")) { result = result.replace("@GIT_BRANCH_NAME@", gitBranchName.get()) } return@filter result } into(layout.buildDirectory.dir("antora-playbook")) } node { download = buildParameters.antora.downloadNode version = providers.fileContents(layout.projectDirectory.file(".tool-versions")).asText.map { it.substringAfter("nodejs").trim() } } tasks.npmInstall { args.addAll("--no-audit", "--no-package-lock", "--no-fund") } tasks.register("antora") { dependsOn(tasks.npmInstall) description = "Runs Antora to generate a documentation site described by the playbook file." command = "antora" args.addAll("--clean", "--stacktrace", "--fetch", "--log-format=pretty", "--log-level=all") args.add("--to-dir") args.add(siteDir.map { it.asFile.toRelativeString(layout.projectDirectory.asFile) }) outputs.dir(siteDir) outputs.upToDateWhen { false } // not all inputs are tracked val playbook = generateAntoraPlaybook.map { it.rootSpec.destinationDir!!.resolve("antora-playbook.yml") } args.add(playbook.map { it.toRelativeString(layout.projectDirectory.asFile) }) inputs.file(playbook) execOverrides { environment["IS_TTY"] = true } } ================================================ FILE: gradle/plugins/backward-compatibility/build.gradle.kts ================================================ import junitbuild.extensions.markerCoordinates plugins { `kotlin-dsl` } dependencies { implementation("junitbuild.base:dsl-extensions") implementation(libs.plugins.download.markerCoordinates) implementation(libs.jackson.dataformat.yaml) } ================================================ FILE: gradle/plugins/backward-compatibility/src/main/kotlin/junitbuild/compatibility/BackwardCompatibilityChecksExtension.kt ================================================ package junitbuild.compatibility import org.gradle.api.provider.Property abstract class BackwardCompatibilityChecksExtension { abstract val enabled: Property abstract val previousVersion: Property } ================================================ FILE: gradle/plugins/backward-compatibility/src/main/kotlin/junitbuild/compatibility/roseau/RoseauDiff.kt ================================================ package junitbuild.compatibility.roseau import org.gradle.api.DefaultTask import org.gradle.api.GradleException import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFileProperty import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.Classpath import org.gradle.api.tasks.CompileClasspath import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.Optional import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.PathSensitive import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.TaskAction import org.gradle.kotlin.dsl.assign import org.gradle.process.ExecOperations import tools.jackson.databind.ObjectMapper import tools.jackson.dataformat.yaml.YAMLFactory import tools.jackson.dataformat.yaml.YAMLWriteFeature.WRITE_DOC_START_MARKER import java.io.ByteArrayOutputStream import java.io.File import java.io.FileOutputStream import javax.inject.Inject @CacheableTask abstract class RoseauDiff : DefaultTask() { @get:Inject abstract val execOperations: ExecOperations @get:Classpath abstract val toolClasspath: ConfigurableFileCollection @get:CompileClasspath abstract val libraryClasspath: ConfigurableFileCollection @get:CompileClasspath abstract val v1: RegularFileProperty @get:CompileClasspath abstract val v2: RegularFileProperty @get:InputFile @get:PathSensitive(PathSensitivity.NONE) abstract val configFile: RegularFileProperty @get:InputFile @get:PathSensitive(PathSensitivity.NONE) @get:Optional abstract val acceptedChangesCsvFile: RegularFileProperty @get:OutputDirectory abstract val reportDir: DirectoryProperty @TaskAction fun run() { val reportDir = reportDir.get().asFile.absoluteFile val reports = listOf( Report(reportDir.resolve("breaking-changes.html"), Report.Format.HTML), Report(reportDir.resolve("breaking-changes.csv"), Report.Format.CSV) ) reports.forEach { report -> report.file.delete() } val effectiveConfigFile = writeEffectiveConfigFile(reports) val output = ByteArrayOutputStream() val result = execOperations.javaexec { mainClass = "io.github.alien.roseau.cli.RoseauCLI" classpath = toolClasspath args( "--classpath", libraryClasspath.asPath, "--v1", v1.get().asFile.absolutePath, "--v2", v2.get().asFile.absolutePath, "--diff", "--fail-on-bc", "--config", effectiveConfigFile.absolutePath, "-vv", ) if (acceptedChangesCsvFile.isPresent) { args("--ignored", acceptedChangesCsvFile.get().asFile.absolutePath) } standardOutput = output errorOutput = output isIgnoreExitValue = true } if (result.exitValue != 0) { System.out.write(output.toByteArray()) System.out.flush() if (result.exitValue == 1) { throw GradleException("Breaking API changes detected") } result.assertNormalExitValue() } } private fun writeEffectiveConfigFile(reports: List): File { val effectiveConfigFile = temporaryDir.resolve("roseau.yaml") configFile.get().asFile.copyTo(effectiveConfigFile, overwrite = true) FileOutputStream(effectiveConfigFile, true).bufferedWriter().use { writer -> val yamlFactory = YAMLFactory.builder().disable(WRITE_DOC_START_MARKER).build() val mapper = ObjectMapper(yamlFactory) mapper.writeValue( writer, mapOf( "reports" to reports ) ) } return effectiveConfigFile } private data class Report(val file: File, val format: Format) { enum class Format { HTML, CSV } } } ================================================ FILE: gradle/plugins/backward-compatibility/src/main/kotlin/junitbuild.backward-compatibility.gradle.kts ================================================ import de.undercouch.gradle.tasks.download.Download import junitbuild.compatibility.BackwardCompatibilityChecksExtension import junitbuild.compatibility.roseau.RoseauDiff import junitbuild.extensions.dependencyFromLibs plugins { java id("de.undercouch.download") } val roseauDependencies = configurations.dependencyScope("roseau") val roseauClasspath = configurations.resolvable("roseauClasspath") { extendsFrom(roseauDependencies.get()) } dependencies { roseauDependencies(dependencyFromLibs("roseau-cli")) roseauDependencies(platform(dependencyFromLibs("log4j-bom"))) { because("Workaround for CVE-2025-68161") } constraints { roseauDependencies("org.apache.commons:commons-lang3") { version { require("3.18.0") } because("Workaround for CVE-2025-48924") } roseauDependencies("com.fasterxml.jackson.core:jackson-core") { version { require("2.21.1") } because("Workaround for GHSA-72hv-8253-57qq") } } } val extension = extensions.create("backwardCompatibilityChecks").apply { enabled.convention(true) previousVersion.apply { convention(providers.gradleProperty("apiBaselineVersion")) finalizeValueOnRead() } } val downloadPreviousReleaseJar by tasks.registering(Download::class) { if (gradle.startParameter.isOffline) { enabled = false } onlyIf { extension.enabled.get() } val previousVersion = extension.previousVersion.get() src("https://repo1.maven.org/maven2/${project.group.toString().replace(".", "/")}/${project.name}/$previousVersion/${project.name}-$previousVersion.jar") dest(layout.buildDirectory.dir("previousRelease")) overwrite(false) quiet(true) retries(2) outputs.cacheIf { true } } val roseauCsvFile = layout.buildDirectory.file("reports/roseau/breaking-changes.csv") val roseau by tasks.registering(RoseauDiff::class) { if (gradle.startParameter.isOffline) { enabled = false } onlyIf { extension.enabled.get() } toolClasspath.from(roseauClasspath) libraryClasspath.from(configurations.compileClasspath) v1 = downloadPreviousReleaseJar.map { it.outputFiles.single() } v2 = tasks.jar.flatMap { it.archiveFile }.map { it.asFile } configFile = rootProject.layout.projectDirectory.file("gradle/config/roseau/config.yaml") rootProject.layout.projectDirectory.file("gradle/config/roseau/accepted-breaking-changes.csv").asFile.let { if (it.exists()) { acceptedChangesCsvFile = it } } reportDir = layout.buildDirectory.dir("reports/roseau") } val checkBackwardCompatibility by tasks.registering { dependsOn(roseau) } tasks.check { dependsOn(checkBackwardCompatibility) } ================================================ FILE: gradle/plugins/build-parameters/build.gradle.kts ================================================ plugins { alias(libs.plugins.buildParameters) } group = "junitbuild" buildParameters { pluginId("junitbuild.build-parameters") bool("ci") { description = "Whether or not this build is running in a CI environment" defaultValue = false fromEnvironment() } group("javaToolchain") { description = "Parameters controlling the Java toolchain used for compiling code and running tests" integer("version") { description = "JDK version" } string("implementation") { description = "JDK implementation (for example, 'j9')" } } group("documentation") { description = "Parameters controlling how the documentation is built" bool("replaceCurrentDocs") { description = "The documentation that is being deployed will replace what's currently deployed as 'current'" defaultValue = false } } group("junit") { group("develocity") { description = "Parameters controlling Develocity features" group("buildCache") { string("server") { description = "Remote build cache server address (protocol and hostname), e.g. https://eu-build-cache-ge.junit.org" } bool("pushEnabled") { description = "Whether to push to the remote build cache" defaultValue = false } } group("predictiveTestSelection") { bool("enabled") { description = "Whether or not to use Predictive Test Selection for selecting tests to execute" defaultValue = true } bool("selectRemainingTests") { // see https://docs.gradle.com/develocity/predictive-test-selection/#gradle-selection-mode description = "Whether or not to use PTS' 'remaining tests' selection mode" defaultValue = false } } group("testDistribution") { bool("enabled") { description = "Whether or not to use Test Distribution for executing tests" defaultValue = false fromEnvironment() } integer("maxLocalExecutors") { description = "How many local executors to use for executing tests" defaultValue = 1 } integer("maxRemoteExecutors") { description = "How many remote executors to request for executing tests" } } } } group("testing") { description = "Testing related parameters" bool("dryRun") { description = "Enables dry run mode for tests" defaultValue = false } bool("enableJaCoCo") { description = "Enables JaCoCo test coverage reporting" defaultValue = true } bool("enableJFR") { description = "Enables Java Flight Recorder functionality" defaultValue = false } integer("retries") { description = "Configures the number of times failing test are retried" } bool("hideOpenTestReportHtmlGeneratorOutput") { description = "Whether or not to hide the output of the OpenTestReportHtmlGenerator" defaultValue = true } } group("publishing") { bool("signArtifacts") { description = "Sign artifacts before publishing them to Maven repos" } string("group") { description = "Group ID for published Maven artifacts" } } group("jitpack") { string("version") { description = "The version computed by Jitpack" } } group("manifest") { string("buildTimestamp") { description = "Overrides the value of the 'Build-Date' and 'Build-Time' jar manifest entries. Can be set as a String (e.g. '2023-11-05 17:49:13.996+0100') or as seconds since the epoch." fromEnvironment("SOURCE_DATE_EPOCH") // see https://reproducible-builds.org/docs/source-date-epoch/ } string("builtBy") { description = "Overrides the value of the 'Built-By' jar manifest entry" } string("createdBy") { description = "Overrides the value of the 'Created-By' jar manifest entry" } } group("antora") { bool("downloadNode") { description = "Whether to download Node.js from the Gradle build" defaultValue = true } } } ================================================ FILE: gradle/plugins/code-generator/build.gradle.kts ================================================ plugins { `kotlin-dsl` } dependencies { implementation("junitbuild.base:code-generator-model") implementation("junitbuild.base:dsl-extensions") implementation(projects.common) implementation(libs.jackson.dataformat.yaml) implementation(libs.jackson.module.kotlin) implementation(libs.jte) } ================================================ FILE: gradle/plugins/code-generator/src/main/kotlin/junitbuild/generator/GenerateJreRelatedSourceCode.kt ================================================ package junitbuild.generator import gg.jte.ContentType import gg.jte.TemplateEngine import gg.jte.output.FileOutput import gg.jte.resolve.DirectoryCodeResolver import junitbuild.generator.model.JRE import org.gradle.api.DefaultTask import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.MapProperty import org.gradle.api.provider.Property import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputDirectory import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.Optional import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.PathSensitive import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.SkipWhenEmpty import org.gradle.api.tasks.TaskAction import tools.jackson.core.type.TypeReference import tools.jackson.dataformat.yaml.YAMLMapper import tools.jackson.module.kotlin.KotlinModule import java.time.Year @CacheableTask abstract class GenerateJreRelatedSourceCode : DefaultTask() { @get:InputDirectory @get:SkipWhenEmpty @get:PathSensitive(PathSensitivity.RELATIVE) abstract val templateDir: DirectoryProperty @get:OutputDirectory abstract val targetDir: DirectoryProperty @get:InputFile @get:PathSensitive(PathSensitivity.NONE) abstract val licenseHeaderFile: RegularFileProperty @get:Input abstract val licenseHeaderYear: Property @get:Input @get:Optional abstract val maxVersion: Property @get:Input @get:Optional abstract val fileNamePrefix: Property @get:Input abstract val additionalTemplateParameters: MapProperty @TaskAction fun generateSourceCode() { val mainTargetDir = targetDir.get().asFile mainTargetDir.deleteRecursively() val templateDir = templateDir.get().asFile val codeResolver = DirectoryCodeResolver(templateDir.toPath()) val templateEngine = TemplateEngine.create(codeResolver, temporaryDir.toPath(), ContentType.Plain, javaClass.classLoader) val templates = templateDir.walkTopDown() .filter { it.extension == "jte" } .map { it.relativeTo(templateDir) } .toList() if (templates.isNotEmpty()) { var jres = javaClass.getResourceAsStream("/jre.yaml").use { input -> val mapper = YAMLMapper.builder() .addModule(KotlinModule.Builder().build()) .build() mapper.readValue(input, object : TypeReference>() {}) } if (maxVersion.isPresent) { jres = jres.filter { it.version <= maxVersion.get() } } val minRuntimeVersion = 17 val supportedJres = jres.filter { it.version >= minRuntimeVersion } val licenseHeader = licenseHeaderFile.asFile.get().readText().trimEnd() .replace($$"$YEAR", licenseHeaderYear.get().toString()) + "\n" val params = additionalTemplateParameters.get() + mapOf( "minRuntimeVersion" to minRuntimeVersion, "allJres" to jres, "supportedJres" to supportedJres, "supportedJresSortedByStringValue" to supportedJres.sortedBy { it.version.toString() }, "licenseHeader" to licenseHeader, ) templates.forEach { val fileName = "${fileNamePrefix.getOrElse("")}${it.nameWithoutExtension}" val targetFile = mainTargetDir.toPath().resolve(it.resolveSibling(fileName).path) FileOutput(targetFile).use { output -> // JTE does not support Windows paths, so we need to replace them val safePath = it.path.replace('\\', '/') templateEngine.render(safePath, params, output) } } } } } ================================================ FILE: gradle/plugins/code-generator/src/main/kotlin/junitbuild.code-generator.gradle.kts ================================================ import junitbuild.extensions.dependencyFromLibs import junitbuild.generator.GenerateJreRelatedSourceCode import java.time.Year plugins { java } val templates by sourceSets.creating val templatesCompileOnly = configurations[templates.compileOnlyConfigurationName] dependencies { templatesCompileOnly(dependencyFromLibs("jte")) templatesCompileOnly("junitbuild.base:code-generator-model") } val license: License by rootProject.extra val rootTargetDir = layout.buildDirectory.dir("generated/sources/jte") val generateCode by tasks.registering { dependsOn(tasks.withType()) group = LifecycleBasePlugin.BUILD_GROUP description = "Generates JRE-related source code." } tasks.withType().configureEach { licenseHeaderFile.convention(license.headerFile) licenseHeaderYear.convention(Year.now()) additionalTemplateParameters.convention(emptyMap()) } sourceSets.named { it != templates.name }.configureEach { val sourceSetName = name val task = tasks.register(getTaskName("generateJreRelated", "SourceCode"), GenerateJreRelatedSourceCode::class) { templateDir.convention(layout.dir(provider { templates.resources.srcDirs.single().resolve(sourceSetName) })) targetDir.convention(rootTargetDir.map { it.dir(sourceSetName) }) } java.srcDir(task.map { it.targetDir }) } ================================================ FILE: gradle/plugins/common/build.gradle.kts ================================================ import junitbuild.extensions.dependencyFromLibs import junitbuild.extensions.markerCoordinates plugins { `kotlin-dsl` } dependencies { implementation("junitbuild.base:dsl-extensions") implementation(projects.buildParameters) implementation(projects.backwardCompatibility) implementation(libs.plugins.kotlin.markerCoordinates) implementation(libs.plugins.bnd.markerCoordinates) implementation(libs.plugins.commonCustomUserData.markerCoordinates) implementation(libs.plugins.develocity.markerCoordinates) implementation(libs.plugins.errorProne.markerCoordinates) implementation(libs.plugins.foojayResolver.markerCoordinates) implementation(libs.plugins.jmh.markerCoordinates) implementation(libs.plugins.nullaway.markerCoordinates) implementation(libs.plugins.shadow.markerCoordinates) implementation(libs.plugins.spotless.markerCoordinates) implementation(platform(dependencyFromLibs("log4j-bom"))) { because("Workaround for CVE-2025-68161") } constraints { implementation("org.codehaus.plexus:plexus-utils") { version { require("4.0.3") } because("Workaround for CVE-2025-67030 (used by shadow plugin)") } } } ================================================ FILE: gradle/plugins/common/src/main/kotlin/JavaLibraryExtension.kt ================================================ import org.gradle.api.JavaVersion import org.gradle.api.provider.Property @Suppress("LeakingThis") abstract class JavaLibraryExtension { abstract val mainJavaVersion: Property abstract val testJavaVersion: Property init { mainJavaVersion.convention(JavaVersion.VERSION_17) testJavaVersion.convention(JavaVersion.VERSION_25) } } ================================================ FILE: gradle/plugins/common/src/main/kotlin/License.kt ================================================ import org.gradle.api.file.RegularFile import java.net.URI data class License(val name: String, val url: URI, val headerFile: RegularFile) ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild/deps/ByteBuddyAlignmentRule.kt ================================================ package junitbuild.deps import org.gradle.api.artifacts.ComponentMetadataContext import org.gradle.api.artifacts.ComponentMetadataRule abstract class ByteBuddyAlignmentRule : ComponentMetadataRule { override fun execute(ctx: ComponentMetadataContext) { ctx.details.run { if (id.group.startsWith("net.bytebuddy")) { belongsTo("net.bytebuddy:byte-buddy-virtual-platform:${id.version}") } } } } ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild/eclipse/EclipseConventionsExtension.kt ================================================ package junitbuild.eclipse import org.gradle.api.provider.Property abstract class EclipseConventionsExtension { abstract val hideModularity: Property } ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild/exec/CaptureJavaExecOutput.kt ================================================ package junitbuild.exec import org.gradle.api.DefaultTask import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.Classpath import org.gradle.api.tasks.Input import org.gradle.api.tasks.Nested import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.TaskAction import org.gradle.process.CommandLineArgumentProvider import org.gradle.process.ExecOperations import java.nio.file.Files import javax.inject.Inject @CacheableTask abstract class CaptureJavaExecOutput @Inject constructor(private val execOperations: ExecOperations) : DefaultTask() { @get:Classpath abstract val classpath: ConfigurableFileCollection @get:Input abstract val mainClass: Property @get:Input abstract val args: ListProperty @get:Nested val jvmArgumentProviders = mutableListOf() @get:OutputFile abstract val outputFile: RegularFileProperty @TaskAction fun execute() { val outputFile = outputFile.get().asFile.toPath() Files.newOutputStream(outputFile).use { out -> execOperations.javaexec { classpath = this@CaptureJavaExecOutput.classpath mainClass.set(this@CaptureJavaExecOutput.mainClass) args = this@CaptureJavaExecOutput.args.get() jvmArgumentProviders.addAll(this@CaptureJavaExecOutput.jvmArgumentProviders) standardOutput = out } } } } ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild/exec/ClasspathSystemPropertyProvider.kt ================================================ package junitbuild.exec import org.gradle.api.file.FileCollection import org.gradle.api.tasks.Classpath import org.gradle.process.CommandLineArgumentProvider class ClasspathSystemPropertyProvider(private val propertyName: String, @get:Classpath val files: FileCollection) : CommandLineArgumentProvider { override fun asArguments() = listOf("-D$propertyName=${files.asPath}") } ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild/exec/GenerateStandaloneConsoleLauncherShadowedArtifactsFile.kt ================================================ package junitbuild.exec import org.gradle.api.DefaultTask import org.gradle.api.file.ArchiveOperations import org.gradle.api.file.FileSystemOperations import org.gradle.api.file.RegularFileProperty import org.gradle.api.file.RelativePath import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.Classpath import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.TaskAction import javax.inject.Inject @CacheableTask abstract class GenerateStandaloneConsoleLauncherShadowedArtifactsFile @Inject constructor( private val fileSystem: FileSystemOperations, private val archives: ArchiveOperations ) : DefaultTask() { @get:Classpath abstract val inputJar: RegularFileProperty @get:OutputFile abstract val outputFile: RegularFileProperty @TaskAction fun execute() { fileSystem.copy { from(archives.zipTree(inputJar)) { include("META-INF/shadowed-artifacts") includeEmptyDirs = false eachFile { relativePath = RelativePath(true, outputFile.get().asFile.name) } filter { line -> "- `${line}`" } } into(outputFile.get().asFile.parentFile) } } } ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild/exec/RunConsoleLauncher.kt ================================================ package junitbuild.exec import org.apache.tools.ant.types.Commandline import org.gradle.api.DefaultTask import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.plugins.JavaPluginExtension import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.Classpath import org.gradle.api.tasks.Input import org.gradle.api.tasks.Internal import org.gradle.api.tasks.Nested import org.gradle.api.tasks.SourceSetContainer import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.options.Option import org.gradle.jvm.toolchain.JavaLauncher import org.gradle.jvm.toolchain.JavaToolchainService import org.gradle.kotlin.dsl.get import org.gradle.kotlin.dsl.the import org.gradle.process.CommandLineArgumentProvider import org.gradle.process.ExecOperations import junitbuild.extensions.trackOperationSystemAsInput import java.io.ByteArrayOutputStream import javax.inject.Inject @CacheableTask abstract class RunConsoleLauncher @Inject constructor(private val execOperations: ExecOperations) : DefaultTask() { @get:Classpath abstract val runtimeClasspath: ConfigurableFileCollection @get:Input abstract val args: ListProperty @get:Nested abstract val argumentProviders: ListProperty @get:Input abstract val commandLineArgs: ListProperty @get:Nested abstract val javaLauncher: Property @get:Internal abstract val debugging: Property @get:Internal abstract val hideOutput: Property init { runtimeClasspath.from(project.the()["test"].runtimeClasspath) javaLauncher.set(project.the().launcherFor(project.the().toolchain)) debugging.convention(false) commandLineArgs.convention(emptyList()) outputs.cacheIf { !debugging.get() } outputs.upToDateWhen { !debugging.get() } hideOutput.convention(debugging.map { !it }) trackOperationSystemAsInput() } @TaskAction fun execute() { val output = ByteArrayOutputStream() val result = execOperations.javaexec { executable = javaLauncher.get().executablePath.asFile.absolutePath classpath = runtimeClasspath mainClass.set("org.junit.platform.console.ConsoleLauncher") args(this@RunConsoleLauncher.args.get()) args(this@RunConsoleLauncher.commandLineArgs.get()) argumentProviders.addAll(this@RunConsoleLauncher.argumentProviders.get()) systemProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager") debug = debugging.get() if (hideOutput.get()) { standardOutput = output errorOutput = output } isIgnoreExitValue = true } if (result.exitValue != 0 && hideOutput.get()) { System.out.write(output.toByteArray()) System.out.flush() } result.rethrowFailure().assertNormalExitValue() } @Suppress("unused") @Option(option = "args", description = "Additional command line arguments for the console launcher") fun setCliArgs(args: String) { commandLineArgs.set(Commandline.translateCommandline(args).toList()) } @Suppress("unused") @Option( option = "debug-jvm", description = "Enable debugging. The process is started suspended and listening on port 5005." ) fun setDebug(enabled: Boolean) { debugging.set(enabled) } @Suppress("unused") @Option( option = "show-output", description = "Show output" ) fun setShowOutput(showOutput: Boolean) { hideOutput.set(!showOutput) } } ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild/graalvm/NativeImagePropertiesExtension.kt ================================================ package junitbuild.graalvm import org.gradle.api.provider.SetProperty abstract class NativeImagePropertiesExtension { abstract val initializeAtBuildTime: SetProperty } ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild/java/UpdateJarAction.kt ================================================ package junitbuild.java import org.gradle.api.Action import org.gradle.api.Task import org.gradle.api.internal.file.archive.ZipEntryConstants import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property import org.gradle.jvm.toolchain.JavaLauncher import org.gradle.process.ExecOperations import java.time.Instant import java.time.LocalDateTime import java.time.ZoneId import java.time.ZoneOffset import javax.inject.Inject abstract class UpdateJarAction @Inject constructor(private val operations: ExecOperations): Action { companion object { // Since ZipCopyAction.CONSTANT_TIME_FOR_ZIP_ENTRIES is in the default time zone (see its Javadoc), // we're converting it to the same time in UTC here to make the jar reproducible regardless of the // build's time zone. private val CONSTANT_TIME_FOR_ZIP_ENTRIES = LocalDateTime.ofInstant(Instant.ofEpochMilli(ZipEntryConstants.CONSTANT_TIME_FOR_ZIP_ENTRIES), ZoneId.systemDefault()) .toInstant(ZoneOffset.UTC) .toString() } abstract val javaLauncher: Property abstract val args: ListProperty init { args.addAll( "--update", // Use a constant time to make the JAR reproducible. "--date=$CONSTANT_TIME_FOR_ZIP_ENTRIES", ) } override fun execute(t: Task) { operations.exec { executable = javaLauncher.get() .metadata.installationPath.file("bin/jar").asFile.absolutePath args = this@UpdateJarAction.args.get() } } } ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild/java/WriteArtifactsFile.kt ================================================ package junitbuild.java import org.gradle.api.DefaultTask import org.gradle.api.artifacts.Configuration import org.gradle.api.artifacts.ModuleVersionIdentifier import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Provider import org.gradle.api.provider.SetProperty import org.gradle.api.tasks.Input import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.TaskAction abstract class WriteArtifactsFile : DefaultTask() { @get:OutputFile abstract val outputFile: RegularFileProperty @get:Input abstract val moduleVersions: SetProperty fun from(configuration: Provider) { moduleVersions.addAll(configuration.map { it.resolvedConfiguration.resolvedArtifacts.map { it.moduleVersion.id } }) } @TaskAction fun writeFile() { outputFile.get().asFile.printWriter().use { out -> moduleVersions.get() .map { "${it.group}:${it.name}:${it.version}" } .sorted() .forEach(out::println) } } } ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild/javadoc/JavadocConventionsExtension.kt ================================================ package junitbuild.javadoc import junitbuild.extensions.javaModuleName import org.gradle.api.Project import org.gradle.api.artifacts.Configuration import org.gradle.api.artifacts.ProjectDependency import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.javadoc.Javadoc import org.gradle.external.javadoc.StandardJavadocDocletOptions import org.gradle.kotlin.dsl.dependencies class JavadocConventionsExtension(val project: Project, val dependencyScope: Configuration, val task: TaskProvider) { fun addExtraModuleReferences(vararg projectDependencies: ProjectDependency) { project.dependencies { projectDependencies.forEach { dependencyScope(it) } } task.configure { options { (this as StandardJavadocDocletOptions).apply { val referencedModuleNames = projectDependencies.joinToString(",") { it.javaModuleName } addStringOption("-add-modules", referencedModuleNames) addStringOption("-add-reads", "${project.javaModuleName}=$referencedModuleNames") } } } } } ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild/javadoc/JavadocValuesOption.kt ================================================ package junitbuild.javadoc import org.gradle.api.provider.Provider import org.gradle.external.javadoc.JavadocOptionFileOption import org.gradle.external.javadoc.internal.JavadocOptionFileWriterContext class JavadocValuesOption(private val option: String, private var values: Provider>) : JavadocOptionFileOption>> { override fun getOption() = option override fun getValue() = values override fun setValue(value: Provider>) { this.values = value } override fun write(writerContext: JavadocOptionFileWriterContext) { writerContext.writeValuesOption(option, values.get(), ",") } } ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild/javadoc/ModuleSpecificJavadocFileOption.kt ================================================ package junitbuild.javadoc import org.gradle.api.provider.Provider import org.gradle.external.javadoc.JavadocOptionFileOption import org.gradle.external.javadoc.internal.JavadocOptionFileWriterContext class ModuleSpecificJavadocFileOption(private val option: String, private var valuePerModule: Map>) : JavadocOptionFileOption>> { override fun getOption() = option override fun getValue() = valuePerModule override fun setValue(value: Map>) { this.valuePerModule = value } override fun write(writerContext: JavadocOptionFileWriterContext) { valuePerModule.forEach { (moduleName, value) -> writerContext .writeOptionHeader(option) .write(moduleName) .write("=") .write(value.get()) .newLine() } } } ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild/javadoc/VersionNumber.kt ================================================ package junitbuild.javadoc data class VersionNumber(val components: List) : Comparable { constructor(version: String) : this(version.split('.').map { it.toInt() }) override fun compareTo(other: VersionNumber): Int { for (i in 0 until maxOf(this.components.size, other.components.size)) { val thisComponent = this.components.getOrElse(i) { 0 } val otherComponent = other.components.getOrElse(i) { 0 } if (thisComponent != otherComponent) { return thisComponent - otherComponent } } return 0 } } ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild.base-conventions.gradle.kts ================================================ plugins { eclipse id("junitbuild.java-toolchain-conventions") id("junitbuild.spotless-conventions") } ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild.build-metadata.gradle.kts ================================================ import java.time.Instant import java.time.OffsetDateTime import java.time.ZoneOffset import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatterBuilder plugins { id("junitbuild.build-parameters") } val dateFormatter = DateTimeFormatter.ISO_LOCAL_DATE val timeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss.SSSZ") val buildTimeAndDate = buildParameters.manifest.buildTimestamp .map { it.toLongOrNull() ?.let { s -> Instant.ofEpochSecond(s).atOffset(ZoneOffset.UTC) } ?: DateTimeFormatterBuilder() .append(dateFormatter) .appendLiteral(' ') .append(timeFormatter) .toFormatter() .parse(it) } .orNull ?: OffsetDateTime.now() val buildDate: String by extra { dateFormatter.format(buildTimeAndDate) } val buildTime: String by extra { timeFormatter.format(buildTimeAndDate) } val buildRevision: String by extra { providers.exec { commandLine("git", "rev-parse", "--verify", "HEAD") }.standardOutput.asText.get().trim() } ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild.checkstyle-conventions.gradle.kts ================================================ import junitbuild.extensions.requiredVersionFromLibs plugins { base checkstyle } dependencies { constraints { checkstyle("org.apache.commons:commons-lang3") { version { require("3.18.0") } because("Workaround for CVE-2025-48924") } checkstyle("org.codehaus.plexus:plexus-utils") { version { require("3.6.1") } because("Workaround for CVE-2025-67030") } } } checkstyle { toolVersion = requiredVersionFromLibs("checkstyle") configDirectory = rootProject.layout.projectDirectory.dir("gradle/config/checkstyle") } tasks.check { dependsOn(tasks.withType()) } ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild.checkstyle-nohttp.gradle.kts ================================================ import junitbuild.extensions.dependencyFromLibs import junitbuild.extensions.requiredVersionFromLibs plugins { id("junitbuild.checkstyle-conventions") } dependencies { checkstyle(dependencyFromLibs("nohttp-checkstyle")) constraints { checkstyle("com.puppycrawl.tools:checkstyle") { version { require(requiredVersionFromLibs("checkstyle")) } } checkstyle("ch.qos.logback:logback-classic") { version { require("1.5.25") } because("Workaround for CVE-2026-1225") } checkstyle("org.codehaus.plexus:plexus-utils") { version { require("3.6.1") } because("Workaround for CVE-2025-67030") } } } tasks.register("checkstyleNohttp") { group = "verification" description = "Checks for illegal uses of http://" classpath = files(configurations.checkstyle) config = resources.text.fromFile(checkstyle.configDirectory.file("checkstyleNohttp.xml")) source = fileTree(layout.projectDirectory) { exclude(".git/**", "**/.gradle/**") exclude(".idea/**", "**/.settings/**", "**/.classpath", "**/.project") exclude("**/*.class") exclude("**/*.hprof") exclude("**/*.jar") exclude("**/*.jpg", "**/*.png") exclude("**/*.jks") exclude("**/build/**") exclude("**/.kotlin") exclude("**/node_modules/**") } } ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild.eclipse-conventions.gradle.kts ================================================ import junitbuild.eclipse.EclipseConventionsExtension import org.gradle.plugins.ide.eclipse.model.Classpath import org.gradle.plugins.ide.eclipse.model.Library import org.gradle.plugins.ide.eclipse.model.ProjectDependency import org.gradle.plugins.ide.eclipse.model.SourceFolder plugins { eclipse } val extension = extensions.create("eclipseConventions").apply { hideModularity.convention(true) } eclipse { jdt { sourceCompatibility = JavaVersion.VERSION_25 targetCompatibility = JavaVersion.VERSION_25 file { // Set properties for org.eclipse.jdt.core.prefs withProperties { // Configure Eclipse projects with -release compiler flag. setProperty("org.eclipse.jdt.core.compiler.release", "enabled") // Configure Eclipse projects with -parameters compiler flag. setProperty("org.eclipse.jdt.core.compiler.codegen.methodParameters", "generate") } } } classpath.file.whenMerged { this as Classpath // Remove classpath entries for non-existent libraries added by various // plugins, such as "junit-jupiter-api/build/classes/kotlin/testFixtures". entries.removeIf { it is Library && !file(it.path).exists() } // Remove classpath entries for the code generator model used by the // Java Template Engine (JTE) which is used to generate the JRE enum and // dependent tests. entries.removeIf { it is ProjectDependency && it.path.equals("/code-generator-model") } // Remove classpath entries for anything used by the Gradle Wrapper. entries.removeIf { it is Library && it.path.contains("gradle/wrapper") } if (extension.hideModularity.get()) { entries.filterIsInstance().forEach { it.excludes.add("**/module-info.java") } entries.filterIsInstance().forEach { it.entryAttributes.remove("module") } entries.filterIsInstance().forEach { it.entryAttributes.remove("module") } } } } ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild.jacoco-aggregation-conventions.gradle.kts ================================================ plugins { id("junitbuild.jacoco-conventions") `jacoco-report-aggregation` } val jacocoRootReport by reporting.reports.creating(JacocoCoverageReport::class) { testSuiteName = "test" } val classesView = configurations["aggregateCodeCoverageReportResults"].incoming.artifactView { withVariantReselection() // Required to ensure the transformed classes are selected componentFilter { it is ProjectComponentIdentifier } attributes { attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, named(LibraryElements.CLASSES)) } } tasks { val reportTask = named(jacocoRootReport.reportTask.name) val jacocoRootCoverageVerification by registering(JacocoCoverageVerification::class) { enabled = !buildParameters.junit.develocity.predictiveTestSelection.enabled executionData.from(reportTask.map { it.executionData }) classDirectories.from(reportTask.map { it.classDirectories }) sourceDirectories.from(reportTask.map { it.sourceDirectories }) violationRules { rule { limit { // In order to detect problems with coverage aggregation, we require a minimum coverage percentage minimum = "0.90".toBigDecimal() } } } } reportTask { // Override to restore behavior of pre-8.2.1 Gradle (see https://github.com/gradle/gradle/issues/25618) classDirectories.setFrom(classesView.files) finalizedBy(jacocoRootCoverageVerification) } } ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild.jacoco-conventions.gradle.kts ================================================ import junitbuild.extensions.requiredVersionFromLibs plugins { jacoco id("junitbuild.build-parameters") } jacoco { toolVersion = requiredVersionFromLibs("jacoco") } tasks.withType().configureEach { enabled = buildParameters.testing.enableJaCoCo } ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild.jacoco-java-conventions.gradle.kts ================================================ import org.gradle.api.attributes.LibraryElements.CLASSES import org.gradle.api.attributes.LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE plugins { java id("junitbuild.build-parameters") id("junitbuild.jacoco-conventions") } val mavenizedProjects: List by rootProject.extra tasks.withType().configureEach { configure { isEnabled = buildParameters.testing.enableJaCoCo } } val codeCoverageClassesJar by tasks.registering(Jar::class) { from(tasks.jar.map { zipTree(it.archiveFile) }) archiveClassifier = "jacoco" enabled = project in mavenizedProjects duplicatesStrategy = DuplicatesStrategy.INCLUDE } configurations.consumable("codeCoverageReportClasses") { attributes { attribute(LIBRARY_ELEMENTS_ATTRIBUTE, named(CLASSES)) } outgoing.artifact(codeCoverageClassesJar) } ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild.java-aggregator-conventions.gradle.kts ================================================ import junitbuild.compatibility.BackwardCompatibilityChecksExtension plugins { id("junitbuild.java-library-conventions") } tasks.javadoc { // Since this JAR contains no classes, running Javadoc fails with: // "No public or protected classes found to document" enabled = false } the().apply { enabled = false // already checked by individual projects } ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild.java-errorprone-conventions.gradle.kts ================================================ import junitbuild.extensions.dependencyFromLibs import net.ltgt.gradle.errorprone.errorprone import net.ltgt.gradle.nullaway.nullaway plugins { `java-library` id("net.ltgt.errorprone") id("net.ltgt.nullaway") } dependencies { errorprone(dependencyFromLibs("error-prone-contrib")) errorprone(dependencyFromLibs("error-prone-core")) errorprone(dependencyFromLibs("nullaway")) } tasks.withType().configureEach { options.errorprone { val shouldDisableErrorProne = java.toolchain.implementation.orNull == JvmImplementation.J9 if (name == "compileJava" && !shouldDisableErrorProne) { disable( "AnnotateFormatMethod", // We don`t want to use ErrorProne's annotations. "BadImport", // This check is opinionated wrt. which method names it considers unsuitable for import which includes a few of our own methods in `ReflectionUtils` etc. "DoNotCallSuggester", // We don`t want to use ErrorProne's annotations. "ImmutableEnumChecker", // We don`t want to use ErrorProne's annotations. "InlineMeSuggester", // We don`t want to use ErrorProne's annotations. "MissingSummary", // Produces a lot of findings that we consider to be false positives, for example for package-private classes and methods. "StringSplitter", // We don`t want to use Guava. "UnnecessaryLambda", // The findings of this check are subjective because a named constant can be more readable in many cases. // picnic (https://error-prone.picnic.tech) "ConstantNaming", "DirectReturn", // We don`t want to use this: https://github.com/junit-team/junit-framework/pull/5006#discussion_r2403984446 "FormatStringConcatenation", "IdentityConversion", "LexicographicalAnnotationAttributeListing", // We don`t want to use this: https://github.com/junit-team/junit-framework/pull/5043#pullrequestreview-3330615838 "LexicographicalAnnotationListing", "MissingTestCall", "NestedOptionals", "NonStaticImport", "OptionalOrElseGet", "PrimitiveComparison", "StaticImport", "TimeZoneUsage", ) error( "CanonicalAnnotationSyntax", "IsInstanceLambdaUsage", "PackageLocation", "RedundantStringConversion", "RedundantStringEscape", ) } else { disableAllChecks = true } nullaway { if (shouldDisableErrorProne) { disable() } else { enable() } onlyNullMarked = true jspecifyMode = true checkContracts = true suppressionNameAliases.add("DataFlowIssue") } } } tasks.withType().named { it.startsWith("compileTest") }.configureEach { options.errorprone.nullaway { handleTestAssertionLibraries = true excludedFieldAnnotations.addAll( "org.junit.jupiter.params.Parameter", "org.junit.runners.Parameterized.Parameter", ) } } ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild.java-library-conventions.gradle.kts ================================================ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import junitbuild.extensions.isSnapshot plugins { `java-library` id("junitbuild.base-conventions") id("junitbuild.build-parameters") id("junitbuild.checkstyle-conventions") id("junitbuild.eclipse-conventions") id("junitbuild.jacoco-java-conventions") id("junitbuild.java-errorprone-conventions") } val mavenizedProjects: List by rootProject.extra val buildDate: String by rootProject.extra val buildTime: String by rootProject.extra val buildRevision: Any by rootProject.extra val extension = extensions.create("javaLibrary") java { modularity.inferModulePath = true } if (project in mavenizedProjects) { apply(plugin = "junitbuild.javadoc-conventions") apply(plugin = "junitbuild.publishing-conventions") apply(plugin = "junitbuild.osgi-conventions") apply(plugin = "junitbuild.backward-compatibility") java { withSourcesJar() } tasks.named("sourcesJar").configure { duplicatesStrategy = DuplicatesStrategy.EXCLUDE } pluginManager.withPlugin("java-test-fixtures") { val javaComponent = components["java"] as AdhocComponentWithVariants javaComponent.withVariantsFromConfiguration(configurations["testFixturesApiElements"]) { skip() } javaComponent.withVariantsFromConfiguration(configurations["testFixturesRuntimeElements"]) { skip() } } configure { publications { named("maven") { from(components["java"]) if (!buildParameters.jitpack.version.isPresent) { versionMapping { allVariants { fromResolutionResult() } } } pom { description = provider { "Module \"${project.name}\" of JUnit" } } } } } if (!project.version.isSnapshot()) { configurations { compileClasspath { resolutionStrategy.failOnChangingVersions() } runtimeClasspath { resolutionStrategy.failOnChangingVersions() } } } } else { tasks { jar { enabled = false } javadoc { enabled = false } } } tasks.withType().configureEach { isPreserveFileTimestamps = false isReproducibleFileOrder = true dirPermissions { unix("rwxr-xr-x") } filePermissions { unix("rw-r--r--") } } normalization { runtimeClasspath { metaInf { // Ignore inconsequential JAR manifest attributes such as timestamps and the commit checksum. // This is used when checking whether runtime classpaths, e.g. of test tasks, have changed and // improves cacheability of such tasks. ignoreAttribute("Built-By") ignoreAttribute("Build-Date") ignoreAttribute("Build-Time") ignoreAttribute("Build-Revision") ignoreAttribute("Created-By") } } } tasks.withType().configureEach { from(rootDir) { include("LICENSE.md") into("META-INF") } from(rootDir) { include("NOTICE.md") rename { "LICENSE-notice.md" } into("META-INF") } } tasks.jar { manifest { attributes( "Created-By" to (buildParameters.manifest.createdBy.orNull ?: "${System.getProperty("java.version")} (${System.getProperty("java.vendor")} ${System.getProperty("java.vm.version")})"), "Built-By" to buildParameters.manifest.builtBy.orElse("JUnit Team"), "Build-Date" to buildDate, "Build-Time" to buildTime, "Build-Revision" to buildRevision, "Specification-Title" to project.name, "Specification-Version" to (project.version as String).substringBefore('-'), "Specification-Vendor" to "junit.org", "Implementation-Title" to project.name, "Implementation-Version" to project.version, "Implementation-Vendor" to "junit.org" ) } } tasks.withType().configureEach { outputs.doNotCacheIf("Shadow jar contains a Manifest with Build-Time") { true } } tasks.withType().configureEach { options.encoding = "UTF-8" options.compilerArgs.addAll(listOf( "-Xlint:all", // Enables all recommended warnings. "-Werror", // Terminates compilation when warnings occur. "-parameters", // Generates metadata for reflection on method parameters. )) } tasks.compileJava { options.compilerArgs.addAll(listOf( "--module-version", "${project.version}" )) } configurations { apiElements { attributes { attributeProvider(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, extension.mainJavaVersion.map { it.majorVersion.toInt() }) } } runtimeElements { attributes { attributeProvider(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, extension.mainJavaVersion.map { it.majorVersion.toInt() }) } } } tasks { compileJava { options.release = extension.mainJavaVersion.map { it.majorVersion.toInt() } } compileTestJava { options.release = extension.testJavaVersion.map { it.majorVersion.toInt() } } } afterEvaluate { pluginManager.withPlugin("groovy") { tasks.named("compileGroovy").configure { // Groovy compiler does not support the --release flag. sourceCompatibility = extension.mainJavaVersion.get().majorVersion targetCompatibility = extension.mainJavaVersion.get().majorVersion } tasks.withType().named { it.startsWith("compileTest") }.configureEach { // Groovy compiler does not support the --release flag. sourceCompatibility = extension.testJavaVersion.get().majorVersion targetCompatibility = extension.testJavaVersion.get().majorVersion } } } tasks { checkstyleMain { config = resources.text.fromFile(checkstyle.configDirectory.file("checkstyleMain.xml")) } checkstyleTest { config = resources.text.fromFile(checkstyle.configDirectory.file("checkstyleTest.xml")) } } pluginManager.withPlugin("java-test-fixtures") { tasks.named("checkstyleTestFixtures") { config = resources.text.fromFile(checkstyle.configDirectory.file("checkstyleTest.xml")) } tasks.named("compileTestFixturesJava") { options.release = extension.testJavaVersion.map { it.majorVersion.toInt() } } } ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild.java-toolchain-conventions.gradle.kts ================================================ import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension plugins { id("junitbuild.build-parameters") } project.pluginManager.withPlugin("java") { val defaultLanguageVersion = JavaLanguageVersion.of(25) val javaLanguageVersion = buildParameters.javaToolchain.version.map { JavaLanguageVersion.of(it) }.getOrElse(defaultLanguageVersion) val jvmImplementation = buildParameters.javaToolchain.implementation.map { when(it) { "j9" -> JvmImplementation.J9 else -> throw InvalidUserDataException("Unsupported JDK implementation: $it") } }.getOrElse(JvmImplementation.VENDOR_SPECIFIC) val extension = the() val javaToolchainService = the() extension.toolchain { languageVersion = javaLanguageVersion implementation = jvmImplementation } pluginManager.withPlugin("org.jetbrains.kotlin.jvm") { configure { jvmToolchain { languageVersion = javaLanguageVersion } } } tasks.withType().configureEach { javaLauncher = javaToolchainService.launcherFor(extension.toolchain) if (javaLanguageVersion != defaultLanguageVersion) { // Track exact version of Java to detect changes in behavior between EA builds sooner inputs.property("javaRuntimeVersion", javaLauncher.get().metadata.javaRuntimeVersion) } } tasks.withType().configureEach { if (javaLanguageVersion != defaultLanguageVersion) { // Track exact version of Java to detect changes in behavior between EA builds sooner inputs.property("javaRuntimeVersion", javaLauncher.get().metadata.javaRuntimeVersion) } } tasks.withType().configureEach { outputs.cacheIf { javaLanguageVersion == defaultLanguageVersion } doFirst { if (options.release.orNull == 8 && javaLanguageVersion.asInt() >= 20) { options.compilerArgs.add( "-Xlint:-options" // see https://github.com/junit-team/junit-framework/issues/3029 ) } } } tasks.withType().configureEach { javaLauncher.set(javaToolchainService.launcherFor { // Groovy does not yet support JDK 19, see https://issues.apache.org/jira/browse/GROOVY-10569 languageVersion = defaultLanguageVersion }) } } ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild.javadoc-conventions.gradle.kts ================================================ import junitbuild.javadoc.JavadocConventionsExtension import java.nio.file.Files import kotlin.io.path.writeLines plugins { `java-library` } java { withJavadocJar() } val javadocReference = configurations.dependencyScope("javadocReference") val extension = JavadocConventionsExtension(project, javadocReference.get(), tasks.javadoc) project.extensions.add("javadocConventions", extension) val javadocClasspath = configurations.resolvable("javadocClasspath") { extendsFrom(configurations.compileClasspath.get()) extendsFrom(javadocReference.get()) } tasks.javadoc { classpath = javadocClasspath.get() options { memberLevel = JavadocMemberLevel.PROTECTED header = project.name encoding = "UTF-8" locale = "en" (this as StandardJavadocDocletOptions).apply { addBooleanOption("Xdoclint:all,-missing", true) addBooleanOption("html5", true) addBooleanOption("Werror", true) addMultilineStringsOption("tag").value = listOf( "apiNote:a:API Note:", "implNote:a:Implementation Note:" ) use(true) noTimestamp(true) } } val sourceUrl = "https://docs.junit.org/current" val targetUrl = "https://docs.junit.org/${version.toString().replace("-SNAPSHOT", "")}" doLast { destinationDir!!.walkTopDown() .filter { it.extension == "html" } .forEach { file -> val content = file.readText() if (content.contains(sourceUrl)) { val updatedContent = content.replace(sourceUrl, targetUrl) file.writeText(updatedContent) } } } } tasks.named("javadocJar").configure { from(tasks.javadoc.map { File(it.destinationDir, "element-list") }) { // For compatibility with older tools, e.g. NetBeans 11 rename { "package-list" } } } val extractJavadocSinceValues by tasks.registering { inputs.files(sourceSets.main.get().allJava).withPathSensitivity(PathSensitivity.NONE) val outputFile = layout.buildDirectory.file("docs/javadoc-since-values.txt") outputs.file(outputFile) outputs.cacheIf { true } doFirst { val regex = "\\s+(?:\\*|///) @since ([0-9.]+).*".toRegex() val values = sourceSets.main.get().allJava.files.asSequence() .flatMap { file -> file.readLines().asSequence() } .mapNotNull(regex::matchEntire) .map { result -> result.groupValues[1] } .sorted() .distinct() with(outputFile.get().asFile.toPath()) { Files.createDirectories(parent) writeLines(values) } } } configurations.consumable("javadocSinceValues") { attributes { attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, named("javadoc-since-values")) } outgoing { artifact(extractJavadocSinceValues) } } ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild.jmh-conventions.gradle.kts ================================================ import junitbuild.extensions.requiredVersionFromLibs import junitbuild.extensions.dependencyFromLibs plugins { id("me.champeau.jmh") } jmh { jmhVersion = requiredVersionFromLibs("jmh") } dependencies { jmh(dependencyFromLibs("jmh-core")) jmhAnnotationProcessor(dependencyFromLibs("jmh-generator-annprocess")) } pluginManager.withPlugin("checkstyle") { tasks.named("checkstyleJmh").configure { // use same style rules as defined for tests config = resources.text.fromFile(project.the().configDirectory.file("checkstyleTest.xml")) } } ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild.junit4-compatibility.gradle.kts ================================================ import junitbuild.extensions.dependencyFromLibs plugins { `java-library` } val junit_4_12 = configurations.dependencyScope("junit_4_12") val junit_4_12_classpath = configurations.resolvable("junit_4_12_classpath") { extendsFrom(configurations.testRuntimeClasspath.get()) extendsFrom(junit_4_12.get()) } dependencies { constraints { junit_4_12("junit:junit") { version { strictly("4.12") } } } pluginManager.withPlugin("junitbuild.osgi-conventions") { "osgiVerification"(dependencyFromLibs("junit4-bundle")) } } tasks { val test_4_12 by registering(Test::class) { val test by testing.suites.existing(JvmTestSuite::class) testClassesDirs = files(test.map { it.sources.output.classesDirs }) classpath = files(sourceSets.main.map { it.output }) + files(test.map { it.sources.output }) + junit_4_12_classpath.get() } check { dependsOn(test_4_12) } } ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild.kotlin-library-conventions.gradle.kts ================================================ import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_1 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { id("junitbuild.java-library-conventions") kotlin("jvm") } tasks.named("kotlinSourcesJar") { enabled = false } val javaLibraryExtension = project.the() tasks.withType().configureEach { compilerOptions { jvmTarget = javaLibraryExtension.mainJavaVersion.map { JvmTarget.fromTarget(it.toString()) } apiVersion = KOTLIN_2_1 languageVersion = apiVersion allWarningsAsErrors.convention(true) javaParameters = true freeCompilerArgs.add("-opt-in=kotlin.RequiresOptIn") freeCompilerArgs.add(jvmTarget.map { "-Xjdk-release=${JavaVersion.toVersion(it.target).majorVersion}" }) } } tasks.named("compileTestKotlin") { compilerOptions.jvmTarget = javaLibraryExtension.testJavaVersion.map { JvmTarget.fromTarget(it.toString()) } } configurations.named { it == "kotlinBouncyCastleConfiguration" }.configureEach { resolutionStrategy { eachDependency { if (requested.group == "org.bouncycastle") { useVersion("1.84") because("Workaround for CVE-2026-3505 et al (used by kotlin plugin)") } } } } ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild.osgi-conventions.gradle.kts ================================================ import aQute.bnd.gradle.BundleTaskExtension import aQute.bnd.gradle.Resolve plugins { `java-library` } val projectDescription = objects.property().convention(provider { project.description }) // This task enhances `jar` and `shadowJar` tasks with the bnd // `BundleTaskExtension` extension which allows for generating OSGi // metadata into the jar tasks.withType().named { it == "jar" || it == "shadowJar" }.all { // configure tasks eagerly as workaround for https://github.com/bndtools/bnd/issues/5695 val importAPIGuardian by extra { "org.apiguardian.*;resolution:=\"optional\"" } val importJSpecify by extra { "org.jspecify.*;resolution:=\"optional\"" } val importCommonsLogging by extra { "org.junit.platform.commons.logging;status=INTERNAL" } extensions.create(BundleTaskExtension.NAME, this).apply { properties.set(projectDescription.map { mapOf("project.description" to it) }) // These are bnd instructions necessary for generating OSGi metadata. // We've generalized these so that they are widely applicable limiting // module configurations to special cases. setBnd( """ # Set the Bundle-SymbolicName to the archiveBaseName. # We don't use the archiveClassifier which Bnd will use # in the default Bundle-SymbolicName value. Bundle-SymbolicName: ${'$'}{task.archiveBaseName} # Set the Bundle-Name from the project description Bundle-Name: ${'$'}{project.description} # These are the general rules for package imports. Import-Package: \ ${importAPIGuardian},\ ${importJSpecify},\ ${importCommonsLogging},\ kotlin.*;resolution:="optional",\ * # This tells bnd not to complain if a module doesn't actually import # the kotlin and apiguardian packages, but enough modules do to make it a default. -fixupmessages.kotlin.import: "Unused Import-Package instructions: \\[kotlin.*\\]";is:=ignore -fixupmessages.apiguardian.import: "Unused Import-Package instructions: \\[org.apiguardian.*\\]";is:=ignore -fixupmessages.warningsAsErrors: ".*";restrict:=warning;is:=error # Don't scan for Class.forName package imports. # See https://bnd.bndtools.org/instructions/noclassforname.html -noclassforname: true # Don't add all the extra headers bnd normally adds. # See https://bnd.bndtools.org/instructions/noextraheaders.html -noextraheaders: true # Don't add the Private-Package header. # See https://bnd.bndtools.org/instructions/removeheaders.html -removeheaders: Private-Package # 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 """ ) // Do the actual work putting OSGi stuff in the jar. doLast(buildAction()) } } // Bnd's Resolve task uses a properties file for its configuration. This // task writes out the properties necessary for it to verify the OSGi // metadata. val osgiProperties by tasks.registering(WriteProperties::class) { destinationFile = layout.buildDirectory.file("verifyOSGiProperties.bndrun") property("-standalone", true) project.extensions.getByType(JavaLibraryExtension::class).let { javaLibrary -> property("-runee", Callable { "JavaSE-${javaLibrary.mainJavaVersion.get()}" }) } property("-runrequires", "osgi.identity;filter:='(osgi.identity=${project.name})'") property("-runsystempackages", "jdk.internal.misc,sun.misc") // API Guardian and JDK JFR should be optional -> instruct resolver to ignore them // during resolution. Resolve should still pass. property("-runblacklist", "org.apiguardian.api,jdk.jfr") } val osgiVerification = configurations.dependencyScope("osgiVerification") val osgiVerificationClasspath = configurations.resolvable("osgiVerificationClasspath") { extendsFrom(configurations.runtimeClasspath.get()) extendsFrom(osgiVerification.get()) } // Bnd's Resolve task is what verifies that a jar can be used in OSGi and // that its metadata is valid. If the metadata is invalid this task will // fail. val verifyOSGi by tasks.registering(Resolve::class) { bndrun = osgiProperties.flatMap { it.destinationFile } outputBndrun = layout.buildDirectory.file("resolvedOSGiProperties.bndrun") isReportOptional = false // By default bnd will use jars found in: // 1. project.sourceSets.main.runtimeClasspath // 2. project.configurations.archives.artifacts.files // to validate the metadata. // This adds jars defined in `osgiVerification` also so that bnd // can use them to validate the metadata without causing those to // end up in the dependencies of those projects. bundles(osgiVerificationClasspath) properties.empty() } tasks.check { dependsOn(verifyOSGi) } ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild.publishing-conventions.gradle.kts ================================================ import junitbuild.extensions.isSnapshot plugins { `maven-publish` signing id("junitbuild.base-conventions") id("junitbuild.build-parameters") } val jupiterProjects: List by rootProject val platformProjects: List by rootProject val vintageProjects: List by rootProject group = buildParameters.publishing.group .getOrElse(when (project) { in jupiterProjects -> "org.junit.jupiter" in platformProjects -> "org.junit.platform" in vintageProjects -> "org.junit.vintage" else -> "org.junit" }) val signArtifacts = buildParameters.publishing.signArtifacts.getOrElse(!(project.version.isSnapshot() || buildParameters.ci)) signing { useGpgCmd() sign(publishing.publications) isRequired = signArtifacts } tasks.withType().configureEach { enabled = signArtifacts } publishing { publications { create("maven") { version = buildParameters.jitpack.version .map { value -> "(.+)-[0-9a-f]+-\\d+".toRegex().matchEntire(value)!!.groupValues[1] + "-SNAPSHOT" } .getOrElse(project.version.toString()) pom { name.set(provider { project.description ?: "${project.group}:${project.name}" }) url = "https://junit.org/" scm { connection = "scm:git:git://github.com/junit-team/junit-framework.git" developerConnection = "scm:git:git://github.com/junit-team/junit-framework.git" url = "https://github.com/junit-team/junit-framework" } licenses { license { val license: License by rootProject.extra name = license.name url = license.url.toString() } } developers { developer { id = "bechte" name = "Stefan Bechtold" email = "stefan.bechtold@me.com" } developer { id = "jlink" name = "Johannes Link" email = "business@johanneslink.net" } developer { id = "marcphilipp" name = "Marc Philipp" email = "mail@marcphilipp.de" } developer { id = "mmerdes" name = "Matthias Merdes" email = "matthias.merdes@heidelpay.com" } developer { id = "sbrannen" name = "Sam Brannen" email = "sam@sambrannen.com" } developer { id = "sormuras" name = "Christian Stein" email = "sormuras@gmail.com" } developer { id = "juliette-derancourt" name = "Juliette de Rancourt" email = "derancourt.juliette@gmail.com" } developer { id = "mpkorstanje" name = "M.P. Korstanje" email = "mpkorstanje@junit.org" } } } } } } ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild.settings-conventions.settings.gradle.kts ================================================ plugins { id("com.gradle.develocity") id("com.gradle.common-custom-user-data-gradle-plugin") id("org.gradle.toolchains.foojay-resolver-convention") } ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild.shadow-conventions.gradle.kts ================================================ plugins { id("junitbuild.java-library-conventions") id("com.gradleup.shadow") } val shadowed = configurations.dependencyScope("shadowed") val shadowedClasspath = configurations.resolvable("shadowedClasspath") { extendsFrom(shadowed.get()) } configurations { listOf(apiElements, runtimeElements).forEach { it.configure { outgoing { artifacts.clear() artifact(tasks.shadowJar) { classifier = "" } } } } compileClasspath { extendsFrom(shadowed.get()) } } shadow { addShadowVariantIntoJavaComponent = false } tasks { javadoc { classpath += shadowedClasspath.get() } checkstyleMain { classpath += shadowedClasspath.get() } shadowJar { configurations = listOf(shadowedClasspath.get()) exclude("META-INF/maven/**") excludes.remove("module-info.class") archiveClassifier = "" from(sourceSets.main.get().output.classesDirs) { include("module-info.class") } addMultiReleaseAttribute = false } jar { dependsOn(shadowJar) enabled = false } named("codeCoverageClassesJar") { from(shadowJar.map { zipTree(it.archiveFile) }) exclude("**/shadow/**") } } ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild.spotless-conventions.gradle.kts ================================================ import com.diffplug.spotless.LineEnding import junitbuild.extensions.requiredVersionFromLibs plugins { id("com.diffplug.spotless") } val license: License by rootProject.extra spotless { format("misc") { target("*.gradle.kts", "gradle/plugins/**/*.gradle.kts", "*.gitignore") targetExclude("gradle/plugins/**/build/**") leadingSpacesToTabs() trimTrailingWhitespace() endWithNewline() } format("documentation") { target("*.adoc", "*.md", "src/**/*.adoc", "src/**/*.md") trimTrailingWhitespace() endWithNewline() } pluginManager.withPlugin("java") { val configDir = rootProject.layout.projectDirectory.dir("gradle/config/eclipse") val importOrderConfigFile = configDir.file("junit-eclipse.importorder") val javaFormatterConfigFile = configDir.file("junit-eclipse-formatter-settings.xml") java { targetExclude("**/module-info.java", "**/package-info.java") licenseHeaderFile(license.headerFile, "(package|import) ") importOrderFile(importOrderConfigFile) val fullVersion = requiredVersionFromLibs("eclipse") val majorMinorVersion = "([0-9]+\\.[0-9]+).*".toRegex().matchEntire(fullVersion)!!.let { it.groups[1]!!.value } eclipse(majorMinorVersion).configFile(javaFormatterConfigFile) trimTrailingWhitespace() endWithNewline() removeUnusedImports() } format("moduleAndPackageInfo") { target(fileTree(layout.projectDirectory.dir("src/main/java")) { include("module-info.java", "**/package-info.java") }) licenseHeaderFile(license.headerFile, "((/(//|\\*\\*))|((open )?module )|package|@.+)") trimTrailingWhitespace() endWithNewline() leadingSpacesToTabs() } } pluginManager.withPlugin("org.jetbrains.kotlin.jvm") { kotlin { targetExclude("**/src/test/resources/**") ktlint(requiredVersionFromLibs("ktlint")) licenseHeaderFile(license.headerFile) trimTrailingWhitespace() endWithNewline() toggleOffOn("formatter:off", "formatter:on") } } pluginManager.withPlugin("groovy") { groovy { licenseHeaderFile(license.headerFile) trimTrailingWhitespace() endWithNewline() } } // Explicitly configure line endings to avoid Spotless to search for .gitattributes file // see https://github.com/gradle/gradle/issues/25469#issuecomment-3444231151 lineEndings = LineEnding.UNIX } tasks { named("spotlessDocumentation") { outputs.doNotCacheIf("negative avoidance savings") { true } } named("spotlessMisc") { outputs.doNotCacheIf("negative avoidance savings") { true } } } ================================================ FILE: gradle/plugins/common/src/main/kotlin/junitbuild.testing-conventions.gradle.kts ================================================ import com.gradle.develocity.agent.gradle.internal.test.PredictiveTestSelectionConfigurationInternal import com.gradle.develocity.agent.gradle.test.PredictiveTestSelectionMode import junitbuild.deps.ByteBuddyAlignmentRule import junitbuild.extensions.bundleFromLibs import junitbuild.extensions.dependencyFromLibs import junitbuild.extensions.trackOperationSystemAsInput import org.gradle.api.tasks.PathSensitivity.RELATIVE import org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL import org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED import org.gradle.internal.os.OperatingSystem import java.io.IOException import java.io.OutputStream import java.nio.file.FileVisitResult import java.nio.file.Files import java.nio.file.NoSuchFileException import java.nio.file.Path import java.nio.file.SimpleFileVisitor import java.nio.file.attribute.BasicFileAttributes plugins { `java-library` id("junitbuild.build-parameters") } var javaAgent = configurations.dependencyScope("javaAgent") var javaAgentClasspath = configurations.resolvable("javaAgentClasspath") { extendsFrom(javaAgent.get()) } var openTestReportingCli = configurations.dependencyScope("openTestReportingCli") var openTestReportingCliClasspath = configurations.resolvable("openTestReportingCliClasspath") { extendsFrom(openTestReportingCli.get()) attributes { // Avoid using the shadowed variant of junit-platform-reporting attribute(Bundling.BUNDLING_ATTRIBUTE, named(Bundling.EXTERNAL)) } } val generateOpenTestHtmlReport by tasks.registering(JavaExec::class) { mustRunAfter(tasks.withType()) mainModule.set("org.opentest4j.reporting.cli") modularity.inferModulePath = true args("html-report") classpath(openTestReportingCliClasspath) argumentProviders += objects.newInstance(HtmlReportParameters::class).apply { eventXmlFiles.from(tasks.withType().map { objects.fileTree() .from(it.reports.junitXml.outputLocation) .include("junit-*/open-test-report.xml") }) outputLocation = layout.buildDirectory.file("reports/open-test-report.html") } if (buildParameters.testing.hideOpenTestReportHtmlGeneratorOutput) { standardOutput = object : OutputStream() { override fun write(b: Int) { // discard output } } } outputs.cacheIf { true } } abstract class HtmlReportParameters : CommandLineArgumentProvider { @get:InputFiles @get:PathSensitive(RELATIVE) @get:SkipWhenEmpty abstract val eventXmlFiles: ConfigurableFileCollection @get:OutputFile abstract val outputLocation: RegularFileProperty override fun asArguments() = listOf("--output", outputLocation.get().asFile.absolutePath) + eventXmlFiles.map { it.absolutePath }.toList() } tasks.withType().configureEach { useJUnitPlatform { includeEngines("junit-jupiter") } include("**/*Test.class", "**/*Tests.class") testLogging { events = setOf(FAILED) exceptionFormat = FULL } develocity { testRetry { maxRetries.convention(buildParameters.testing.retries.orElse(if (buildParameters.ci) 2 else 0)) } testDistribution { enabled.convention(buildParameters.junit.develocity.testDistribution.enabled && (!buildParameters.ci || !System.getenv("DEVELOCITY_ACCESS_KEY").isNullOrBlank())) maxLocalExecutors.convention(buildParameters.junit.develocity.testDistribution.maxLocalExecutors) maxRemoteExecutors.convention(buildParameters.junit.develocity.testDistribution.maxRemoteExecutors) if (buildParameters.ci) { when { OperatingSystem.current().isLinux -> requirements.add("os=linux") OperatingSystem.current().isWindows -> requirements.add("os=windows") OperatingSystem.current().isMacOsX -> requirements.add("os=macos") } } } predictiveTestSelection { enabled.convention(buildParameters.junit.develocity.predictiveTestSelection.enabled) if (buildParameters.junit.develocity.predictiveTestSelection.selectRemainingTests) { mode.convention(PredictiveTestSelectionMode.REMAINING_TESTS) } // Ensure PTS works when publishing Build Scans to scans.gradle.com this as PredictiveTestSelectionConfigurationInternal server = uri("https://ge.junit.org") mergeCodeCoverage = true } } systemProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager") // https://github.com/gradle/gradle/issues/30554 systemProperty("log4j2.julLoggerAdapter", "org.apache.logging.log4j.jul.CoreLoggerAdapter") // Avoid overhead (see https://logging.apache.org/log4j/2.x/manual/jmx.html#enabling-jmx) systemProperty("log4j2.disableJmx", "true") // https://github.com/raphw/byte-buddy/issues/1803 systemProperty("net.bytebuddy.safe", true) // Required until ASM officially supports the JDK 14 systemProperty("net.bytebuddy.experimental", true) if (buildParameters.testing.enableJFR) { jvmArgs( "-XX:+UnlockDiagnosticVMOptions", "-XX:+DebugNonSafepoints", "-XX:StartFlightRecording=filename=${reports.junitXml.outputLocation.get()},dumponexit=true,settings=profile.jfc", "-XX:FlightRecorderOptions=stackdepth=1024" ) } systemProperty("junit.platform.execution.dryRun.enabled", buildParameters.testing.dryRun) // Track OS as input so that tests are executed on all configured operating systems on CI trackOperationSystemAsInput() // Avoid passing unnecessary environment variables to the JVM (from GitHub Actions) if (buildParameters.ci) { environment.remove("RUNNER_TEMP") environment.remove("GITHUB_ACTION") } jvmArgumentProviders += CommandLineArgumentProvider { listOf( "-Djunit.platform.reporting.open.xml.enabled=true", "-Djunit.platform.reporting.open.xml.git.enabled=true", "-Djunit.platform.reporting.output.dir=${reports.junitXml.outputLocation.get().asFile.absolutePath}/junit-{uniqueNumber}", ) } systemProperty("junit.platform.output.capture.stdout", "true") systemProperty("junit.platform.output.capture.stderr", "true") systemProperty("junit.platform.discovery.issue.severity.critical", "info") jvmArgumentProviders += objects.newInstance(JavaAgentArgumentProvider::class).apply { classpath.from(javaAgentClasspath) } jvmArgs("-Xshare:off") // https://github.com/mockito/mockito/issues/3111 doFirst { reports.junitXml.outputLocation.asFile.get() .listFiles { _, name -> name.startsWith("junit-") } ?.forEach { dir -> Files.walkFileTree(dir.toPath(), object : SimpleFileVisitor() { override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult { return deleteIfExistsAndContinue(file) } override fun postVisitDirectory(dir: Path, ex: IOException?): FileVisitResult { if (ex is NoSuchFileException) { return FileVisitResult.CONTINUE } if (ex != null) { throw ex } return deleteIfExistsAndContinue(dir) } private fun deleteIfExistsAndContinue(dir: Path): FileVisitResult { Files.deleteIfExists(dir) return FileVisitResult.CONTINUE } override fun visitFileFailed(file: Path, ex: IOException): FileVisitResult { if (ex is NoSuchFileException) { return FileVisitResult.CONTINUE } throw ex } }) } } finalizedBy(generateOpenTestHtmlReport) } dependencies { components.all() constraints { testImplementation(dependencyFromLibs("byteBuddy")) } testImplementation(platform(dependencyFromLibs("mockito-bom"))) testImplementation(dependencyFromLibs("assertj")) testImplementation(dependencyFromLibs("mockito-junit-jupiter")) testImplementation(dependencyFromLibs("testingAnnotations")) testImplementation(project(":junit-jupiter")) testRuntimeOnly(project(":junit-platform-launcher")) testRuntimeOnly(project(":junit-platform-reporting")) testRuntimeOnly(bundleFromLibs("log4j")) testRuntimeOnly(dependencyFromLibs("openTestReporting-events")) { because("it's required to run tests via IntelliJ which does not consumed the shadowed jar of junit-platform-reporting") } openTestReportingCli(dependencyFromLibs("openTestReporting-cli")) openTestReportingCli(project(":junit-platform-reporting")) openTestReportingCli(platform(dependencyFromLibs("log4j-bom"))) { because("Workaround for CVE-2025-68161") } javaAgent(platform(dependencyFromLibs("mockito-bom"))) javaAgent(dependencyFromLibs("mockito-core")) { isTransitive = false } } abstract class JavaAgentArgumentProvider : CommandLineArgumentProvider { @get:Classpath abstract val classpath: ConfigurableFileCollection override fun asArguments() = listOf("-javaagent:${classpath.singleFile.absolutePath}") } ================================================ FILE: gradle/plugins/publishing/build.gradle.kts ================================================ import junitbuild.extensions.markerCoordinates plugins { `kotlin-dsl` } dependencies { implementation("junitbuild.base:dsl-extensions") implementation(libs.plugins.nmcp.settings.markerCoordinates) } ================================================ FILE: gradle/plugins/publishing/src/main/kotlin/junitbuild/release/VerifyBinaryArtifactsAreIdentical.kt ================================================ package junitbuild.release import org.gradle.api.DefaultTask import org.gradle.api.file.DirectoryProperty import org.gradle.api.provider.Property import org.gradle.api.provider.ProviderFactory import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputDirectory import org.gradle.api.tasks.Internal import org.gradle.api.tasks.PathSensitive import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.options.Option import java.io.File import java.net.URI import java.net.http.HttpClient import java.net.http.HttpRequest import java.net.http.HttpResponse.BodyHandlers import javax.inject.Inject abstract class VerifyBinaryArtifactsAreIdentical @Inject constructor(providers: ProviderFactory): DefaultTask() { @get:InputDirectory @get:PathSensitive(PathSensitivity.RELATIVE) abstract val localRepoDir: DirectoryProperty @get:Input abstract val remoteRepoUrl: Property @get:Internal abstract val remoteRepoBearerToken: Property init { // Depends on contents of remote repository outputs.upToDateWhen { false } remoteRepoBearerToken.convention(providers.environmentVariable("MAVEN_CENTRAL_USER_TOKEN")) } @Suppress("unused") @Option( option = "remote-repo-url", description = "The URL of the remote repository to compare the local repository against" ) fun remoteRepo(url: String) { remoteRepoUrl.set(url) } @TaskAction fun execute() { val localRootDir = localRepoDir.get().asFile val baseUrl = remoteRepoUrl.get() val mismatches = mutableListOf() var numChecks = 0 HttpClient.newHttpClient().use { httpClient -> localRootDir.walk().forEach { file -> if (file.isFile && file.name.endsWith(".jar.sha512") && !file.name.endsWith("-javadoc.jar.sha512")) { val localSha512 = file.readText() val relativeFile = file.relativeTo(localRootDir) val url = URI.create("${baseUrl}/${relativeFile.path}") logger.info("Checking {}...", url) val request = HttpRequest.newBuilder().GET() .uri(url) .header("Authorization", "Bearer ${remoteRepoBearerToken.get()}") .build() val response = httpClient.send(request, BodyHandlers.ofString()) val remoteSha512 = if (response.statusCode() == 200) response.body() else "status=${response.statusCode()}" if (localSha512 != remoteSha512) { mismatches.add(Mismatch(relativeFile, localSha512, remoteSha512)) } numChecks++ } } } require(numChecks > 0) { "No files found to compare" } require(mismatches.isEmpty()) { "The following files have different SHA-512 checksums in the local and remote repositories:\n\n" + mismatches.joinToString("\n\n") { """ ${it.file} local: ${it.localSha512} remote: ${it.remoteSha512} """.trimIndent() } } } private data class Mismatch(val file: File, val localSha512: String, val remoteSha512: String) } ================================================ FILE: gradle/plugins/publishing/src/main/kotlin/junitbuild.maven-central-publishing.settings.gradle.kts ================================================ import kotlin.time.Duration.Companion.minutes import kotlin.time.toJavaDuration plugins { id("com.gradleup.nmcp.settings") } nmcpSettings { centralPortal { username = providers.gradleProperty("mavenCentralUsername") password = providers.gradleProperty("mavenCentralPassword") publishingType = "USER_MANAGED" validationTimeout = 10.minutes.toJavaDuration() publishingTimeout = 30.minutes.toJavaDuration() } } ================================================ FILE: gradle/plugins/publishing/src/main/kotlin/junitbuild.temp-maven-repo.gradle.kts ================================================ import junitbuild.extensions.capitalized import junitbuild.release.VerifyBinaryArtifactsAreIdentical val tempRepoName by extra("temp") val tempRepoDir by extra { layout.buildDirectory.dir("repo").get().asFile } val clearTempRepoDir by tasks.registering { val dir = tempRepoDir doFirst { dir.deleteRecursively() } } val publishAllSubprojectsToTempRepository by tasks.registering tasks.register("verifyArtifactsInStagingRepositoryAreReproducible") { dependsOn(publishAllSubprojectsToTempRepository) localRepoDir.set(tempRepoDir) } subprojects { pluginManager.withPlugin("maven-publish") { configure { repositories { maven { name = tempRepoName url = uri(tempRepoDir) } } } val publishingTasks = tasks.withType() .named { it.endsWith("To${tempRepoName.capitalized()}Repository") } publishingTasks.configureEach { dependsOn(clearTempRepoDir) } publishAllSubprojectsToTempRepository { dependsOn(publishingTasks) } } } ================================================ FILE: gradle/plugins/settings.gradle.kts ================================================ pluginManagement { includeBuild("../base") } plugins { id("junitbuild.dsl-extensions") apply false } dependencyResolutionManagement { versionCatalogs { create("libs") { from(files("../libs.versions.toml")) } } repositories { gradlePluginPortal() } } rootProject.name = "plugins" include("antora") include("backward-compatibility") include("build-parameters") include("common") include("code-generator") include("publishing") enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionSha256Sum=553c78f50dafcd54d65b9a444649057857469edf836431389695608536d6b746 distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: gradle.properties ================================================ version = 6.1.0-SNAPSHOT # For backward compatibility checks apiBaselineVersion = 6.0.2 org.gradle.jvmargs=-Xmx1g -XX:+HeapDumpOnOutOfMemoryError org.gradle.caching=true org.gradle.parallel=true org.gradle.configuration-cache.parallel=true org.gradle.java.installations.fromEnv=GRAALVM_HOME,JDK17,JDK21,JDK24,JDK25 org.gradle.kotlin.dsl.allWarningsAsErrors=true # Test Distribution develocity.internal.testdistribution.writeTraceFile=true # Omit automatic compile dependency on kotlin-stdlib # https://kotlinlang.org/docs/gradle.html#dependency-on-the-standard-library kotlin.stdlib.default.dependency=false # Avoid Gradle deprecation warnings from Kotlin plugin kotlin.mpp.keepMppDependenciesIntactInPoms=true ================================================ FILE: gradlew ================================================ #!/bin/sh # # Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # SPDX-License-Identifier: Apache-2.0 # ############################################################################## # # Gradle start up script for POSIX generated by Gradle. # # Important for running: # # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is # noncompliant, but you have some other compliant shell such as ksh or # bash, then to run this script, type that shell name before the whole # command line, like: # # ksh Gradle # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: # * functions; # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», # «${var#prefix}», «${var%suffix}», and «$( cmd )»; # * compound commands having a testable exit status, especially «case»; # * various built-in commands including «command», «set», and «ulimit». # # Important for patching: # # (2) This script targets any POSIX shell, so it avoids extensions provided # by Bash, Ksh, etc; in particular arrays are avoided. # # The "traditional" practice of packing multiple parameters into a # space-separated string is a well documented source of bugs and security # problems, so this is (mostly) avoided, by progressively accumulating # options in "$@", and eventually passing that to Java. # # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; # see the in-line comments for details. # # There are tweaks for specific operating systems such as AIX, CygWin, # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template # https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. # ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link app_path=$0 # Need this for daisy-chained symlinks. while APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path [ -h "$app_path" ] do ls=$( ls -ld "$app_path" ) link=${ls#*' -> '} case $link in #( /*) app_path=$link ;; #( *) app_path=$APP_HOME$link ;; esac done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum warn () { echo "$*" } >&2 die () { echo echo "$*" echo exit 1 } >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "$( uname )" in #( CYGWIN* ) cygwin=true ;; #( Darwin* ) darwin=true ;; #( MSYS* | MINGW* ) msys=true ;; #( NONSTOP* ) nonstop=true ;; esac # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD=$JAVA_HOME/jre/sh/java else JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD=java if ! command -v java >/dev/null 2>&1 then die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac fi # Collect all arguments for the java command, stacking in reverse order: # * args from the command line # * the main class name # * -classpath # * -D...appname settings # * --module-path (only if needed) # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) # Now convert the arguments - kludge to limit ourselves to /bin/sh for arg do if case $arg in #( -*) false ;; # don't mess with options #( /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath [ -e "$t" ] ;; #( *) false ;; esac then arg=$( cygpath --path --ignore --mixed "$arg" ) fi # Roll the args list around exactly as many times as the number of # args, so each arg winds up back in the position where it started, but # possibly modified. # # NB: a `for` loop captures its iteration list before it begins, so # changing the positional parameters here affects neither the number of # iterations, nor the values presented in `arg`. shift # remove old arg set -- "$@" "$arg" # push replacement arg done fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. if ! command -v xargs >/dev/null 2>&1 then die "xargs is not available" fi # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. # # In Bash we could simply go: # # readarray ARGS < <( xargs -n1 <<<"$var" ) && # set -- "${ARGS[@]}" "$@" # # but POSIX shell has neither arrays nor command substitution, so instead we # post-process each arg (as a line of input to sed) to backslash-escape any # character that might be a shell metacharacter, then use eval to reverse # that process (while maintaining the separation between arguments), and wrap # the whole thing up as a single "set" statement. # # This will of course break if any of these variables contains a newline or # an unmatched quote. # eval "set -- $( printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | xargs -n1 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | tr '\n' ' ' )" '"$@"' exec "$JAVACMD" "$@" ================================================ FILE: gradlew.bat ================================================ @rem @rem Copyright 2015 the original author or authors. @rem @rem Licensed under the Apache License, Version 2.0 (the "License"); @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem @rem https://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem @rem SPDX-License-Identifier: Apache-2.0 @rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables, and ensure extensions are enabled setlocal EnableExtensions set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Resolve any "." and ".." in APP_HOME to make it shorter. for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute echo. 1>&2 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 "%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute echo. 1>&2 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 "%COMSPEC%" /c exit 1 :execute @rem Setup the command line @rem Execute Gradle @rem endlocal doesn't take effect until after the line is parsed and variables are expanded @rem which allows us to clear the local environment before executing the java command endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel :exitWithErrorLevel @rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts "%COMSPEC%" /c exit %ERRORLEVEL% ================================================ FILE: junit-bom/README.md ================================================ # JUnit Bill of Materials (BOM) This module provides a Bill of Materials POM to ease dependency management using [Maven] or [Gradle]. Please refer to the [User Guide] for details. [Maven]: https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Importing_Dependencies [Gradle]: https://docs.gradle.org/current/userguide/platforms.html#sub:bom_import [User Guide]: https://docs.junit.org/current/appendix.html#dependency-metadata-junit-bom ================================================ FILE: junit-bom/junit-bom.gradle.kts ================================================ plugins { `java-platform` id("junitbuild.publishing-conventions") } description = "${rootProject.description} (Bill of Materials)" dependencies { constraints { val mavenizedProjects: List by rootProject.extra mavenizedProjects.sorted() .filter { it.name != "junit-platform-console-standalone" } .forEach { val version = buildParameters.jitpack.version .map { value -> "(.+)-[0-9a-f]+-\\d+".toRegex().matchEntire(value)!!.groupValues[1] + "-SNAPSHOT" } .getOrElse(it.version.toString()) api("${it.group}:${it.name}:${version}") } } } publishing.publications.named("maven") { from(components["javaPlatform"]) pom { description = "This Bill of Materials POM can be used to ease dependency management " + "when referencing multiple JUnit artifacts using Gradle or Maven." withXml { val filteredContent = asString().replace("\\s*compile".toRegex(), "") asString().clear().append(filteredContent) } } } tasks.withType().configureEach { doLast { val xml = destination.readText() require(xml.indexOf("") == xml.lastIndexOf("")) { "BOM must contain exactly one element but contained multiple:\n$destination" } require(xml.contains("")) { "BOM must contain a element:\n$destination" } require(!xml.contains("")) { "BOM must not contain elements:\n$destination" } } } ================================================ FILE: junit-jupiter/junit-jupiter.gradle.kts ================================================ plugins { id("junitbuild.java-aggregator-conventions") } description = "JUnit Jupiter (Aggregator)" dependencies { api(platform(projects.junitBom)) api(projects.junitJupiterApi) api(projects.junitJupiterParams) implementation(projects.junitJupiterEngine) osgiVerification(projects.junitJupiterEngine) osgiVerification(projects.junitPlatformLauncher) } ================================================ FILE: junit-jupiter/src/main/java/module-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Aggregates all JUnit Jupiter modules. * * @since 5.4 */ module org.junit.jupiter { requires transitive org.junit.jupiter.api; requires transitive org.junit.jupiter.engine; requires transitive org.junit.jupiter.params; } ================================================ FILE: junit-jupiter-api/junit-jupiter-api.gradle.kts ================================================ import junitbuild.generator.GenerateJreRelatedSourceCode plugins { id("junitbuild.kotlin-library-conventions") id("junitbuild.code-generator") `java-test-fixtures` } description = "JUnit Jupiter API" dependencies { api(platform(projects.junitBom)) api(libs.opentest4j) api(projects.junitPlatformCommons) compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) compileOnly(kotlin("stdlib")) testFixturesImplementation(libs.assertj) testFixturesImplementation(testFixtures(projects.junitPlatformCommons)) osgiVerification(projects.junitJupiterEngine) osgiVerification(projects.junitPlatformLauncher) } javadocConventions { addExtraModuleReferences(projects.junitPlatformEngine, projects.junitPlatformLauncher, projects.junitJupiterParams) } eclipseConventions { hideModularity = false } tasks { compileJava { options.compilerArgs.add("-Xlint:-module") // due to qualified exports } jar { bundle { val version = project.version bnd(""" Require-Capability:\ org.junit.platform.engine;\ filter:='(&(org.junit.platform.engine=junit-jupiter)(version>=${'$'}{version_cleanup;${version}})(!(version>=${'$'}{versionmask;+;${'$'}{version_cleanup;${version}}})))';\ effective:=active """) } } val generateJreTestDouble by registering(GenerateJreRelatedSourceCode::class) { templateDir = layout.projectDirectory.dir("src/templates/resources/main") targetDir = layout.buildDirectory.dir("generated/sources/jte/testDouble") maxVersion = 22 fileNamePrefix = "TestDouble" additionalTemplateParameters = mapOf("classNamePrefix" to "TestDouble") } sourceSets.testFixtures.get().java.srcDir(generateJreTestDouble.map { it.targetDir }) } ================================================ FILE: junit-jupiter-api/src/main/java/module-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Defines the JUnit Jupiter API for writing tests. * * @since 5.0 */ module org.junit.jupiter.api { requires static transitive org.apiguardian.api; requires static transitive org.jspecify; requires transitive org.junit.platform.commons; requires transitive org.opentest4j; requires static kotlin.stdlib; exports org.junit.jupiter.api; exports org.junit.jupiter.api.condition; exports org.junit.jupiter.api.extension; exports org.junit.jupiter.api.extension.support; exports org.junit.jupiter.api.function; exports org.junit.jupiter.api.io; exports org.junit.jupiter.api.parallel; exports org.junit.jupiter.api.timeout to org.junit.jupiter.engine; exports org.junit.jupiter.api.util; opens org.junit.jupiter.api.condition to org.junit.platform.commons; opens org.junit.jupiter.api.util to org.junit.platform.commons; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterAll.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @AfterAll} is used to signal that the annotated method should be * executed after all tests in the current test class. * *

In contrast to {@link AfterEach @AfterEach} methods, {@code @AfterAll} * methods are only executed once per execution of a given test class. If the * test class is annotated with {@link ClassTemplate @ClassTemplate}, the * {@code @AfterAll} methods are executed once after the last invocation of the * class template. If a {@link Nested @Nested} test class is declared in a * {@link ClassTemplate @ClassTemplate}, its {@code @AfterAll} methods are * called once per execution of the nested test class, namely, once per * invocation of the outer class template. * *

Method Signatures

* *

{@code @AfterAll} methods must have a {@code void} return type and must * be {@code static} unless the test class is annotated with * {@link TestInstance @TestInstance(Lifecycle.PER_CLASS)}. In addition, * {@code @AfterAll} methods may optionally declare parameters to be resolved by * {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolvers}. * *

Using {@code private} visibility for {@code @AfterAll} methods is strongly * discouraged and will be disallowed in a future release. * *

Inheritance and Execution Order

* *

{@code @AfterAll} methods are inherited from superclasses as long as they * are not overridden according to the visibility rules of the Java * language. Furthermore, {@code @AfterAll} methods from superclasses will be * executed after {@code @AfterAll} methods in subclasses. * *

Similarly, {@code @AfterAll} methods declared in an interface are inherited * as long as they are not overridden, and {@code @AfterAll} methods from an * interface will be executed after {@code @AfterAll} methods in the class that * implements the interface. * *

JUnit Jupiter does not guarantee the execution order of multiple * {@code @AfterAll} methods that are declared within a single test class or * test interface. While it may at times appear that these methods are invoked * in alphabetical order, they are in fact sorted using an algorithm that is * deterministic but intentionally non-obvious. * *

In addition, {@code @AfterAll} methods are in no way linked to * {@code @BeforeAll} methods. Consequently, there are no guarantees with regard * to their wrapping behavior. For example, given two * {@code @BeforeAll} methods {@code createA()} and {@code createB()} as well as * two {@code @AfterAll} methods {@code destroyA()} and {@code destroyB()}, the * order in which the {@code @BeforeAll} methods are executed (e.g. * {@code createA()} before {@code createB()}) does not imply any order for the * seemingly corresponding {@code @AfterAll} methods. In other words, * {@code destroyA()} might be called before or after * {@code destroyB()}. The JUnit Team therefore recommends that developers * declare at most one {@code @BeforeAll} method and at most one * {@code @AfterAll} method per test class or test interface unless there are no * dependencies between the {@code @BeforeAll} methods or between the * {@code @AfterAll} methods. * *

Composition

* *

{@code @AfterAll} may be used as a meta-annotation in order to create * a custom composed annotation that inherits the semantics of * {@code @AfterAll}. * * @since 5.0 * @see BeforeAll * @see BeforeEach * @see AfterEach * @see Test * @see TestFactory * @see TestInstance */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = STABLE, since = "5.0") public @interface AfterAll { } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterEach.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @AfterEach} is used to signal that the annotated method should be * executed after each {@code @Test}, * {@code @RepeatedTest}, {@code @ParameterizedTest}, {@code @TestFactory}, * and {@code @TestTemplate} method in the current test class. * *

Method Signatures

* *

{@code @AfterEach} methods must have a {@code void} return type and must * not be {@code static}. In addition, {@code @AfterEach} methods may optionally * declare parameters to be resolved by * {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolvers}. * *

Using {@code private} visibility for {@code @AfterEach} methods is strongly * discouraged and will be disallowed in a future release. * *

Inheritance and Execution Order

* *

{@code @AfterEach} methods are inherited from superclasses as long as they * are not overridden according to the visibility rules of the Java * language. Furthermore, {@code @AfterEach} methods from superclasses will be * executed after {@code @AfterEach} methods in subclasses. * *

Similarly, {@code @AfterEach} methods declared as interface default * methods are inherited as long as they are not overridden, and * {@code @AfterEach} default methods will be executed after {@code @AfterEach} * methods in the class that implements the interface. * *

JUnit Jupiter does not guarantee the execution order of multiple * {@code @AfterEach} methods that are declared within a single test class or * test interface. While it may at times appear that these methods are invoked * in alphabetical order, they are in fact sorted using an algorithm that is * deterministic but intentionally non-obvious. * *

In addition, {@code @AfterEach} methods are in no way linked to * {@code @BeforeEach} methods. Consequently, there are no guarantees with * regard to their wrapping behavior. For example, given two * {@code @BeforeEach} methods {@code createA()} and {@code createB()} as well * as two {@code @AfterEach} methods {@code destroyA()} and {@code destroyB()}, * the order in which the {@code @BeforeEach} methods are executed (e.g. * {@code createA()} before {@code createB()}) does not imply any order for the * seemingly corresponding {@code @AfterEach} methods. In other words, * {@code destroyA()} might be called before or after * {@code destroyB()}. The JUnit Team therefore recommends that developers * declare at most one {@code @BeforeEach} method and at most one * {@code @AfterEach} method per test class or test interface unless there are * no dependencies between the {@code @BeforeEach} methods or between the * {@code @AfterEach} methods. * *

Composition

* *

{@code @AfterEach} may be used as a meta-annotation in order to create * a custom composed annotation that inherits the semantics of * {@code @AfterEach}. * * @since 5.0 * @see BeforeEach * @see BeforeAll * @see AfterAll * @see Test * @see RepeatedTest * @see TestFactory * @see TestTemplate */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = STABLE, since = "5.0") public @interface AfterEach { } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertAll.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.function.Executable; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.UnrecoverableExceptions; import org.opentest4j.MultipleFailuresError; /** * {@code AssertAll} is a collection of utility methods that support asserting * multiple conditions in tests at once. * * @since 5.0 */ class AssertAll { private AssertAll() { /* no-op */ } static void assertAll(Executable... executables) { assertAll(null, executables); } static void assertAll(@Nullable String heading, Executable... executables) { Preconditions.notEmpty(executables, "executables array must not be null or empty"); Preconditions.containsNoNullElements(executables, "individual executables must not be null"); assertAll(heading, Arrays.stream(executables)); } static void assertAll(Collection executables) { assertAll(null, executables); } static void assertAll(@Nullable String heading, Collection executables) { Preconditions.notNull(executables, "executables collection must not be null"); Preconditions.containsNoNullElements(executables, "individual executables must not be null"); assertAll(heading, executables.stream()); } static void assertAll(Stream executables) { assertAll(null, executables); } static void assertAll(@Nullable String heading, Stream executables) { Preconditions.notNull(executables, "executables stream must not be null"); List failures = executables // .map(executable -> { Preconditions.notNull(executable, "individual executables must not be null"); try { executable.execute(); return null; } catch (Throwable t) { UnrecoverableExceptions.rethrowIfUnrecoverable(t); return t; } }) // .filter(Objects::nonNull) // .toList(); if (!failures.isEmpty()) { MultipleFailuresError multipleFailuresError = new MultipleFailuresError(heading, failures); failures.forEach(multipleFailuresError::addSuppressed); throw multipleFailuresError; } } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertArrayEquals.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import static org.junit.jupiter.api.AssertionUtils.formatIndexes; import static org.junit.platform.commons.util.ReflectionUtils.isArray; import java.util.ArrayDeque; import java.util.Deque; import java.util.Objects; import java.util.function.Supplier; import org.jspecify.annotations.Nullable; import org.opentest4j.AssertionFailedError; /** * {@code AssertArrayEquals} is a collection of utility methods that support asserting * array equality in tests. * * @since 5.0 */ class AssertArrayEquals { private AssertArrayEquals() { /* no-op */ } static void assertArrayEquals(boolean @Nullable [] expected, boolean @Nullable [] actual) { assertArrayEquals(expected, actual, (String) null); } static void assertArrayEquals(boolean @Nullable [] expected, boolean @Nullable [] actual, @Nullable String message) { assertArrayEquals(expected, actual, null, message); } static void assertArrayEquals(boolean @Nullable [] expected, boolean @Nullable [] actual, Supplier<@Nullable String> messageSupplier) { assertArrayEquals(expected, actual, null, messageSupplier); } static void assertArrayEquals(char @Nullable [] expected, char @Nullable [] actual, @Nullable String message) { assertArrayEquals(expected, actual, null, message); } static void assertArrayEquals(char @Nullable [] expected, char @Nullable [] actual) { assertArrayEquals(expected, actual, (String) null); } static void assertArrayEquals(char @Nullable [] expected, char @Nullable [] actual, @Nullable Supplier<@Nullable String> messageSupplier) { assertArrayEquals(expected, actual, null, messageSupplier); } static void assertArrayEquals(byte @Nullable [] expected, byte @Nullable [] actual) { assertArrayEquals(expected, actual, (String) null); } static void assertArrayEquals(byte @Nullable [] expected, byte @Nullable [] actual, @Nullable String message) { assertArrayEquals(expected, actual, null, message); } static void assertArrayEquals(byte @Nullable [] expected, byte @Nullable [] actual, Supplier<@Nullable String> messageSupplier) { assertArrayEquals(expected, actual, null, messageSupplier); } static void assertArrayEquals(short @Nullable [] expected, short @Nullable [] actual) { assertArrayEquals(expected, actual, (String) null); } static void assertArrayEquals(short @Nullable [] expected, short @Nullable [] actual, @Nullable String message) { assertArrayEquals(expected, actual, null, message); } static void assertArrayEquals(short @Nullable [] expected, short @Nullable [] actual, Supplier<@Nullable String> messageSupplier) { assertArrayEquals(expected, actual, null, messageSupplier); } static void assertArrayEquals(int @Nullable [] expected, int @Nullable [] actual) { assertArrayEquals(expected, actual, (String) null); } static void assertArrayEquals(int @Nullable [] expected, int @Nullable [] actual, @Nullable String message) { assertArrayEquals(expected, actual, null, message); } static void assertArrayEquals(int @Nullable [] expected, int @Nullable [] actual, Supplier<@Nullable String> messageSupplier) { assertArrayEquals(expected, actual, null, messageSupplier); } static void assertArrayEquals(long @Nullable [] expected, long @Nullable [] actual) { assertArrayEquals(expected, actual, (String) null); } static void assertArrayEquals(long @Nullable [] expected, long @Nullable [] actual, @Nullable String message) { assertArrayEquals(expected, actual, null, message); } static void assertArrayEquals(long @Nullable [] expected, long @Nullable [] actual, Supplier<@Nullable String> messageSupplier) { assertArrayEquals(expected, actual, null, messageSupplier); } static void assertArrayEquals(float @Nullable [] expected, float @Nullable [] actual) { assertArrayEquals(expected, actual, (String) null); } static void assertArrayEquals(float @Nullable [] expected, float @Nullable [] actual, @Nullable String message) { assertArrayEquals(expected, actual, null, message); } static void assertArrayEquals(float @Nullable [] expected, float @Nullable [] actual, Supplier<@Nullable String> messageSupplier) { assertArrayEquals(expected, actual, null, messageSupplier); } static void assertArrayEquals(float @Nullable [] expected, float @Nullable [] actual, float delta) { assertArrayEquals(expected, actual, delta, (String) null); } static void assertArrayEquals(float @Nullable [] expected, float @Nullable [] actual, float delta, @Nullable String message) { assertArrayEquals(expected, actual, delta, null, message); } static void assertArrayEquals(float @Nullable [] expected, float @Nullable [] actual, float delta, Supplier<@Nullable String> messageSupplier) { assertArrayEquals(expected, actual, delta, null, messageSupplier); } static void assertArrayEquals(double @Nullable [] expected, double @Nullable [] actual) { assertArrayEquals(expected, actual, (String) null); } static void assertArrayEquals(double @Nullable [] expected, double @Nullable [] actual, @Nullable String message) { assertArrayEquals(expected, actual, null, message); } static void assertArrayEquals(double @Nullable [] expected, double @Nullable [] actual, Supplier<@Nullable String> messageSupplier) { assertArrayEquals(expected, actual, null, messageSupplier); } static void assertArrayEquals(double @Nullable [] expected, double @Nullable [] actual, double delta) { assertArrayEquals(expected, actual, delta, (String) null); } static void assertArrayEquals(double @Nullable [] expected, double @Nullable [] actual, double delta, @Nullable String message) { assertArrayEquals(expected, actual, delta, null, message); } static void assertArrayEquals(double @Nullable [] expected, double @Nullable [] actual, double delta, Supplier<@Nullable String> messageSupplier) { assertArrayEquals(expected, actual, delta, null, messageSupplier); } static void assertArrayEquals(@Nullable Object @Nullable [] expected, @Nullable Object @Nullable [] actual) { assertArrayEquals(expected, actual, (String) null); } static void assertArrayEquals(@Nullable Object @Nullable [] expected, @Nullable Object @Nullable [] actual, @Nullable String message) { assertArrayEquals(expected, actual, new ArrayDeque<>(), message); } static void assertArrayEquals(@Nullable Object @Nullable [] expected, @Nullable Object @Nullable [] actual, Supplier<@Nullable String> messageSupplier) { assertArrayEquals(expected, actual, new ArrayDeque<>(), messageSupplier); } private static void assertArrayEquals(boolean @Nullable [] expected, boolean @Nullable [] actual, @Nullable Deque indexes, @Nullable Object messageOrSupplier) { if (expected == actual) { return; } if (expected == null) { throw expectedArrayIsNullFailure(indexes, messageOrSupplier); } if (actual == null) { throw actualArrayIsNullFailure(indexes, messageOrSupplier); } assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); for (int i = 0; i < expected.length; i++) { if (expected[i] != actual[i]) { failArraysNotEqual(expected[i], actual[i], nullSafeIndexes(indexes, i), messageOrSupplier); } } } private static void assertArrayEquals(char @Nullable [] expected, char @Nullable [] actual, @Nullable Deque indexes, @Nullable Object messageOrSupplier) { if (expected == actual) { return; } if (expected == null) { throw expectedArrayIsNullFailure(indexes, messageOrSupplier); } if (actual == null) { throw actualArrayIsNullFailure(indexes, messageOrSupplier); } assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); for (int i = 0; i < expected.length; i++) { if (expected[i] != actual[i]) { failArraysNotEqual(expected[i], actual[i], nullSafeIndexes(indexes, i), messageOrSupplier); } } } private static void assertArrayEquals(byte @Nullable [] expected, byte @Nullable [] actual, @Nullable Deque indexes, @Nullable Object messageOrSupplier) { if (expected == actual) { return; } if (expected == null) { throw expectedArrayIsNullFailure(indexes, messageOrSupplier); } if (actual == null) { throw actualArrayIsNullFailure(indexes, messageOrSupplier); } assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); for (int i = 0; i < expected.length; i++) { if (expected[i] != actual[i]) { failArraysNotEqual(expected[i], actual[i], nullSafeIndexes(indexes, i), messageOrSupplier); } } } private static void assertArrayEquals(short @Nullable [] expected, short @Nullable [] actual, @Nullable Deque indexes, @Nullable Object messageOrSupplier) { if (expected == actual) { return; } if (expected == null) { throw expectedArrayIsNullFailure(indexes, messageOrSupplier); } if (actual == null) { throw actualArrayIsNullFailure(indexes, messageOrSupplier); } assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); for (int i = 0; i < expected.length; i++) { if (expected[i] != actual[i]) { failArraysNotEqual(expected[i], actual[i], nullSafeIndexes(indexes, i), messageOrSupplier); } } } private static void assertArrayEquals(int @Nullable [] expected, int @Nullable [] actual, @Nullable Deque indexes, @Nullable Object messageOrSupplier) { if (expected == actual) { return; } if (expected == null) { throw expectedArrayIsNullFailure(indexes, messageOrSupplier); } if (actual == null) { throw actualArrayIsNullFailure(indexes, messageOrSupplier); } assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); for (int i = 0; i < expected.length; i++) { if (expected[i] != actual[i]) { failArraysNotEqual(expected[i], actual[i], nullSafeIndexes(indexes, i), messageOrSupplier); } } } private static void assertArrayEquals(long @Nullable [] expected, long @Nullable [] actual, @Nullable Deque indexes, @Nullable Object messageOrSupplier) { if (expected == actual) { return; } if (expected == null) { throw expectedArrayIsNullFailure(indexes, messageOrSupplier); } if (actual == null) { throw actualArrayIsNullFailure(indexes, messageOrSupplier); } assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); for (int i = 0; i < expected.length; i++) { if (expected[i] != actual[i]) { failArraysNotEqual(expected[i], actual[i], nullSafeIndexes(indexes, i), messageOrSupplier); } } } private static void assertArrayEquals(float @Nullable [] expected, float @Nullable [] actual, @Nullable Deque indexes, @Nullable Object messageOrSupplier) { if (expected == actual) { return; } if (expected == null) { throw expectedArrayIsNullFailure(indexes, messageOrSupplier); } if (actual == null) { throw actualArrayIsNullFailure(indexes, messageOrSupplier); } assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); for (int i = 0; i < expected.length; i++) { if (!AssertionUtils.floatsAreEqual(expected[i], actual[i])) { failArraysNotEqual(expected[i], actual[i], nullSafeIndexes(indexes, i), messageOrSupplier); } } } private static void assertArrayEquals(float @Nullable [] expected, float @Nullable [] actual, float delta, @Nullable Deque indexes, @Nullable Object messageOrSupplier) { AssertionUtils.assertValidDelta(delta); if (expected == actual) { return; } if (expected == null) { throw expectedArrayIsNullFailure(indexes, messageOrSupplier); } if (actual == null) { throw actualArrayIsNullFailure(indexes, messageOrSupplier); } assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); for (int i = 0; i < expected.length; i++) { if (!AssertionUtils.floatsAreEqual(expected[i], actual[i], delta)) { failArraysNotEqual(expected[i], actual[i], nullSafeIndexes(indexes, i), messageOrSupplier); } } } private static void assertArrayEquals(double @Nullable [] expected, double @Nullable [] actual, @Nullable Deque indexes, @Nullable Object messageOrSupplier) { if (expected == actual) { return; } if (expected == null) { throw expectedArrayIsNullFailure(indexes, messageOrSupplier); } if (actual == null) { throw actualArrayIsNullFailure(indexes, messageOrSupplier); } assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); for (int i = 0; i < expected.length; i++) { if (!AssertionUtils.doublesAreEqual(expected[i], actual[i])) { failArraysNotEqual(expected[i], actual[i], nullSafeIndexes(indexes, i), messageOrSupplier); } } } private static void assertArrayEquals(double @Nullable [] expected, double @Nullable [] actual, double delta, @Nullable Deque indexes, @Nullable Object messageOrSupplier) { AssertionUtils.assertValidDelta(delta); if (expected == actual) { return; } if (expected == null) { throw expectedArrayIsNullFailure(indexes, messageOrSupplier); } if (actual == null) { throw actualArrayIsNullFailure(indexes, messageOrSupplier); } assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); for (int i = 0; i < expected.length; i++) { if (!AssertionUtils.doublesAreEqual(expected[i], actual[i], delta)) { failArraysNotEqual(expected[i], actual[i], nullSafeIndexes(indexes, i), messageOrSupplier); } } } private static void assertArrayEquals(@Nullable Object @Nullable [] expected, @Nullable Object @Nullable [] actual, Deque indexes, @Nullable Object messageOrSupplier) { if (expected == actual) { return; } if (expected == null) { throw expectedArrayIsNullFailure(indexes, messageOrSupplier); } if (actual == null) { throw actualArrayIsNullFailure(indexes, messageOrSupplier); } assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); for (int i = 0; i < expected.length; i++) { Object expectedElement = expected[i]; Object actualElement = actual[i]; if (expectedElement == actualElement) { continue; } indexes.addLast(i); assertArrayElementsEqual(expectedElement, actualElement, indexes, messageOrSupplier); indexes.removeLast(); } } private static void assertArrayElementsEqual(@Nullable Object expected, @Nullable Object actual, Deque indexes, @Nullable Object messageOrSupplier) { if (expected instanceof Object[] expectedArray && actual instanceof Object[] actualArray) { assertArrayEquals(expectedArray, actualArray, indexes, messageOrSupplier); } else if (expected instanceof byte[] expectedArray && actual instanceof byte[] actualArray) { assertArrayEquals(expectedArray, actualArray, indexes, messageOrSupplier); } else if (expected instanceof short[] expectedArray && actual instanceof short[] actualArray) { assertArrayEquals(expectedArray, actualArray, indexes, messageOrSupplier); } else if (expected instanceof int[] expectedArray && actual instanceof int[] actualArray) { assertArrayEquals(expectedArray, actualArray, indexes, messageOrSupplier); } else if (expected instanceof long[] expectedArray && actual instanceof long[] actualArray) { assertArrayEquals(expectedArray, actualArray, indexes, messageOrSupplier); } else if (expected instanceof char[] expectedArray && actual instanceof char[] actualArray) { assertArrayEquals(expectedArray, actualArray, indexes, messageOrSupplier); } else if (expected instanceof float[] expectedArray && actual instanceof float[] actualArray) { assertArrayEquals(expectedArray, actualArray, indexes, messageOrSupplier); } else if (expected instanceof double[] expectedArray && actual instanceof double[] actualArray) { assertArrayEquals(expectedArray, actualArray, indexes, messageOrSupplier); } else if (expected instanceof boolean[] expectedArray && actual instanceof boolean[] actualArray) { assertArrayEquals(expectedArray, actualArray, indexes, messageOrSupplier); } else if (!Objects.equals(expected, actual)) { if (expected == null && isArray(actual)) { failExpectedArrayIsNull(indexes, messageOrSupplier); } else if (isArray(expected) && actual == null) { failActualArrayIsNull(indexes, messageOrSupplier); } else { failArraysNotEqual(expected, actual, indexes, messageOrSupplier); } } } private static void failExpectedArrayIsNull(@Nullable Deque indexes, @Nullable Object messageOrSupplier) { throw expectedArrayIsNullFailure(indexes, messageOrSupplier); } private static AssertionFailedError expectedArrayIsNullFailure(@Nullable Deque indexes, @Nullable Object messageOrSupplier) { return assertionFailure() // .message(messageOrSupplier) // .reason("expected array was " + formatIndexes(indexes)) // .trimStacktrace(Assertions.class) // .build(); } private static void failActualArrayIsNull(@Nullable Deque indexes, @Nullable Object messageOrSupplier) { throw actualArrayIsNullFailure(indexes, messageOrSupplier); } private static AssertionFailedError actualArrayIsNullFailure(@Nullable Deque indexes, @Nullable Object messageOrSupplier) { return assertionFailure() // .message(messageOrSupplier) // .reason("actual array was " + formatIndexes(indexes)) // .trimStacktrace(Assertions.class) // .build(); } private static void assertArraysHaveSameLength(int expected, int actual, @Nullable Deque indexes, @Nullable Object messageOrSupplier) { if (expected != actual) { assertionFailure() // .message(messageOrSupplier) // .reason("array lengths differ" + formatIndexes(indexes)) // .expected(expected) // .actual(actual) // .trimStacktrace(Assertions.class) // .buildAndThrow(); } } private static void failArraysNotEqual(@Nullable Object expected, @Nullable Object actual, @Nullable Deque indexes, @Nullable Object messageOrSupplier) { assertionFailure() // .message(messageOrSupplier) // .reason("array contents differ" + formatIndexes(indexes)) // .expected(expected) // .actual(actual) // .trimStacktrace(Assertions.class) // .buildAndThrow(); } private static Deque nullSafeIndexes(@Nullable Deque indexes, int newIndex) { Deque result = (indexes != null ? indexes : new ArrayDeque<>()); result.addLast(newIndex); return result; } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertDoesNotThrow.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import java.util.function.Supplier; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.api.function.ThrowingSupplier; import org.junit.platform.commons.util.StringUtils; import org.junit.platform.commons.util.UnrecoverableExceptions; import org.opentest4j.AssertionFailedError; /** * {@code AssertDoesNotThrow} is a collection of utility methods that support * explicitly asserting that a given code block does not throw an exception. * * @since 5.2 */ class AssertDoesNotThrow { private AssertDoesNotThrow() { /* no-op */ } static void assertDoesNotThrow(Executable executable) { assertDoesNotThrow(executable, (Object) null); } static void assertDoesNotThrow(Executable executable, @Nullable String message) { assertDoesNotThrow(executable, (Object) message); } static void assertDoesNotThrow(Executable executable, Supplier<@Nullable String> messageSupplier) { assertDoesNotThrow(executable, (Object) messageSupplier); } private static void assertDoesNotThrow(Executable executable, @Nullable Object messageOrSupplier) { try { executable.execute(); } catch (Throwable t) { UnrecoverableExceptions.rethrowIfUnrecoverable(t); throw createAssertionFailedError(messageOrSupplier, t); } } static T assertDoesNotThrow(ThrowingSupplier supplier) { return assertDoesNotThrow(supplier, (Object) null); } static T assertDoesNotThrow(ThrowingSupplier supplier, @Nullable String message) { return assertDoesNotThrow(supplier, (Object) message); } static T assertDoesNotThrow(ThrowingSupplier supplier, Supplier<@Nullable String> messageSupplier) { return assertDoesNotThrow(supplier, (Object) messageSupplier); } private static T assertDoesNotThrow(ThrowingSupplier supplier, @Nullable Object messageOrSupplier) { try { return supplier.get(); } catch (Throwable t) { UnrecoverableExceptions.rethrowIfUnrecoverable(t); throw createAssertionFailedError(messageOrSupplier, t); } } @API(status = INTERNAL, since = "6.0") public static AssertionFailedError createAssertionFailedError(@Nullable Object messageOrSupplier, Throwable t) { return assertionFailure() // .message(messageOrSupplier) // .reason("Unexpected exception thrown: " + t.getClass().getName() + buildSuffix(t.getMessage())) // .cause(t) // .trimStacktrace(Assertions.class) // .build(); } private static String buildSuffix(@Nullable String message) { return StringUtils.isNotBlank(message) ? ": " + message : ""; } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertEquals.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import static org.junit.jupiter.api.AssertionUtils.doublesAreEqual; import static org.junit.jupiter.api.AssertionUtils.floatsAreEqual; import static org.junit.jupiter.api.AssertionUtils.objectsAreEqual; import java.util.function.Supplier; import org.jspecify.annotations.Nullable; /** * {@code AssertEquals} is a collection of utility methods that support asserting * equality on objects and primitives in tests. * * @since 5.0 */ class AssertEquals { private AssertEquals() { /* no-op */ } static void assertEquals(byte expected, byte actual) { assertEquals(expected, actual, (String) null); } static void assertEquals(byte expected, byte actual, @Nullable String message) { if (expected != actual) { failNotEqual(expected, actual, message); } } static void assertEquals(byte expected, byte actual, Supplier<@Nullable String> messageSupplier) { if (expected != actual) { failNotEqual(expected, actual, messageSupplier); } } static void assertEquals(char expected, char actual) { assertEquals(expected, actual, (String) null); } static void assertEquals(char expected, char actual, @Nullable String message) { if (expected != actual) { failNotEqual(expected, actual, message); } } static void assertEquals(char expected, char actual, Supplier<@Nullable String> messageSupplier) { if (expected != actual) { failNotEqual(expected, actual, messageSupplier); } } static void assertEquals(double expected, double actual) { assertEquals(expected, actual, (String) null); } static void assertEquals(double expected, double actual, @Nullable String message) { if (!doublesAreEqual(expected, actual)) { failNotEqual(expected, actual, message); } } static void assertEquals(double expected, double actual, Supplier<@Nullable String> messageSupplier) { if (!doublesAreEqual(expected, actual)) { failNotEqual(expected, actual, messageSupplier); } } static void assertEquals(double expected, double actual, double delta) { assertEquals(expected, actual, delta, (String) null); } static void assertEquals(double expected, double actual, double delta, @Nullable String message) { if (!doublesAreEqual(expected, actual, delta)) { failNotEqual(expected, actual, message); } } static void assertEquals(double expected, double actual, double delta, Supplier<@Nullable String> messageSupplier) { if (!doublesAreEqual(expected, actual, delta)) { failNotEqual(expected, actual, messageSupplier); } } static void assertEquals(float expected, float actual) { assertEquals(expected, actual, (String) null); } static void assertEquals(float expected, float actual, @Nullable String message) { if (!floatsAreEqual(expected, actual)) { failNotEqual(expected, actual, message); } } static void assertEquals(float expected, float actual, Supplier<@Nullable String> messageSupplier) { if (!floatsAreEqual(expected, actual)) { failNotEqual(expected, actual, messageSupplier); } } static void assertEquals(float expected, float actual, float delta) { assertEquals(expected, actual, delta, (String) null); } static void assertEquals(float expected, float actual, float delta, @Nullable String message) { if (!floatsAreEqual(expected, actual, delta)) { failNotEqual(expected, actual, message); } } static void assertEquals(float expected, float actual, float delta, Supplier<@Nullable String> messageSupplier) { if (!floatsAreEqual(expected, actual, delta)) { failNotEqual(expected, actual, messageSupplier); } } static void assertEquals(short expected, short actual) { assertEquals(expected, actual, (String) null); } static void assertEquals(short expected, short actual, @Nullable String message) { if (expected != actual) { failNotEqual(expected, actual, message); } } static void assertEquals(short expected, short actual, Supplier<@Nullable String> messageSupplier) { if (expected != actual) { failNotEqual(expected, actual, messageSupplier); } } static void assertEquals(int expected, int actual) { assertEquals(expected, actual, (String) null); } static void assertEquals(int expected, int actual, @Nullable String message) { if (expected != actual) { failNotEqual(expected, actual, message); } } static void assertEquals(int expected, int actual, Supplier<@Nullable String> messageSupplier) { if (expected != actual) { failNotEqual(expected, actual, messageSupplier); } } static void assertEquals(long expected, long actual) { assertEquals(expected, actual, (String) null); } static void assertEquals(long expected, long actual, @Nullable String message) { if (expected != actual) { failNotEqual(expected, actual, message); } } static void assertEquals(long expected, long actual, Supplier<@Nullable String> messageSupplier) { if (expected != actual) { failNotEqual(expected, actual, messageSupplier); } } static void assertEquals(@Nullable Object expected, @Nullable Object actual) { assertEquals(expected, actual, (String) null); } static void assertEquals(@Nullable Object expected, @Nullable Object actual, @Nullable String message) { if (!objectsAreEqual(expected, actual)) { failNotEqual(expected, actual, message); } } static void assertEquals(@Nullable Object expected, @Nullable Object actual, Supplier<@Nullable String> messageSupplier) { if (!objectsAreEqual(expected, actual)) { failNotEqual(expected, actual, messageSupplier); } } private static void failNotEqual(@Nullable Object expected, @Nullable Object actual, @Nullable Object messageOrSupplier) { assertionFailure() // .message(messageOrSupplier) // .expected(expected) // .actual(actual) // .trimStacktrace(Assertions.class) // .buildAndThrow(); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertFalse.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import java.util.function.BooleanSupplier; import java.util.function.Supplier; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.annotation.Contract; /** * {@code AssertFalse} is a collection of utility methods that support asserting * {@code false} in tests. * * @since 5.0 */ class AssertFalse { private AssertFalse() { /* no-op */ } @Contract("true -> fail") static void assertFalse(boolean condition) { assertFalse(condition, (String) null); } @Contract("true, _ -> fail") static void assertFalse(boolean condition, @Nullable String message) { if (condition) { failNotFalse(message); } } @Contract("true, _ -> fail") static void assertFalse(boolean condition, Supplier<@Nullable String> messageSupplier) { if (condition) { failNotFalse(messageSupplier); } } static void assertFalse(BooleanSupplier booleanSupplier) { assertFalse(booleanSupplier.getAsBoolean(), (String) null); } static void assertFalse(BooleanSupplier booleanSupplier, @Nullable String message) { assertFalse(booleanSupplier.getAsBoolean(), message); } static void assertFalse(BooleanSupplier booleanSupplier, Supplier<@Nullable String> messageSupplier) { assertFalse(booleanSupplier.getAsBoolean(), messageSupplier); } private static void failNotFalse(@Nullable Object messageOrSupplier) { assertionFailure() // .message(messageOrSupplier) // .expected(false) // .actual(true) // .trimStacktrace(Assertions.class) // .buildAndThrow(); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertInstanceOf.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import java.util.function.Supplier; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.annotation.Contract; /** * {@code AssertInstanceOf} is a collection of utility methods that support * asserting that an object is of an expected type — in other words, if it * can be assigned to the expected type. * * @since 5.8 */ class AssertInstanceOf { private AssertInstanceOf() { /* no-op */ } @Contract("_, null -> fail") static T assertInstanceOf(Class expectedType, @Nullable Object actualValue) { return assertInstanceOf(expectedType, actualValue, (Object) null); } @Contract("_, null, _ -> fail") static T assertInstanceOf(Class expectedType, @Nullable Object actualValue, @Nullable String message) { return assertInstanceOf(expectedType, actualValue, (Object) message); } @Contract("_, null, _ -> fail") static T assertInstanceOf(Class expectedType, @Nullable Object actualValue, Supplier<@Nullable String> messageSupplier) { return assertInstanceOf(expectedType, actualValue, (Object) messageSupplier); } private static T assertInstanceOf(Class expectedType, @Nullable Object actualValue, @Nullable Object messageOrSupplier) { if (!expectedType.isInstance(actualValue)) { throw assertionFailure() // .message(messageOrSupplier) // .reason(actualValue == null ? "Unexpected null value" : "Unexpected type") // .expected(expectedType) // .actual(actualValue == null ? null : actualValue.getClass()) // .cause(actualValue instanceof Throwable t ? t : null) // .trimStacktrace(Assertions.class) // .build(); } return expectedType.cast(actualValue); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertIterableEquals.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import static org.junit.jupiter.api.AssertionUtils.formatIndexes; import java.util.ArrayDeque; import java.util.Deque; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; import org.jspecify.annotations.Nullable; import org.opentest4j.AssertionFailedError; /** * {@code AssertIterable} is a collection of utility methods that support asserting * Iterable equality in tests. * * @since 5.0 */ class AssertIterableEquals { private AssertIterableEquals() { /* no-op */ } static void assertIterableEquals(@Nullable Iterable expected, @Nullable Iterable actual) { assertIterableEquals(expected, actual, (String) null); } static void assertIterableEquals(@Nullable Iterable expected, @Nullable Iterable actual, @Nullable String message) { assertIterableEquals(expected, actual, new ArrayDeque<>(), message); } static void assertIterableEquals(@Nullable Iterable expected, @Nullable Iterable actual, Supplier<@Nullable String> messageSupplier) { assertIterableEquals(expected, actual, new ArrayDeque<>(), messageSupplier); } private static void assertIterableEquals(@Nullable Iterable expected, @Nullable Iterable actual, Deque indexes, @Nullable Object messageOrSupplier) { assertIterableEquals(expected, actual, indexes, messageOrSupplier, new LinkedHashMap<>()); } private static void assertIterableEquals(@Nullable Iterable expected, @Nullable Iterable actual, Deque indexes, @Nullable Object messageOrSupplier, Map investigatedElements) { if (expected == actual) { return; } if (expected == null) { throw expectedIterableIsNullFailure(indexes, messageOrSupplier); } if (actual == null) { throw actualIterableIsNullFailure(indexes, messageOrSupplier); } Iterator expectedIterator = expected.iterator(); Iterator actualIterator = actual.iterator(); int processed = 0; while (expectedIterator.hasNext() && actualIterator.hasNext()) { Object expectedElement = expectedIterator.next(); Object actualElement = actualIterator.next(); indexes.addLast(processed); assertIterableElementsEqual(expectedElement, actualElement, indexes, messageOrSupplier, investigatedElements); indexes.removeLast(); processed++; } assertIteratorsAreEmpty(expectedIterator, actualIterator, processed, indexes, messageOrSupplier); } private static void assertIterableElementsEqual(Object expected, Object actual, Deque indexes, @Nullable Object messageOrSupplier, Map investigatedElements) { // If both are equal, we don't need to check recursively. if (Objects.equals(expected, actual)) { return; } // If both are iterables, we need to check whether they contain the same elements. if (expected instanceof Iterable expectedIterable && actual instanceof Iterable actualIterable) { Pair pair = new Pair(expected, actual); // Before comparing their elements, we check whether we have already checked this pair. Status status = investigatedElements.get(pair); // If we've already determined that both contain the same elements, we don't need to check them again. if (status == Status.CONTAIN_SAME_ELEMENTS) { return; } // If the pair is already under investigation, we fail in order to avoid infinite recursion. if (status == Status.UNDER_INVESTIGATION) { indexes.removeLast(); failIterablesNotEqual(expected, actual, indexes, messageOrSupplier); } // Otherwise, we put the pair under investigation and recurse. investigatedElements.put(pair, Status.UNDER_INVESTIGATION); assertIterableEquals(expectedIterable, actualIterable, indexes, messageOrSupplier, investigatedElements); // If we reach this point, we've checked that the two iterables contain the same elements so we store this information // in case we come across the same pair again. investigatedElements.put(pair, Status.CONTAIN_SAME_ELEMENTS); } // Otherwise, they are neither equal nor iterables, so we fail. else { assertIterablesNotNull(expected, actual, indexes, messageOrSupplier); failIterablesNotEqual(expected, actual, indexes, messageOrSupplier); } } private static void assertIterablesNotNull(@Nullable Object expected, @Nullable Object actual, Deque indexes, @Nullable Object messageOrSupplier) { if (expected == null) { failExpectedIterableIsNull(indexes, messageOrSupplier); } if (actual == null) { failActualIterableIsNull(indexes, messageOrSupplier); } } private static void failExpectedIterableIsNull(Deque indexes, @Nullable Object messageOrSupplier) { throw expectedIterableIsNullFailure(indexes, messageOrSupplier); } private static AssertionFailedError expectedIterableIsNullFailure(Deque indexes, @Nullable Object messageOrSupplier) { return assertionFailure() // .message(messageOrSupplier) // .reason("expected iterable was " + formatIndexes(indexes)) // .trimStacktrace(Assertions.class) // .build(); } private static void failActualIterableIsNull(Deque indexes, @Nullable Object messageOrSupplier) { throw actualIterableIsNullFailure(indexes, messageOrSupplier); } private static AssertionFailedError actualIterableIsNullFailure(Deque indexes, @Nullable Object messageOrSupplier) { return assertionFailure() // .message(messageOrSupplier) // .reason("actual iterable was " + formatIndexes(indexes)) // .trimStacktrace(Assertions.class) // .build(); } private static void assertIteratorsAreEmpty(Iterator expected, Iterator actual, int processed, Deque indexes, @Nullable Object messageOrSupplier) { if (expected.hasNext() || actual.hasNext()) { AtomicInteger expectedCount = new AtomicInteger(processed); expected.forEachRemaining(e -> expectedCount.incrementAndGet()); AtomicInteger actualCount = new AtomicInteger(processed); actual.forEachRemaining(e -> actualCount.incrementAndGet()); assertionFailure() // .message(messageOrSupplier) // .reason("iterable lengths differ" + formatIndexes(indexes)) // .expected(expectedCount.get()) // .actual(actualCount.get()) // .buildAndThrow(); } } private static void failIterablesNotEqual(Object expected, Object actual, Deque indexes, @Nullable Object messageOrSupplier) { assertionFailure() // .message(messageOrSupplier) // .reason("iterable contents differ" + formatIndexes(indexes)) // .expected(expected) // .actual(actual) // .buildAndThrow(); } private record Pair(Object left, Object right) { } private enum Status { UNDER_INVESTIGATION, CONTAIN_SAME_ELEMENTS } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertLinesMatch.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static java.lang.String.join; import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import static org.junit.platform.commons.util.Preconditions.condition; import static org.junit.platform.commons.util.Preconditions.notNull; import java.util.ArrayDeque; import java.util.Deque; import java.util.List; import java.util.regex.PatternSyntaxException; import java.util.stream.IntStream; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; /** * {@code AssertLinesMatch} is a collection of utility methods that support asserting * lines of {@link String} equality or {@link java.util.regex.Pattern}-match in tests. * * @since 5.0 */ class AssertLinesMatch { private AssertLinesMatch() { /* no-op */ } private static final int MAX_SNIPPET_LENGTH = 21; static void assertLinesMatch(List expectedLines, List actualLines) { assertLinesMatch(expectedLines, actualLines, (Object) null); } static void assertLinesMatch(List expectedLines, List actualLines, @Nullable String message) { assertLinesMatch(expectedLines, actualLines, (Object) message); } static void assertLinesMatch(Stream expectedLines, Stream actualLines) { assertLinesMatch(expectedLines, actualLines, (Object) null); } static void assertLinesMatch(Stream expectedLines, Stream actualLines, @Nullable String message) { assertLinesMatch(expectedLines, actualLines, (Object) message); } @SuppressWarnings("ReferenceEquality") static void assertLinesMatch(Stream expectedLines, Stream actualLines, @Nullable Object messageOrSupplier) { notNull(expectedLines, "expectedLines must not be null"); notNull(actualLines, "actualLines must not be null"); // trivial case: same stream instance if (expectedLines == actualLines) { return; } List expectedListOfStrings = expectedLines.toList(); List actualListOfStrings = actualLines.toList(); assertLinesMatch(expectedListOfStrings, actualListOfStrings, messageOrSupplier); } @SuppressWarnings("ReferenceEquality") static void assertLinesMatch(List expectedLines, List actualLines, @Nullable Object messageOrSupplier) { notNull(expectedLines, "expectedLines must not be null"); notNull(actualLines, "actualLines must not be null"); // trivial case: same list instance if (expectedLines == actualLines) { return; } new LinesMatcher(expectedLines, actualLines, messageOrSupplier).assertLinesMatch(); } private record LinesMatcher(List expectedLines, List actualLines, @Nullable Object messageOrSupplier) { void assertLinesMatch() { int expectedSize = expectedLines.size(); int actualSize = actualLines.size(); // trivial case: when expecting more than actual lines available, something is wrong if (expectedSize > actualSize) { fail("expected %d lines, but only got %d", expectedSize, actualSize); } // simple case: both list are equally sized, compare them line-by-line if (expectedSize == actualSize) { if (IntStream.range(0, expectedSize).allMatch(i -> matches(expectedLines.get(i), actualLines.get(i)))) { return; } // else fall-through to "with fast-forward" matching } assertLinesMatchWithFastForward(); } void assertLinesMatchWithFastForward() { Deque expectedDeque = new ArrayDeque<>(expectedLines); Deque actualDeque = new ArrayDeque<>(actualLines); main: while (!expectedDeque.isEmpty()) { String expectedLine = expectedDeque.pop(); int expectedLineNumber = expectedLines.size() - expectedDeque.size(); // 1-based line number // trivial case: no more actual lines available if (actualDeque.isEmpty()) { fail("expected line #%d:`%s` not found - actual lines depleted", expectedLineNumber, snippet(expectedLine)); } String actualLine = actualDeque.peek(); // trivial case: take the fast path when they match if (matches(expectedLine, actualLine)) { actualDeque.pop(); continue; // main } // fast-forward marker found in expected line: fast-forward actual line... if (isFastForwardLine(expectedLine)) { int fastForwardLimit = parseFastForwardLimit(expectedLine); int actualRemaining = actualDeque.size(); // trivial case: fast-forward marker was in last expected line if (expectedDeque.isEmpty()) { // no limit given or perfect match? we're done. if (fastForwardLimit == Integer.MAX_VALUE || fastForwardLimit == actualRemaining) { return; } fail("terminal fast-forward(%d) error: fast-forward(%d) expected", fastForwardLimit, actualRemaining); } // fast-forward limit was given: use it if (fastForwardLimit != Integer.MAX_VALUE) { if (actualRemaining < fastForwardLimit) { fail("fast-forward(%d) error: not enough actual lines remaining (%s)", fastForwardLimit, actualRemaining); } // fast-forward now: actualDeque.pop(fastForwardLimit) for (int i = 0; i < fastForwardLimit; i++) { actualDeque.pop(); } continue; // main } // peek next expected line expectedLine = expectedDeque.peek(); // fast-forward "unlimited": until next match while (true) { if (actualDeque.isEmpty()) { fail("fast-forward(∞) didn't find: `%s`", snippet(expectedLine)); } if (matches(expectedLine, actualDeque.peek())) { continue main; } actualDeque.pop(); } } int actualLineNumber = actualLines.size() - actualDeque.size() + 1; // 1-based line number fail("expected line #%d doesn't match actual line #%d%n" + "\texpected: `%s`%n" + "\t actual: `%s`", expectedLineNumber, actualLineNumber, expectedLine, actualLine); } // after math if (!actualDeque.isEmpty()) { fail("more actual lines than expected: %d", actualDeque.size()); } } String snippet(String line) { if (line.length() <= MAX_SNIPPET_LENGTH) { return line; } return line.substring(0, MAX_SNIPPET_LENGTH - 5) + "[...]"; } void fail(String format, Object... args) { String newLine = System.lineSeparator(); assertionFailure() // .message(messageOrSupplier) // .reason(format.formatted(args)) // .expected(join(newLine, expectedLines)) // .actual(join(newLine, actualLines)) // .includeValuesInMessage(false) // .trimStacktrace(Assertions.class) // .buildAndThrow(); } } static boolean isFastForwardLine(String line) { line = line.strip(); return line.length() >= 4 && line.startsWith(">>") && line.endsWith(">>"); } static int parseFastForwardLimit(String fastForwardLine) { fastForwardLine = fastForwardLine.strip(); String text = fastForwardLine.substring(2, fastForwardLine.length() - 2).strip(); try { int limit = Integer.parseInt(text); condition(limit > 0, () -> "fast-forward(%d) limit must be greater than zero".formatted(limit)); return limit; } catch (NumberFormatException e) { return Integer.MAX_VALUE; } } static boolean matches(String expectedLine, String actualLine) { notNull(expectedLine, "expected line must not be null"); notNull(actualLine, "actual line must not be null"); if (expectedLine.equals(actualLine)) { return true; } try { return actualLine.matches(expectedLine); } catch (PatternSyntaxException ignore) { return false; } } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotEquals.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import static org.junit.jupiter.api.AssertionUtils.doublesAreEqual; import static org.junit.jupiter.api.AssertionUtils.floatsAreEqual; import static org.junit.jupiter.api.AssertionUtils.objectsAreEqual; import java.util.function.Supplier; import org.jspecify.annotations.Nullable; /** * {@code AssertNotEquals} is a collection of utility methods that support asserting * inequality in objects and primitive values in tests. * * @since 5.0 */ class AssertNotEquals { private AssertNotEquals() { /* no-op */ } /** * @since 5.4 */ static void assertNotEquals(byte unexpected, byte actual) { assertNotEquals(unexpected, actual, (String) null); } /** * @since 5.4 */ static void assertNotEquals(byte unexpected, byte actual, @Nullable String message) { if (unexpected == actual) { failEqual(actual, message); } } /** * @since 5.4 */ static void assertNotEquals(byte unexpected, byte actual, Supplier<@Nullable String> messageSupplier) { if (unexpected == actual) { failEqual(actual, messageSupplier); } } /** * @since 5.4 */ static void assertNotEquals(short unexpected, short actual) { assertNotEquals(unexpected, actual, (String) null); } /** * @since 5.4 */ static void assertNotEquals(short unexpected, short actual, @Nullable String message) { if (unexpected == actual) { failEqual(actual, message); } } /** * @since 5.4 */ static void assertNotEquals(short unexpected, short actual, Supplier<@Nullable String> messageSupplier) { if (unexpected == actual) { failEqual(actual, messageSupplier); } } /** * @since 5.4 */ static void assertNotEquals(int unexpected, int actual) { assertNotEquals(unexpected, actual, (String) null); } /** * @since 5.4 */ static void assertNotEquals(int unexpected, int actual, @Nullable String message) { if (unexpected == actual) { failEqual(actual, message); } } /** * @since 5.4 */ static void assertNotEquals(int unexpected, int actual, Supplier<@Nullable String> messageSupplier) { if (unexpected == actual) { failEqual(actual, messageSupplier); } } /** * @since 5.4 */ static void assertNotEquals(long unexpected, long actual) { assertNotEquals(unexpected, actual, (String) null); } /** * @since 5.4 */ static void assertNotEquals(long unexpected, long actual, @Nullable String message) { if (unexpected == actual) { failEqual(actual, message); } } /** * @since 5.4 */ static void assertNotEquals(long unexpected, long actual, Supplier<@Nullable String> messageSupplier) { if (unexpected == actual) { failEqual(actual, messageSupplier); } } /** * @since 5.4 */ static void assertNotEquals(float unexpected, float actual) { assertNotEquals(unexpected, actual, (String) null); } /** * @since 5.4 */ static void assertNotEquals(float unexpected, float actual, @Nullable String message) { if (floatsAreEqual(unexpected, actual)) { failEqual(actual, message); } } /** * @since 5.4 */ static void assertNotEquals(float unexpected, float actual, Supplier<@Nullable String> messageSupplier) { if (floatsAreEqual(unexpected, actual)) { failEqual(actual, messageSupplier); } } /** * @since 5.4 */ static void assertNotEquals(float unexpected, float actual, float delta) { assertNotEquals(unexpected, actual, delta, (String) null); } /** * @since 5.4 */ static void assertNotEquals(float unexpected, float actual, float delta, @Nullable String message) { if (floatsAreEqual(unexpected, actual, delta)) { failEqual(actual, message); } } /** * @since 5.4 */ static void assertNotEquals(float unexpected, float actual, float delta, Supplier<@Nullable String> messageSupplier) { if (floatsAreEqual(unexpected, actual, delta)) { failEqual(actual, messageSupplier); } } /** * @since 5.4 */ static void assertNotEquals(double unexpected, double actual) { assertNotEquals(unexpected, actual, (String) null); } /** * @since 5.4 */ static void assertNotEquals(double unexpected, double actual, @Nullable String message) { if (doublesAreEqual(unexpected, actual)) { failEqual(actual, message); } } /** * @since 5.4 */ static void assertNotEquals(double unexpected, double actual, Supplier<@Nullable String> messageSupplier) { if (doublesAreEqual(unexpected, actual)) { failEqual(actual, messageSupplier); } } /** * @since 5.4 */ static void assertNotEquals(double unexpected, double actual, double delta) { assertNotEquals(unexpected, actual, delta, (String) null); } /** * @since 5.4 */ static void assertNotEquals(double unexpected, double actual, double delta, @Nullable String message) { if (doublesAreEqual(unexpected, actual, delta)) { failEqual(actual, message); } } /** * @since 5.4 */ static void assertNotEquals(double unexpected, double actual, double delta, Supplier<@Nullable String> messageSupplier) { if (doublesAreEqual(unexpected, actual, delta)) { failEqual(actual, messageSupplier); } } /** * @since 5.4 */ static void assertNotEquals(char unexpected, char actual) { assertNotEquals(unexpected, actual, (String) null); } /** * @since 5.4 */ static void assertNotEquals(char unexpected, char actual, @Nullable String message) { if (unexpected == actual) { failEqual(actual, message); } } /** * @since 5.4 */ static void assertNotEquals(char unexpected, char actual, Supplier<@Nullable String> messageSupplier) { if (unexpected == actual) { failEqual(actual, messageSupplier); } } static void assertNotEquals(@Nullable Object unexpected, @Nullable Object actual) { assertNotEquals(unexpected, actual, (String) null); } static void assertNotEquals(@Nullable Object unexpected, @Nullable Object actual, @Nullable String message) { if (objectsAreEqual(unexpected, actual)) { failEqual(actual, message); } } static void assertNotEquals(@Nullable Object unexpected, @Nullable Object actual, Supplier<@Nullable String> messageSupplier) { if (objectsAreEqual(unexpected, actual)) { failEqual(actual, messageSupplier); } } private static void failEqual(@Nullable Object actual, @Nullable Object messageOrSupplier) { assertionFailure() // .message(messageOrSupplier) // .reason("expected: not equal but was: <" + actual + ">") // .trimStacktrace(Assertions.class) // .buildAndThrow(); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotNull.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import java.util.function.Supplier; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.annotation.Contract; /** * {@code AssertNotNull} is a collection of utility methods that support asserting * that there is an object. * * @since 5.0 */ class AssertNotNull { private AssertNotNull() { /* no-op */ } @Contract("null -> fail") static void assertNotNull(@Nullable Object actual) { assertNotNull(actual, (String) null); } @Contract("null, _ -> fail") static void assertNotNull(@Nullable Object actual, @Nullable String message) { if (actual == null) { failNull(message); } } @Contract("null, _ -> fail") static void assertNotNull(@Nullable Object actual, Supplier<@Nullable String> messageSupplier) { if (actual == null) { failNull(messageSupplier); } } private static void failNull(@Nullable Object messageOrSupplier) { assertionFailure() // .message(messageOrSupplier) // .reason("expected: not ") // .trimStacktrace(Assertions.class) // .buildAndThrow(); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotSame.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import java.util.function.Supplier; import org.jspecify.annotations.Nullable; /** * {@code AssertNotSame} is a collection of utility methods that support asserting * two objects are not the same. * * @since 5.0 */ class AssertNotSame { private AssertNotSame() { /* no-op */ } static void assertNotSame(@Nullable Object unexpected, @Nullable Object actual) { assertNotSame(unexpected, actual, (String) null); } static void assertNotSame(@Nullable Object unexpected, @Nullable Object actual, @Nullable String message) { if (unexpected == actual) { failSame(actual, message); } } static void assertNotSame(@Nullable Object unexpected, @Nullable Object actual, Supplier<@Nullable String> messageSupplier) { if (unexpected == actual) { failSame(actual, messageSupplier); } } private static void failSame(@Nullable Object actual, @Nullable Object messageOrSupplier) { assertionFailure() // .message(messageOrSupplier) // .reason("expected: not same but was: <" + actual + ">") // .trimStacktrace(Assertions.class) // .buildAndThrow(); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNull.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import java.util.function.Supplier; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.annotation.Contract; /** * {@code AssertNull} is a collection of utility methods that support asserting * there is no object. * * @since 5.0 */ class AssertNull { private AssertNull() { /* no-op */ } @Contract("!null -> fail") static void assertNull(@Nullable Object actual) { assertNull(actual, (String) null); } @Contract("!null, _ -> fail") static void assertNull(@Nullable Object actual, @Nullable String message) { if (actual != null) { failNotNull(actual, message); } } @Contract("!null, _ -> fail") static void assertNull(@Nullable Object actual, Supplier<@Nullable String> messageSupplier) { if (actual != null) { failNotNull(actual, messageSupplier); } } private static void failNotNull(@Nullable Object actual, @Nullable Object messageOrSupplier) { assertionFailure() // .message(messageOrSupplier) // .expected(null) // .actual(actual) // .trimStacktrace(Assertions.class) // .buildAndThrow(); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertSame.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import java.util.function.Supplier; import org.jspecify.annotations.Nullable; /** * {@code AssertSame} is a collection of utility methods that support asserting * two objects are the same. * * @since 5.0 */ class AssertSame { private AssertSame() { /* no-op */ } static void assertSame(@Nullable Object expected, @Nullable Object actual) { assertSame(expected, actual, (String) null); } static void assertSame(@Nullable Object expected, @Nullable Object actual, @Nullable String message) { if (expected != actual) { failNotSame(expected, actual, message); } } static void assertSame(@Nullable Object expected, @Nullable Object actual, Supplier<@Nullable String> messageSupplier) { if (expected != actual) { failNotSame(expected, actual, messageSupplier); } } private static void failNotSame(@Nullable Object expected, @Nullable Object actual, @Nullable Object messageOrSupplier) { assertionFailure() // .message(messageOrSupplier) // .expected(expected) // .actual(actual) // .trimStacktrace(Assertions.class) // .buildAndThrow(); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrows.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import static org.junit.jupiter.api.AssertionUtils.getCanonicalName; import java.util.function.Supplier; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.function.Executable; import org.junit.platform.commons.util.UnrecoverableExceptions; /** * {@code AssertThrows} is a collection of utility methods that support asserting * an exception of an expected type is thrown. * * @since 5.0 */ class AssertThrows { private AssertThrows() { /* no-op */ } static T assertThrows(Class expectedType, Executable executable) { return assertThrows(expectedType, executable, (Object) null); } static T assertThrows(Class expectedType, Executable executable, @Nullable String message) { return assertThrows(expectedType, executable, (Object) message); } static T assertThrows(Class expectedType, Executable executable, Supplier<@Nullable String> messageSupplier) { return assertThrows(expectedType, executable, (Object) messageSupplier); } @SuppressWarnings("unchecked") private static T assertThrows(Class expectedType, Executable executable, @Nullable Object messageOrSupplier) { try { executable.execute(); } catch (Throwable actualException) { if (expectedType.isInstance(actualException)) { return (T) actualException; } else { UnrecoverableExceptions.rethrowIfUnrecoverable(actualException); throw assertionFailure() // .message(messageOrSupplier) // .expected(expectedType) // .actual(actualException.getClass()) // .reason("Unexpected exception type thrown") // .cause(actualException) // .trimStacktrace(Assertions.class) // .build(); } } throw assertionFailure() // .message(messageOrSupplier) // .reason("Expected %s to be thrown, but nothing was thrown.".formatted(getCanonicalName(expectedType))) // .trimStacktrace(Assertions.class) // .build(); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrowsExactly.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import static org.junit.jupiter.api.AssertionUtils.getCanonicalName; import java.util.function.Supplier; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.function.Executable; import org.junit.platform.commons.util.UnrecoverableExceptions; /** * {@code AssertThrowsExactly} is a collection of utility methods that support asserting * an exception of an exact type is thrown. * * @since 5.8 */ class AssertThrowsExactly { private AssertThrowsExactly() { /* no-op */ } static T assertThrowsExactly(Class expectedType, Executable executable) { return assertThrowsExactly(expectedType, executable, (Object) null); } static T assertThrowsExactly(Class expectedType, Executable executable, @Nullable String message) { return assertThrowsExactly(expectedType, executable, (Object) message); } static T assertThrowsExactly(Class expectedType, Executable executable, Supplier<@Nullable String> messageSupplier) { return assertThrowsExactly(expectedType, executable, (Object) messageSupplier); } @SuppressWarnings("unchecked") private static T assertThrowsExactly(Class expectedType, Executable executable, @Nullable Object messageOrSupplier) { try { executable.execute(); } catch (Throwable actualException) { if (expectedType.equals(actualException.getClass())) { return (T) actualException; } else { UnrecoverableExceptions.rethrowIfUnrecoverable(actualException); throw assertionFailure() // .message(messageOrSupplier) // .expected(expectedType) // .actual(actualException.getClass()) // .reason("Unexpected exception type thrown") // .cause(actualException) // .trimStacktrace(Assertions.class) // .build(); } } throw assertionFailure() // .message(messageOrSupplier) // .reason("Expected %s to be thrown, but nothing was thrown.".formatted(getCanonicalName(expectedType))) // .trimStacktrace(Assertions.class) // .build(); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import static org.junit.platform.commons.util.ExceptionUtils.throwAsUncheckedException; import java.time.Duration; import java.util.function.Supplier; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.api.function.ThrowingSupplier; /** * {@code AssertTimeout} is a collection of utility methods that support asserting * the execution of the code under test did not take longer than the timeout duration. * * @since 5.0 */ class AssertTimeout { private AssertTimeout() { /* no-op */ } static void assertTimeout(Duration timeout, Executable executable) { assertTimeout(timeout, executable, (String) null); } static void assertTimeout(Duration timeout, Executable executable, @Nullable String message) { AssertTimeout.<@Nullable Object> assertTimeout(timeout, () -> { executable.execute(); return null; }, message); } static void assertTimeout(Duration timeout, Executable executable, Supplier<@Nullable String> messageSupplier) { AssertTimeout.<@Nullable Object> assertTimeout(timeout, () -> { executable.execute(); return null; }, messageSupplier); } static T assertTimeout(Duration timeout, ThrowingSupplier supplier) { return assertTimeout(timeout, supplier, (Object) null); } static T assertTimeout(Duration timeout, ThrowingSupplier supplier, @Nullable String message) { return assertTimeout(timeout, supplier, (Object) message); } static T assertTimeout(Duration timeout, ThrowingSupplier supplier, Supplier<@Nullable String> messageSupplier) { return assertTimeout(timeout, supplier, (Object) messageSupplier); } private static T assertTimeout(Duration timeout, ThrowingSupplier supplier, @Nullable Object messageOrSupplier) { long timeoutInMillis = timeout.toMillis(); long start = System.currentTimeMillis(); T result; try { result = supplier.get(); } catch (Throwable ex) { throw throwAsUncheckedException(ex); } long timeElapsed = System.currentTimeMillis() - start; if (timeElapsed > timeoutInMillis) { assertionFailure() // .message(messageOrSupplier) // .reason("execution exceeded timeout of " + timeoutInMillis + " ms by " + (timeElapsed - timeoutInMillis) + " ms") // .trimStacktrace(Assertions.class) // .buildAndThrow(); } return result; } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeoutPreemptively.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import static org.junit.jupiter.api.timeout.PreemptiveTimeoutUtils.executeWithPreemptiveTimeout; import java.time.Duration; import java.util.function.Supplier; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.api.function.ThrowingSupplier; import org.opentest4j.AssertionFailedError; /** * {@code AssertTimeout} is a collection of utility methods that support asserting * the execution of the code under test did not take longer than the timeout duration * using a preemptive approach. * * @since 5.9.1 */ class AssertTimeoutPreemptively { static void assertTimeoutPreemptively(Duration timeout, Executable executable) { assertTimeoutPreemptively(timeout, executable, (String) null); } @SuppressWarnings("NullAway") static void assertTimeoutPreemptively(Duration timeout, Executable executable, @Nullable String message) { assertTimeoutPreemptively(timeout, () -> { executable.execute(); return null; }, message); } @SuppressWarnings("NullAway") static void assertTimeoutPreemptively(Duration timeout, Executable executable, Supplier<@Nullable String> messageSupplier) { assertTimeoutPreemptively(timeout, () -> { executable.execute(); return null; }, messageSupplier); } static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier) { return executeWithPreemptiveTimeout(timeout, supplier, null, AssertTimeoutPreemptively::createAssertionFailure); } static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, @Nullable String message) { return executeWithPreemptiveTimeout(timeout, supplier, message == null ? null : () -> message, AssertTimeoutPreemptively::createAssertionFailure); } static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, Supplier<@Nullable String> messageSupplier) { return executeWithPreemptiveTimeout(timeout, supplier, messageSupplier, AssertTimeoutPreemptively::createAssertionFailure); } private static AssertionFailedError createAssertionFailure(Duration timeout, @Nullable Supplier<@Nullable String> messageSupplier, @Nullable Throwable cause, @Nullable Thread thread) { return assertionFailure() // .message(messageSupplier) // .reason("execution timed out after " + timeout.toMillis() + " ms") // .cause(cause) // .trimStacktrace(Assertions.class) // .build(); } private AssertTimeoutPreemptively() { } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTrue.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import java.util.function.Supplier; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.annotation.Contract; /** * {@code AssertTrue} is a collection of utility methods that support asserting * {@code true} in tests. * * @since 5.0 */ class AssertTrue { private AssertTrue() { /* no-op */ } @Contract("false -> fail") static void assertTrue(boolean condition) { assertTrue(condition, (String) null); } @Contract("false, _ -> fail") static void assertTrue(boolean condition, @Nullable String message) { if (!condition) { failNotTrue(message); } } @Contract("false, _ -> fail") static void assertTrue(boolean condition, Supplier<@Nullable String> messageSupplier) { if (!condition) { failNotTrue(messageSupplier); } } private static void failNotTrue(@Nullable Object messageOrSupplier) { assertionFailure() // .message(messageOrSupplier) // .expected(true) // .actual(false) // .trimStacktrace(Assertions.class) // .buildAndThrow(); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionFailureBuilder.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import static org.junit.jupiter.api.AssertionUtils.getCanonicalName; import java.util.Arrays; import java.util.function.Supplier; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.annotation.Contract; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.StringUtils; import org.opentest4j.AssertionFailedError; /** * Builder for {@link AssertionFailedError AssertionFailedErrors}. * *

Using this builder ensures consistency in how failure message are formatted * within JUnit Jupiter and for custom user-defined assertions. * * @since 5.9 * @see AssertionFailedError */ @API(status = STABLE, since = "5.9") public class AssertionFailureBuilder { private static final int DEFAULT_RETAIN_STACKTRACE_ELEMENTS = 1; private @Nullable Object message; private @Nullable Throwable cause; private boolean mismatch; private @Nullable Object expected; private @Nullable Object actual; private @Nullable String reason; private boolean includeValuesInMessage = true; private @Nullable Class trimStackTraceTarget; private int retainStackTraceElements = DEFAULT_RETAIN_STACKTRACE_ELEMENTS; /** * Create a new {@code AssertionFailureBuilder}. */ public static AssertionFailureBuilder assertionFailure() { return new AssertionFailureBuilder(); } private AssertionFailureBuilder() { } /** * Set the user-defined message of the assertion. * *

The {@code message} may be passed as a {@link Supplier} or plain * {@link String}. If any other type is passed, it is converted to * {@code String} as per {@link StringUtils#nullSafeToString(Object)}. * * @param message the user-defined failure message; may be {@code null} * @return this builder for method chaining */ public AssertionFailureBuilder message(@Nullable Object message) { this.message = message; return this; } /** * Set the reason why the assertion failed. * * @param reason the failure reason; may be {@code null} * @return this builder for method chaining */ public AssertionFailureBuilder reason(@Nullable String reason) { this.reason = reason; return this; } /** * Set the cause of the assertion failure. * * @param cause the failure cause; may be {@code null} * @return this builder for method chaining */ public AssertionFailureBuilder cause(@Nullable Throwable cause) { this.cause = cause; return this; } /** * Set the expected value of the assertion. * * @param expected the expected value; may be {@code null} * @return this builder for method chaining */ public AssertionFailureBuilder expected(@Nullable Object expected) { this.mismatch = true; this.expected = expected; return this; } /** * Set the actual value of the assertion. * * @param actual the actual value; may be {@code null} * @return this builder for method chaining */ public AssertionFailureBuilder actual(@Nullable Object actual) { this.mismatch = true; this.actual = actual; return this; } /** * Set whether to include the actual and expected values in the generated * failure message. * * @param includeValuesInMessage whether to include the actual and expected * values * @return this builder for method chaining */ public AssertionFailureBuilder includeValuesInMessage(boolean includeValuesInMessage) { this.includeValuesInMessage = includeValuesInMessage; return this; } /** * Set target to trim the stacktrace to. * *

Unless {@link #retainStackTraceElements(int)} is set all stacktrace * elements before the last element from {@code target} are trimmed. * * @param target class to trim from the stacktrace * @return this builder for method chaining */ @API(status = EXPERIMENTAL, since = "6.1") public AssertionFailureBuilder trimStacktrace(@Nullable Class target) { this.trimStackTraceTarget = target; return this; } /** * Set depth to trim the stacktrace to. Defaults to * {@value #DEFAULT_RETAIN_STACKTRACE_ELEMENTS}. * *

If {@link #trimStacktrace(Class)} was set, all but * {@code retainStackTraceElements - 1} stacktrace elements before the last * element from {@code target} are removed. If * {@code retainStackTraceElements} is zero, all elements including those * from {@code target} are trimmed. * * @param retainStackTraceElements depth of trimming, must be non-negative * @return this builder for method chaining */ @API(status = EXPERIMENTAL, since = "6.1") public AssertionFailureBuilder retainStackTraceElements(int retainStackTraceElements) { Preconditions.condition(retainStackTraceElements >= 0, "retainStackTraceElements must have a non-negative value"); this.retainStackTraceElements = retainStackTraceElements; return this; } /** * Build the {@link AssertionFailedError AssertionFailedError} and throw it. * * @throws AssertionFailedError always */ @Contract(" -> fail") public void buildAndThrow() throws AssertionFailedError { throw build(); } /** * Build the {@link AssertionFailedError AssertionFailedError} without * throwing it. * * @return the built assertion failure */ public AssertionFailedError build() { String reason = nullSafeGet(this.reason); if (mismatch && includeValuesInMessage) { reason = (reason == null ? "" : reason + ", ") + formatValues(expected, actual); } String message = nullSafeGet(this.message); if (reason != null) { message = buildPrefix(message) + reason; } var assertionFailedError = mismatch // ? new AssertionFailedError(message, expected, actual, cause) // : new AssertionFailedError(message, cause); maybeTrimStackTrace(assertionFailedError); return assertionFailedError; } private void maybeTrimStackTrace(Throwable throwable) { if (trimStackTraceTarget == null) { return; } var pruneTargetClassName = trimStackTraceTarget.getName(); var stackTrace = throwable.getStackTrace(); int lastIndexOf = -1; for (int i = 0; i < stackTrace.length; i++) { var element = stackTrace[i]; var className = element.getClassName(); if (className.equals(pruneTargetClassName)) { lastIndexOf = i; } } if (lastIndexOf != -1) { int from = clamp0(lastIndexOf + 1 - retainStackTraceElements, stackTrace.length); var trimmed = Arrays.copyOfRange(stackTrace, from, stackTrace.length); throwable.setStackTrace(trimmed); } } private static int clamp0(int value, int max) { return Math.max(0, Math.min(value, max)); } private static @Nullable String nullSafeGet(@Nullable Object messageOrSupplier) { if (messageOrSupplier == null) { return null; } if (messageOrSupplier instanceof Supplier supplier) { Object message = supplier.get(); return StringUtils.nullSafeToString(message); } return StringUtils.nullSafeToString(messageOrSupplier); } private static String buildPrefix(@Nullable String message) { return (StringUtils.isNotBlank(message) ? message + " ==> " : ""); } private static String formatValues(@Nullable Object expected, @Nullable Object actual) { String expectedString = toString(expected); String actualString = toString(actual); if (expectedString.equals(actualString)) { return "expected: %s but was: %s".formatted(formatClassAndValue(expected, expectedString), formatClassAndValue(actual, actualString)); } return "expected: <%s> but was: <%s>".formatted(expectedString, actualString); } private static String formatClassAndValue(@Nullable Object value, String valueString) { // If the value is null, return instead of null. if (value == null) { return ""; } String classAndHash = getClassName(value) + toHash(value); // if it's a class, there's no need to repeat the class name contained in the valueString. return (value instanceof Class ? "<" + classAndHash + ">" : classAndHash + "<" + valueString + ">"); } private static String toString(@Nullable Object obj) { if (obj instanceof Class clazz) { return getCanonicalName(clazz); } return StringUtils.nullSafeToString(obj); } private static String toHash(@Nullable Object obj) { return (obj == null ? "" : "@" + Integer.toHexString(System.identityHashCode(obj))); } private static String getClassName(@Nullable Object obj) { return (obj == null ? "null" : obj instanceof Class clazz ? getCanonicalName(clazz) : obj.getClass().getName()); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static java.util.stream.Collectors.joining; import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import java.util.Deque; import java.util.function.Supplier; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.UnrecoverableExceptions; import org.opentest4j.AssertionFailedError; /** * {@code AssertionUtils} is a collection of utility methods that are common to * all assertion implementations. * * @since 5.0 */ class AssertionUtils { private AssertionUtils() { /* no-op */ } static AssertionFailedError failure() { throw assertionFailure() // .trimStacktrace(Assertions.class) // .build(); } static AssertionFailedError failure(@Nullable String message) { return assertionFailure() // .message(message) // .trimStacktrace(Assertions.class) // .build(); } static AssertionFailedError failure(@Nullable String message, @Nullable Throwable cause) { return assertionFailure() // .message(message) // .cause(cause) // .trimStacktrace(Assertions.class) // .build(); } static AssertionFailedError failure(@Nullable Throwable cause) { throw assertionFailure() // .cause(cause) // .trimStacktrace(Assertions.class) // .build(); } static AssertionFailedError failure(Supplier<@Nullable String> messageSupplier) { return assertionFailure() // .message(nullSafeGet(messageSupplier)) // .trimStacktrace(Assertions.class) // .build(); } static @Nullable String nullSafeGet(@Nullable Supplier<@Nullable String> messageSupplier) { return (messageSupplier != null ? messageSupplier.get() : null); } static String getCanonicalName(Class clazz) { try { String canonicalName = clazz.getCanonicalName(); return (canonicalName != null ? canonicalName : clazz.getTypeName()); } catch (Throwable t) { UnrecoverableExceptions.rethrowIfUnrecoverable(t); return clazz.getTypeName(); } } static String formatIndexes(@Nullable Deque indexes) { if (indexes == null || indexes.isEmpty()) { return ""; } String indexesString = indexes.stream().map(Object::toString).collect(joining("][", "[", "]")); return " at index " + indexesString; } static boolean floatsAreEqual(float value1, float value2, float delta) { assertValidDelta(delta); return floatsAreEqual(value1, value2) || Math.abs(value1 - value2) <= delta; } static void assertValidDelta(float delta) { if (Float.isNaN(delta) || delta < 0.0) { failIllegalDelta(String.valueOf(delta)); } } static void assertValidDelta(double delta) { if (Double.isNaN(delta) || delta < 0.0) { failIllegalDelta(String.valueOf(delta)); } } static boolean floatsAreEqual(float value1, float value2) { return Float.floatToIntBits(value1) == Float.floatToIntBits(value2); } static boolean doublesAreEqual(double value1, double value2, double delta) { assertValidDelta(delta); return doublesAreEqual(value1, value2) || Math.abs(value1 - value2) <= delta; } static boolean doublesAreEqual(double value1, double value2) { return Double.doubleToLongBits(value1) == Double.doubleToLongBits(value2); } static boolean objectsAreEqual(@Nullable Object obj1, @Nullable Object obj2) { if (obj1 == null) { return (obj2 == null); } return obj1.equals(obj2); } private static void failIllegalDelta(String delta) { throw failure("positive delta expected but was: <" + delta + ">"); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assertions.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.STABLE; import java.time.Duration; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.function.BooleanSupplier; import java.util.function.Supplier; import java.util.stream.Stream; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.api.function.ThrowingSupplier; import org.junit.platform.commons.annotation.Contract; import org.opentest4j.MultipleFailuresError; /** * {@code Assertions} is a collection of utility methods that support asserting * conditions in tests. * *

Unless otherwise noted, a failed assertion will throw an * {@link org.opentest4j.AssertionFailedError} or a subclass thereof. * *

Object Equality

* *

Assertion methods comparing two objects for equality, such as the * {@code assertEquals(expected, actual)} and {@code assertNotEquals(unexpected, actual)} * variants, are only intended to test equality for an (un-)expected value * and an actual value. They are not designed for testing whether a class correctly * implements {@link Object#equals(Object)}. For example, {@code assertEquals()} * might immediately return {@code true} when provided the same object for the * expected and actual values, without calling {@code equals(Object)} at all. * Tests that aim to verify the {@code equals(Object)} implementation should instead * be written to explicitly verify the {@link Object#equals(Object)} contract by * using {@link #assertTrue(boolean) assertTrue()} or {@link #assertFalse(boolean) * assertFalse()} — for example, {@code assertTrue(expected.equals(actual))}, * {@code assertTrue(actual.equals(expected))}, {@code assertFalse(expected.equals(null))}, * etc. * *

Kotlin Support

* *

Additional Kotlin assertions can be * found as top-level functions in the {@link org.junit.jupiter.api} * package. * *

Preemptive Timeouts

* *

The various {@code assertTimeoutPreemptively()} methods in this class * execute the provided callback ({@code executable} or {@code supplier}) in a * different thread than that of the calling code. If the timeout is exceeded, * an attempt will be made to preemptively abort execution of the callback by * {@linkplain Thread#interrupt() interrupting} the callback's thread. If the * callback's thread does not return when interrupted, the thread will continue * to run in the background after the {@code assertTimeoutPreemptively()} method * has returned. * *

Furthermore, the behavior of {@code assertTimeoutPreemptively()} methods * can lead to undesirable side effects if the code that is executed within the * callback relies on {@link ThreadLocal} storage. One common example of this is * the transactional testing support in the Spring Framework. Specifically, Spring's * testing support binds transaction state to the current thread (via a * {@code ThreadLocal}) before a test method is invoked. Consequently, if a * callback provided to {@code assertTimeoutPreemptively()} invokes Spring-managed * components that participate in transactions, any actions taken by those * components will not be rolled back with the test-managed transaction. On the * contrary, such actions will be committed to the persistent store (e.g., * relational database) even though the test-managed transaction is rolled back. * Similar side effects may be encountered with other frameworks that rely on * {@code ThreadLocal} storage. * *

Extensibility

* *

Although it is technically possible to extend this class, extension is * strongly discouraged. The JUnit Team highly recommends that the methods * defined in this class be used via static imports. * * @since 5.0 * @see org.opentest4j.AssertionFailedError * @see Assumptions */ @API(status = STABLE, since = "5.0") public class Assertions { /** * Protected constructor allowing subclassing but not direct instantiation. * * @since 5.3 */ @API(status = STABLE, since = "5.3") protected Assertions() { /* no-op */ } // --- fail ---------------------------------------------------------------- /** * Fail the test without a failure message. * *

Although failing with an explicit failure message is recommended, * this method may be useful when maintaining legacy code. * *

See Javadoc for {@link #fail(String)} for an explanation of this method's * generic return type {@code V}. */ @Contract(" -> fail") @SuppressWarnings("TypeParameterUnusedInFormals") public static V fail() { throw AssertionUtils.failure(); } /** * Fail the test with the given failure {@code message}. * *

The generic return type {@code V} allows this method to be used * directly as a single-statement lambda expression, thereby avoiding the * need to implement a code block with an explicit return value. Since this * method throws an {@link org.opentest4j.AssertionFailedError} before its * return statement, this method never actually returns a value to its caller. * The following example demonstrates how this may be used in practice. * *

{@code
	 * Stream.of().map(entry -> fail("should not be called"));
	 * }
*/ @Contract("_ -> fail") @SuppressWarnings("TypeParameterUnusedInFormals") public static V fail(@Nullable String message) { throw AssertionUtils.failure(message); } /** * Fail the test with the given failure {@code message} as well * as the underlying {@code cause}. * *

See Javadoc for {@link #fail(String)} for an explanation of this method's * generic return type {@code V}. */ @Contract("_, _ -> fail") @SuppressWarnings("TypeParameterUnusedInFormals") public static V fail(@Nullable String message, @Nullable Throwable cause) { throw AssertionUtils.failure(message, cause); } /** * Fail the test with the given underlying {@code cause}. * *

See Javadoc for {@link #fail(String)} for an explanation of this method's * generic return type {@code V}. */ @Contract("_ -> fail") @SuppressWarnings("TypeParameterUnusedInFormals") public static V fail(@Nullable Throwable cause) { throw AssertionUtils.failure(cause); } /** * Fail the test with the failure message retrieved from the * given {@code messageSupplier}. * *

See Javadoc for {@link #fail(String)} for an explanation of this method's * generic return type {@code V}. */ @Contract("_ -> fail") @SuppressWarnings("TypeParameterUnusedInFormals") public static V fail(Supplier<@Nullable String> messageSupplier) { throw AssertionUtils.failure(messageSupplier); } // --- assertTrue ---------------------------------------------------------- /** * Assert that the supplied {@code condition} is {@code true}. */ @Contract("false -> fail") public static void assertTrue(boolean condition) { AssertTrue.assertTrue(condition); } /** * Assert that the supplied {@code condition} is {@code true}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ @Contract("false, _ -> fail") public static void assertTrue(boolean condition, Supplier<@Nullable String> messageSupplier) { AssertTrue.assertTrue(condition, messageSupplier); } /** * Assert that the boolean condition supplied by * {@code booleanSupplier} is {@code true}. * *

The supplier will be called exactly once so calling this method is * equivalent to calling {@code assertTrue(booleanSupplier.get())}. */ public static void assertTrue(BooleanSupplier booleanSupplier) { assertTrue(booleanSupplier.getAsBoolean()); } /** * Assert that the boolean condition supplied by * {@code booleanSupplier} is {@code true}. * *

Fails with the supplied failure {@code message}. * *

The supplier will be called exactly once so calling this method is * equivalent to calling {@code assertTrue(booleanSupplier.get(), message)}. */ public static void assertTrue(BooleanSupplier booleanSupplier, @Nullable String message) { assertTrue(booleanSupplier.getAsBoolean(), message); } /** * Assert that the supplied {@code condition} is {@code true}. *

Fails with the supplied failure {@code message}. */ @Contract("false, _ -> fail") public static void assertTrue(boolean condition, @Nullable String message) { AssertTrue.assertTrue(condition, message); } /** * Assert that the boolean condition supplied by * {@code booleanSupplier} is {@code true}. * *

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. * *

The {@code booleanSupplier} will be called exactly once so calling * this method is equivalent to calling * {@code assertTrue(booleanSupplier.get(), messageSupplier)}. */ public static void assertTrue(BooleanSupplier booleanSupplier, Supplier<@Nullable String> messageSupplier) { assertTrue(booleanSupplier.getAsBoolean(), messageSupplier); } // --- assertFalse --------------------------------------------------------- /** * Assert that the supplied {@code condition} is {@code false}. */ @Contract("true -> fail") public static void assertFalse(boolean condition) { AssertFalse.assertFalse(condition); } /** * Assert that the supplied {@code condition} is {@code false}. *

Fails with the supplied failure {@code message}. */ @Contract("true, _ -> fail") public static void assertFalse(boolean condition, @Nullable String message) { AssertFalse.assertFalse(condition, message); } /** * Assert that the supplied {@code condition} is {@code false}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ @Contract("true, _ -> fail") public static void assertFalse(boolean condition, Supplier<@Nullable String> messageSupplier) { AssertFalse.assertFalse(condition, messageSupplier); } /** * Assert that the boolean condition supplied by {@code booleanSupplier} is {@code false}. */ public static void assertFalse(BooleanSupplier booleanSupplier) { AssertFalse.assertFalse(booleanSupplier); } /** * Assert that the boolean condition supplied by {@code booleanSupplier} is {@code false}. *

Fails with the supplied failure {@code message}. */ public static void assertFalse(BooleanSupplier booleanSupplier, @Nullable String message) { AssertFalse.assertFalse(booleanSupplier, message); } /** * Assert that the boolean condition supplied by {@code booleanSupplier} is {@code false}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ public static void assertFalse(BooleanSupplier booleanSupplier, Supplier<@Nullable String> messageSupplier) { AssertFalse.assertFalse(booleanSupplier, messageSupplier); } // --- assertNull ---------------------------------------------------------- /** * Assert that {@code actual} is {@code null}. */ @Contract("!null -> fail") public static void assertNull(@Nullable Object actual) { AssertNull.assertNull(actual); } /** * Assert that {@code actual} is {@code null}. *

Fails with the supplied failure {@code message}. */ @Contract("!null, _ -> fail") public static void assertNull(@Nullable Object actual, @Nullable String message) { AssertNull.assertNull(actual, message); } /** * Assert that {@code actual} is {@code null}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ @Contract("!null, _ -> fail") public static void assertNull(@Nullable Object actual, Supplier<@Nullable String> messageSupplier) { AssertNull.assertNull(actual, messageSupplier); } // --- assertNotNull ------------------------------------------------------- /** * Assert that {@code actual} is not {@code null}. */ @Contract("null -> fail") public static void assertNotNull(@Nullable Object actual) { AssertNotNull.assertNotNull(actual); } /** * Assert that {@code actual} is not {@code null}. *

Fails with the supplied failure {@code message}. */ @Contract("null, _ -> fail") public static void assertNotNull(@Nullable Object actual, @Nullable String message) { AssertNotNull.assertNotNull(actual, message); } /** * Assert that {@code actual} is not {@code null}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ @Contract("null, _ -> fail") public static void assertNotNull(@Nullable Object actual, Supplier<@Nullable String> messageSupplier) { AssertNotNull.assertNotNull(actual, messageSupplier); } // --- assertEquals -------------------------------------------------------- /** * Assert that {@code expected} and {@code actual} are equal. */ public static void assertEquals(short expected, short actual) { AssertEquals.assertEquals(expected, actual); } /** * Assert that {@code expected} and {@code actual} are equal. */ public static void assertEquals(short expected, @Nullable Short actual) { AssertEquals.assertEquals((Short) expected, actual); } /** * Assert that {@code expected} and {@code actual} are equal. */ public static void assertEquals(@Nullable Short expected, short actual) { AssertEquals.assertEquals(expected, (Short) actual); } /** * Assert that {@code expected} and {@code actual} are equal. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertEquals(@Nullable Short expected, @Nullable Short actual) { AssertEquals.assertEquals(expected, actual); } /** * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. */ public static void assertEquals(short expected, short actual, @Nullable String message) { AssertEquals.assertEquals(expected, actual, message); } /** * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. */ public static void assertEquals(short expected, @Nullable Short actual, @Nullable String message) { AssertEquals.assertEquals((Short) expected, actual, message); } /** * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. */ public static void assertEquals(@Nullable Short expected, short actual, @Nullable String message) { AssertEquals.assertEquals(expected, (Short) actual, message); } /** * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertEquals(@Nullable Short expected, @Nullable Short actual, @Nullable String message) { AssertEquals.assertEquals(expected, actual, message); } /** * Assert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ public static void assertEquals(short expected, short actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ public static void assertEquals(short expected, @Nullable Short actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals((Short) expected, actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ public static void assertEquals(@Nullable Short expected, short actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, (Short) actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertEquals(@Nullable Short expected, @Nullable Short actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} are equal. */ public static void assertEquals(byte expected, byte actual) { AssertEquals.assertEquals(expected, actual); } /** * Assert that {@code expected} and {@code actual} are equal. */ public static void assertEquals(byte expected, @Nullable Byte actual) { AssertEquals.assertEquals((Byte) expected, actual); } /** * Assert that {@code expected} and {@code actual} are equal. */ public static void assertEquals(@Nullable Byte expected, byte actual) { AssertEquals.assertEquals(expected, (Byte) actual); } /** * Assert that {@code expected} and {@code actual} are equal. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertEquals(@Nullable Byte expected, @Nullable Byte actual) { AssertEquals.assertEquals(expected, actual); } /** * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. */ public static void assertEquals(byte expected, byte actual, @Nullable String message) { AssertEquals.assertEquals(expected, actual, message); } /** * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. */ public static void assertEquals(byte expected, @Nullable Byte actual, @Nullable String message) { AssertEquals.assertEquals((Byte) expected, actual, message); } /** * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. */ public static void assertEquals(@Nullable Byte expected, byte actual, @Nullable String message) { AssertEquals.assertEquals(expected, (Byte) actual, message); } /** * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertEquals(@Nullable Byte expected, @Nullable Byte actual, @Nullable String message) { AssertEquals.assertEquals(expected, actual, message); } /** * Assert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ public static void assertEquals(byte expected, byte actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ public static void assertEquals(byte expected, @Nullable Byte actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals((Byte) expected, actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ public static void assertEquals(@Nullable Byte expected, byte actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, (Byte) actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertEquals(@Nullable Byte expected, @Nullable Byte actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} are equal. */ public static void assertEquals(int expected, int actual) { AssertEquals.assertEquals(expected, actual); } /** * Assert that {@code expected} and {@code actual} are equal. */ public static void assertEquals(int expected, @Nullable Integer actual) { AssertEquals.assertEquals((Integer) expected, actual); } /** * Assert that {@code expected} and {@code actual} are equal. */ public static void assertEquals(@Nullable Integer expected, int actual) { AssertEquals.assertEquals(expected, (Integer) actual); } /** * Assert that {@code expected} and {@code actual} are equal. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertEquals(@Nullable Integer expected, @Nullable Integer actual) { AssertEquals.assertEquals(expected, actual); } /** * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. */ public static void assertEquals(int expected, int actual, @Nullable String message) { AssertEquals.assertEquals(expected, actual, message); } /** * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. */ public static void assertEquals(int expected, @Nullable Integer actual, @Nullable String message) { AssertEquals.assertEquals((Integer) expected, actual, message); } /** * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. */ public static void assertEquals(@Nullable Integer expected, int actual, @Nullable String message) { AssertEquals.assertEquals(expected, (Integer) actual, message); } /** * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertEquals(@Nullable Integer expected, @Nullable Integer actual, @Nullable String message) { AssertEquals.assertEquals(expected, actual, message); } /** * Assert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ public static void assertEquals(int expected, int actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ public static void assertEquals(int expected, @Nullable Integer actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals((Integer) expected, actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ public static void assertEquals(@Nullable Integer expected, int actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, (Integer) actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertEquals(@Nullable Integer expected, @Nullable Integer actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} are equal. */ public static void assertEquals(long expected, long actual) { AssertEquals.assertEquals(expected, actual); } /** * Assert that {@code expected} and {@code actual} are equal. */ public static void assertEquals(long expected, @Nullable Long actual) { AssertEquals.assertEquals((Long) expected, actual); } /** * Assert that {@code expected} and {@code actual} are equal. */ public static void assertEquals(@Nullable Long expected, long actual) { AssertEquals.assertEquals(expected, (Long) actual); } /** * Assert that {@code expected} and {@code actual} are equal. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertEquals(@Nullable Long expected, @Nullable Long actual) { AssertEquals.assertEquals(expected, actual); } /** * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. */ public static void assertEquals(long expected, long actual, @Nullable String message) { AssertEquals.assertEquals(expected, actual, message); } /** * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. */ public static void assertEquals(long expected, @Nullable Long actual, @Nullable String message) { AssertEquals.assertEquals((Long) expected, actual, message); } /** * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. */ public static void assertEquals(@Nullable Long expected, long actual, @Nullable String message) { AssertEquals.assertEquals(expected, (Long) actual, message); } /** * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertEquals(@Nullable Long expected, @Nullable Long actual, @Nullable String message) { AssertEquals.assertEquals(expected, actual, message); } /** * Assert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ public static void assertEquals(long expected, long actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ public static void assertEquals(long expected, @Nullable Long actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals((Long) expected, actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ public static void assertEquals(@Nullable Long expected, long actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, (Long) actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertEquals(@Nullable Long expected, @Nullable Long actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} are equal. *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and * {@link Float#compare(float, float)}. */ public static void assertEquals(float expected, float actual) { AssertEquals.assertEquals(expected, actual); } /** * Assert that {@code expected} and {@code actual} are equal. *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and * {@link Float#compare(float, float)}. */ public static void assertEquals(float expected, @Nullable Float actual) { AssertEquals.assertEquals((Float) expected, actual); } /** * Assert that {@code expected} and {@code actual} are equal. *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and * {@link Float#compare(float, float)}. */ public static void assertEquals(@Nullable Float expected, float actual) { AssertEquals.assertEquals(expected, (Float) actual); } /** * Assert that {@code expected} and {@code actual} are equal. *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and * {@link Float#compare(float, float)}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertEquals(@Nullable Float expected, @Nullable Float actual) { AssertEquals.assertEquals(expected, actual); } /** * Assert that {@code expected} and {@code actual} are equal. *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and * {@link Float#compare(float, float)}. *

Fails with the supplied failure {@code message}. */ public static void assertEquals(float expected, float actual, @Nullable String message) { AssertEquals.assertEquals(expected, actual, message); } /** * Assert that {@code expected} and {@code actual} are equal. *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and * {@link Float#compare(float, float)}. *

Fails with the supplied failure {@code message}. */ public static void assertEquals(float expected, @Nullable Float actual, @Nullable String message) { AssertEquals.assertEquals((Float) expected, actual, message); } /** * Assert that {@code expected} and {@code actual} are equal. *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and * {@link Float#compare(float, float)}. *

Fails with the supplied failure {@code message}. */ public static void assertEquals(@Nullable Float expected, float actual, @Nullable String message) { AssertEquals.assertEquals(expected, (Float) actual, message); } /** * Assert that {@code expected} and {@code actual} are equal. *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and * {@link Float#compare(float, float)}. *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertEquals(@Nullable Float expected, @Nullable Float actual, @Nullable String message) { AssertEquals.assertEquals(expected, actual, message); } /** * Assert that {@code expected} and {@code actual} are equal. *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and * {@link Float#compare(float, float)}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ public static void assertEquals(float expected, float actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} are equal. *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and * {@link Float#compare(float, float)}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ public static void assertEquals(float expected, @Nullable Float actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals((Float) expected, actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} are equal. *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and * {@link Float#compare(float, float)}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ public static void assertEquals(@Nullable Float expected, float actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, (Float) actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} are equal. *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and * {@link Float#compare(float, float)}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertEquals(@Nullable Float expected, @Nullable Float actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} are equal within the given non-negative {@code delta}. *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and * {@link Float#compare(float, float)}. */ public static void assertEquals(float expected, float actual, float delta) { AssertEquals.assertEquals(expected, actual, delta); } /** * Assert that {@code expected} and {@code actual} are equal within the given non-negative {@code delta}. *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and * {@link Float#compare(float, float)}. *

Fails with the supplied failure {@code message}. */ public static void assertEquals(float expected, float actual, float delta, @Nullable String message) { AssertEquals.assertEquals(expected, actual, delta, message); } /** * Assert that {@code expected} and {@code actual} are equal within the given non-negative {@code delta}. *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and * {@link Float#compare(float, float)}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ public static void assertEquals(float expected, float actual, float delta, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, actual, delta, messageSupplier); } /** * Assert that {@code expected} and {@code actual} are equal. *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and * {@link Double#compare(double, double)}. */ public static void assertEquals(double expected, double actual) { AssertEquals.assertEquals(expected, actual); } /** * Assert that {@code expected} and {@code actual} are equal. *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and * {@link Double#compare(double, double)}. */ public static void assertEquals(double expected, @Nullable Double actual) { AssertEquals.assertEquals((Double) expected, actual); } /** * Assert that {@code expected} and {@code actual} are equal. *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and * {@link Double#compare(double, double)}. */ public static void assertEquals(@Nullable Double expected, double actual) { AssertEquals.assertEquals(expected, (Double) actual); } /** * Assert that {@code expected} and {@code actual} are equal. *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and * {@link Double#compare(double, double)}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertEquals(@Nullable Double expected, @Nullable Double actual) { AssertEquals.assertEquals(expected, actual); } /** * Assert that {@code expected} and {@code actual} are equal. *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and * {@link Double#compare(double, double)}. *

Fails with the supplied failure {@code message}. */ public static void assertEquals(double expected, double actual, @Nullable String message) { AssertEquals.assertEquals(expected, actual, message); } /** * Assert that {@code expected} and {@code actual} are equal. *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and * {@link Double#compare(double, double)}. *

Fails with the supplied failure {@code message}. */ public static void assertEquals(double expected, @Nullable Double actual, @Nullable String message) { AssertEquals.assertEquals((Double) expected, actual, message); } /** * Assert that {@code expected} and {@code actual} are equal. *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and * {@link Double#compare(double, double)}. *

Fails with the supplied failure {@code message}. */ public static void assertEquals(@Nullable Double expected, double actual, @Nullable String message) { AssertEquals.assertEquals(expected, (Double) actual, message); } /** * Assert that {@code expected} and {@code actual} are equal. *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and * {@link Double#compare(double, double)}. *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertEquals(@Nullable Double expected, @Nullable Double actual, @Nullable String message) { AssertEquals.assertEquals(expected, actual, message); } /** * Assert that {@code expected} and {@code actual} are equal. *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and * {@link Double#compare(double, double)}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ public static void assertEquals(double expected, double actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} are equal. *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and * {@link Double#compare(double, double)}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ public static void assertEquals(double expected, @Nullable Double actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals((Double) expected, actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} are equal. *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and * {@link Double#compare(double, double)}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ public static void assertEquals(@Nullable Double expected, double actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, (Double) actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} are equal. *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and * {@link Double#compare(double, double)}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertEquals(@Nullable Double expected, @Nullable Double actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} are equal within the given non-negative {@code delta}. *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and * {@link Double#compare(double, double)}. */ public static void assertEquals(double expected, double actual, double delta) { AssertEquals.assertEquals(expected, actual, delta); } /** * Assert that {@code expected} and {@code actual} are equal within the given non-negative {@code delta}. *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and * {@link Double#compare(double, double)}. *

Fails with the supplied failure {@code message}. */ public static void assertEquals(double expected, double actual, double delta, @Nullable String message) { AssertEquals.assertEquals(expected, actual, delta, message); } /** * Assert that {@code expected} and {@code actual} are equal within the given non-negative {@code delta}. *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and * {@link Double#compare(double, double)}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ public static void assertEquals(double expected, double actual, double delta, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, actual, delta, messageSupplier); } /** * Assert that {@code expected} and {@code actual} are equal. */ public static void assertEquals(char expected, char actual) { AssertEquals.assertEquals(expected, actual); } /** * Assert that {@code expected} and {@code actual} are equal. */ public static void assertEquals(char expected, @Nullable Character actual) { AssertEquals.assertEquals((Character) expected, actual); } /** * Assert that {@code expected} and {@code actual} are equal. */ public static void assertEquals(@Nullable Character expected, char actual) { AssertEquals.assertEquals(expected, (Character) actual); } /** * Assert that {@code expected} and {@code actual} are equal. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertEquals(@Nullable Character expected, @Nullable Character actual) { AssertEquals.assertEquals(expected, actual); } /** * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. */ public static void assertEquals(char expected, char actual, @Nullable String message) { AssertEquals.assertEquals(expected, actual, message); } /** * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. */ public static void assertEquals(char expected, @Nullable Character actual, @Nullable String message) { AssertEquals.assertEquals((Character) expected, actual, message); } /** * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. */ public static void assertEquals(@Nullable Character expected, char actual, @Nullable String message) { AssertEquals.assertEquals(expected, (Character) actual, message); } /** * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertEquals(@Nullable Character expected, @Nullable Character actual, @Nullable String message) { AssertEquals.assertEquals(expected, actual, message); } /** * Assert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ public static void assertEquals(char expected, char actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ public static void assertEquals(char expected, @Nullable Character actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals((Character) expected, actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ public static void assertEquals(@Nullable Character expected, char actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, (Character) actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertEquals(@Nullable Character expected, @Nullable Character actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} are equal. *

If both are {@code null}, they are considered equal. * * @see Object#equals(Object) */ public static void assertEquals(@Nullable Object expected, @Nullable Object actual) { AssertEquals.assertEquals(expected, actual); } /** * Assert that {@code expected} and {@code actual} are equal. *

If both are {@code null}, they are considered equal. *

Fails with the supplied failure {@code message}. * * @see Object#equals(Object) */ public static void assertEquals(@Nullable Object expected, @Nullable Object actual, @Nullable String message) { AssertEquals.assertEquals(expected, actual, message); } /** * Assert that {@code expected} and {@code actual} are equal. *

If both are {@code null}, they are considered equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. * * @see Object#equals(Object) */ public static void assertEquals(@Nullable Object expected, @Nullable Object actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, actual, messageSupplier); } // --- assertArrayEquals --------------------------------------------------- /** * Assert that {@code expected} and {@code actual} boolean arrays are equal. *

If both are {@code null}, they are considered equal. */ public static void assertArrayEquals(boolean @Nullable [] expected, boolean @Nullable [] actual) { AssertArrayEquals.assertArrayEquals(expected, actual); } /** * Assert that {@code expected} and {@code actual} boolean arrays are equal. *

If both are {@code null}, they are considered equal. *

Fails with the supplied failure {@code message}. */ public static void assertArrayEquals(boolean @Nullable [] expected, boolean @Nullable [] actual, @Nullable String message) { AssertArrayEquals.assertArrayEquals(expected, actual, message); } /** * Assert that {@code expected} and {@code actual} boolean arrays are equal. *

If both are {@code null}, they are considered equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ public static void assertArrayEquals(boolean @Nullable [] expected, boolean @Nullable [] actual, Supplier<@Nullable String> messageSupplier) { AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} char arrays are equal. *

If both are {@code null}, they are considered equal. */ public static void assertArrayEquals(char @Nullable [] expected, char @Nullable [] actual) { AssertArrayEquals.assertArrayEquals(expected, actual); } /** * Assert that {@code expected} and {@code actual} char arrays are equal. *

If both are {@code null}, they are considered equal. *

Fails with the supplied failure {@code message}. */ public static void assertArrayEquals(char @Nullable [] expected, char @Nullable [] actual, @Nullable String message) { AssertArrayEquals.assertArrayEquals(expected, actual, message); } /** * Assert that {@code expected} and {@code actual} char arrays are equal. *

If both are {@code null}, they are considered equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ public static void assertArrayEquals(char @Nullable [] expected, char @Nullable [] actual, Supplier<@Nullable String> messageSupplier) { AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} byte arrays are equal. *

If both are {@code null}, they are considered equal. */ public static void assertArrayEquals(byte @Nullable [] expected, byte @Nullable [] actual) { AssertArrayEquals.assertArrayEquals(expected, actual); } /** * Assert that {@code expected} and {@code actual} byte arrays are equal. *

If both are {@code null}, they are considered equal. *

Fails with the supplied failure {@code message}. */ public static void assertArrayEquals(byte @Nullable [] expected, byte @Nullable [] actual, @Nullable String message) { AssertArrayEquals.assertArrayEquals(expected, actual, message); } /** * Assert that {@code expected} and {@code actual} byte arrays are equal. *

If both are {@code null}, they are considered equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ public static void assertArrayEquals(byte @Nullable [] expected, byte @Nullable [] actual, Supplier<@Nullable String> messageSupplier) { AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} short arrays are equal. *

If both are {@code null}, they are considered equal. */ public static void assertArrayEquals(short @Nullable [] expected, short @Nullable [] actual) { AssertArrayEquals.assertArrayEquals(expected, actual); } /** * Assert that {@code expected} and {@code actual} short arrays are equal. *

If both are {@code null}, they are considered equal. *

Fails with the supplied failure {@code message}. */ public static void assertArrayEquals(short @Nullable [] expected, short @Nullable [] actual, @Nullable String message) { AssertArrayEquals.assertArrayEquals(expected, actual, message); } /** * Assert that {@code expected} and {@code actual} short arrays are equal. *

If both are {@code null}, they are considered equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ public static void assertArrayEquals(short @Nullable [] expected, short @Nullable [] actual, Supplier<@Nullable String> messageSupplier) { AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} int arrays are equal. *

If both are {@code null}, they are considered equal. */ public static void assertArrayEquals(int @Nullable [] expected, int @Nullable [] actual) { AssertArrayEquals.assertArrayEquals(expected, actual); } /** * Assert that {@code expected} and {@code actual} int arrays are equal. *

If both are {@code null}, they are considered equal. *

Fails with the supplied failure {@code message}. */ public static void assertArrayEquals(int @Nullable [] expected, int @Nullable [] actual, @Nullable String message) { AssertArrayEquals.assertArrayEquals(expected, actual, message); } /** * Assert that {@code expected} and {@code actual} int arrays are equal. *

If both are {@code null}, they are considered equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ public static void assertArrayEquals(int @Nullable [] expected, int @Nullable [] actual, Supplier<@Nullable String> messageSupplier) { AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} long arrays are equal. *

If both are {@code null}, they are considered equal. */ public static void assertArrayEquals(long @Nullable [] expected, long @Nullable [] actual) { AssertArrayEquals.assertArrayEquals(expected, actual); } /** * Assert that {@code expected} and {@code actual} long arrays are equal. *

If both are {@code null}, they are considered equal. *

Fails with the supplied failure {@code message}. */ public static void assertArrayEquals(long @Nullable [] expected, long @Nullable [] actual, @Nullable String message) { AssertArrayEquals.assertArrayEquals(expected, actual, message); } /** * Assert that {@code expected} and {@code actual} long arrays are equal. *

If both are {@code null}, they are considered equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ public static void assertArrayEquals(long @Nullable [] expected, long @Nullable [] actual, Supplier<@Nullable String> messageSupplier) { AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} float arrays are equal. *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and * {@link Float#compare(float, float)}. */ public static void assertArrayEquals(float @Nullable [] expected, float @Nullable [] actual) { AssertArrayEquals.assertArrayEquals(expected, actual); } /** * Assert that {@code expected} and {@code actual} float arrays are equal. *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and * {@link Float#compare(float, float)}. *

Fails with the supplied failure {@code message}. */ public static void assertArrayEquals(float @Nullable [] expected, float @Nullable [] actual, @Nullable String message) { AssertArrayEquals.assertArrayEquals(expected, actual, message); } /** * Assert that {@code expected} and {@code actual} float arrays are equal. *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and * {@link Float#compare(float, float)}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ public static void assertArrayEquals(float @Nullable [] expected, float @Nullable [] actual, Supplier<@Nullable String> messageSupplier) { AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} float arrays are equal within the given non-negative {@code delta}. *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and * {@link Float#compare(float, float)}. */ public static void assertArrayEquals(float @Nullable [] expected, float @Nullable [] actual, float delta) { AssertArrayEquals.assertArrayEquals(expected, actual, delta); } /** * Assert that {@code expected} and {@code actual} float arrays are equal within the given non-negative {@code delta}. *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and * {@link Float#compare(float, float)}. *

Fails with the supplied failure {@code message}. */ public static void assertArrayEquals(float @Nullable [] expected, float @Nullable [] actual, float delta, @Nullable String message) { AssertArrayEquals.assertArrayEquals(expected, actual, delta, message); } /** * Assert that {@code expected} and {@code actual} float arrays are equal within the given non-negative {@code delta}. *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and * {@link Float#compare(float, float)}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ public static void assertArrayEquals(float @Nullable [] expected, float @Nullable [] actual, float delta, Supplier<@Nullable String> messageSupplier) { AssertArrayEquals.assertArrayEquals(expected, actual, delta, messageSupplier); } /** * Assert that {@code expected} and {@code actual} double arrays are equal. *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and * {@link Double#compare(double, double)}. */ public static void assertArrayEquals(double @Nullable [] expected, double @Nullable [] actual) { AssertArrayEquals.assertArrayEquals(expected, actual); } /** * Assert that {@code expected} and {@code actual} double arrays are equal. *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and * {@link Double#compare(double, double)}. *

Fails with the supplied failure {@code message}. */ public static void assertArrayEquals(double @Nullable [] expected, double @Nullable [] actual, @Nullable String message) { AssertArrayEquals.assertArrayEquals(expected, actual, message); } /** * Assert that {@code expected} and {@code actual} double arrays are equal. *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and * {@link Double#compare(double, double)}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ public static void assertArrayEquals(double @Nullable [] expected, double @Nullable [] actual, Supplier<@Nullable String> messageSupplier) { AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); } /** * Assert that {@code expected} and {@code actual} double arrays are equal within the given non-negative {@code delta}. *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and * {@link Double#compare(double, double)}. */ public static void assertArrayEquals(double @Nullable [] expected, double @Nullable [] actual, double delta) { AssertArrayEquals.assertArrayEquals(expected, actual, delta); } /** * Assert that {@code expected} and {@code actual} double arrays are equal within the given non-negative {@code delta}. *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and * {@link Double#compare(double, double)}. *

Fails with the supplied failure {@code message}. */ public static void assertArrayEquals(double @Nullable [] expected, double @Nullable [] actual, double delta, @Nullable String message) { AssertArrayEquals.assertArrayEquals(expected, actual, delta, message); } /** * Assert that {@code expected} and {@code actual} double arrays are equal within the given non-negative {@code delta}. *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and * {@link Double#compare(double, double)}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ public static void assertArrayEquals(double @Nullable [] expected, double @Nullable [] actual, double delta, Supplier<@Nullable String> messageSupplier) { AssertArrayEquals.assertArrayEquals(expected, actual, delta, messageSupplier); } /** * Assert that {@code expected} and {@code actual} object arrays are deeply equal. *

If both are {@code null}, they are considered equal. *

Nested float arrays are checked as in {@link #assertEquals(float, float)}. *

Nested double arrays are checked as in {@link #assertEquals(double, double)}. * * @see Objects#equals(Object, Object) * @see Arrays#deepEquals(Object[], Object[]) */ public static void assertArrayEquals(@Nullable Object @Nullable [] expected, @Nullable Object @Nullable [] actual) { AssertArrayEquals.assertArrayEquals(expected, actual); } /** * Assert that {@code expected} and {@code actual} object arrays are deeply equal. *

If both are {@code null}, they are considered equal. *

Nested float arrays are checked as in {@link #assertEquals(float, float)}. *

Nested double arrays are checked as in {@link #assertEquals(double, double)}. *

Fails with the supplied failure {@code message}. * * @see Objects#equals(Object, Object) * @see Arrays#deepEquals(Object[], Object[]) */ public static void assertArrayEquals(@Nullable Object @Nullable [] expected, @Nullable Object @Nullable [] actual, @Nullable String message) { AssertArrayEquals.assertArrayEquals(expected, actual, message); } /** * Assert that {@code expected} and {@code actual} object arrays are deeply equal. *

If both are {@code null}, they are considered equal. *

Nested float arrays are checked as in {@link #assertEquals(float, float)}. *

Nested double arrays are checked as in {@link #assertEquals(double, double)}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. * * @see Objects#equals(Object, Object) * @see Arrays#deepEquals(Object[], Object[]) */ public static void assertArrayEquals(@Nullable Object @Nullable [] expected, @Nullable Object @Nullable [] actual, Supplier<@Nullable String> messageSupplier) { AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); } // --- assertIterableEquals -------------------------------------------- /** * Assert that {@code expected} and {@code actual} iterables are deeply equal. *

Similarly to the check for deep equality in {@link #assertArrayEquals(Object[], Object[])}, * if two iterables are encountered (including {@code expected} and {@code actual}) then their * iterators must return equal elements in the same order as each other. Note: * this means that the iterables do not need to be of the same type. Example:

{@code
	 * import static java.util.Arrays.asList;
	 *  ...
	 * Iterable i0 = new ArrayList<>(asList(1, 2, 3));
	 * Iterable i1 = new LinkedList<>(asList(1, 2, 3));
	 * assertIterableEquals(i0, i1); // Passes
	 * }
*

If both {@code expected} and {@code actual} are {@code null}, they are considered equal. * * @see Objects#equals(Object, Object) * @see Arrays#deepEquals(Object[], Object[]) * @see #assertArrayEquals(Object[], Object[]) */ public static void assertIterableEquals(@Nullable Iterable expected, @Nullable Iterable actual) { AssertIterableEquals.assertIterableEquals(expected, actual); } /** * Assert that {@code expected} and {@code actual} iterables are deeply equal. *

Similarly to the check for deep equality in * {@link #assertArrayEquals(Object[], Object[], String)}, if two iterables are encountered * (including {@code expected} and {@code actual}) then their iterators must return equal * elements in the same order as each other. Note: this means that the iterables * do not need to be of the same type. Example:

{@code
	 * import static java.util.Arrays.asList;
	 *  ...
	 * Iterable i0 = new ArrayList<>(asList(1, 2, 3));
	 * Iterable i1 = new LinkedList<>(asList(1, 2, 3));
	 * assertIterableEquals(i0, i1); // Passes
	 * }
*

If both {@code expected} and {@code actual} are {@code null}, they are considered equal. *

Fails with the supplied failure {@code message}. * * @see Objects#equals(Object, Object) * @see Arrays#deepEquals(Object[], Object[]) * @see #assertArrayEquals(Object[], Object[], String) */ public static void assertIterableEquals(@Nullable Iterable expected, @Nullable Iterable actual, @Nullable String message) { AssertIterableEquals.assertIterableEquals(expected, actual, message); } /** * Assert that {@code expected} and {@code actual} iterables are deeply equal. *

Similarly to the check for deep equality in * {@link #assertArrayEquals(Object[], Object[], Supplier)}, if two iterables are encountered * (including {@code expected} and {@code actual}) then their iterators must return equal * elements in the same order as each other. Note: this means that the iterables * do not need to be of the same type. Example:

{@code
	 * import static java.util.Arrays.asList;
	 *  ...
	 * Iterable i0 = new ArrayList<>(asList(1, 2, 3));
	 * Iterable i1 = new LinkedList<>(asList(1, 2, 3));
	 * assertIterableEquals(i0, i1); // Passes
	 * }
*

If both {@code expected} and {@code actual} are {@code null}, they are considered equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. * * @see Objects#equals(Object, Object) * @see Arrays#deepEquals(Object[], Object[]) * @see #assertArrayEquals(Object[], Object[], Supplier) */ public static void assertIterableEquals(@Nullable Iterable expected, @Nullable Iterable actual, Supplier<@Nullable String> messageSupplier) { AssertIterableEquals.assertIterableEquals(expected, actual, messageSupplier); } // --- assertLinesMatch ---------------------------------------------------- /** * Assert that {@code expected} list of {@linkplain String}s matches {@code actual} * list. * *

This method differs from other assertions that effectively only check {@link String#equals(Object)}, * in that it uses the following staged matching algorithm: * *

For each pair of expected and actual lines do *

    *
  1. check if {@code expected.equals(actual)} - if yes, continue with next pair
  2. *
  3. otherwise treat {@code expected} as a regular expression and check via * {@link String#matches(String)} - if yes, continue with next pair
  4. *
  5. otherwise check if {@code expected} line is a fast-forward marker, if yes apply * fast-forward actual lines accordingly (see below) and goto 1.
  6. *
* *

A valid fast-forward marker is an expected line that starts and ends with the literal * {@code >>} and contains at least 4 characters. Examples: *

    *
  • {@code >>>>}
    {@code >> stacktrace >>}
    {@code >> single line, non Integer.parse()-able comment >>} *
    Skip arbitrary number of actual lines, until first matching subsequent expected line is found. Any * character between the fast-forward literals are discarded.
  • *
  • {@code ">> 21 >>"} *
    Skip strictly 21 lines. If they can't be skipped for any reason, an assertion error is raised.
  • *
* *

Here is an example showing all three kinds of expected line formats: *

{@code
	 * ls -la /
	 * total [\d]+
	 * drwxr-xr-x  0 root root   512 Jan  1  1970 .
	 * drwxr-xr-x  0 root root   512 Jan  1  1970 ..
	 * drwxr-xr-x  0 root root   512 Apr  5 07:45 bin
	 * >> 4 >>
	 * -rwxr-xr-x  1 root root [\d]+ Jan  1  1970 init
	 * >> M A N Y  M O R E  E N T R I E S >>
	 * drwxr-xr-x  0 root root   512 Sep 22  2017 var
	 * }
*

Fails with a generated failure message describing the difference. */ public static void assertLinesMatch(List expectedLines, List actualLines) { AssertLinesMatch.assertLinesMatch(expectedLines, actualLines); } /** * Assert that {@code expected} list of {@linkplain String}s matches {@code actual} * list. * *

Find a detailed description of the matching algorithm in {@link #assertLinesMatch(List, List)}. * *

Fails with the supplied failure {@code message} and the generated message. * * @see #assertLinesMatch(List, List) */ public static void assertLinesMatch(List expectedLines, List actualLines, @Nullable String message) { AssertLinesMatch.assertLinesMatch(expectedLines, actualLines, message); } /** * Assert that {@code expected} list of {@linkplain String}s matches {@code actual} * list. * *

Find a detailed description of the matching algorithm in {@link #assertLinesMatch(List, List)}. * *

If necessary, a custom failure message will be retrieved lazily from the supplied * {@code messageSupplier}. Fails with the custom failure message prepended to * a generated failure message describing the difference. * * @see #assertLinesMatch(List, List) */ public static void assertLinesMatch(List expectedLines, List actualLines, Supplier<@Nullable String> messageSupplier) { AssertLinesMatch.assertLinesMatch(expectedLines, actualLines, messageSupplier); } /** * Assert that {@code expected} stream of {@linkplain String}s matches {@code actual} * stream. * *

Find a detailed description of the matching algorithm in {@link #assertLinesMatch(List, List)}. * *

Note: An implementation of this method may consume all lines of both streams eagerly and * delegate the evaluation to {@link #assertLinesMatch(List, List)}. * * @since 5.7 * @see #assertLinesMatch(List, List) */ public static void assertLinesMatch(Stream expectedLines, Stream actualLines) { AssertLinesMatch.assertLinesMatch(expectedLines, actualLines); } /** * Assert that {@code expected} stream of {@linkplain String}s matches {@code actual} * stream. * *

Find a detailed description of the matching algorithm in {@link #assertLinesMatch(List, List)}. * *

Fails with the supplied failure {@code message} and the generated message. * *

Note: An implementation of this method may consume all lines of both streams eagerly and * delegate the evaluation to {@link #assertLinesMatch(List, List)}. * * @since 5.7 * @see #assertLinesMatch(List, List) */ public static void assertLinesMatch(Stream expectedLines, Stream actualLines, @Nullable String message) { AssertLinesMatch.assertLinesMatch(expectedLines, actualLines, message); } /** * Assert that {@code expected} stream of {@linkplain String}s matches {@code actual} * stream. * *

Find a detailed description of the matching algorithm in {@link #assertLinesMatch(List, List)}. * *

If necessary, a custom failure message will be retrieved lazily from the supplied * {@code messageSupplier}. Fails with the custom failure message prepended to * a generated failure message describing the difference. * *

Note: An implementation of this method may consume all lines of both streams eagerly and * delegate the evaluation to {@link #assertLinesMatch(List, List)}. * * @since 5.7 * @see #assertLinesMatch(List, List) */ public static void assertLinesMatch(Stream expectedLines, Stream actualLines, Supplier<@Nullable String> messageSupplier) { AssertLinesMatch.assertLinesMatch(expectedLines, actualLines, messageSupplier); } // --- assertNotEquals ----------------------------------------------------- /** * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(byte unexpected, byte actual) { AssertNotEquals.assertNotEquals(unexpected, actual); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(byte unexpected, @Nullable Byte actual) { AssertNotEquals.assertNotEquals((Byte) unexpected, actual); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Byte unexpected, byte actual) { AssertNotEquals.assertNotEquals(unexpected, (Byte) actual); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Byte unexpected, @Nullable Byte actual) { AssertNotEquals.assertNotEquals(unexpected, actual); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(byte unexpected, byte actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, actual, message); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(byte unexpected, @Nullable Byte actual, @Nullable String message) { AssertNotEquals.assertNotEquals((Byte) unexpected, actual, message); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Byte unexpected, byte actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, (Byte) actual, message); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Byte unexpected, @Nullable Byte actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, actual, message); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(byte unexpected, byte actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(byte unexpected, @Nullable Byte actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals((Byte) unexpected, actual, messageSupplier); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Byte unexpected, byte actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, (Byte) actual, messageSupplier); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Byte unexpected, @Nullable Byte actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(short unexpected, short actual) { AssertNotEquals.assertNotEquals(unexpected, actual); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(short unexpected, @Nullable Short actual) { AssertNotEquals.assertNotEquals((Short) unexpected, actual); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Short unexpected, short actual) { AssertNotEquals.assertNotEquals(unexpected, (Short) actual); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Short unexpected, @Nullable Short actual) { AssertNotEquals.assertNotEquals(unexpected, actual); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(short unexpected, short actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, actual, message); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(short unexpected, @Nullable Short actual, @Nullable String message) { AssertNotEquals.assertNotEquals((Short) unexpected, actual, message); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Short unexpected, short actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, (Short) actual, message); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Short unexpected, @Nullable Short actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, actual, message); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(short unexpected, short actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(short unexpected, @Nullable Short actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals((Short) unexpected, actual, messageSupplier); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Short unexpected, short actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, (Short) actual, messageSupplier); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Short unexpected, @Nullable Short actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(int unexpected, int actual) { AssertNotEquals.assertNotEquals(unexpected, actual); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(int unexpected, @Nullable Integer actual) { AssertNotEquals.assertNotEquals((Integer) unexpected, actual); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Integer unexpected, int actual) { AssertNotEquals.assertNotEquals(unexpected, (Integer) actual); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Integer unexpected, @Nullable Integer actual) { AssertNotEquals.assertNotEquals(unexpected, actual); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(int unexpected, int actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, actual, message); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(int unexpected, @Nullable Integer actual, @Nullable String message) { AssertNotEquals.assertNotEquals((Integer) unexpected, actual, message); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Integer unexpected, int actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, (Integer) actual, message); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Integer unexpected, @Nullable Integer actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, actual, message); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(int unexpected, int actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(int unexpected, @Nullable Integer actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals((Integer) unexpected, actual, messageSupplier); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Integer unexpected, int actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, (Integer) actual, messageSupplier); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Integer unexpected, @Nullable Integer actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(long unexpected, long actual) { AssertNotEquals.assertNotEquals(unexpected, actual); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(long unexpected, @Nullable Long actual) { AssertNotEquals.assertNotEquals((Long) unexpected, actual); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Long unexpected, long actual) { AssertNotEquals.assertNotEquals(unexpected, (Long) actual); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Long unexpected, @Nullable Long actual) { AssertNotEquals.assertNotEquals(unexpected, actual); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(long unexpected, long actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, actual, message); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(long unexpected, @Nullable Long actual, @Nullable String message) { AssertNotEquals.assertNotEquals((Long) unexpected, actual, message); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Long unexpected, long actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, (Long) actual, message); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Long unexpected, @Nullable Long actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, actual, message); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(long unexpected, long actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(long unexpected, @Nullable Long actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals((Long) unexpected, actual, messageSupplier); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Long unexpected, long actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, (Long) actual, messageSupplier); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Long unexpected, @Nullable Long actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Inequality imposed by this method is consistent with * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(float unexpected, float actual) { AssertNotEquals.assertNotEquals(unexpected, actual); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Inequality imposed by this method is consistent with * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(float unexpected, @Nullable Float actual) { AssertNotEquals.assertNotEquals((Float) unexpected, actual); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Inequality imposed by this method is consistent with * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Float unexpected, float actual) { AssertNotEquals.assertNotEquals(unexpected, (Float) actual); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Inequality imposed by this method is consistent with * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Float unexpected, @Nullable Float actual) { AssertNotEquals.assertNotEquals(unexpected, actual); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Inequality imposed by this method is consistent with * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. * *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(float unexpected, float actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, actual, message); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Inequality imposed by this method is consistent with * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. * *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(float unexpected, @Nullable Float actual, @Nullable String message) { AssertNotEquals.assertNotEquals((Float) unexpected, actual, message); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Inequality imposed by this method is consistent with * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. * *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Float unexpected, float actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, (Float) actual, message); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Inequality imposed by this method is consistent with * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. * *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Float unexpected, @Nullable Float actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, actual, message); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Inequality imposed by this method is consistent with * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(float unexpected, float actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Inequality imposed by this method is consistent with * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(float unexpected, @Nullable Float actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals((Float) unexpected, actual, messageSupplier); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Inequality imposed by this method is consistent with * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Float unexpected, float actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, (Float) actual, messageSupplier); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Inequality imposed by this method is consistent with * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Float unexpected, @Nullable Float actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); } /** * Assert that {@code unexpected} and {@code actual} are not equal * within the given {@code delta}. * *

Inequality imposed by this method is consistent with * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(float unexpected, float actual, float delta) { AssertNotEquals.assertNotEquals(unexpected, actual, delta); } /** * Assert that {@code unexpected} and {@code actual} are not equal * within the given {@code delta}. * *

Inequality imposed by this method is consistent with * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. * *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(float unexpected, float actual, float delta, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, actual, delta, message); } /** * Assert that {@code unexpected} and {@code actual} are not equal * within the given {@code delta}. * *

Inequality imposed by this method is consistent with * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(float unexpected, float actual, float delta, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, actual, delta, messageSupplier); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Inequality imposed by this method is consistent with * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(double unexpected, double actual) { AssertNotEquals.assertNotEquals(unexpected, actual); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Inequality imposed by this method is consistent with * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(double unexpected, @Nullable Double actual) { AssertNotEquals.assertNotEquals((Double) unexpected, actual); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Inequality imposed by this method is consistent with * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Double unexpected, double actual) { AssertNotEquals.assertNotEquals(unexpected, (Double) actual); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Inequality imposed by this method is consistent with * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Double unexpected, @Nullable Double actual) { AssertNotEquals.assertNotEquals(unexpected, actual); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Inequality imposed by this method is consistent with * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. * *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(double unexpected, double actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, actual, message); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Inequality imposed by this method is consistent with * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. * *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(double unexpected, @Nullable Double actual, @Nullable String message) { AssertNotEquals.assertNotEquals((Double) unexpected, actual, message); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Inequality imposed by this method is consistent with * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. * *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Double unexpected, double actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, (Double) actual, message); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Inequality imposed by this method is consistent with * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. * *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Double unexpected, @Nullable Double actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, actual, message); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Inequality imposed by this method is consistent with * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(double unexpected, double actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Inequality imposed by this method is consistent with * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(double unexpected, @Nullable Double actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals((Double) unexpected, actual, messageSupplier); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Inequality imposed by this method is consistent with * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Double unexpected, double actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, (Double) actual, messageSupplier); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Inequality imposed by this method is consistent with * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Double unexpected, @Nullable Double actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); } /** * Assert that {@code unexpected} and {@code actual} are not equal * within the given {@code delta}. * *

Inequality imposed by this method is consistent with * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(double unexpected, double actual, double delta) { AssertNotEquals.assertNotEquals(unexpected, actual, delta); } /** * Assert that {@code unexpected} and {@code actual} are not equal * within the given {@code delta}. * *

Inequality imposed by this method is consistent with * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. * *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(double unexpected, double actual, double delta, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, actual, delta, message); } /** * Assert that {@code unexpected} and {@code actual} are not equal * within the given {@code delta}. * *

Inequality imposed by this method is consistent with * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(double unexpected, double actual, double delta, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, actual, delta, messageSupplier); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(char unexpected, char actual) { AssertNotEquals.assertNotEquals(unexpected, actual); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(char unexpected, @Nullable Character actual) { AssertNotEquals.assertNotEquals((Character) unexpected, actual); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Character unexpected, char actual) { AssertNotEquals.assertNotEquals(unexpected, (Character) actual); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Character unexpected, @Nullable Character actual) { AssertNotEquals.assertNotEquals(unexpected, actual); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(char unexpected, char actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, actual, message); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(char unexpected, @Nullable Character actual, @Nullable String message) { AssertNotEquals.assertNotEquals((Character) unexpected, actual, message); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Character unexpected, char actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, (Character) actual, message); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Fails with the supplied failure {@code message}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Character unexpected, @Nullable Character actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, actual, message); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(char unexpected, char actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(char unexpected, @Nullable Character actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals((Character) unexpected, actual, messageSupplier); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Character unexpected, char actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, (Character) actual, messageSupplier); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. * * @since 5.4 */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(@Nullable Character unexpected, @Nullable Character actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Fails if both are {@code null}. * * @see Object#equals(Object) */ public static void assertNotEquals(@Nullable Object unexpected, @Nullable Object actual) { AssertNotEquals.assertNotEquals(unexpected, actual); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Fails if both are {@code null}. * *

Fails with the supplied failure {@code message}. * * @see Object#equals(Object) */ public static void assertNotEquals(@Nullable Object unexpected, @Nullable Object actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, actual, message); } /** * Assert that {@code unexpected} and {@code actual} are not equal. * *

Fails if both are {@code null}. * *

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. * * @see Object#equals(Object) */ public static void assertNotEquals(@Nullable Object unexpected, @Nullable Object actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); } // --- assertSame ---------------------------------------------------------- /** * Assert that the {@code expected} object and the {@code actual} object * are the same object. *

This method should only be used to assert identity between objects. * To assert equality between two objects or two primitive values, * use one of the {@code assertEquals(...)} methods instead — for example, * use {@code assertEquals(999, 999)} instead of {@code assertSame(999, 999)}. */ public static void assertSame(@Nullable Object expected, @Nullable Object actual) { AssertSame.assertSame(expected, actual); } /** * Assert that the {@code expected} object and the {@code actual} object * are the same object. *

This method should only be used to assert identity between objects. * To assert equality between two objects or two primitive values, * use one of the {@code assertEquals(...)} methods instead — for example, * use {@code assertEquals(999, 999)} instead of {@code assertSame(999, 999)}. *

Fails with the supplied failure {@code message}. */ public static void assertSame(@Nullable Object expected, @Nullable Object actual, @Nullable String message) { AssertSame.assertSame(expected, actual, message); } /** * Assert that the {@code expected} object and the {@code actual} object * are the same object. *

This method should only be used to assert identity between objects. * To assert equality between two objects or two primitive values, * use one of the {@code assertEquals(...)} methods instead — for example, * use {@code assertEquals(999, 999)} instead of {@code assertSame(999, 999)}. *

If necessary, the failure message will be retrieved lazily from the supplied * {@code messageSupplier}. */ public static void assertSame(@Nullable Object expected, @Nullable Object actual, Supplier<@Nullable String> messageSupplier) { AssertSame.assertSame(expected, actual, messageSupplier); } // --- assertNotSame ------------------------------------------------------- /** * Assert that the {@code unexpected} object and the {@code actual} * object are not the same object. *

This method should only be used to compare the identity of two * objects. To assert that two objects or two primitive values are not * equal, use one of the {@code assertNotEquals(...)} methods instead. */ public static void assertNotSame(@Nullable Object unexpected, @Nullable Object actual) { AssertNotSame.assertNotSame(unexpected, actual); } /** * Assert that the {@code unexpected} object and the {@code actual} * object are not the same object. *

This method should only be used to compare the identity of two * objects. To assert that two objects or two primitive values are not * equal, use one of the {@code assertNotEquals(...)} methods instead. *

Fails with the supplied failure {@code message}. */ public static void assertNotSame(@Nullable Object unexpected, @Nullable Object actual, @Nullable String message) { AssertNotSame.assertNotSame(unexpected, actual, message); } /** * Assert that the {@code unexpected} object and the {@code actual} * object are not the same object. *

This method should only be used to compare the identity of two * objects. To assert that two objects or two primitive values are not * equal, use one of the {@code assertNotEquals(...)} methods instead. *

If necessary, the failure message will be retrieved lazily from the supplied * {@code messageSupplier}. */ public static void assertNotSame(@Nullable Object unexpected, @Nullable Object actual, Supplier<@Nullable String> messageSupplier) { AssertNotSame.assertNotSame(unexpected, actual, messageSupplier); } // --- assertAll ----------------------------------------------------------- /** * Assert that all supplied {@code executables} do not throw * exceptions. * *

See Javadoc for {@link #assertAll(String, Stream)} for an explanation of this * method's exception handling semantics. * * @see #assertAll(String, Executable...) * @see #assertAll(Collection) * @see #assertAll(String, Collection) * @see #assertAll(Stream) * @see #assertAll(String, Stream) */ public static void assertAll(Executable... executables) throws MultipleFailuresError { AssertAll.assertAll(executables); } /** * Assert that all supplied {@code executables} do not throw * exceptions. * *

See Javadoc for {@link #assertAll(String, Stream)} for an explanation of this * method's exception handling semantics. * * @see #assertAll(Executable...) * @see #assertAll(Collection) * @see #assertAll(Stream) * @see #assertAll(String, Collection) * @see #assertAll(String, Stream) */ public static void assertAll(@Nullable String heading, Executable... executables) throws MultipleFailuresError { AssertAll.assertAll(heading, executables); } /** * Assert that all supplied {@code executables} do not throw * exceptions. * *

See Javadoc for {@link #assertAll(String, Stream)} for an explanation of this * method's exception handling semantics. * * @see #assertAll(Executable...) * @see #assertAll(String, Executable...) * @see #assertAll(String, Collection) * @see #assertAll(Stream) * @see #assertAll(String, Stream) */ public static void assertAll(Collection executables) throws MultipleFailuresError { AssertAll.assertAll(executables); } /** * Assert that all supplied {@code executables} do not throw * exceptions. * *

See Javadoc for {@link #assertAll(String, Stream)} for an explanation of this * method's exception handling semantics. * * @see #assertAll(Executable...) * @see #assertAll(String, Executable...) * @see #assertAll(Collection) * @see #assertAll(Stream) * @see #assertAll(String, Stream) */ public static void assertAll(@Nullable String heading, Collection executables) throws MultipleFailuresError { AssertAll.assertAll(heading, executables); } /** * Assert that all supplied {@code executables} do not throw * exceptions. * *

See Javadoc for {@link #assertAll(String, Stream)} for an explanation of this * method's exception handling semantics. * * @see #assertAll(Executable...) * @see #assertAll(String, Executable...) * @see #assertAll(Collection) * @see #assertAll(String, Collection) * @see #assertAll(String, Stream) */ public static void assertAll(Stream executables) throws MultipleFailuresError { AssertAll.assertAll(executables); } /** * Assert that all supplied {@code executables} do not throw * exceptions. * *

If any supplied {@link Executable} throws an exception (i.e., a {@link Throwable} * or any subclass thereof), all remaining {@code executables} will still be executed, * and all exceptions will be aggregated and reported in a {@link MultipleFailuresError}. * In addition, all aggregated exceptions will be added as {@linkplain * Throwable#addSuppressed(Throwable) suppressed exceptions} to the * {@code MultipleFailuresError}. However, if one of the {@code executables} throws an * unrecoverable exception — for example, an {@link OutOfMemoryError} * — execution will halt immediately, and the unrecoverable exception will be * rethrown as is but masked as an unchecked exception. * *

The supplied {@code heading} will be included in the message string for the * {@code MultipleFailuresError}. * * @see #assertAll(Executable...) * @see #assertAll(String, Executable...) * @see #assertAll(Collection) * @see #assertAll(String, Collection) * @see #assertAll(Stream) */ public static void assertAll(@Nullable String heading, Stream executables) throws MultipleFailuresError { AssertAll.assertAll(heading, executables); } // --- assert exceptions --------------------------------------------------- // --- executable --- /** * Assert that execution of the supplied {@code executable} throws * an exception of exactly the {@code expectedType} and return the exception. * *

If no exception is thrown, or if an exception of a different type is * thrown, this method will fail. * *

If you do not want to perform additional checks on the exception instance, * ignore the return value. * * @since 5.8 */ @API(status = STABLE, since = "5.10") public static T assertThrowsExactly(Class expectedType, Executable executable) { return AssertThrowsExactly.assertThrowsExactly(expectedType, executable); } /** * Assert that execution of the supplied {@code executable} throws * an exception of exactly the {@code expectedType} and return the exception. * *

If no exception is thrown, or if an exception of a different type is * thrown, this method will fail. * *

If you do not want to perform additional checks on the exception instance, * ignore the return value. * *

Fails with the supplied failure {@code message}. Note that the supplied * {@code message} is not the expected message of the thrown * exception. To assert the expected message of the thrown exception, you must * use a separate, subsequent assertion against the exception returned from * this method. * * @since 5.8 */ @API(status = STABLE, since = "5.10") public static T assertThrowsExactly(Class expectedType, Executable executable, @Nullable String message) { return AssertThrowsExactly.assertThrowsExactly(expectedType, executable, message); } /** * Assert that execution of the supplied {@code executable} throws * an exception of exactly the {@code expectedType} and return the exception. * *

If no exception is thrown, or if an exception of a different type is * thrown, this method will fail. * *

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. Note that the failure message is * not the expected message of the thrown exception. To * assert the expected message of the thrown exception, you must use a * separate, subsequent assertion against the exception returned from this * method. * *

If you do not want to perform additional checks on the exception instance, * ignore the return value. * * @since 5.8 */ @API(status = STABLE, since = "5.10") public static T assertThrowsExactly(Class expectedType, Executable executable, Supplier<@Nullable String> messageSupplier) { return AssertThrowsExactly.assertThrowsExactly(expectedType, executable, messageSupplier); } /** * Assert that execution of the supplied {@code executable} throws * an exception of the {@code expectedType} and return the exception. * *

The assertion passes if the thrown exception type is the same as * {@code expectedType} or a subtype thereof. To check for the exact thrown * type use {@link #assertThrowsExactly(Class, Executable) assertThrowsExactly}. * If no exception is thrown, or if an exception of a different type is thrown, * this method will fail. * *

If you do not want to perform additional checks on the exception instance, * ignore the return value. * * @see #assertThrowsExactly(Class, Executable) */ public static T assertThrows(Class expectedType, Executable executable) { return AssertThrows.assertThrows(expectedType, executable); } /** * Assert that execution of the supplied {@code executable} throws * an exception of the {@code expectedType} and return the exception. * *

The assertion passes if the thrown exception type is the same as * {@code expectedType} or a subtype thereof. To check for the exact thrown * type use {@link #assertThrowsExactly(Class, Executable, String) assertThrowsExactly}. * If no exception is thrown, or if an exception of a different type is thrown, * this method will fail. * *

If you do not want to perform additional checks on the exception instance, * ignore the return value. * *

Fails with the supplied failure {@code message}. Note that the supplied * {@code message} is not the expected message of the thrown * exception. To assert the expected message of the thrown exception, you must * use a separate, subsequent assertion against the exception returned from * this method. * * @see #assertThrowsExactly(Class, Executable, String) */ public static T assertThrows(Class expectedType, Executable executable, @Nullable String message) { return AssertThrows.assertThrows(expectedType, executable, message); } /** * Assert that execution of the supplied {@code executable} throws * an exception of the {@code expectedType} and return the exception. * *

The assertion passes if the thrown exception type is the same as * {@code expectedType} or a subtype thereof. To check for the exact thrown * type use {@link #assertThrowsExactly(Class, Executable, Supplier) assertThrowsExactly}. * If no exception is thrown, or if an exception of a different type is thrown, * this method will fail. * *

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. Note that the failure message is * not the expected message of the thrown exception. To * assert the expected message of the thrown exception, you must use a * separate, subsequent assertion against the exception returned from this * method. * *

If you do not want to perform additional checks on the exception instance, * ignore the return value. * * @see #assertThrowsExactly(Class, Executable, Supplier) */ public static T assertThrows(Class expectedType, Executable executable, Supplier<@Nullable String> messageSupplier) { return AssertThrows.assertThrows(expectedType, executable, messageSupplier); } // --- executable --- /** * Assert that execution of the supplied {@code executable} does * not throw any kind of {@linkplain Throwable exception}. * *

Usage Note

*

Although any exception thrown from a test method will cause the test * to fail, there are certain use cases where it can be beneficial * to explicitly assert that an exception is not thrown for a given code * block within a test method. * * @since 5.2 */ @API(status = STABLE, since = "5.2") public static void assertDoesNotThrow(Executable executable) { AssertDoesNotThrow.assertDoesNotThrow(executable); } /** * Assert that execution of the supplied {@code executable} does * not throw any kind of {@linkplain Throwable exception}. * *

Usage Note

*

Although any exception thrown from a test method will cause the test * to fail, there are certain use cases where it can be beneficial * to explicitly assert that an exception is not thrown for a given code * block within a test method. * *

Fails with the supplied failure {@code message}. * * @since 5.2 */ @API(status = STABLE, since = "5.2") public static void assertDoesNotThrow(Executable executable, @Nullable String message) { AssertDoesNotThrow.assertDoesNotThrow(executable, message); } /** * Assert that execution of the supplied {@code executable} does * not throw any kind of {@linkplain Throwable exception}. * *

Usage Note

*

Although any exception thrown from a test method will cause the test * to fail, there are certain use cases where it can be beneficial * to explicitly assert that an exception is not thrown for a given code * block within a test method. * *

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. * * @since 5.2 */ @API(status = STABLE, since = "5.2") public static void assertDoesNotThrow(Executable executable, Supplier<@Nullable String> messageSupplier) { AssertDoesNotThrow.assertDoesNotThrow(executable, messageSupplier); } // --- supplier --- /** * Assert that execution of the supplied {@code supplier} does * not throw any kind of {@linkplain Throwable exception}. * *

If the assertion passes, the {@code supplier}'s result will be returned. * *

Usage Note

*

Although any exception thrown from a test method will cause the test * to fail, there are certain use cases where it can be beneficial * to explicitly assert that an exception is not thrown for a given code * block within a test method. * * @since 5.2 */ @API(status = STABLE, since = "5.2") public static T assertDoesNotThrow(ThrowingSupplier supplier) { return AssertDoesNotThrow.assertDoesNotThrow(supplier); } /** * Assert that execution of the supplied {@code supplier} does * not throw any kind of {@linkplain Throwable exception}. * *

If the assertion passes, the {@code supplier}'s result will be returned. * *

Fails with the supplied failure {@code message}. * *

Usage Note

*

Although any exception thrown from a test method will cause the test * to fail, there are certain use cases where it can be beneficial * to explicitly assert that an exception is not thrown for a given code * block within a test method. * * @since 5.2 */ @API(status = STABLE, since = "5.2") public static T assertDoesNotThrow(ThrowingSupplier supplier, @Nullable String message) { return AssertDoesNotThrow.assertDoesNotThrow(supplier, message); } /** * Assert that execution of the supplied {@code supplier} does * not throw any kind of {@linkplain Throwable exception}. * *

If the assertion passes, the {@code supplier}'s result will be returned. * *

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. * *

Usage Note

*

Although any exception thrown from a test method will cause the test * to fail, there are certain use cases where it can be beneficial * to explicitly assert that an exception is not thrown for a given code * block within a test method. * * @since 5.2 */ @API(status = STABLE, since = "5.2") public static T assertDoesNotThrow(ThrowingSupplier supplier, Supplier<@Nullable String> messageSupplier) { return AssertDoesNotThrow.assertDoesNotThrow(supplier, messageSupplier); } // --- assertTimeout ------------------------------------------------------- // --- executable --- /** * Assert that execution of the supplied {@code executable} * completes before the given {@code timeout} is exceeded. * *

Note: the {@code executable} will be executed in the same thread as that * of the calling code. Consequently, execution of the {@code executable} will * not be preemptively aborted if the timeout is exceeded. * * @see #assertTimeout(Duration, Executable, String) * @see #assertTimeout(Duration, Executable, Supplier) * @see #assertTimeout(Duration, ThrowingSupplier) * @see #assertTimeout(Duration, ThrowingSupplier, String) * @see #assertTimeout(Duration, ThrowingSupplier, Supplier) * @see #assertTimeoutPreemptively(Duration, Executable) */ public static void assertTimeout(Duration timeout, Executable executable) { AssertTimeout.assertTimeout(timeout, executable); } /** * Assert that execution of the supplied {@code executable} * completes before the given {@code timeout} is exceeded. * *

Note: the {@code executable} will be executed in the same thread as that * of the calling code. Consequently, execution of the {@code executable} will * not be preemptively aborted if the timeout is exceeded. * *

Fails with the supplied failure {@code message}. * * @see #assertTimeout(Duration, Executable) * @see #assertTimeout(Duration, Executable, Supplier) * @see #assertTimeout(Duration, ThrowingSupplier) * @see #assertTimeout(Duration, ThrowingSupplier, String) * @see #assertTimeout(Duration, ThrowingSupplier, Supplier) * @see #assertTimeoutPreemptively(Duration, Executable, String) */ public static void assertTimeout(Duration timeout, Executable executable, @Nullable String message) { AssertTimeout.assertTimeout(timeout, executable, message); } /** * Assert that execution of the supplied {@code executable} * completes before the given {@code timeout} is exceeded. * *

Note: the {@code executable} will be executed in the same thread as that * of the calling code. Consequently, execution of the {@code executable} will * not be preemptively aborted if the timeout is exceeded. * *

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. * * @see #assertTimeout(Duration, Executable) * @see #assertTimeout(Duration, Executable, String) * @see #assertTimeout(Duration, ThrowingSupplier) * @see #assertTimeout(Duration, ThrowingSupplier, String) * @see #assertTimeout(Duration, ThrowingSupplier, Supplier) * @see #assertTimeoutPreemptively(Duration, Executable, Supplier) */ public static void assertTimeout(Duration timeout, Executable executable, Supplier<@Nullable String> messageSupplier) { AssertTimeout.assertTimeout(timeout, executable, messageSupplier); } // --- supplier --- /** * Assert that execution of the supplied {@code supplier} * completes before the given {@code timeout} is exceeded. * *

If the assertion passes then the {@code supplier}'s result is returned. * *

Note: the {@code supplier} will be executed in the same thread as that * of the calling code. Consequently, execution of the {@code supplier} will * not be preemptively aborted if the timeout is exceeded. * * @see #assertTimeout(Duration, Executable) * @see #assertTimeout(Duration, Executable, String) * @see #assertTimeout(Duration, Executable, Supplier) * @see #assertTimeout(Duration, ThrowingSupplier, String) * @see #assertTimeout(Duration, ThrowingSupplier, Supplier) * @see #assertTimeoutPreemptively(Duration, Executable) */ public static T assertTimeout(Duration timeout, ThrowingSupplier supplier) { return AssertTimeout.assertTimeout(timeout, supplier); } /** * Assert that execution of the supplied {@code supplier} * completes before the given {@code timeout} is exceeded. * *

If the assertion passes then the {@code supplier}'s result is returned. * *

Note: the {@code supplier} will be executed in the same thread as that * of the calling code. Consequently, execution of the {@code supplier} will * not be preemptively aborted if the timeout is exceeded. * *

Fails with the supplied failure {@code message}. * * @see #assertTimeout(Duration, Executable) * @see #assertTimeout(Duration, Executable, String) * @see #assertTimeout(Duration, Executable, Supplier) * @see #assertTimeout(Duration, ThrowingSupplier) * @see #assertTimeout(Duration, ThrowingSupplier, Supplier) * @see #assertTimeoutPreemptively(Duration, Executable, String) */ public static T assertTimeout(Duration timeout, ThrowingSupplier supplier, @Nullable String message) { return AssertTimeout.assertTimeout(timeout, supplier, message); } /** * Assert that execution of the supplied {@code supplier} * completes before the given {@code timeout} is exceeded. * *

If the assertion passes then the {@code supplier}'s result is returned. * *

Note: the {@code supplier} will be executed in the same thread as that * of the calling code. Consequently, execution of the {@code supplier} will * not be preemptively aborted if the timeout is exceeded. * *

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. * * @see #assertTimeout(Duration, Executable) * @see #assertTimeout(Duration, Executable, String) * @see #assertTimeout(Duration, Executable, Supplier) * @see #assertTimeout(Duration, ThrowingSupplier) * @see #assertTimeout(Duration, ThrowingSupplier, String) * @see #assertTimeoutPreemptively(Duration, Executable, Supplier) */ public static T assertTimeout(Duration timeout, ThrowingSupplier supplier, Supplier<@Nullable String> messageSupplier) { return AssertTimeout.assertTimeout(timeout, supplier, messageSupplier); } // --- executable - preemptively --- /** * Assert that execution of the supplied {@code executable} * completes before the given {@code timeout} is exceeded. * *

See the {@linkplain Assertions Preemptive Timeouts} section of the * class-level Javadoc for further details. * * @see #assertTimeoutPreemptively(Duration, Executable, String) * @see #assertTimeoutPreemptively(Duration, Executable, Supplier) * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier) * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, String) * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, Supplier) * @see #assertTimeout(Duration, Executable) */ public static void assertTimeoutPreemptively(Duration timeout, Executable executable) { AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, executable); } /** * Assert that execution of the supplied {@code executable} * completes before the given {@code timeout} is exceeded. * *

See the {@linkplain Assertions Preemptive Timeouts} section of the * class-level Javadoc for further details. * *

Fails with the supplied failure {@code message}. * * @see #assertTimeoutPreemptively(Duration, Executable) * @see #assertTimeoutPreemptively(Duration, Executable, Supplier) * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier) * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, String) * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, Supplier) * @see #assertTimeout(Duration, Executable, String) */ public static void assertTimeoutPreemptively(Duration timeout, Executable executable, @Nullable String message) { AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, executable, message); } /** * Assert that execution of the supplied {@code executable} * completes before the given {@code timeout} is exceeded. * *

See the {@linkplain Assertions Preemptive Timeouts} section of the * class-level Javadoc for further details. * *

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. * * @see #assertTimeoutPreemptively(Duration, Executable) * @see #assertTimeoutPreemptively(Duration, Executable, String) * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier) * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, String) * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, Supplier) * @see #assertTimeout(Duration, Executable, Supplier) */ public static void assertTimeoutPreemptively(Duration timeout, Executable executable, Supplier<@Nullable String> messageSupplier) { AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, executable, messageSupplier); } // --- supplier - preemptively --- /** * Assert that execution of the supplied {@code supplier} * completes before the given {@code timeout} is exceeded. * *

See the {@linkplain Assertions Preemptive Timeouts} section of the * class-level Javadoc for further details. * *

If the assertion passes then the {@code supplier}'s result is returned. * * @see #assertTimeoutPreemptively(Duration, Executable) * @see #assertTimeoutPreemptively(Duration, Executable, String) * @see #assertTimeoutPreemptively(Duration, Executable, Supplier) * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, String) * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, Supplier) * @see #assertTimeout(Duration, Executable) */ public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier) { return AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, supplier); } /** * Assert that execution of the supplied {@code supplier} * completes before the given {@code timeout} is exceeded. * *

See the {@linkplain Assertions Preemptive Timeouts} section of the * class-level Javadoc for further details. * *

If the assertion passes then the {@code supplier}'s result is returned. * *

Fails with the supplied failure {@code message}. * * @see #assertTimeoutPreemptively(Duration, Executable) * @see #assertTimeoutPreemptively(Duration, Executable, String) * @see #assertTimeoutPreemptively(Duration, Executable, Supplier) * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier) * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, Supplier) * @see #assertTimeout(Duration, Executable, String) */ public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, @Nullable String message) { return AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, supplier, message); } /** * Assert that execution of the supplied {@code supplier} * completes before the given {@code timeout} is exceeded. * *

See the {@linkplain Assertions Preemptive Timeouts} section of the * class-level Javadoc for further details. * *

If the assertion passes then the {@code supplier}'s result is returned. * *

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. * * @see #assertTimeoutPreemptively(Duration, Executable) * @see #assertTimeoutPreemptively(Duration, Executable, String) * @see #assertTimeoutPreemptively(Duration, Executable, Supplier) * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier) * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, String) * @see #assertTimeout(Duration, Executable, Supplier) */ public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, Supplier<@Nullable String> messageSupplier) { return AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, supplier, messageSupplier); } // --- assertInstanceOf ---------------------------------------------------- /** * Assert that the supplied {@code actualValue} is an instance of the * {@code expectedType}. * *

Like the {@code instanceof} operator a {@code null} value is not * considered to be of the {@code expectedType} and does not pass the assertion. * * @since 5.8 */ @API(status = STABLE, since = "5.10") @Contract("_, null -> fail") public static T assertInstanceOf(Class expectedType, @Nullable Object actualValue) { return AssertInstanceOf.assertInstanceOf(expectedType, actualValue); } /** * Assert that the supplied {@code actualValue} is an instance of the * {@code expectedType}. * *

Like the {@code instanceof} operator a {@code null} value is not * considered to be of the {@code expectedType} and does not pass the assertion. * *

Fails with the supplied failure {@code message}. * * @since 5.8 */ @API(status = STABLE, since = "5.10") @Contract("_, null, _ -> fail") public static T assertInstanceOf(Class expectedType, @Nullable Object actualValue, @Nullable String message) { return AssertInstanceOf.assertInstanceOf(expectedType, actualValue, message); } /** * Assert that the supplied {@code actualValue} is an instance of the * {@code expectedType}. * *

Like the {@code instanceof} operator a {@code null} value is not * considered to be of the {@code expectedType} and does not pass the assertion. * *

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. * * @since 5.8 */ @Contract("_, null, _ -> fail") @API(status = STABLE, since = "5.10") public static T assertInstanceOf(Class expectedType, @Nullable Object actualValue, Supplier<@Nullable String> messageSupplier) { return AssertInstanceOf.assertInstanceOf(expectedType, actualValue, messageSupplier); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assumptions.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.STABLE; import java.util.function.BooleanSupplier; import java.util.function.Supplier; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.function.Executable; import org.junit.platform.commons.annotation.Contract; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.commons.util.StringUtils; import org.opentest4j.TestAbortedException; /** * {@code Assumptions} is a collection of utility methods that support * conditional test execution based on assumptions. * *

In direct contrast to failed {@linkplain Assertions assertions}, * failed assumptions do not result in a test failure; rather, * a failed assumption results in a test being aborted. However, * failed assertions and other exceptions thrown by tests take precedence over * failed assumptions when both are thrown during the execution of a test * (for example, by different lifecycle methods), regardless of the order they * are thrown in. In such cases, the test will be reported as failed * rather than aborted. * *

Assumptions are typically used whenever it does not make sense to * continue execution of a given test method — for example, if the * test depends on something that does not exist in the current runtime * environment. * *

Although it is technically possible to extend this class, extension is * strongly discouraged. The JUnit Team highly recommends that the methods * defined in this class be used via static imports. * * @since 5.0 * @see TestAbortedException * @see Assertions */ @API(status = STABLE, since = "5.0") public class Assumptions { /** * Protected constructor allowing subclassing but not direct instantiation. * * @since 5.3 */ protected Assumptions() { /* no-op */ } // --- assumeTrue ---------------------------------------------------------- /** * Validate the given assumption. * * @param assumption the assumption to validate * @throws TestAbortedException if the assumption is not {@code true} */ @Contract("false -> fail") public static void assumeTrue(boolean assumption) throws TestAbortedException { assumeTrue(assumption, "assumption is not true"); } /** * Validate the given assumption. * * @param assumptionSupplier the supplier of the assumption to validate * @throws TestAbortedException if the assumption is not {@code true} */ public static void assumeTrue(BooleanSupplier assumptionSupplier) throws TestAbortedException { assumeTrue(assumptionSupplier.getAsBoolean(), "assumption is not true"); } /** * Validate the given assumption. * * @param assumptionSupplier the supplier of the assumption to validate * @param message the message to be included in the {@code TestAbortedException} * if the assumption is invalid * @throws TestAbortedException if the assumption is not {@code true} */ public static void assumeTrue(BooleanSupplier assumptionSupplier, @Nullable String message) throws TestAbortedException { assumeTrue(assumptionSupplier.getAsBoolean(), message); } /** * Validate the given assumption. * * @param assumption the assumption to validate * @param messageSupplier the supplier of the message to be included in * the {@code TestAbortedException} if the assumption is invalid * @throws TestAbortedException if the assumption is not {@code true} */ @Contract("false, _ -> fail") public static void assumeTrue(boolean assumption, Supplier<@Nullable String> messageSupplier) throws TestAbortedException { if (!assumption) { throwAssumptionFailed(messageSupplier.get()); } } /** * Validate the given assumption. * * @param assumption the assumption to validate * @param message the message to be included in the {@code TestAbortedException} * if the assumption is invalid * @throws TestAbortedException if the assumption is not {@code true} */ @Contract("false, _ -> fail") public static void assumeTrue(boolean assumption, @Nullable String message) throws TestAbortedException { if (!assumption) { throwAssumptionFailed(message); } } /** * Validate the given assumption. * * @param assumptionSupplier the supplier of the assumption to validate * @param messageSupplier the supplier of the message to be included in * the {@code TestAbortedException} if the assumption is invalid * @throws TestAbortedException if the assumption is not {@code true} */ public static void assumeTrue(BooleanSupplier assumptionSupplier, Supplier<@Nullable String> messageSupplier) throws TestAbortedException { assumeTrue(assumptionSupplier.getAsBoolean(), messageSupplier); } // --- assumeFalse --------------------------------------------------------- /** * Validate the given assumption. * * @param assumption the assumption to validate * @throws TestAbortedException if the assumption is not {@code false} */ @Contract("true -> fail") public static void assumeFalse(boolean assumption) throws TestAbortedException { assumeFalse(assumption, "assumption is not false"); } /** * Validate the given assumption. * * @param assumptionSupplier the supplier of the assumption to validate * @throws TestAbortedException if the assumption is not {@code false} */ public static void assumeFalse(BooleanSupplier assumptionSupplier) throws TestAbortedException { assumeFalse(assumptionSupplier.getAsBoolean(), "assumption is not false"); } /** * Validate the given assumption. * * @param assumptionSupplier the supplier of the assumption to validate * @param message the message to be included in the {@code TestAbortedException} * if the assumption is invalid * @throws TestAbortedException if the assumption is not {@code false} */ public static void assumeFalse(BooleanSupplier assumptionSupplier, @Nullable String message) throws TestAbortedException { assumeFalse(assumptionSupplier.getAsBoolean(), message); } /** * Validate the given assumption. * * @param assumption the assumption to validate * @param messageSupplier the supplier of the message to be included in * the {@code TestAbortedException} if the assumption is invalid * @throws TestAbortedException if the assumption is not {@code false} */ @Contract("true, _ -> fail") public static void assumeFalse(boolean assumption, Supplier<@Nullable String> messageSupplier) throws TestAbortedException { if (assumption) { throwAssumptionFailed(messageSupplier.get()); } } /** * Validate the given assumption. * * @param assumption the assumption to validate * @param message the message to be included in the {@code TestAbortedException} * if the assumption is invalid * @throws TestAbortedException if the assumption is not {@code false} */ @Contract("true, _ -> fail") public static void assumeFalse(boolean assumption, @Nullable String message) throws TestAbortedException { if (assumption) { throwAssumptionFailed(message); } } /** * Validate the given assumption. * * @param assumptionSupplier the supplier of the assumption to validate * @param messageSupplier the supplier of the message to be included in * the {@code TestAbortedException} if the assumption is invalid * @throws TestAbortedException if the assumption is not {@code false} */ public static void assumeFalse(BooleanSupplier assumptionSupplier, Supplier<@Nullable String> messageSupplier) throws TestAbortedException { assumeFalse(assumptionSupplier.getAsBoolean(), messageSupplier); } // --- assumingThat -------------------------------------------------------- /** * Execute the supplied {@link Executable}, but only if the supplied * assumption is valid. * *

Unlike the other assumption methods, this method will not abort the test. * If the assumption is invalid, this method does nothing. If the assumption is * valid and the {@code executable} throws an exception, it will be treated like * a regular test failure. That exception will be rethrown as is * but {@link ExceptionUtils#throwAsUncheckedException masked} as an unchecked * exception. * * @param assumptionSupplier the supplier of the assumption to validate * @param executable the block of code to execute if the assumption is valid * @see #assumingThat(boolean, Executable) */ public static void assumingThat(BooleanSupplier assumptionSupplier, Executable executable) { assumingThat(assumptionSupplier.getAsBoolean(), executable); } /** * Execute the supplied {@link Executable}, but only if the supplied * assumption is valid. * *

Unlike the other assumption methods, this method will not abort the test. * If the assumption is invalid, this method does nothing. If the assumption is * valid and the {@code executable} throws an exception, it will be treated like * a regular test failure. That exception will be rethrown as is * but {@link ExceptionUtils#throwAsUncheckedException masked} as an unchecked * exception. * * @param assumption the assumption to validate * @param executable the block of code to execute if the assumption is valid * @see #assumingThat(BooleanSupplier, Executable) */ public static void assumingThat(boolean assumption, Executable executable) { if (assumption) { try { executable.execute(); } catch (Throwable t) { throw ExceptionUtils.throwAsUncheckedException(t); } } } // --- abort --------------------------------------------------------------- /** * Abort the test without a message. * *

Although aborting with an explicit message is recommended, this may be * useful when maintaining legacy code. * *

See Javadoc for {@link #abort(String)} for an explanation of this * method's generic return type {@code V}. * * @throws TestAbortedException always * @since 5.9 */ @Contract(" -> fail") @API(status = STABLE, since = "5.9") @SuppressWarnings("TypeParameterUnusedInFormals") public static V abort() { throw new TestAbortedException(); } /** * Abort the test with the given {@code message}. * *

The generic return type {@code V} allows this method to be used * directly as a single-statement lambda expression, thereby avoiding the * need to implement a code block with an explicit return value. Since this * method throws a {@link TestAbortedException} before its return statement, * this method never actually returns a value to its caller. The following * example demonstrates how this may be used in practice. * *

{@code
	 * Stream.of().map(entry -> abort("assumption not met"));
	 * }
* * @param message the message to be included in the {@code TestAbortedException} * @throws TestAbortedException always * @since 5.9 */ @Contract("_ -> fail") @API(status = STABLE, since = "5.9") @SuppressWarnings("TypeParameterUnusedInFormals") public static V abort(String message) { throw new TestAbortedException(message); } /** * Abort the test with the supplied message. * *

See Javadoc for {@link #abort(String)} for an explanation of this * method's generic return type {@code V}. * * @param messageSupplier the supplier of the message to be included in the * {@code TestAbortedException} * @throws TestAbortedException always * @since 5.9 */ @Contract("_ -> fail") @API(status = STABLE, since = "5.9") @SuppressWarnings("TypeParameterUnusedInFormals") public static V abort(Supplier messageSupplier) { throw new TestAbortedException(messageSupplier.get()); } @Contract("_ -> fail") private static void throwAssumptionFailed(@Nullable String message) { throw new TestAbortedException( StringUtils.isNotBlank(message) ? "Assumption failed: " + message : "Assumption failed"); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/AutoClose.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.MAINTAINED; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @AutoClose} is used to indicate that an annotated field will be * automatically closed after test execution. * *

{@code @AutoClose} fields may be either {@code static} or non-static. If * the value of an {@code @AutoClose} field is {@code null} when it is evaluated * the field will be ignored, but a warning message will be logged to inform you. * *

By default, {@code @AutoClose} expects the value of the annotated field to * implement a {@code close()} method that will be invoked to close the resource. * However, developers can customize the name of the {@code close} method via the * {@link #value} attribute. For example, {@code @AutoClose("shutdown")} instructs * JUnit to look for a {@code shutdown()} method to close the resource. * *

{@code @AutoClose} may be used as a meta-annotation in order to create a * custom composed annotation that inherits the semantics of * {@code @AutoClose}. * *

Inheritance

* *

{@code @AutoClose} fields are inherited from superclasses. Furthermore, * {@code @AutoClose} fields from subclasses will be closed before * {@code @AutoClose} fields in superclasses. * *

Evaluation Order

* *

When multiple {@code @AutoClose} fields exist within a given test class, * the order in which the resources are closed depends on an algorithm that is * deterministic but intentionally nonobvious. This ensures that subsequent runs * of a test suite close resources in the same order, thereby allowing for * repeatable builds. * *

Scope and Lifecycle

* *

The extension that closes {@code @AutoClose} fields implements the * {@link org.junit.jupiter.api.extension.AfterAllCallback AfterAllCallback} and * {@link org.junit.jupiter.api.extension.TestInstancePreDestroyCallback * TestInstancePreDestroyCallback} extension APIs. Consequently, a {@code static} * {@code @AutoClose} field will be closed after all tests in the current test * class have completed, effectively after {@code @AfterAll} methods have executed * for the test class. A non-static {@code @AutoClose} field will be closed before * the current test class instance is destroyed. Specifically, if the test class * is configured with * {@link TestInstance.Lifecycle#PER_METHOD @TestInstance(Lifecycle.PER_METHOD)} * semantics, a non-static {@code @AutoClose} field will be closed after the * execution of each test method, test factory method, or test template method. * However, if the test class is configured with * {@link TestInstance.Lifecycle#PER_CLASS @TestInstance(Lifecycle.PER_CLASS)} * semantics, a non-static {@code @AutoClose} field will not be closed until the * current test class instance is no longer needed, which means after * {@code @AfterAll} methods and after all {@code static} {@code @AutoClose} fields * have been closed. * * @since 5.11 */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = MAINTAINED, since = "5.13.3") public @interface AutoClose { /** * Specify the name of the method to invoke to close the resource. * *

The default value is {@code "close"} which works with any type that * implements {@link AutoCloseable} or has a {@code close()} method. * * @return the name of the method to invoke to close the resource */ String value() default "close"; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeAll.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @BeforeAll} is used to signal that the annotated method should be * executed before all tests in the current test class. * *

In contrast to {@link BeforeEach @BeforeEach} methods, {@code @BeforeAll} * methods are only executed once per execution of a given test class. If the * test class is annotated with {@link ClassTemplate @ClassTemplate}, the * {@code @BeforeAll} methods are executed once before the first invocation of * the class template. If a {@link Nested @Nested} test class is declared in a * {@link ClassTemplate @ClassTemplate}, its {@code @BeforeAll} methods are * called once per execution of the nested test class, namely, once per * invocation of the outer class template. * *

Method Signatures

* *

{@code @BeforeAll} methods must have a {@code void} return type and must * be {@code static} unless the test class is annotated with * {@link TestInstance @TestInstance(Lifecycle.PER_CLASS)}. In addition, * {@code @BeforeAll} methods may optionally declare parameters to be resolved by * {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolvers}. * *

Using {@code private} visibility for {@code @BeforeAll} methods is strongly * discouraged and will be disallowed in a future release. * *

Inheritance and Execution Order

* *

{@code @BeforeAll} methods are inherited from superclasses as long as they * are not overridden according to the visibility rules of the Java * language. Furthermore, {@code @BeforeAll} methods from superclasses will be * executed before {@code @BeforeAll} methods in subclasses. * *

Similarly, {@code @BeforeAll} methods declared in an interface are inherited * as long as they are not overridden, and {@code @BeforeAll} methods from an * interface will be executed before {@code @BeforeAll} methods in the class that * implements the interface. * *

JUnit Jupiter does not guarantee the execution order of multiple * {@code @BeforeAll} methods that are declared within a single test class or * test interface. While it may at times appear that these methods are invoked * in alphabetical order, they are in fact sorted using an algorithm that is * deterministic but intentionally non-obvious. * *

In addition, {@code @BeforeAll} methods are in no way linked to * {@code @AfterAll} methods. Consequently, there are no guarantees with regard * to their wrapping behavior. For example, given two * {@code @BeforeAll} methods {@code createA()} and {@code createB()} as well as * two {@code @AfterAll} methods {@code destroyA()} and {@code destroyB()}, the * order in which the {@code @BeforeAll} methods are executed (e.g. * {@code createA()} before {@code createB()}) does not imply any order for the * seemingly corresponding {@code @AfterAll} methods. In other words, * {@code destroyA()} might be called before or after * {@code destroyB()}. The JUnit Team therefore recommends that developers * declare at most one {@code @BeforeAll} method and at most one * {@code @AfterAll} method per test class or test interface unless there are no * dependencies between the {@code @BeforeAll} methods or between the * {@code @AfterAll} methods. * *

Composition

* *

{@code @BeforeAll} may be used as a meta-annotation in order to create * a custom composed annotation that inherits the semantics of * {@code @BeforeAll}. * * @since 5.0 * @see AfterAll * @see BeforeEach * @see AfterEach * @see Test * @see TestFactory * @see TestInstance */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = STABLE, since = "5.0") public @interface BeforeAll { } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeEach.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @BeforeEach} is used to signal that the annotated method should be * executed before each {@code @Test}, * {@code @RepeatedTest}, {@code @ParameterizedTest}, {@code @TestFactory}, * and {@code @TestTemplate} method in the current test class. * *

Method Signatures

* *

{@code @BeforeEach} methods must have a {@code void} return type and must * not be {@code static}. In addition, {@code @BeforeEach} methods may optionally * declare parameters to be resolved by * {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolvers}. * *

Using {@code private} visibility for {@code @BeforeEach} methods is strongly * discouraged and will be disallowed in a future release. * *

Inheritance and Execution Order

* *

{@code @BeforeEach} methods are inherited from superclasses as long as they * are not overridden according to the visibility rules of the Java * language. Furthermore, {@code @BeforeEach} methods from superclasses will be * executed before {@code @BeforeEach} methods in subclasses. * *

Similarly, {@code @BeforeEach} methods declared as interface default * methods are inherited as long as they are not overridden, and * {@code @BeforeEach} default methods will be executed before {@code @BeforeEach} * methods in the class that implements the interface. * *

JUnit Jupiter does not guarantee the execution order of multiple * {@code @BeforeEach} methods that are declared within a single test class or * test interface. While it may at times appear that these methods are invoked * in alphabetical order, they are in fact sorted using an algorithm that is * deterministic but intentionally non-obvious. * *

In addition, {@code @BeforeEach} methods are in no way linked to * {@code @AfterEach} methods. Consequently, there are no guarantees with regard * to their wrapping behavior. For example, given two * {@code @BeforeEach} methods {@code createA()} and {@code createB()} as well * as two {@code @AfterEach} methods {@code destroyA()} and {@code destroyB()}, * the order in which the {@code @BeforeEach} methods are executed (e.g. * {@code createA()} before {@code createB()}) does not imply any order for the * seemingly corresponding {@code @AfterEach} methods. In other words, * {@code destroyA()} might be called before or after * {@code destroyB()}. The JUnit Team therefore recommends that developers * declare at most one {@code @BeforeEach} method and at most one * {@code @AfterEach} method per test class or test interface unless there are * no dependencies between the {@code @BeforeEach} methods or between the * {@code @AfterEach} methods. * *

Composition

* *

{@code @BeforeEach} may be used as a meta-annotation in order to create * a custom composed annotation that inherits the semantics of * {@code @BeforeEach}. * * @since 5.0 * @see AfterEach * @see BeforeAll * @see AfterAll * @see Test * @see RepeatedTest * @see TestFactory * @see TestTemplate */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = STABLE, since = "5.0") public @interface BeforeEach { } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassDescriptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Annotation; import java.util.List; import java.util.Optional; import org.apiguardian.api.API; /** * {@code ClassDescriptor} encapsulates functionality for a given {@link Class}. * * @since 5.8 * @see ClassOrdererContext */ @API(status = STABLE, since = "5.10") public interface ClassDescriptor { /** * Get the class for this descriptor. * * @return the class; never {@code null} */ Class getTestClass(); /** * Get the display name for this descriptor's {@link #getTestClass() class}. * * @return the display name for this descriptor's class; never {@code null} * or blank */ String getDisplayName(); /** * Determine if an annotation of {@code annotationType} is either * present or meta-present on the {@link Class} for * this descriptor. * * @param annotationType the annotation type to search for; never {@code null} * @return {@code true} if the annotation is present or meta-present * @see #findAnnotation(Class) * @see #findRepeatableAnnotations(Class) */ boolean isAnnotated(Class annotationType); /** * Find the first annotation of {@code annotationType} that is either * present or meta-present on the {@link Class} for * this descriptor. * * @param the annotation type * @param annotationType the annotation type to search for; never {@code null} * @return an {@code Optional} containing the annotation; never {@code null} but * potentially empty * @see #isAnnotated(Class) * @see #findRepeatableAnnotations(Class) */ Optional findAnnotation(Class annotationType); /** * Find all repeatable {@linkplain Annotation annotations} of * {@code annotationType} that are either present or * meta-present on the {@link Class} for this descriptor. * * @param the annotation type * @param annotationType the repeatable annotation type to search for; never * {@code null} * @return the list of all such annotations found; neither {@code null} nor * mutable, but potentially empty * @see #isAnnotated(Class) * @see #findAnnotation(Class) * @see java.lang.annotation.Repeatable */ List findRepeatableAnnotations(Class annotationType); } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrderer.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static java.util.Comparator.comparingInt; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import java.util.Collections; import java.util.Comparator; import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; /** * {@code ClassOrderer} defines the API for ordering top-level test classes and * {@link Nested @Nested} test classes. * *

In this context, the term "test class" refers to any class containing methods * annotated with {@code @Test}, {@code @RepeatedTest}, {@code @ParameterizedTest}, * {@code @TestFactory}, or {@code @TestTemplate}. * *

Top-level test classes will be ordered relative to each other; whereas, * {@code @Nested} test classes will be ordered relative to other {@code @Nested} * test classes sharing the same {@linkplain Class#getEnclosingClass() enclosing * class}. * *

A {@link ClassOrderer} can be configured globally for the entire * test suite via the {@value #DEFAULT_ORDER_PROPERTY_NAME} configuration * parameter (see the User Guide for details) or locally for * {@link Nested @Nested} test classes via the {@link TestClassOrder @TestClassOrder} * annotation. * *

Built-in Implementations

* *

JUnit Jupiter provides the following built-in {@code ClassOrderer} * implementations. * *

    *
  • {@link ClassOrderer.ClassName}
  • *
  • {@link ClassOrderer.Default}
  • *
  • {@link ClassOrderer.DisplayName}
  • *
  • {@link ClassOrderer.OrderAnnotation}
  • *
  • {@link ClassOrderer.Random}
  • *
* * @since 5.8 * @see TestClassOrder * @see ClassOrdererContext * @see #orderClasses(ClassOrdererContext) * @see MethodOrderer */ @API(status = STABLE, since = "5.10") public interface ClassOrderer { /** * Property name used to set the default class orderer class name: {@value} * *

Supported Values

* *

Supported values include fully qualified class names for types that * implement {@link org.junit.jupiter.api.ClassOrderer}. * *

If not specified, test classes are not ordered unless test classes are * annotated with {@link TestClassOrder @TestClassOrder}. * * @since 5.8 */ @API(status = STABLE, since = "5.9") String DEFAULT_ORDER_PROPERTY_NAME = "junit.jupiter.testclass.order.default"; /** * Order the classes encapsulated in the supplied {@link ClassOrdererContext}. * *

The classes to order or sort are made indirectly available via * {@link ClassOrdererContext#getClassDescriptors()}. Since this method * has a {@code void} return type, the list of class descriptors must be * modified directly. * *

For example, a simplified implementation of the {@link ClassOrderer.Random} * {@code ClassOrderer} might look like the following. * *

	 * public void orderClasses(ClassOrdererContext context) {
	 *     Collections.shuffle(context.getClassDescriptors());
	 * }
* * @param context the {@code ClassOrdererContext} containing the * {@linkplain ClassDescriptor class descriptors} to order; never {@code null} */ void orderClasses(ClassOrdererContext context); /** * {@code ClassOrderer} that allows to explicitly specify that the default * ordering should be applied. * *

If the {@value #DEFAULT_ORDER_PROPERTY_NAME} is set, specifying this * {@code ClassOrderer} has the same effect as referencing the configured * class directly. Otherwise, it has the same effect as not specifying any * {@code ClassOrderer}. * *

This class can be used to reset the {@code ClassOrderer} for a * {@link Nested @Nested} class and its {@code @Nested} inner classes, * recursively, when a {@code ClassOrderer} is configured using * {@link TestClassOrder @TestClassOrder} on an enclosing class. * * @since 6.0 */ @API(status = EXPERIMENTAL, since = "6.0") final class Default implements ClassOrderer { private Default() { throw new JUnitException("This class must not be instantiated"); } @Override public void orderClasses(ClassOrdererContext context) { // never called } } /** * {@code ClassOrderer} that sorts classes alphanumerically based on their * fully qualified names using {@link String#compareTo(String)}. */ class ClassName implements ClassOrderer { public ClassName() { } /** * Sort the classes encapsulated in the supplied * {@link ClassOrdererContext} alphanumerically based on their fully * qualified names. */ @Override public void orderClasses(ClassOrdererContext context) { context.getClassDescriptors().sort(comparator); } private static final Comparator comparator = Comparator.comparing( descriptor -> descriptor.getTestClass().getName()); } /** * {@code ClassOrderer} that sorts classes alphanumerically based on their * display names using {@link String#compareTo(String)} */ class DisplayName implements ClassOrderer { public DisplayName() { } /** * Sort the classes encapsulated in the supplied * {@link ClassOrdererContext} alphanumerically based on their display * names. */ @Override public void orderClasses(ClassOrdererContext context) { context.getClassDescriptors().sort(comparator); } private static final Comparator comparator = Comparator.comparing( ClassDescriptor::getDisplayName); } /** * {@code ClassOrderer} that sorts classes based on the {@link Order @Order} * annotation. * *

Any classes that are assigned the same order value will be sorted * arbitrarily adjacent to each other. * *

Any classes not annotated with {@code @Order} will be assigned the * {@linkplain Order#DEFAULT default order} value which will effectively cause them * to appear at the end of the sorted list, unless certain classes are assigned * an explicit order value greater than the default order value. Any classes * assigned an explicit order value greater than the default order value will * appear after non-annotated classes in the sorted list. */ class OrderAnnotation implements ClassOrderer { public OrderAnnotation() { } /** * Sort the classes encapsulated in the supplied * {@link ClassOrdererContext} based on the {@link Order @Order} * annotation. */ @Override public void orderClasses(ClassOrdererContext context) { context.getClassDescriptors().sort(comparingInt(OrderAnnotation::getOrder)); } private static int getOrder(ClassDescriptor descriptor) { return descriptor.findAnnotation(Order.class).map(Order::value).orElse(Order.DEFAULT); } } /** * {@code ClassOrderer} that orders classes pseudo-randomly. * *

Custom Seed

* *

By default, the random seed used for ordering classes is the * value returned by {@link System#nanoTime()} during static class * initialization. In order to support repeatable builds, the value of the * default random seed is logged at {@code CONFIG} level. In addition, a * custom seed (potentially the default seed from the previous test plan * execution) may be specified via the {@value Random#RANDOM_SEED_PROPERTY_NAME} * configuration parameter which can be supplied via the {@code Launcher} * API, build tools (e.g., Gradle and Maven), a JVM system property, or the JUnit * Platform configuration file (i.e., a file named {@code junit-platform.properties} * in the root of the class path). Consult the User Guide for further information. * * @see Random#RANDOM_SEED_PROPERTY_NAME * @see java.util.Random */ class Random implements ClassOrderer { private static final Logger logger = LoggerFactory.getLogger(Random.class); static { logger.config(() -> "ClassOrderer.Random default seed: " + RandomOrdererUtils.DEFAULT_SEED); } /** * Property name used to set the random seed used by this * {@code ClassOrderer}: {@value} * *

The same property is used by {@link MethodOrderer.Random} for * consistency between the two random orderers. * *

Supported Values

* *

Supported values include any string that can be converted to a * {@link Long} via {@link Long#valueOf(String)}. * *

If not specified or if the specified value cannot be converted to * a {@link Long}, the default random seed will be used (see the * {@linkplain Random class-level Javadoc} for details). * * @see MethodOrderer.Random */ public static final String RANDOM_SEED_PROPERTY_NAME = RandomOrdererUtils.RANDOM_SEED_PROPERTY_NAME; public Random() { } /** * Order the classes encapsulated in the supplied * {@link ClassOrdererContext} pseudo-randomly. */ @Override public void orderClasses(ClassOrdererContext context) { Collections.shuffle(context.getClassDescriptors(), new java.util.Random(RandomOrdererUtils.getSeed(context::getConfigurationParameter, logger))); } } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrdererContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.STABLE; import java.util.List; import java.util.Optional; import org.apiguardian.api.API; /** * {@code ClassOrdererContext} encapsulates the context in which * a {@link ClassOrderer} will be invoked. * * @since 5.8 * @see ClassOrderer * @see ClassDescriptor */ @API(status = STABLE, since = "5.10") public interface ClassOrdererContext { /** * Get the list of {@linkplain ClassDescriptor class descriptors} to * order. * * @return the list of class descriptors; never {@code null} */ List getClassDescriptors(); /** * Get the configuration parameter stored under the specified {@code key}. * *

If no such key is present in the {@code ConfigurationParameters} for * the JUnit Platform, an attempt will be made to look up the value as a * JVM system property. If no such system property exists, an attempt will * be made to look up the value in the JUnit Platform properties file. * * @param key the key to look up; never {@code null} or blank * @return an {@code Optional} containing the value; never {@code null} * but potentially empty * * @see System#getProperty(String) * @see org.junit.platform.engine.ConfigurationParameters */ Optional getConfigurationParameter(String key); } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassTemplate.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.platform.commons.annotation.Testable; /** * {@code @ClassTemplate} is used to signal that the annotated class is a * class template. * *

In contrast to regular test classes, a class template is not directly * a test class but rather a template for a set of test cases. As such, it is * designed to be invoked multiple times depending on the number of {@linkplain * org.junit.jupiter.api.extension.ClassTemplateInvocationContext invocation contexts} * returned by the registered {@linkplain * org.junit.jupiter.api.extension.ClassTemplateInvocationContextProvider providers}. * Must be used together with at least one provider. Otherwise, execution will fail. * *

Each invocation of a class template behaves like the execution of a regular * test class with full support for the same lifecycle callbacks and extensions. * *

{@code @ClassTemplate} may be combined with {@link Nested @Nested}, and a * class template may contain regular nested test classes or nested class templates. * *

{@code @ClassTemplate} may also be used as a meta-annotation in order * to create a custom composed annotation that inherits the semantics * of {@code @ClassTemplate}. * *

Inheritance

* *

This annotation is {@linkplain Inherited inherited} within class hierarchies. * * @since 5.13 * @see TestTemplate @TestTemplate * @see org.junit.jupiter.api.extension.ClassTemplateInvocationContext ClassTemplateInvocationContext * @see org.junit.jupiter.api.extension.ClassTemplateInvocationContextProvider ClassTemplateInvocationContextProvider * @see org.junit.jupiter.api.extension.BeforeClassTemplateInvocationCallback BeforeClassTemplateInvocationCallback * @see org.junit.jupiter.api.extension.AfterClassTemplateInvocationCallback AfterClassTemplateInvocationCallback */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @API(status = EXPERIMENTAL, since = "6.0") @Testable public @interface ClassTemplate { } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/Constants.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.PreInterruptCallback; import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope; import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.parallel.Execution; /** * Collection of configuration constants for the Jupiter test engine. * * @since 6.1 * @see User Guide * section about configuration parameters */ @API(status = STABLE, since = "6.1") public final class Constants { /** * Property name used to include patterns for auto-detecting extensions: {@value} * *

Pattern Matching Syntax

* *

If the property value consists solely of an asterisk ({@code *}), all * extensions will be included. Otherwise, the property value will be treated * as a comma-separated list of patterns where each individual pattern will be * matched against the fully qualified class name (FQCN) of each extension. * Any dot ({@code .}) in a pattern will match against a dot ({@code .}) * or a dollar sign ({@code $}) in a FQCN. Any asterisk ({@code *}) will match * against one or more characters in a FQCN. All other characters in a pattern * will be matched one-to-one against a FQCN. * *

Examples

* *
    *
  • {@code *}: includes all extensions. *
  • {@code org.junit.*}: includes every extension under the {@code org.junit} * base package and any of its subpackages. *
  • {@code *.MyExtension}: includes every extension whose simple class name is * exactly {@code MyExtension}. *
  • {@code *System*}: includes every extension whose FQCN contains * {@code System}. *
  • {@code *System*, *Dev*}: includes every extension whose FQCN contains * {@code System} or {@code Dev}. *
  • {@code org.example.MyExtension, org.example.TheirExtension}: includes * extensions whose FQCN is exactly {@code org.example.MyExtension} or * {@code org.example.TheirExtension}. *
* *

Note: A class that matches both an inclusion and exclusion pattern will be excluded. */ public static final String EXTENSIONS_AUTODETECTION_INCLUDE_PROPERTY_NAME = "junit.jupiter.extensions.autodetection.include"; /** * Property name used to exclude patterns for auto-detecting extensions: {@value} * *

Pattern Matching Syntax

* *

If the property value consists solely of an asterisk ({@code *}), all * extensions will be excluded. Otherwise, the property value will be treated * as a comma-separated list of patterns where each individual pattern will be * matched against the fully qualified class name (FQCN) of each extension. * Any dot ({@code .}) in a pattern will match against a dot ({@code .}) * or a dollar sign ({@code $}) in a FQCN. Any asterisk ({@code *}) will match * against one or more characters in a FQCN. All other characters in a pattern * will be matched one-to-one against a FQCN. * *

Examples

* *
    *
  • {@code *}: excludes all extensions. *
  • {@code org.junit.*}: excludes every extension under the {@code org.junit} * base package and any of its subpackages. *
  • {@code *.MyExtension}: excludes every extension whose simple class name is * exactly {@code MyExtension}. *
  • {@code *System*}: excludes every extension whose FQCN contains * {@code System}. *
  • {@code *System*, *Dev*}: excludes every extension whose FQCN contains * {@code System} or {@code Dev}. *
  • {@code org.example.MyExtension, org.example.TheirExtension}: excludes * extensions whose FQCN is exactly {@code org.example.MyExtension} or * {@code org.example.TheirExtension}. *
* *

Note: A class that matches both an inclusion and exclusion pattern will be excluded. */ public static final String EXTENSIONS_AUTODETECTION_EXCLUDE_PROPERTY_NAME = "junit.jupiter.extensions.autodetection.exclude"; /** * Property name used to enable auto-detection and registration of extensions via * Java's {@link java.util.ServiceLoader} mechanism: {@value} * *

The default behavior is not to perform auto-detection. */ public static final String EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME = "junit.jupiter.extensions.autodetection.enabled"; /** * Property name used to enable auto-closing of {@link AutoCloseable} instances: {@value} * *

By default, auto-closing is enabled. * */ public static final String CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_PROPERTY_NAME = "junit.jupiter.extensions.store.close.autocloseable.enabled"; /** * Property name used to provide patterns for deactivating conditions: {@value} * *

Pattern Matching Syntax

* *

If the property value consists solely of an asterisk ({@code *}), all * conditions will be deactivated. Otherwise, the property value will be treated * as a comma-separated list of patterns where each individual pattern will be * matched against the fully qualified class name (FQCN) of each registered * condition. Any dot ({@code .}) in a pattern will match against a dot ({@code .}) * or a dollar sign ({@code $}) in a FQCN. Any asterisk ({@code *}) will match * against one or more characters in a FQCN. All other characters in a pattern * will be matched one-to-one against a FQCN. * *

Examples

* *
    *
  • {@code *}: deactivates all conditions. *
  • {@code org.junit.*}: deactivates every condition under the {@code org.junit} * base package and any of its subpackages. *
  • {@code *.MyCondition}: deactivates every condition whose simple class name is * exactly {@code MyCondition}. *
  • {@code *System*}: deactivates every condition whose FQCN contains * {@code System}. *
  • {@code *System*, *Dev*}: deactivates every condition whose FQCN contains * {@code System} or {@code Dev}. *
  • {@code org.example.MyCondition, org.example.TheirCondition}: deactivates * conditions whose FQCN is exactly {@code org.example.MyCondition} or * {@code org.example.TheirCondition}. *
* * @see #DEACTIVATE_ALL_CONDITIONS_PATTERN * @see org.junit.jupiter.api.extension.ExecutionCondition */ public static final String DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME = "junit.jupiter.conditions.deactivate"; /** * Wildcard pattern which signals that all conditions should be deactivated: {@value} * * @see #DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME * @see org.junit.jupiter.api.extension.ExecutionCondition */ public static final String DEACTIVATE_ALL_CONDITIONS_PATTERN = "*"; /** * Property name used to set the default display name generator class name: {@value} * * @see DisplayNameGenerator#DEFAULT_GENERATOR_PROPERTY_NAME */ public static final String DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME = DisplayNameGenerator.DEFAULT_GENERATOR_PROPERTY_NAME; /** * Property name used to enable dumping the stack of all * {@linkplain Thread threads} to {@code System.out} when a timeout has occurred: {@value} * *

This behavior is disabled by default. */ public static final String EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_PROPERTY_NAME = PreInterruptCallback.THREAD_DUMP_ENABLED_PROPERTY_NAME; /** * Property name used to set the default test instance lifecycle mode: {@value} * * @see TestInstance.Lifecycle#DEFAULT_LIFECYCLE_PROPERTY_NAME */ public static final String DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME = TestInstance.Lifecycle.DEFAULT_LIFECYCLE_PROPERTY_NAME; /** * Property name used to enable parallel test execution: {@value} * *

By default, tests are executed sequentially in a single thread. * */ public static final String PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME = "junit.jupiter.execution.parallel.enabled"; /** * Property name used to set the default test execution mode: {@value} * * @see Execution#DEFAULT_EXECUTION_MODE_PROPERTY_NAME */ public static final String DEFAULT_EXECUTION_MODE_PROPERTY_NAME = Execution.DEFAULT_EXECUTION_MODE_PROPERTY_NAME; /** * Property name used to set the default test execution mode for top-level * classes: {@value} * * @see Execution#DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME */ public static final String DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME = Execution.DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME; /** * Internal prefix for all configuration parameters concerning parallel test * execution. */ @API(status = INTERNAL, since = "6.1") public static final String PARALLEL_CONFIG_PREFIX = "junit.jupiter.execution.parallel.config."; /** * Property name used to determine the desired parallel executor service * type: {@value} * *

Value must be {@code FORK_JOIN_POOL} or {@code WORKER_THREAD_POOL}, * ignoring case. * */ public static final String PARALLEL_CONFIG_EXECUTOR_SERVICE_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + "executor-service"; /** * Property name used to select the parallel execution configuration * strategy: {@value} * *

Potential values: {@code dynamic} (default), {@code fixed}, or * {@code custom}. * */ public static final String PARALLEL_CONFIG_STRATEGY_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + "strategy"; /** * Property name used to set the desired parallelism for the {@code fixed} * configuration strategy: {@value} * *

No default value; must be a positive integer. * */ public static final String PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + "fixed.parallelism"; /** * Property name used to configure the maximum pool size of the underlying * fork-join pool for the {@code fixed} configuration strategy: {@value} * *

Value must be an integer and greater than or equal to * {@value #PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME}; defaults to * {@code 256 + fixed.parallelism}. * */ public static final String PARALLEL_CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + "fixed.max-pool-size"; /** * Property name used to disable saturation of the underlying fork-join pool * for the {@code fixed} configuration strategy: {@value} * *

When set to {@code false} the underlying fork-join pool will reject * additional tasks if all available workers are busy and the maximum * pool-size would be exceeded. * *

Value must either {@code true} or {@code false}; defaults to {@code true}. * */ public static final String PARALLEL_CONFIG_FIXED_SATURATE_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + "fixed.saturate"; /** * Property name used to set the factor to be multiplied with the number of * available processors/cores to determine the desired parallelism for the * {@code dynamic} configuration strategy: {@value} * *

Value must be a positive decimal number; defaults to {@code 1}. * */ public static final String PARALLEL_CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + "dynamic.factor"; /** * Property name used to specify the fully qualified class name of the * {@code custom} parallel execution configuration strategy to be used: * {@value} * */ public static final String PARALLEL_CONFIG_CUSTOM_CLASS_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX + "custom.class"; /** * Property name used to set the default timeout for all testable and * lifecycle methods: {@value} * * @see Timeout#DEFAULT_TIMEOUT_PROPERTY_NAME */ public static final String DEFAULT_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_TIMEOUT_PROPERTY_NAME; /** * Property name used to set the default timeout for all testable methods: {@value} * * @see Timeout#DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME */ public static final String DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME; /** * Property name used to set the default timeout for all * {@link Test @Test} methods: {@value} * * @see Timeout#DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME */ public static final String DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME; /** * Property name used to set the default timeout for all * {@link TestTemplate @TestTemplate} methods: {@value} * * @see Timeout#DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME */ public static final String DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME; /** * Property name used to set the default timeout for all * {@link TestFactory @TestFactory} methods: {@value} * * @see Timeout#DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME */ public static final String DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME; /** * Property name used to set the default timeout for all lifecycle methods: {@value} * * @see Timeout#DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME */ public static final String DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME; /** * Property name used to set the default timeout for all * {@link BeforeAll @BeforeAll} methods: {@value} * * @see Timeout#DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME */ public static final String DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME; /** * Property name used to set the default timeout for all * {@link BeforeEach @BeforeEach} methods: {@value} * * @see Timeout#DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME */ public static final String DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME; /** * Property name used to set the default timeout for all * {@link AfterEach @AfterEach} methods: {@value} * * @see Timeout#DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME */ public static final String DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME; /** * Property name used to set the default timeout for all * {@link AfterAll @AfterAll} methods: {@value} * * @see Timeout#DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME */ public static final String DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME = Timeout.DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME; /** * Property name used to configure whether timeouts are applied to tests: {@value} * * @see Timeout#TIMEOUT_MODE_PROPERTY_NAME */ public static final String TIMEOUT_MODE_PROPERTY_NAME = Timeout.TIMEOUT_MODE_PROPERTY_NAME; /** * Property name used to set the default method orderer class name: {@value} * * @see MethodOrderer#DEFAULT_ORDER_PROPERTY_NAME */ public static final String DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME = MethodOrderer.DEFAULT_ORDER_PROPERTY_NAME; /** * Property name used to set the default class orderer class name: {@value} * * @see ClassOrderer#DEFAULT_ORDER_PROPERTY_NAME */ public static final String DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME = ClassOrderer.DEFAULT_ORDER_PROPERTY_NAME; /** * Property name used to set the default timeout thread mode: {@value} * * @see Timeout * @see Timeout.ThreadMode */ public static final String DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME = Timeout.DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME; /** * Property name used to set the default factory for temporary directories * created via the {@link TempDir @TempDir} annotation: {@value} * * @see TempDir#DEFAULT_FACTORY_PROPERTY_NAME */ public static final String DEFAULT_TEMP_DIR_FACTORY_PROPERTY_NAME = TempDir.DEFAULT_FACTORY_PROPERTY_NAME; /** * Property name used to configure the default {@link CleanupMode} for * temporary directories created via the {@link TempDir @TempDir} * annotation: {@value} * * @see TempDir#DEFAULT_CLEANUP_MODE_PROPERTY_NAME */ public static final String DEFAULT_TEMP_DIR_CLEANUP_MODE_PROPERTY_NAME = TempDir.DEFAULT_CLEANUP_MODE_PROPERTY_NAME; /** * Property name used to set the default deletion strategy class name for * temporary directories created via the {@link TempDir @TempDir} * annotation: {@value} * * @see TempDir#DEFAULT_DELETION_STRATEGY_PROPERTY_NAME */ @API(status = EXPERIMENTAL, since = "6.1") public static final String DEFAULT_TEMP_DIR_DELETION_STRATEGY_PROPERTY_NAME = TempDir.DEFAULT_DELETION_STRATEGY_PROPERTY_NAME; /** * Property name used to set the default extension context scope for * extensions that participate in test instantiation: {@value} * * @see org.junit.jupiter.api.extension.TestInstantiationAwareExtension */ public static final String DEFAULT_TEST_CLASS_INSTANCE_CONSTRUCTION_EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME = ExtensionContextScope.DEFAULT_SCOPE_PROPERTY_NAME; private Constants() { /* no-op */ } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/Disabled.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @Disabled} is used to signal that the annotated test class or * test method is currently disabled and should not be executed. * *

{@code @Disabled} may optionally be declared with a {@linkplain #value * reason} to document why the annotated test class or test method is disabled. * *

When applied at the class level, all test methods within that class * are automatically disabled as well. * *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. * Consequently, if you wish to apply the same semantics to a subclass, this * annotation must be redeclared on the subclass. * *

If a test method is disabled via this annotation, that prevents execution * of the test method and method-level lifecycle callbacks such as * {@code @BeforeEach} methods, {@code @AfterEach} methods, and corresponding * extension APIs. However, that does not prevent the test class from being * instantiated, and it does not prevent the execution of class-level lifecycle * callbacks such as {@code @BeforeAll} methods, {@code @AfterAll} methods, and * corresponding extension APIs. * * @since 5.0 * @see #value * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty * @see org.junit.jupiter.api.condition.EnabledOnJre * @see org.junit.jupiter.api.condition.DisabledOnJre * @see org.junit.jupiter.api.condition.EnabledForJreRange * @see org.junit.jupiter.api.condition.DisabledForJreRange * @see org.junit.jupiter.api.condition.EnabledOnOs * @see org.junit.jupiter.api.condition.DisabledOnOs * @see org.junit.jupiter.api.condition.EnabledInNativeImage * @see org.junit.jupiter.api.condition.DisabledInNativeImage * @see org.junit.jupiter.api.extension.ExecutionCondition */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = STABLE, since = "5.0") public @interface Disabled { /** * The reason this annotated test class or test method is disabled. */ String value() default ""; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayName.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @DisplayName} is used to declare a {@linkplain #value custom display * name} for the annotated test class or test method. * *

Display names are typically used for test reporting in IDEs and build * tools and may contain spaces, special characters, and even emoji. * * @since 5.0 * @see Test * @see Tag * @see TestInfo * @see DisplayNameGeneration * @see DisplayNameGenerator */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = STABLE, since = "5.0") public @interface DisplayName { /** * Custom display name for the annotated class or method. * * @return a custom display name; never blank or consisting solely of * whitespace */ String value(); } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGeneration.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @DisplayNameGeneration} is used to declare a custom display name * generator for the annotated test class. * *

This annotation is inherited from superclasses and implemented * interfaces. It is also inherited from {@linkplain Class#getEnclosingClass() * enclosing classes} for {@link Nested @Nested} test classes. * *

As an alternative to {@code @DisplayNameGeneration}, a global * {@link DisplayNameGenerator} can be configured for the entire test suite via * the {@value DisplayNameGenerator#DEFAULT_GENERATOR_PROPERTY_NAME} configuration parameter. See * the User Guide for details. Note, however, that a {@code @DisplayNameGeneration} * declaration always overrides a global {@code DisplayNameGenerator}. * * @since 5.4 * @see DisplayName * @see DisplayNameGenerator * @see IndicativeSentencesGeneration */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @API(status = STABLE, since = "5.7") public @interface DisplayNameGeneration { /** * Custom display name generator. * * @return custom display name generator class */ Class value(); } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static java.util.Collections.emptyList; import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import static org.junit.platform.commons.support.ModifierSupport.isStatic; import static org.junit.platform.commons.util.KotlinReflectionUtils.getKotlinSuspendingFunctionParameterTypes; import static org.junit.platform.commons.util.KotlinReflectionUtils.isKotlinSuspendingFunction; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.util.List; import java.util.Optional; import java.util.function.Predicate; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.ClassUtils; import org.junit.platform.commons.util.Preconditions; /** * {@code DisplayNameGenerator} defines the SPI for generating display names * programmatically. * *

Display names are typically used for test reporting in IDEs and build * tools and may contain spaces, special characters, and even emoji. * *

Concrete implementations must have a default constructor. * *

A {@link DisplayNameGenerator} can be configured globally for the * entire test suite via the {@value #DEFAULT_GENERATOR_PROPERTY_NAME} * configuration parameter (see the User Guide for details) or locally * for a test class via the {@link DisplayNameGeneration @DisplayNameGeneration} * annotation. * *

Built-in Implementations

*
    *
  • {@link Standard}
  • *
  • {@link Simple}
  • *
  • {@link ReplaceUnderscores}
  • *
  • {@link IndicativeSentences}
  • *
* * @since 5.4 * @see DisplayName @DisplayName * @see DisplayNameGeneration @DisplayNameGeneration */ @API(status = STABLE, since = "5.7") public interface DisplayNameGenerator { /** * Property name used to set the default display name generator class name: * {@value} * *

Supported Values

* *

Supported values include fully qualified class names for types that * implement {@link DisplayNameGenerator}. * *

If not specified, the default is * {@link DisplayNameGenerator.Standard}. * * @since 5.5 */ @API(status = STABLE, since = "5.9") String DEFAULT_GENERATOR_PROPERTY_NAME = "junit.jupiter.displayname.generator.default"; /** * Generate a display name for the given top-level or {@code static} nested test class. * *

If this method returns {@code null}, the default display name * generator will be used instead. * * @param testClass the class to generate a name for; never {@code null} * @return the display name for the class; never blank */ String generateDisplayNameForClass(Class testClass); /** * Generate a display name for the given {@link Nested @Nested} inner test * class. * *

If this method returns {@code null}, the default display name * generator will be used instead. * * @param nestedClass the class to generate a name for; never {@code null} * @return the display name for the nested class; never blank * @deprecated in favor of {@link #generateDisplayNameForNestedClass(List, Class)} */ @API(status = DEPRECATED, since = "5.12") @Deprecated(since = "5.12") default String generateDisplayNameForNestedClass(Class nestedClass) { throw new UnsupportedOperationException( "Implement generateDisplayNameForNestedClass(List>, Class) instead"); } /** * Generate a display name for the given {@link Nested @Nested} inner test * class. * *

If this method returns {@code null}, the default display name * generator will be used instead. * * @implNote The classes supplied as {@code enclosingInstanceTypes} may * differ from the classes returned from invocations of * {@link Class#getEnclosingClass()} — for example, when a nested test * class is inherited from a superclass. * * @param enclosingInstanceTypes the runtime types of the enclosing * instances for the test class, ordered from outermost to innermost, * excluding {@code nestedClass}; never {@code null} * @param nestedClass the class to generate a name for; never {@code null} * @return the display name for the nested class; never blank * @since 5.12 */ @API(status = MAINTAINED, since = "5.13.3") default String generateDisplayNameForNestedClass(List> enclosingInstanceTypes, Class nestedClass) { return generateDisplayNameForNestedClass(nestedClass); } /** * Generate a display name for the given method. * *

If this method returns {@code null}, the default display name * generator will be used instead. * * @implNote The class instance supplied as {@code testClass} may differ from * the class returned by {@code testMethod.getDeclaringClass()} — for * example, when a test method is inherited from a superclass. * * @param testClass the class the test method is invoked on; never {@code null} * @param testMethod method to generate a display name for; never {@code null} * @return the display name for the test; never blank * @deprecated in favor of {@link #generateDisplayNameForMethod(List, Class, Method)} */ @API(status = DEPRECATED, since = "5.12") @Deprecated(since = "5.12") default String generateDisplayNameForMethod(Class testClass, Method testMethod) { throw new UnsupportedOperationException( "Implement generateDisplayNameForMethod(List>, Class, Method) instead"); } /** * Generate a display name for the given method. * *

If this method returns {@code null}, the default display name * generator will be used instead. * * @implNote The classes supplied as {@code enclosingInstanceTypes} may * differ from the classes returned from invocations of * {@link Class#getEnclosingClass()} — for example, when a nested test * class is inherited from a superclass. Similarly, the class instance * supplied as {@code testClass} may differ from the class returned by * {@code testMethod.getDeclaringClass()} — for example, when a test * method is inherited from a superclass. * * @param enclosingInstanceTypes the runtime types of the enclosing * instances for the test class, ordered from outermost to innermost, * excluding {@code testClass}; never {@code null} * @param testClass the class the test method is invoked on; never {@code null} * @param testMethod method to generate a display name for; never {@code null} * @return the display name for the test; never blank * @since 5.12 */ @API(status = MAINTAINED, since = "5.13.3") default String generateDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, Method testMethod) { return generateDisplayNameForMethod(testClass, testMethod); } /** * Generate a string representation of the formal parameters of the supplied * method, consisting of the {@linkplain Class#getSimpleName() simple names} * of the parameter types, separated by commas, and enclosed in parentheses. * * @param method the method from to extract the parameter types from; never * {@code null} * @return a string representation of all parameter types of the supplied * method or {@code "()"} if the method declares no parameters */ static String parameterTypesAsString(Method method) { Preconditions.notNull(method, "Method must not be null"); var parameterTypes = isKotlinSuspendingFunction(method) // ? getKotlinSuspendingFunctionParameterTypes(method) // : method.getParameterTypes(); return '(' + ClassUtils.nullSafeToString(Class::getSimpleName, parameterTypes) + ')'; } /** * Standard {@code DisplayNameGenerator}. * *

This implementation matches the standard display name generation * behavior in place since JUnit Jupiter was introduced. */ class Standard implements DisplayNameGenerator { static final DisplayNameGenerator INSTANCE = new Standard(); public Standard() { } @Override public String generateDisplayNameForClass(Class testClass) { String name = testClass.getName(); int lastDot = name.lastIndexOf('.'); return name.substring(lastDot + 1); } @Override public String generateDisplayNameForNestedClass(List> enclosingInstanceTypes, Class nestedClass) { return nestedClass.getSimpleName(); } @Override public String generateDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, Method testMethod) { return testMethod.getName() + parameterTypesAsString(testMethod); } } /** * Simple {@code DisplayNameGenerator} that removes trailing parentheses * for methods with no parameters. * *

This generator extends the functionality of {@link Standard} by * removing parentheses ({@code '()'}) found at the end of method names * with no parameters. * * @since 5.7 */ @API(status = STABLE, since = "5.7") class Simple extends Standard { static final DisplayNameGenerator INSTANCE = new Simple(); public Simple() { } @Override public String generateDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, Method testMethod) { String displayName = testMethod.getName(); if (hasParameters(testMethod)) { displayName += ' ' + parameterTypesAsString(testMethod); } return displayName; } private static boolean hasParameters(Method method) { return method.getParameterCount() > 0; } } /** * {@code DisplayNameGenerator} that replaces underscores with spaces. * *

This generator extends the functionality of {@link Simple} by * replacing all underscores ({@code '_'}) found in class and method names * with spaces ({@code ' '}). */ class ReplaceUnderscores extends Simple { static final DisplayNameGenerator INSTANCE = new ReplaceUnderscores(); public ReplaceUnderscores() { } @Override public String generateDisplayNameForClass(Class testClass) { return replaceUnderscores(super.generateDisplayNameForClass(testClass)); } @Override public String generateDisplayNameForNestedClass(List> enclosingInstanceTypes, Class nestedClass) { return replaceUnderscores(super.generateDisplayNameForNestedClass(enclosingInstanceTypes, nestedClass)); } @Override public String generateDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, Method testMethod) { return replaceUnderscores( super.generateDisplayNameForMethod(enclosingInstanceTypes, testClass, testMethod)); } private static String replaceUnderscores(String name) { return name.replace('_', ' '); } } /** * {@code DisplayNameGenerator} that generates complete sentences. * *

This generator generates display names that build up complete sentences * by concatenating the names of the test and the enclosing classes. The * sentence fragments are concatenated using a separator. The separator and * the display name generator for individual sentence fragments can be configured * via the {@link IndicativeSentencesGeneration @IndicativeSentencesGeneration} * annotation. * *

If you do not want to rely on a display name generator for individual * sentence fragments, you can supply custom text for individual fragments * via the {@link SentenceFragment @SentenceFragment} annotation. * * @since 5.7 */ @API(status = STABLE, since = "5.10") class IndicativeSentences implements DisplayNameGenerator { /** * {@code @SentenceFragment} is used to configure a custom sentence fragment * for a sentence generated by the {@link IndicativeSentences IndicativeSentences} * {@code DisplayNameGenerator}. * *

Note that {@link DisplayName @DisplayName} always takes precedence * over {@code @SentenceFragment}. * * @since 5.13 */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @API(status = EXPERIMENTAL, since = "6.0") public @interface SentenceFragment { /** * Custom sentence fragment for the annotated class or method. * * @return a custom sentence fragment; never blank or consisting solely * of whitespace */ String value(); } static final DisplayNameGenerator INSTANCE = new IndicativeSentences(); private static final Predicate> notIndicativeSentences = clazz -> clazz != IndicativeSentences.class; public IndicativeSentences() { } @Override public String generateDisplayNameForClass(Class testClass) { String sentenceFragment = getSentenceFragment(testClass); return (sentenceFragment != null ? sentenceFragment : getGeneratorFor(testClass, emptyList()).generateDisplayNameForClass(testClass)); } @Override public String generateDisplayNameForNestedClass(List> enclosingInstanceTypes, Class nestedClass) { return getSentenceBeginning(nestedClass, enclosingInstanceTypes); } @Override public String generateDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, Method testMethod) { String displayName = getSentenceBeginning(testClass, enclosingInstanceTypes) + getFragmentSeparator(testClass, enclosingInstanceTypes); String sentenceFragment = getSentenceFragment(testMethod); displayName += (sentenceFragment != null ? sentenceFragment : getGeneratorFor(testClass, enclosingInstanceTypes).generateDisplayNameForMethod( enclosingInstanceTypes, testClass, testMethod)); return displayName; } private String getSentenceBeginning(Class testClass, List> enclosingInstanceTypes) { Class enclosingClass = enclosingInstanceTypes.isEmpty() ? null : enclosingInstanceTypes.get(enclosingInstanceTypes.size() - 1); String sentenceFragment = findAnnotation(testClass, DisplayName.class)// .map(DisplayName::value)// .map(String::strip)// .orElseGet(() -> getSentenceFragment(testClass)); if (enclosingClass == null || isStatic(testClass)) { // top-level class if (sentenceFragment != null) { return sentenceFragment; } Class generatorClass = findDisplayNameGeneration(testClass, enclosingInstanceTypes)// .map(DisplayNameGeneration::value)// .filter(notIndicativeSentences)// .orElse(null); if (generatorClass != null) { return getDisplayNameGenerator(generatorClass).generateDisplayNameForClass(testClass); } return generateDisplayNameForClass(testClass); } List> remainingEnclosingInstanceTypes = enclosingInstanceTypes.isEmpty() ? emptyList() : enclosingInstanceTypes.subList(0, enclosingInstanceTypes.size() - 1); // Only build prefix based on the enclosing class if the enclosing // class is also configured to use the IndicativeSentences generator. boolean buildPrefix = findDisplayNameGeneration(enclosingClass, remainingEnclosingInstanceTypes)// .map(DisplayNameGeneration::value)// .filter(IndicativeSentences.class::equals)// .isPresent(); String prefix = (buildPrefix ? getSentenceBeginning(enclosingClass, remainingEnclosingInstanceTypes) + getFragmentSeparator(testClass, enclosingInstanceTypes) : ""); return prefix + (sentenceFragment != null ? sentenceFragment : getGeneratorFor(testClass, enclosingInstanceTypes).generateDisplayNameForNestedClass( remainingEnclosingInstanceTypes, testClass)); } /** * Get the sentence fragment separator. * *

If {@link IndicativeSentencesGeneration @IndicativeSentencesGeneration} * is present (searching enclosing classes if not found locally), the * configured {@link IndicativeSentencesGeneration#separator() separator} * will be used. Otherwise, {@link IndicativeSentencesGeneration#DEFAULT_SEPARATOR} * will be used. * * @param testClass the test class to search on for {@code @IndicativeSentencesGeneration} * @param enclosingInstanceTypes the runtime types of the enclosing * instances; never {@code null} * @return the sentence fragment separator */ private static String getFragmentSeparator(Class testClass, List> enclosingInstanceTypes) { return findIndicativeSentencesGeneration(testClass, enclosingInstanceTypes)// .map(IndicativeSentencesGeneration::separator)// .orElse(IndicativeSentencesGeneration.DEFAULT_SEPARATOR); } /** * Get the display name generator to use for the supplied test class. * *

If {@link IndicativeSentencesGeneration @IndicativeSentencesGeneration} * is present (searching enclosing classes if not found locally), the * configured {@link IndicativeSentencesGeneration#generator() generator} * will be used. Otherwise, {@link IndicativeSentencesGeneration#DEFAULT_GENERATOR} * will be used. * * @param testClass the test class to search on for {@code @IndicativeSentencesGeneration} * @param enclosingInstanceTypes the runtime types of the enclosing * instances; never {@code null} * @return the {@code DisplayNameGenerator} instance to use */ private static DisplayNameGenerator getGeneratorFor(Class testClass, List> enclosingInstanceTypes) { return findIndicativeSentencesGeneration(testClass, enclosingInstanceTypes)// .map(IndicativeSentencesGeneration::generator)// .filter(notIndicativeSentences)// .map(DisplayNameGenerator::getDisplayNameGenerator)// .orElseGet(() -> getDisplayNameGenerator(IndicativeSentencesGeneration.DEFAULT_GENERATOR)); } /** * Find the first {@code DisplayNameGeneration} annotation that is either * directly present, meta-present, or indirectly present * on the supplied {@code testClass} or on an enclosing instance type. * * @param testClass the test class on which to find the annotation; never {@code null} * @param enclosingInstanceTypes the runtime types of the enclosing * instances; never {@code null} * @return an {@code Optional} containing the annotation, potentially empty if not found */ @API(status = INTERNAL, since = "5.12") private static Optional findDisplayNameGeneration(Class testClass, List> enclosingInstanceTypes) { return findAnnotation(testClass, DisplayNameGeneration.class, enclosingInstanceTypes); } /** * Find the first {@code IndicativeSentencesGeneration} annotation that is either * directly present, meta-present, or indirectly present * on the supplied {@code testClass} or on an enclosing instance type. * * @param testClass the test class on which to find the annotation; never {@code null} * @param enclosingInstanceTypes the runtime types of the enclosing * instances; never {@code null} * @return an {@code Optional} containing the annotation, potentially empty if not found */ private static Optional findIndicativeSentencesGeneration(Class testClass, List> enclosingInstanceTypes) { return findAnnotation(testClass, IndicativeSentencesGeneration.class, enclosingInstanceTypes); } private static @Nullable String getSentenceFragment(AnnotatedElement element) { return findAnnotation(element, SentenceFragment.class) // .map(SentenceFragment::value) // .map(sentenceFragment -> { Preconditions.notBlank(sentenceFragment, "@SentenceFragment on [%s] must be declared with a non-blank value.".formatted(element)); return sentenceFragment.strip(); }) // .orElse(null); } } /** * Return the {@code DisplayNameGenerator} instance corresponding to the * given {@code Class}. * * @param generatorClass the generator's {@code Class}; never {@code null}, * has to be a {@code DisplayNameGenerator} implementation * @return a {@code DisplayNameGenerator} implementation instance */ static DisplayNameGenerator getDisplayNameGenerator(Class generatorClass) { Preconditions.notNull(generatorClass, "Class must not be null"); Preconditions.condition(DisplayNameGenerator.class.isAssignableFrom(generatorClass), "Class must be a DisplayNameGenerator implementation"); if (generatorClass == Standard.class) { return Standard.INSTANCE; } if (generatorClass == Simple.class) { return Simple.INSTANCE; } if (generatorClass == ReplaceUnderscores.class) { return ReplaceUnderscores.INSTANCE; } if (generatorClass == IndicativeSentences.class) { return IndicativeSentences.INSTANCE; } return (DisplayNameGenerator) ReflectionSupport.newInstance(generatorClass); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicContainer.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import java.net.URI; import java.util.List; import java.util.Optional; import java.util.function.Consumer; import java.util.stream.Stream; import java.util.stream.StreamSupport; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.platform.commons.util.Preconditions; /** * A {@code DynamicContainer} is a container generated at runtime. * *

It is composed of a {@linkplain DynamicNode#getDisplayName display name} * and an {@link Iterable} or {@link Stream} of {@link DynamicNode DynamicNodes}. * *

Instances of {@code DynamicContainer} must be generated by factory methods * annotated with {@link TestFactory @TestFactory}. * * @since 5.0 * @see #dynamicContainer(String, Iterable) * @see #dynamicContainer(String, Stream) * @see TestFactory * @see DynamicTest */ @API(status = MAINTAINED, since = "5.3") public class DynamicContainer extends DynamicNode { private final @Nullable ExecutionMode childExecutionMode; /** * Factory for creating a new {@code DynamicContainer} for the supplied display * name and collection of dynamic nodes. * *

The collection of dynamic nodes must not contain {@code null} elements. * * @param displayName the display name for the dynamic container; never * {@code null} or blank * @param dynamicNodes collection of dynamic nodes to execute; * never {@code null} * @see #dynamicContainer(String, Stream) */ public static DynamicContainer dynamicContainer(String displayName, Iterable dynamicNodes) { return dynamicContainer(config -> config.displayName(displayName).children(dynamicNodes)); } /** * Factory for creating a new {@code DynamicContainer} for the supplied display * name and stream of dynamic nodes. * *

The stream of dynamic nodes must not contain {@code null} elements. * * @param displayName the display name for the dynamic container; never * {@code null} or blank * @param dynamicNodes stream of dynamic nodes to execute; * never {@code null} * @see #dynamicContainer(String, Iterable) */ public static DynamicContainer dynamicContainer(String displayName, Stream dynamicNodes) { return dynamicContainer(config -> config.displayName(displayName).children(dynamicNodes)); } /** * Factory for creating a new {@code DynamicContainer} for the supplied display * name, custom test source {@link URI}, and stream of dynamic nodes. * *

The stream of dynamic nodes must not contain {@code null} elements. * * @param displayName the display name for the dynamic container; never * {@code null} or blank * @param testSourceUri a custom test source URI for the dynamic container; * may be {@code null} if the framework should generate the test source based * on the {@code @TestFactory} method * @param dynamicNodes stream of dynamic nodes to execute; never {@code null} * @since 5.3 * @see #dynamicContainer(String, Iterable) */ public static DynamicContainer dynamicContainer(String displayName, @Nullable URI testSourceUri, Stream dynamicNodes) { return dynamicContainer( config -> config.displayName(displayName).testSourceUri(testSourceUri).children(dynamicNodes)); } /** * Factory for creating a new {@code DynamicTest} that is configured via the * supplied {@link Consumer} of {@link DynamicTest.Configuration}. * * @param configurer callback for configuring the resulting * {@code DynamicTest}; never {@code null}. * * @since 6.1 */ @API(status = EXPERIMENTAL, since = "6.1") public static DynamicContainer dynamicContainer(Consumer configurer) { var configuration = new DefaultConfiguration(); configurer.accept(configuration); return new DynamicContainer(configuration); } private final Stream children; private DynamicContainer(DefaultConfiguration configuration) { super(configuration); this.children = Preconditions.notNull(configuration.children, "children must not be null"); this.childExecutionMode = configuration.childExecutionMode; } /** * Get the {@link Stream} of {@link DynamicNode DynamicNodes} associated * with this {@code DynamicContainer}. */ public Stream getChildren() { return children; } /** * {@return the {@link ExecutionMode} for * {@linkplain #getChildren() children} of this {@code DynamicContainer} * that is used unless they are * {@linkplain DynamicTest#getExecutionMode() configured} differently}. * * @since 6.1 * @see DynamicTest#getExecutionMode() */ @API(status = EXPERIMENTAL, since = "6.1") public Optional getChildExecutionMode() { return Optional.ofNullable(childExecutionMode); } /** * {@code Configuration} of a {@link DynamicContainer}. * * @since 6.1 * @see DynamicContainer#dynamicContainer(Consumer) */ @API(status = EXPERIMENTAL, since = "6.1") public sealed interface Configuration extends DynamicNode.Configuration { /** * Set the * {@linkplain DynamicContainer#getChildExecutionMode() child execution mode} * to use for the configured {@link DynamicContainer}. * * @return this configuration for method chaining */ Configuration childExecutionMode(ExecutionMode executionMode); /** * Set the {@linkplain DynamicContainer#getChildren() children} of the * configured {@link DynamicContainer}. * *

Any previously configured value is overridden. * * @param children the children; never {@code null} or containing * {@code null} elements * @return this configuration for method chaining */ default Configuration children(Iterable children) { Preconditions.notNull(children, "children must not be null"); return children(StreamSupport.stream(children.spliterator(), false)); } /** * Set the {@linkplain DynamicContainer#getChildren() children} of the * configured {@link DynamicContainer}. * *

Any previously configured value is overridden. * * @param children the children; never {@code null} or containing * {@code null} elements * @return this configuration for method chaining */ default Configuration children(DynamicNode... children) { Preconditions.notNull(children, "children must not be null"); Preconditions.containsNoNullElements(children, "children must not contain null elements"); return children(List.of(children)); } /** * Set the {@linkplain DynamicContainer#getChildren() children} of the * configured {@link DynamicContainer}. * *

Any previously configured value is overridden. * * @param children the children; never {@code null} or containing * {@code null} elements * @return this configuration for method chaining */ Configuration children(Stream children); } static final class DefaultConfiguration extends AbstractConfiguration implements Configuration { private @Nullable Stream children; private @Nullable ExecutionMode childExecutionMode; @Override public Configuration childExecutionMode(ExecutionMode executionMode) { this.childExecutionMode = Preconditions.notNull(executionMode, "executionMode must not be null"); return this; } @Override public Configuration children(Stream children) { Preconditions.notNull(children, "children must not be null"); Preconditions.condition(this.children == null, "children can only be set once"); this.children = children; return this; } @Override protected Configuration self() { return this; } } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicNode.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import java.net.URI; import java.util.Optional; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; /** * {@code DynamicNode} serves as the abstract base class for a container or a * test case generated at runtime. * * @since 5.0 * @see DynamicTest * @see DynamicContainer */ @API(status = MAINTAINED, since = "5.3") public abstract class DynamicNode { private final String displayName; /** Custom test source {@link URI} associated with this node; potentially {@code null}. */ private final @Nullable URI testSourceUri; private final @Nullable ExecutionMode executionMode; DynamicNode(AbstractConfiguration configuration) { this.displayName = Preconditions.notBlank(configuration.displayName, "displayName must not be null or blank"); this.testSourceUri = configuration.testSourceUri; this.executionMode = configuration.executionMode; } /** * Get the display name of this {@code DynamicNode}. * * @return the display name */ public String getDisplayName() { return this.displayName; } /** * Get the custom test source {@link URI} of this {@code DynamicNode}. * * @return an {@code Optional} containing the custom test source {@link URI}; * never {@code null} but potentially empty * @since 5.3 */ public Optional getTestSourceUri() { return Optional.ofNullable(testSourceUri); } /** * {@return the {@link ExecutionMode} of this {@code DynamicNode}} * * @since 6.1 * @see DynamicContainer#getChildExecutionMode() */ @API(status = EXPERIMENTAL, since = "6.1") public Optional getExecutionMode() { return Optional.ofNullable(executionMode); } @Override public String toString() { return new ToStringBuilder(this) // .append("displayName", displayName) // .append("testSourceUri", testSourceUri) // .toString(); } /** * {@code Configuration} of a {@link DynamicNode} or one of its * subinterfaces. * * @since 6.1 * @see DynamicTest.Configuration * @see DynamicContainer.Configuration */ @API(status = EXPERIMENTAL, since = "6.1") public sealed interface Configuration> permits AbstractConfiguration, DynamicContainer.Configuration, DynamicTest.Configuration { /** * Set the {@linkplain DynamicNode#getDisplayName() display name} to use * for the configured {@link DynamicNode}. * * @param displayName the display name; never {@code null} or blank * @return this configuration for method chaining */ T displayName(String displayName); /** * Set the {@linkplain DynamicNode#getTestSourceUri() test source URI} * to use for the configured {@link DynamicNode}. * * @param testSourceUri the test source URI; may be {@code null} * @return this configuration for method chaining */ T testSourceUri(@Nullable URI testSourceUri); /** * Set the {@linkplain DynamicNode#getExecutionMode() execution mode} to * use for the configured {@link DynamicNode}. * * @param executionMode the execution mode; never {@code null} * @return this configuration for method chaining */ T executionMode(ExecutionMode executionMode); } abstract static sealed class AbstractConfiguration> implements Configuration permits DynamicContainer.DefaultConfiguration, DynamicTest.DefaultConfiguration { private @Nullable String displayName; private @Nullable URI testSourceUri; private @Nullable ExecutionMode executionMode; @Override public T displayName(String displayName) { this.displayName = Preconditions.notBlank(displayName, "displayName must not be null or blank"); return self(); } @Override public T testSourceUri(@Nullable URI testSourceUri) { this.testSourceUri = testSourceUri; return self(); } @Override public T executionMode(ExecutionMode executionMode) { this.executionMode = Preconditions.notNull(executionMode, "executionMode must not be null"); return self(); } protected abstract T self(); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static java.util.Spliterator.ORDERED; import static java.util.Spliterators.spliteratorUnknownSize; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import java.net.URI; import java.util.Iterator; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Stream; import java.util.stream.StreamSupport; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.api.function.ThrowingConsumer; import org.junit.platform.commons.util.Preconditions; /** * A {@code DynamicTest} is a test case generated at runtime. * *

It is composed of a {@linkplain DynamicNode#getDisplayName display name} * and an {@link #getExecutable Executable}. * *

Instances of {@code DynamicTest} must be generated by factory methods * annotated with {@link TestFactory @TestFactory}. * *

Note that dynamic tests are quite different from standard {@link Test @Test} * cases since callbacks such as {@link BeforeEach @BeforeEach} and * {@link AfterEach @AfterEach} methods are not executed for dynamic tests. * * @since 5.0 * @see #dynamicTest(String, Executable) * @see #stream(Iterator, Function, ThrowingConsumer) * @see Test * @see TestFactory * @see DynamicContainer * @see Executable */ @API(status = MAINTAINED, since = "5.3") public class DynamicTest extends DynamicNode { /** * Factory for creating a new {@code DynamicTest} for the supplied display * name and executable code block. * * @param displayName the display name for the dynamic test; never * {@code null} or blank * @param executable the executable code block for the dynamic test; * never {@code null} * @see #stream(Iterator, Function, ThrowingConsumer) */ public static DynamicTest dynamicTest(String displayName, Executable executable) { return dynamicTest(config -> config.displayName(displayName).executable(executable)); } /** * Factory for creating a new {@code DynamicTest} for the supplied display * name, custom test source {@link URI}, and executable code block. * * @param displayName the display name for the dynamic test; never * {@code null} or blank * @param testSourceUri a custom test source URI for the dynamic test; may * be {@code null} if the framework should generate the test source based on * the {@code @TestFactory} method * @param executable the executable code block for the dynamic test; * never {@code null} * @since 5.3 * @see #stream(Iterator, Function, ThrowingConsumer) */ public static DynamicTest dynamicTest(String displayName, @Nullable URI testSourceUri, Executable executable) { return dynamicTest( config -> config.displayName(displayName).testSourceUri(testSourceUri).executable(executable)); } /** * Factory for creating a new {@code DynamicTest} that is configured via the * supplied {@link Consumer} of {@link Configuration}. * * @param configurer callback for configuring the resulting * {@code DynamicTest}; never {@code null}. * * @since 6.1 */ @API(status = EXPERIMENTAL, since = "6.1") public static DynamicTest dynamicTest(Consumer configurer) { var configuration = new DefaultConfiguration(); configurer.accept(configuration); return new DynamicTest(configuration); } /** * Generate a stream of dynamic tests based on the given generator and test * executor. * *

Use this method when the set of dynamic tests is nondeterministic in * nature or when the input comes from an existing {@link Iterator}. See * {@link #stream(Stream, Function, ThrowingConsumer)} as an alternative. * *

The given {@code inputGenerator} is responsible for generating * input values. A {@link DynamicTest} will be added to the resulting * stream for each dynamically generated input value, using the given * {@code displayNameGenerator} and {@code testExecutor}. * * @param inputGenerator an {@code Iterator} that serves as a dynamic * input generator; never {@code null} * @param displayNameGenerator a function that generates a display name * based on an input value; never {@code null} * @param testExecutor a consumer that executes a test based on an input * value; never {@code null} * @param the type of input generated by the {@code inputGenerator} * and used by the {@code displayNameGenerator} and {@code testExecutor} * @return a stream of dynamic tests based on the given generator and * executor; never {@code null} * @see #dynamicTest(String, Executable) * @see #stream(Stream, Function, ThrowingConsumer) */ public static Stream stream(Iterator inputGenerator, Function displayNameGenerator, ThrowingConsumer testExecutor) { Preconditions.notNull(inputGenerator, "inputGenerator must not be null"); return stream(StreamSupport.stream(spliteratorUnknownSize(inputGenerator, ORDERED), false), displayNameGenerator, testExecutor); } /** * Generate a stream of dynamic tests based on the given input stream and * test executor. * *

Use this method when the set of dynamic tests is nondeterministic in * nature or when the input comes from an existing {@link Stream}. See * {@link #stream(Iterator, Function, ThrowingConsumer)} as an alternative. * *

The given {@code inputStream} is responsible for supplying input values. * A {@link DynamicTest} will be added to the resulting stream for each * dynamically supplied input value, using the given {@code displayNameGenerator} * and {@code testExecutor}. * * @param inputStream a {@code Stream} that supplies dynamic input values; * never {@code null} * @param displayNameGenerator a function that generates a display name * based on an input value; never {@code null} * @param testExecutor a consumer that executes a test based on an input * value; never {@code null} * @param the type of input supplied by the {@code inputStream} * and used by the {@code displayNameGenerator} and {@code testExecutor} * @return a stream of dynamic tests based on the given generator and * executor; never {@code null} * @since 5.7 * @see #dynamicTest(String, Executable) * @see #stream(Iterator, Function, ThrowingConsumer) */ @API(status = MAINTAINED, since = "5.7") public static Stream stream(Stream inputStream, Function displayNameGenerator, ThrowingConsumer testExecutor) { Preconditions.notNull(inputStream, "inputStream must not be null"); Preconditions.notNull(displayNameGenerator, "displayNameGenerator must not be null"); Preconditions.notNull(testExecutor, "testExecutor must not be null"); return inputStream // .map(input -> dynamicTest(displayNameGenerator.apply(input), () -> testExecutor.accept(input))); } /** * Generate a stream of dynamic tests based on the given generator and test * executor. * *

Use this method when the set of dynamic tests is nondeterministic in * nature or when the input comes from an existing {@link Iterator}. See * {@link #stream(Stream, ThrowingConsumer)} as an alternative. * *

The given {@code inputGenerator} is responsible for generating * input values and display names. A {@link DynamicTest} will be added to * the resulting stream for each dynamically generated input value, * using the given {@code testExecutor}. * * @param inputGenerator an {@code Iterator} with {@code Named} values * that serves as a dynamic input generator; never {@code null} * @param testExecutor a consumer that executes a test based on an input * value; never {@code null} * @param the type of input generated by the {@code inputGenerator} * and used by the {@code testExecutor} * @return a stream of dynamic tests based on the given generator and * executor; never {@code null} * @since 5.8 * * @see #dynamicTest(String, Executable) * @see #stream(Stream, ThrowingConsumer) * @see Named */ @API(status = MAINTAINED, since = "5.8") public static Stream stream(Iterator> inputGenerator, ThrowingConsumer testExecutor) { Preconditions.notNull(inputGenerator, "inputGenerator must not be null"); return stream(StreamSupport.stream(spliteratorUnknownSize(inputGenerator, ORDERED), false), testExecutor); } /** * Generate a stream of dynamic tests based on the given input stream and * test executor. * *

Use this method when the set of dynamic tests is nondeterministic in * nature or when the input comes from an existing {@link Stream}. See * {@link #stream(Iterator, ThrowingConsumer)} as an alternative. * *

The given {@code inputStream} is responsible for supplying input values * and display names. A {@link DynamicTest} will be added to the resulting stream for * each dynamically supplied input value, using the given {@code testExecutor}. * * @param inputStream a {@code Stream} that supplies dynamic {@code Named} * input values; never {@code null} * @param testExecutor a consumer that executes a test based on an input * value; never {@code null} * @param the type of input supplied by the {@code inputStream} * and used by the {@code displayNameGenerator} and {@code testExecutor} * @return a stream of dynamic tests based on the given generator and * executor; never {@code null} * @since 5.8 * * @see #dynamicTest(String, Executable) * @see #stream(Iterator, ThrowingConsumer) * @see Named */ @API(status = MAINTAINED, since = "5.8") public static Stream stream(Stream> inputStream, ThrowingConsumer testExecutor) { Preconditions.notNull(inputStream, "inputStream must not be null"); Preconditions.notNull(testExecutor, "testExecutor must not be null"); return inputStream // .map(input -> dynamicTest(input.getName(), () -> testExecutor.accept(input.getPayload()))); } /** * Generate a stream of dynamic tests based on the given iterator. * *

Use this method when the set of dynamic tests is nondeterministic in * nature or when the input comes from an existing {@link Iterator}. See * {@link #stream(Stream)} as an alternative. * *

The given {@code iterator} is responsible for supplying * {@link Named} input values that provide an {@link Executable} code block. * A {@link DynamicTest} comprised of both parts will be added to the * resulting stream for each dynamically supplied input value. * * @param iterator an {@code Iterator} that supplies named executables; * never {@code null} * @param the type of input supplied by the {@code inputStream} * @return a stream of dynamic tests based on the given iterator; never * {@code null} * @since 5.11 * @see #dynamicTest(String, Executable) * @see #stream(Stream) * @see NamedExecutable */ @API(status = MAINTAINED, since = "5.13.3") public static , E extends Executable> Stream stream( Iterator iterator) { Preconditions.notNull(iterator, "iterator must not be null"); return stream(StreamSupport.stream(spliteratorUnknownSize(iterator, ORDERED), false)); } /** * Generate a stream of dynamic tests based on the given input stream. * *

Use this method when the set of dynamic tests is nondeterministic in * nature or when the input comes from an existing {@link Stream}. See * {@link #stream(Iterator)} as an alternative. * *

The given {@code inputStream} is responsible for supplying * {@link Named} input values that provide an {@link Executable} code block. * A {@link DynamicTest} comprised of both parts will be added to the * resulting stream for each dynamically supplied input value. * * @param inputStream a {@code Stream} that supplies named executables; * never {@code null} * @param the type of input supplied by the {@code inputStream} * @return a stream of dynamic tests based on the given stream; never * {@code null} * @since 5.11 * @see #dynamicTest(String, Executable) * @see #stream(Iterator) * @see NamedExecutable */ @API(status = MAINTAINED, since = "5.13.3") public static , E extends Executable> Stream stream( Stream inputStream) { Preconditions.notNull(inputStream, "inputStream must not be null"); return inputStream. // map(input -> dynamicTest(input.getName(), input.getPayload())); } private final Executable executable; private DynamicTest(DefaultConfiguration configuration) { super(configuration); this.executable = Preconditions.notNull(configuration.executable, "executable must not be null"); } /** * Get the {@code executable} code block associated with this {@code DynamicTest}. */ public Executable getExecutable() { return this.executable; } /** * {@code Configuration} of a {@link DynamicTest}. * * @since 6.1 * @see DynamicTest#dynamicTest(Consumer) */ @API(status = EXPERIMENTAL, since = "6.1") public sealed interface Configuration extends DynamicNode.Configuration { /** * Set the {@linkplain DynamicTest#getExecutable() executable} to use * for the configured {@link DynamicTest}. * * @param executable the executable; never {@code null} or blank * @return this configuration for method chaining */ Configuration executable(Executable executable); } static final class DefaultConfiguration extends AbstractConfiguration implements Configuration { private @Nullable Executable executable; @Override public Configuration executable(Executable executable) { this.executable = Preconditions.notNull(executable, "executable must not be null"); return this; } @Override protected Configuration self() { return this; } } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/IndicativeSentencesGeneration.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.api.DisplayNameGenerator.IndicativeSentences; /** * {@code @IndicativeSentencesGeneration} is used to register the * {@link IndicativeSentences} display name generator and configure it. * *

The {@link #separator} for sentence fragments and the display name * {@link #generator} for sentence fragments are configurable. If this annotation * is declared without any attributes — for example, * {@code @IndicativeSentencesGeneration} or {@code @IndicativeSentencesGeneration()} * — the default configuration will be used. * *

This annotation is inherited from superclasses and implemented * interfaces. It is also inherited from {@linkplain Class#getEnclosingClass() * enclosing classes} for {@link Nested @Nested} test classes. * * @since 5.7 * @see DisplayName * @see DisplayNameGenerator * @see DisplayNameGenerator.IndicativeSentences * @see DisplayNameGeneration */ @DisplayNameGeneration(IndicativeSentences.class) @Target({ ElementType.ANNOTATION_TYPE, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @API(status = STABLE, since = "5.10") public @interface IndicativeSentencesGeneration { String DEFAULT_SEPARATOR = ", "; Class DEFAULT_GENERATOR = DisplayNameGenerator.Standard.class; /** * Custom separator for sentence fragments. * *

Defaults to {@value #DEFAULT_SEPARATOR}. */ String separator() default DEFAULT_SEPARATOR; /** * Custom display name generator to use for sentence fragments. * *

Defaults to {@link DisplayNameGenerator.Standard}. */ Class generator() default DisplayNameGenerator.Standard.class; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/MediaType.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static java.nio.charset.StandardCharsets.UTF_8; import static org.apiguardian.api.API.Status.MAINTAINED; import java.nio.charset.Charset; import java.nio.file.Path; import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; /** * Represents a media type as defined by * RFC 2045. * *

WARNING: This type should not be extended by third parties. * * @since 5.14 * @see TestReporter#publishFile(Path, MediaType) * @see TestReporter#publishFile(String, MediaType, org.junit.jupiter.api.function.ThrowingConsumer) * @see org.junit.jupiter.api.extension.ExtensionContext#publishFile(String, MediaType, org.junit.jupiter.api.function.ThrowingConsumer) */ @SuppressWarnings("removal") @API(status = MAINTAINED, since = "5.14") public sealed class MediaType permits org.junit.jupiter.api.extension.MediaType { private static final Pattern PATTERN; static { // https://datatracker.ietf.org/doc/html/rfc2045#section-5.1 String whitespace = "[ \t]*"; String token = "[0-9A-Za-z!#$%&'*+.^_`|~-]+"; String quotedString = "\"(?:[^\"\\\\]|\\.)*\""; String parameter = ";" + whitespace + token + "=" + "(?:" + token + "|" + quotedString + ")"; PATTERN = Pattern.compile(token + "/" + token + "(?:" + whitespace + parameter + ")*"); } /** * The {@code text/plain} media type. */ public static final MediaType TEXT_PLAIN = create("text", "plain"); /** * The {@code text/plain; charset=UTF-8} media type. */ public static final MediaType TEXT_PLAIN_UTF_8 = create("text", "plain", UTF_8); /** * The {@code application/json} media type. */ public static final MediaType APPLICATION_JSON = create("application", "json"); /** * The {@code application/octet-stream} media type. */ public static final MediaType APPLICATION_OCTET_STREAM = create("application", "octet-stream"); /** * The {@code image/jpeg} media type. */ public static final MediaType IMAGE_JPEG = create("image", "jpeg"); /** * The {@code image/png} media type. */ public static final MediaType IMAGE_PNG = create("image", "png"); private final String value; /** * Parse the given media type value. * *

Must be valid according to * RFC 2045. * * @param value the media type value to parse; never {@code null} or blank * @return the parsed media type */ public static MediaType parse(String value) { return new MediaType(value); } /** * Create a media type with the given type and subtype. * * @param type the type; never {@code null} or blank * @param subtype the subtype; never {@code null} or blank * @return the media type */ public static MediaType create(String type, String subtype) { return new MediaType(type, subtype, null); } /** * Create a media type with the given type, subtype, and charset. * * @param type the type; never {@code null} or blank * @param subtype the subtype; never {@code null} or blank * @param charset the charset; never {@code null} * @return the media type */ public static MediaType create(String type, String subtype, Charset charset) { Preconditions.notNull(charset, "charset must not be null"); return new MediaType(type, subtype, charset); } protected MediaType(String type, String subtype, @Nullable Charset charset) { this("%s/%s%s".formatted(// Preconditions.notBlank(type, "type must not be null or blank").strip(), Preconditions.notBlank(subtype, "subtype must not be null or blank").strip(), (charset != null ? ("; charset=" + charset.name()) : ""))); } protected MediaType(String value) { String strippedValue = Preconditions.notBlank(value, "value must not be null or blank").strip(); Matcher matcher = PATTERN.matcher(strippedValue); Preconditions.condition(matcher.matches(), () -> "Invalid media type: '" + strippedValue + "'"); this.value = strippedValue; } /** * {@return a string representation of this media type} */ @Override public final String toString() { return this.value; } @Override public final boolean equals(Object obj) { return this == obj || (obj instanceof MediaType that && this.value.equals(that.value)); } @Override public final int hashCode() { return Objects.hashCode(this.value); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodDescriptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.List; import java.util.Optional; import org.apiguardian.api.API; /** * {@code MethodDescriptor} encapsulates functionality for a given {@link Method}. * * @since 5.4 * @see MethodOrdererContext */ @API(status = STABLE, since = "5.7") public interface MethodDescriptor { /** * Get the method for this descriptor. * * @return the method; never {@code null} */ Method getMethod(); /** * Get the display name for this descriptor's {@link #getMethod() method}. * * @return the display name for this descriptor's method; never {@code null} * or blank * @since 5.7 */ @API(status = STABLE, since = "5.10") String getDisplayName(); /** * Determine if an annotation of {@code annotationType} is either * present or meta-present on the {@link Method} for * this descriptor. * * @param annotationType the annotation type to search for; never {@code null} * @return {@code true} if the annotation is present or meta-present * @see #findAnnotation(Class) * @see #findRepeatableAnnotations(Class) */ boolean isAnnotated(Class annotationType); /** * Find the first annotation of {@code annotationType} that is either * present or meta-present on the {@link Method} for * this descriptor. * * @param the annotation type * @param annotationType the annotation type to search for; never {@code null} * @return an {@code Optional} containing the annotation; never {@code null} but * potentially empty * @see #isAnnotated(Class) * @see #findRepeatableAnnotations(Class) */ Optional findAnnotation(Class annotationType); /** * Find all repeatable {@linkplain Annotation annotations} of * {@code annotationType} that are either present or * meta-present on the {@link Method} for this descriptor. * * @param the annotation type * @param annotationType the repeatable annotation type to search for; never * {@code null} * @return the list of all such annotations found; neither {@code null} nor * mutable, but potentially empty * @see #isAnnotated(Class) * @see #findAnnotation(Class) * @see java.lang.annotation.Repeatable */ List findRepeatableAnnotations(Class annotationType); } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrderer.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static java.util.Comparator.comparingInt; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import java.lang.reflect.Method; import java.util.Collections; import java.util.Comparator; import java.util.Optional; import org.apiguardian.api.API; import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.ClassUtils; /** * {@code MethodOrderer} defines the API for ordering the test methods * in a given test class. * *

In this context, the term "test method" refers to any method annotated with * {@code @Test}, {@code @RepeatedTest}, {@code @ParameterizedTest}, * {@code @TestFactory}, or {@code @TestTemplate}. * *

A {@link MethodOrderer} can be configured globally for the entire * test suite via the {@value #DEFAULT_ORDER_PROPERTY_NAME} configuration * parameter (see the User Guide for details) or locally for a test * class via the {@link TestMethodOrder @TestMethodOrder} annotation. * *

Built-in Implementations

* *

JUnit Jupiter provides the following built-in {@code MethodOrderer} * implementations. * *

    *
  • {@link Default}
  • *
  • {@link MethodName}
  • *
  • {@link OrderAnnotation}
  • *
  • {@link Random}
  • *
* * @since 5.4 * @see TestMethodOrder * @see MethodOrdererContext * @see #orderMethods(MethodOrdererContext) * @see ClassOrderer */ @API(status = STABLE, since = "5.7") public interface MethodOrderer { /** * Property name used to set the default method orderer class name: {@value} * *

Supported Values

* *

Supported values include fully qualified class names for types that * implement {@link org.junit.jupiter.api.MethodOrderer}. * *

If not specified, test methods will be ordered using an algorithm that * is deterministic but intentionally non-obvious. * * @since 5.7 */ @API(status = STABLE, since = "5.9") String DEFAULT_ORDER_PROPERTY_NAME = "junit.jupiter.testmethod.order.default"; /** * Order the methods encapsulated in the supplied {@link MethodOrdererContext}. * *

The methods to order or sort are made indirectly available via * {@link MethodOrdererContext#getMethodDescriptors()}. Since this method * has a {@code void} return type, the list of method descriptors must be * modified directly. * *

For example, a simplified implementation of the {@link Random} * {@code MethodOrderer} might look like the following. * *

	 * public void orderMethods(MethodOrdererContext context) {
	 *     Collections.shuffle(context.getMethodDescriptors());
	 * }
* * @param context the {@code MethodOrdererContext} containing the * {@linkplain MethodDescriptor method descriptors} to order; never {@code null} * @see #getDefaultExecutionMode() */ void orderMethods(MethodOrdererContext context); /** * Get the default {@link ExecutionMode} for the test class * configured with this {@link MethodOrderer}. * *

This method is guaranteed to be invoked after * {@link #orderMethods(MethodOrdererContext)} which allows implementations * of this method to determine the appropriate return value programmatically, * potentially based on actions that were taken in {@code orderMethods()}. * *

Defaults to {@link ExecutionMode#SAME_THREAD SAME_THREAD}, since * ordered methods are typically sorted in a fashion that would conflict * with concurrent execution. * *

In case the ordering does not conflict with concurrent execution, * implementations should return an empty {@link Optional} to signal that * the engine should decide which execution mode to use. * *

Can be overridden via an explicit * {@link org.junit.jupiter.api.parallel.Execution @Execution} declaration * on the test class or in concrete implementations of the * {@code MethodOrderer} API. * * @return the default {@code ExecutionMode}; never {@code null} but * potentially empty * @see #orderMethods(MethodOrdererContext) */ default Optional getDefaultExecutionMode() { return Optional.of(ExecutionMode.SAME_THREAD); } /** * {@code MethodOrderer} that allows to explicitly specify that the default * ordering should be applied. * *

If the {@value #DEFAULT_ORDER_PROPERTY_NAME} is set, specifying this * {@code MethodOrderer} has the same effect as referencing the configured * class directly. Otherwise, it has the same effect as not specifying any * {@code MethodOrderer}. * *

This class can be used to reset the {@code MethodOrderer} for a * {@link Nested @Nested} class and its {@code @Nested} inner classes, * recursively, when a {@code MethodOrderer} is configured using * {@link TestMethodOrder @TestMethodOrder} on an enclosing class. * * @since 6.0 */ @API(status = EXPERIMENTAL, since = "6.0") final class Default implements MethodOrderer { private Default() { throw new JUnitException("This class must not be instantiated"); } @Override public void orderMethods(MethodOrdererContext context) { // never called } } /** * {@code MethodOrderer} that sorts methods alphanumerically based on their * names using {@link String#compareTo(String)}. * *

If two methods have the same name, {@code String} representations of * their formal parameter lists will be used as a fallback for comparing the * methods. * * @since 5.7 */ @API(status = STABLE, since = "5.10") class MethodName implements MethodOrderer { public MethodName() { } /** * Sort the methods encapsulated in the supplied * {@link MethodOrdererContext} alphanumerically based on their names * and formal parameter lists. */ @Override public void orderMethods(MethodOrdererContext context) { context.getMethodDescriptors().sort(comparator); } private static final Comparator comparator = Comparator. // comparing(descriptor -> descriptor.getMethod().getName())// .thenComparing(descriptor -> parameterList(descriptor.getMethod())); private static String parameterList(Method method) { return ClassUtils.nullSafeToString(method.getParameterTypes()); } } /** * {@code MethodOrderer} that sorts methods alphanumerically based on their * display names using {@link String#compareTo(String)} * * @since 5.7 */ @API(status = STABLE, since = "5.10") class DisplayName implements MethodOrderer { public DisplayName() { } /** * Sort the methods encapsulated in the supplied * {@link MethodOrdererContext} alphanumerically based on their display * names. */ @Override public void orderMethods(MethodOrdererContext context) { context.getMethodDescriptors().sort(comparator); } private static final Comparator comparator = Comparator.comparing( MethodDescriptor::getDisplayName); } /** * {@code MethodOrderer} that sorts methods based on the {@link Order @Order} * annotation. * *

Any methods that are assigned the same order value will be sorted * arbitrarily adjacent to each other. * *

Any methods not annotated with {@code @Order} will be assigned the * {@linkplain Order#DEFAULT default order} value which will effectively cause them * to appear at the end of the sorted list, unless certain methods are assigned * an explicit order value greater than the default order value. Any methods * assigned an explicit order value greater than the default order value will * appear after non-annotated methods in the sorted list. */ class OrderAnnotation implements MethodOrderer { public OrderAnnotation() { } /** * Sort the methods encapsulated in the supplied * {@link MethodOrdererContext} based on the {@link Order @Order} * annotation. */ @Override public void orderMethods(MethodOrdererContext context) { context.getMethodDescriptors().sort(comparingInt(OrderAnnotation::getOrder)); } private static int getOrder(MethodDescriptor descriptor) { return descriptor.findAnnotation(Order.class).map(Order::value).orElse(Order.DEFAULT); } } /** * {@code MethodOrderer} that orders methods pseudo-randomly. * *

Custom Seed

* *

By default, the random seed used for ordering methods is the * value returned by {@link System#nanoTime()} during static class * initialization. In order to support repeatable builds, the value of the * default random seed is logged at {@code CONFIG} level. In addition, a * custom seed (potentially the default seed from the previous test plan * execution) may be specified via the {@value Random#RANDOM_SEED_PROPERTY_NAME} * configuration parameter which can be supplied via the {@code Launcher} * API, build tools (e.g., Gradle and Maven), a JVM system property, or the JUnit * Platform configuration file (i.e., a file named {@code junit-platform.properties} * in the root of the class path). Consult the User Guide for further information. * * @see Random#RANDOM_SEED_PROPERTY_NAME * @see java.util.Random */ class Random implements MethodOrderer { private static final Logger logger = LoggerFactory.getLogger(Random.class); static { logger.config(() -> "MethodOrderer.Random default seed: " + RandomOrdererUtils.DEFAULT_SEED); } /** * Property name used to set the random seed used by this * {@code MethodOrderer}: {@value} * *

The same property is used by {@link ClassOrderer.Random} for * consistency between the two random orderers. * *

Supported Values

* *

Supported values include any string that can be converted to a * {@link Long} via {@link Long#valueOf(String)}. * *

If not specified or if the specified value cannot be converted to * a {@link Long}, the default random seed will be used (see the * {@linkplain Random class-level Javadoc} for details). * * @see ClassOrderer.Random */ public static final String RANDOM_SEED_PROPERTY_NAME = RandomOrdererUtils.RANDOM_SEED_PROPERTY_NAME; public Random() { } /** * Order the methods encapsulated in the supplied * {@link MethodOrdererContext} pseudo-randomly. */ @Override public void orderMethods(MethodOrdererContext context) { Collections.shuffle(context.getMethodDescriptors(), new java.util.Random(RandomOrdererUtils.getSeed(context::getConfigurationParameter, logger))); } } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrdererContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.STABLE; import java.util.List; import java.util.Optional; import org.apiguardian.api.API; /** * {@code MethodOrdererContext} encapsulates the context in which * a {@link MethodOrderer} will be invoked. * * @since 5.4 * @see MethodOrderer * @see MethodDescriptor */ @API(status = STABLE, since = "5.7") public interface MethodOrdererContext { /** * Get the test class for this context. * * @return the test class; never {@code null} */ Class getTestClass(); /** * Get the list of {@linkplain MethodDescriptor method descriptors} to * order. * * @return the list of method descriptors; never {@code null} */ List getMethodDescriptors(); /** * Get the configuration parameter stored under the specified {@code key}. * *

If no such key is present in the {@code ConfigurationParameters} for * the JUnit Platform, an attempt will be made to look up the value as a * JVM system property. If no such system property exists, an attempt will * be made to look up the value in the JUnit Platform properties file. * * @param key the key to look up; never {@code null} or blank * @return an {@code Optional} containing the value; never {@code null} * but potentially empty * * @see System#getProperty(String) * @see org.junit.platform.engine.ConfigurationParameters */ Optional getConfigurationParameter(String key); } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/Named.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; /** * {@code Named} is a container that associates a name with a given payload. * * @param the type of the payload * * @since 5.8 */ @API(status = STABLE, since = "5.8") public interface Named { /** * Factory method for creating an instance of {@code Named} based on a * {@code name} and a {@code payload}. * * @param name the name associated with the payload; never {@code null} or * blank * @param payload the object that serves as the payload; may be {@code null} * depending on the use case * @param the type of the payload * @return an instance of {@code Named}; never {@code null} * @see #named(String, java.lang.Object) */ static Named of(String name, T payload) { Preconditions.notBlank(name, "name must not be null or blank"); return new Named<>() { @Override public String getName() { return name; } @Override public T getPayload() { return payload; } @Override public String toString() { return name; } }; } /** * Factory method for creating an instance of {@code Named} based on a * {@code name} and a {@code payload}. * *

This method is an alias for {@link Named#of} and is * intended to be used when statically imported — for example, via: * {@code import static org.junit.jupiter.api.Named.named;} * * @param name the name associated with the payload; never {@code null} or * blank * @param payload the object that serves as the payload; may be {@code null} * depending on the use case * @param the type of the payload * @return an instance of {@code Named}; never {@code null} */ static Named named(String name, T payload) { return of(name, payload); } /** * Get the name of the payload. * * @return the name of the payload; never {@code null} or blank */ String getName(); /** * Get the payload. * * @return the payload; may be {@code null} depending on the use case */ T getPayload(); } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/NamedExecutable.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.MAINTAINED; import java.util.Iterator; import java.util.stream.Stream; import org.apiguardian.api.API; import org.junit.jupiter.api.function.Executable; /** * {@code NamedExecutable} joins {@code Executable} and {@code Named} in a * one self-typed functional interface. * *

The default implementation of {@link #getName()} returns the result of * calling {@link Object#toString()} on the implementing instance but may be * overridden by concrete implementations to provide a more meaningful name. * *

It is recommended to implement this interface using a record type. * * @since 5.11 * @see DynamicTest#stream(Stream) * @see DynamicTest#stream(Iterator) */ @FunctionalInterface @API(status = MAINTAINED, since = "5.13.3") public interface NamedExecutable extends Named, Executable { @Override default String getName() { return toString(); } @Override default Executable getPayload() { return this; } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/Nested.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.api.TestInstance.Lifecycle; /** * {@code @Nested} is used to signal that the annotated class is a nested, * non-static test class (i.e., an inner class) that can share * setup and state with an instance of its {@linkplain Class#getEnclosingClass() * enclosing class}. The enclosing class may be a top-level test class or * another {@code @Nested} test class, and nesting can be arbitrarily deep. * *

{@code @Nested} test classes may be ordered via * {@link TestClassOrder @TestClassOrder} or a global {@link ClassOrderer}. * *

{@code @Nested} may be combined with {@link ClassTemplate @ClassTemplate}. * *

Test Instance Lifecycle

* *
    *
  • A {@code @Nested} test class can be configured with its own * {@link Lifecycle} mode which may differ from that of an enclosing test * class.
  • *
  • A {@code @Nested} test class cannot change the {@link Lifecycle} * mode of an enclosing test class.
  • *
* * @since 5.0 * @see ClassTemplate * @see Test * @see TestInstance * @see TestClassOrder */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = STABLE, since = "5.0") public @interface Nested { } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/Order.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @Order} is an annotation that is used to configure the * {@linkplain #value order} in which the annotated element (i.e., field, * method, or class) should be evaluated or executed relative to other elements * of the same category. * *

When used with * {@link org.junit.jupiter.api.extension.RegisterExtension @RegisterExtension} or * {@link org.junit.jupiter.api.extension.ExtendWith @ExtendWith}, * the category applies to extension fields. When used with * {@link MethodOrderer.OrderAnnotation}, the category applies to test methods. * When used with {@link ClassOrderer.OrderAnnotation}, the category applies to * test classes. * *

If {@code @Order} is not explicitly declared on an element, the * {@link #DEFAULT} order value will be assigned to the element. * * @since 5.4 * @see MethodOrderer.OrderAnnotation * @see ClassOrderer.OrderAnnotation * @see org.junit.jupiter.api.extension.RegisterExtension @RegisterExtension * @see org.junit.jupiter.api.extension.ExtendWith @ExtendWith */ @Target({ ElementType.FIELD, ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = STABLE, since = "5.9") public @interface Order { /** * Default order value for elements not explicitly annotated with {@code @Order}, * equal to the value of {@code Integer.MAX_VALUE / 2}. * * @since 5.6 * @see Order#value */ int DEFAULT = Integer.MAX_VALUE / 2; /** * The order value for the annotated element (i.e., field, method, or class). * *

Elements are ordered based on priority where a lower value has greater * priority than a higher value. For example, {@link Integer#MAX_VALUE} has * the lowest priority. * * @see #DEFAULT */ int value(); } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/RandomOrdererUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import java.util.Optional; import java.util.function.Function; import org.junit.platform.commons.logging.Logger; /** * Shared utility methods for ordering test classes and test methods randomly. * * @since 5.11 * @see ClassOrderer.Random * @see MethodOrderer.Random */ class RandomOrdererUtils { static final String RANDOM_SEED_PROPERTY_NAME = "junit.jupiter.execution.order.random.seed"; static final long DEFAULT_SEED = System.nanoTime(); static Long getSeed(Function> configurationParameterLookup, Logger logger) { return getCustomSeed(configurationParameterLookup, logger).orElse(DEFAULT_SEED); } private static Optional getCustomSeed(Function> configurationParameterLookup, Logger logger) { return configurationParameterLookup.apply(RANDOM_SEED_PROPERTY_NAME).map(configurationParameter -> { try { logger.config(() -> "Using custom seed for configuration parameter [%s] with value [%s].".formatted( RANDOM_SEED_PROPERTY_NAME, configurationParameter)); return Long.valueOf(configurationParameter); } catch (NumberFormatException ex) { logger.warn(ex, () -> """ Failed to convert configuration parameter [%s] with value [%s] to a long. \ Using default seed [%s] as fallback.""".formatted(RANDOM_SEED_PROPERTY_NAME, configurationParameter, DEFAULT_SEED)); return null; } }); } private RandomOrdererUtils() { } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/RepeatedTest.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @RepeatedTest} is used to signal that the annotated method is a * test template method that should be repeated a {@linkplain #value * specified number of times} with a configurable {@linkplain #name display * name} and an optional {@linkplain #failureThreshold() failure threshold}. * *

Each invocation of the repeated test behaves like the execution of a * regular {@link Test @Test} method with full support for the same lifecycle * callbacks and extensions. In addition, the current repetition and total * number of repetitions can be accessed by having the {@link RepetitionInfo} * injected. * *

{@code @RepeatedTest} methods must not be {@code private} or {@code static} * and must return {@code void}. * *

{@code @RepeatedTest} methods may optionally declare parameters to be * resolved by {@link org.junit.jupiter.api.extension.ParameterResolver * ParameterResolvers}. * *

{@code @RepeatedTest} may also be used as a meta-annotation in order to * create a custom composed annotation that inherits the semantics * of {@code @RepeatedTest}. * *

Inheritance

* *

{@code @RepeatedTest} methods are inherited from superclasses as long as * they are not overridden according to the visibility rules of the Java * language. Similarly, {@code @RepeatedTest} methods declared as interface * default methods are inherited as long as they are not overridden. * *

Test Execution Order

* *

By default, test methods will be ordered using an algorithm that is * deterministic but intentionally nonobvious. This ensures that subsequent runs * of a test suite execute test methods in the same order, thereby allowing for * repeatable builds. In this context, a test method is any instance * method that is directly annotated or meta-annotated with {@code @Test}, * {@code @RepeatedTest}, {@code @ParameterizedTest}, {@code @TestFactory}, or * {@code @TestTemplate}. * *

Although true unit tests typically should not rely on the order * in which they are executed, there are times when it is necessary to enforce * a specific test method execution order — for example, when writing * integration tests or functional tests where the sequence of * the tests is important, especially in conjunction with * {@link TestInstance @TestInstance(Lifecycle.PER_CLASS)}. * *

To control the order in which test methods are executed, annotate your * test class or test interface with {@link TestMethodOrder @TestMethodOrder} * and specify the desired {@link MethodOrderer} implementation. * * @since 5.0 * @see DisplayName * @see RepetitionInfo * @see TestTemplate * @see TestInfo * @see Test */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = STABLE, since = "5.0") @TestTemplate public @interface RepeatedTest { /** * Placeholder for the {@linkplain TestInfo#getDisplayName display name} of * a {@code @RepeatedTest} method: {displayName} */ String DISPLAY_NAME_PLACEHOLDER = "{displayName}"; /** * Placeholder for the current repetition count of a {@code @RepeatedTest} * method: {currentRepetition} */ String CURRENT_REPETITION_PLACEHOLDER = "{currentRepetition}"; /** * Placeholder for the total number of repetitions of a {@code @RepeatedTest} * method: {totalRepetitions} */ String TOTAL_REPETITIONS_PLACEHOLDER = "{totalRepetitions}"; /** * Short display name pattern for a repeated test: {@value} * * @see #CURRENT_REPETITION_PLACEHOLDER * @see #TOTAL_REPETITIONS_PLACEHOLDER * @see #LONG_DISPLAY_NAME */ String SHORT_DISPLAY_NAME = "repetition " + CURRENT_REPETITION_PLACEHOLDER + " of " + TOTAL_REPETITIONS_PLACEHOLDER; /** * Long display name pattern for a repeated test: {@value} * * @see #DISPLAY_NAME_PLACEHOLDER * @see #SHORT_DISPLAY_NAME */ String LONG_DISPLAY_NAME = DISPLAY_NAME_PLACEHOLDER + " :: " + SHORT_DISPLAY_NAME; /** * The number of repetitions. * * @return the number of repetitions; must be greater than zero */ int value(); /** * The display name for each repetition of the repeated test. * *

Supported placeholders

*
    *
  • {@link #DISPLAY_NAME_PLACEHOLDER}
  • *
  • {@link #CURRENT_REPETITION_PLACEHOLDER}
  • *
  • {@link #TOTAL_REPETITIONS_PLACEHOLDER}
  • *
* *

Defaults to {@link #SHORT_DISPLAY_NAME}, resulting in * names such as {@code "repetition 1 of 2"}, {@code "repetition 2 of 2"}, * etc. * *

Can be set to {@link #LONG_DISPLAY_NAME}, resulting in * names such as {@code "myRepeatedTest() :: repetition 1 of 2"}, * {@code "myRepeatedTest() :: repetition 2 of 2"}, etc. * *

Alternatively, you can provide a custom display name, optionally * using the aforementioned placeholders. * * @return a custom display name; never blank or consisting solely of * whitespace * @see #SHORT_DISPLAY_NAME * @see #LONG_DISPLAY_NAME * @see #DISPLAY_NAME_PLACEHOLDER * @see #CURRENT_REPETITION_PLACEHOLDER * @see #TOTAL_REPETITIONS_PLACEHOLDER * @see TestInfo#getDisplayName() */ String name() default SHORT_DISPLAY_NAME; /** * Configures the number of failures after which remaining repetitions will * be automatically skipped. * *

Set this to a positive number less than the total {@linkplain #value() * number of repetitions} in order to skip the invocations of remaining * repetitions after the specified number of failures has been encountered. * *

For example, if you are using {@code @RepeatedTest} to repeatedly invoke * a test that you suspect to be flaky, a single failure is sufficient * to demonstrate that the test is flaky, and there is no need to invoke the * remaining repetitions. To support that specific use case, set * {@code failureThreshold = 1}. You can alternatively set the threshold to * a number greater than {@code 1} depending on your use case. * *

Defaults to {@link Integer#MAX_VALUE}, signaling that no failure * threshold will be applied, which effectively means that the specified * {@linkplain #value() number of repetitions} will be invoked regardless of * whether any repetitions fail. * *

WARNING: if the repetitions of a {@code @RepeatedTest} * method are executed in parallel, no guarantees can be made regarding the * failure threshold. It is therefore recommended that a {@code @RepeatedTest} * method be annotated with * {@link org.junit.jupiter.api.parallel.Execution @Execution(SAME_THREAD)} * when parallel execution is configured. * * @since 5.10 * @return the failure threshold; must be greater than zero and less than the * total number of repetitions */ @API(status = MAINTAINED, since = "5.13.3") int failureThreshold() default Integer.MAX_VALUE; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/RepetitionInfo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; /** * {@code RepetitionInfo} is used to inject information about the current * repetition of a repeated test into {@code @RepeatedTest}, {@code @BeforeEach}, * and {@code @AfterEach} methods. * *

If a method parameter is of type {@code RepetitionInfo}, JUnit will * supply an instance of {@code RepetitionInfo} corresponding to the current * repeated test as the value for the parameter. * *

WARNING: {@code RepetitionInfo} cannot be injected into * a {@code @BeforeEach} or {@code @AfterEach} method if the corresponding test * method is not a {@code @RepeatedTest}. Any attempt to do so will result in a * {@link org.junit.jupiter.api.extension.ParameterResolutionException * ParameterResolutionException}. * * @since 5.0 * @see RepeatedTest * @see TestInfo */ @API(status = STABLE, since = "5.0") public interface RepetitionInfo { /** * Get the current repetition of the corresponding * {@link RepeatedTest @RepeatedTest} method. */ int getCurrentRepetition(); /** * Get the total number of repetitions of the corresponding * {@link RepeatedTest @RepeatedTest} method. * * @see RepeatedTest#value */ int getTotalRepetitions(); /** * Get the current number of repetitions of the corresponding * {@link RepeatedTest @RepeatedTest} method that have ended in a failure. * * @since 5.10 * @see #getFailureThreshold() */ @API(status = MAINTAINED, since = "5.13.3") int getFailureCount(); /** * Get the configured failure threshold of the corresponding * {@link RepeatedTest @RepeatedTest} method. * * @since 5.10 * @see RepeatedTest#failureThreshold() */ @API(status = MAINTAINED, since = "5.13.3") int getFailureThreshold(); } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/Tag.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @Tag} is a {@linkplain Repeatable repeatable} annotation that is * used to declare a tag for the annotated test class or test method. * *

Tags are used to filter which tests are executed for a given test * plan. For example, a development team may tag tests with values such as * {@code "fast"}, {@code "slow"}, {@code "ci-server"}, etc. and then supply a * list of tags to be included in or excluded from the current test plan, * potentially dependent on the current environment. * *

Syntax Rules for Tags

*
    *
  • A tag must not be blank.
  • *
  • A trimmed tag must not contain whitespace.
  • *
  • A trimmed tag must not contain ISO control characters.
  • *
  • A trimmed tag must not contain any of the following * reserved characters. *
      *
    • {@code ,}: comma
    • *
    • {@code (}: left parenthesis
    • *
    • {@code )}: right parenthesis
    • *
    • {@code &}: ampersand
    • *
    • {@code |}: vertical bar
    • *
    • {@code !}: exclamation point
    • *
    *
  • *
* * @since 5.0 * @see Tags * @see Test */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Repeatable(Tags.class) @API(status = STABLE, since = "5.0") public @interface Tag { /** * The tag. * *

Note: the tag will first be {@linkplain String#strip() stripped}. If the * supplied tag is syntactically invalid after trimming, the error will be * logged as a warning, and the invalid tag will be effectively ignored. See * {@linkplain Tag Syntax Rules for Tags}. */ String value(); } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/Tags.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @Tags} is a container for one or more {@link Tag @Tag} declarations. * *

Note, however, that use of the {@code @Tags} container is completely * optional since {@code @Tag} is a {@linkplain java.lang.annotation.Repeatable * repeatable} annotation. * * @since 5.0 * @see Tag * @see java.lang.annotation.Repeatable */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @API(status = STABLE, since = "5.0") public @interface Tags { /** * An array of one or more {@link Tag Tags}. */ Tag[] value(); } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/Test.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.platform.commons.annotation.Testable; /** * {@code @Test} is used to signal that the annotated method is a test * method. * *

{@code @Test} methods must not be {@code private} or {@code static} and * must not return a value. * *

{@code @Test} methods may optionally declare parameters to be resolved by * {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolvers}. * *

{@code @Test} may also be used as a meta-annotation in order to create a * custom composed annotation that inherits the semantics of {@code @Test}. * *

Inheritance

* *

{@code @Test} methods are inherited from superclasses as long as they are * not overridden according to the visibility rules of the Java language. * Similarly, {@code @Test} methods declared as interface default methods * are inherited as long as they are not overridden. * *

Test Execution Order

* *

By default, test methods will be ordered using an algorithm that is * deterministic but intentionally nonobvious. This ensures that subsequent runs * of a test suite execute test methods in the same order, thereby allowing for * repeatable builds. In this context, a test method is any instance * method that is directly annotated or meta-annotated with {@code @Test}, * {@code @RepeatedTest}, {@code @ParameterizedTest}, {@code @TestFactory}, or * {@code @TestTemplate}. * *

Although true unit tests typically should not rely on the order * in which they are executed, there are times when it is necessary to enforce * a specific test method execution order — for example, when writing * integration tests or functional tests where the sequence of * the tests is important, especially in conjunction with * {@link TestInstance @TestInstance(Lifecycle.PER_CLASS)}. * *

To control the order in which test methods are executed, annotate your * test class or test interface with {@link TestMethodOrder @TestMethodOrder} * and specify the desired {@link MethodOrderer} implementation. * * @since 5.0 * @see RepeatedTest * @see org.junit.jupiter.params.ParameterizedTest * @see TestTemplate * @see TestFactory * @see TestInfo * @see DisplayName * @see Tag * @see BeforeAll * @see AfterAll * @see BeforeEach * @see AfterEach */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = STABLE, since = "5.0") @Testable public @interface Test { } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestClassOrder.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @TestClassOrder} is a type-level annotation that is used to configure * a {@link #value ClassOrderer} for the {@link Nested @Nested} test classes of * the annotated test class. * *

If {@code @TestClassOrder} is not explicitly declared on a test class, * inherited from a parent class, declared on a test interface implemented by * a test class, or inherited from an {@linkplain Class#getEnclosingClass() enclosing * class}, {@code @Nested} test classes will be ordered using a default * algorithm that is deterministic but intentionally nonobvious. * *

As an alternative to {@code @TestClassOrder}, a global {@link ClassOrderer} * can be configured for the entire test suite via the * {@value ClassOrderer#DEFAULT_ORDER_PROPERTY_NAME} configuration parameter. See * the User Guide for details. Note, however, that a {@code @TestClassOrder} * declaration always overrides a global {@code ClassOrderer}. * *

Example Usage

* *

The following demonstrates how to guarantee that {@code @Nested} test classes * are executed in the order specified via the {@link Order @Order} annotation. * *

 * {@literal @}TestClassOrder(ClassOrderer.OrderAnnotation.class)
 * class OrderedNestedTests {
 *
 *     {@literal @}Nested
 *     {@literal @}Order(1)
 *     class PrimaryTests {
 *         // {@literal @}Test methods ...
 *     }
 *
 *     {@literal @}Nested
 *     {@literal @}Order(2)
 *     class SecondaryTests {
 *         // {@literal @}Test methods ...
 *     }
 * }
* * @since 5.8 * @see ClassOrderer * @see TestMethodOrder */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @API(status = STABLE, since = "5.10") public @interface TestClassOrder { /** * The {@link ClassOrderer} to use. * * @see ClassOrderer * @see ClassOrderer.ClassName * @see ClassOrderer.DisplayName * @see ClassOrderer.OrderAnnotation * @see ClassOrderer.Random */ Class value(); } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestFactory.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.MAINTAINED; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.platform.commons.annotation.Testable; /** * {@code @TestFactory} is used to signal that the annotated method is a * test factory method. * *

In contrast to {@link Test @Test} methods, a test factory is not itself * a test case but rather a factory for test cases. * *

{@code @TestFactory} methods must not be {@code private} or {@code static} * and must return a {@code Stream}, {@code Collection}, {@code Iterable}, * {@code Iterator}, array of {@link DynamicNode} instances, or any type that * provides an {@link java.util.Iterator Iterator}-returning {@code iterator()} * method (such as, for example, a {@code kotlin.sequences.Sequence}). Supported * subclasses of {@code DynamicNode} include {@link DynamicContainer} and * {@link DynamicTest}. Dynamic tests will be executed lazily, * enabling dynamic and even non-deterministic generation of test cases. * *

Any {@code Stream} returned by a {@code @TestFactory} will be properly * closed by calling {@code stream.close()}, making it safe to use a resource * such as {@code Files.lines()} as the initial source of the stream. * *

{@code @TestFactory} methods may optionally declare parameters to be * resolved by {@link org.junit.jupiter.api.extension.ParameterResolver * ParameterResolvers}. * *

Inheritance

* *

{@code @TestFactory} methods are inherited from superclasses as long as * they are not overridden according to the visibility rules of the Java * language. Similarly, {@code @TestFactory} methods declared as interface * default methods are inherited as long as they are not overridden. * *

Test Execution Order

* *

By default, test methods will be ordered using an algorithm that is * deterministic but intentionally nonobvious. This ensures that subsequent runs * of a test suite execute test methods in the same order, thereby allowing for * repeatable builds. In this context, a test method is any instance * method that is directly annotated or meta-annotated with {@code @Test}, * {@code @RepeatedTest}, {@code @ParameterizedTest}, {@code @TestFactory}, or * {@code @TestTemplate}. * *

Although true unit tests typically should not rely on the order * in which they are executed, there are times when it is necessary to enforce * a specific test method execution order — for example, when writing * integration tests or functional tests where the sequence of * the tests is important, especially in conjunction with * {@link TestInstance @TestInstance(Lifecycle.PER_CLASS)}. * *

To control the order in which test methods are executed, annotate your * test class or test interface with {@link TestMethodOrder @TestMethodOrder} * and specify the desired {@link MethodOrderer} implementation. * * @since 5.0 * @see Test * @see DynamicNode * @see DynamicTest * @see DynamicContainer */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = MAINTAINED, since = "5.3") @Testable public @interface TestFactory { } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInfo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.STABLE; import java.lang.reflect.Method; import java.util.Optional; import java.util.Set; import org.apiguardian.api.API; /** * {@code TestInfo} is used to inject information about the current test or * container into to {@code @Test}, {@code @RepeatedTest}, * {@code @ParameterizedTest}, {@code @TestFactory}, {@code @BeforeEach}, * {@code @AfterEach}, {@code @BeforeAll}, and {@code @AfterAll} methods. * *

If a method parameter is of type {@link TestInfo}, JUnit will supply * an instance of {@code TestInfo} corresponding to the current test or * container as the value for the parameter. * * @since 5.0 * @see Test * @see RepeatedTest * @see TestFactory * @see BeforeEach * @see AfterEach * @see BeforeAll * @see AfterAll * @see DisplayName * @see Tag */ @API(status = STABLE, since = "5.0") public interface TestInfo { /** * Get the display name of the current test or container. * *

The display name is either a default name or a custom name configured * via {@link DisplayName @DisplayName}. * *

Default Display Names

* *

If the context in which {@code TestInfo} is used is at the container * level, the default display name is generated based on the name of the * test class. For top-level and {@link Nested @Nested} test classes, the * default display name is the {@linkplain Class#getSimpleName simple name} * of the class. For {@code static} nested test classes, the default display * name is the default display name for the enclosing class concatenated with * the {@linkplain Class#getSimpleName simple name} of the {@code static} * nested class, separated by a dollar sign ({@code $}). For example, the * default display names for the following test classes are * {@code TopLevelTests}, {@code NestedTests}, and {@code TopLevelTests$StaticTests}. * *

	 *   class TopLevelTests {
	 *
	 *      {@literal @}Nested
	 *      class NestedTests {}
	 *
	 *      static class StaticTests {}
	 *   }
* *

If the context in which {@code TestInfo} is used is at the test level, * the default display name is the name of the test method concatenated with * a comma-separated list of {@linkplain Class#getSimpleName simple names} * of the parameter types in parentheses. For example, the default display * name for the following test method is {@code testUser(TestInfo, User)}. * *

	 *   {@literal @}Test
	 *   void testUser(TestInfo testInfo, {@literal @}Mock User user) {}
* *

Note that display names are typically used for test reporting in IDEs * and build tools and may contain spaces, special characters, and even emoji. * * @return the display name of the test or container; never {@code null} or blank */ String getDisplayName(); /** * Get the set of all tags for the current test or container. * *

Tags may be declared directly on the test element or inherited * from an outer context. */ Set getTags(); /** * Get the {@link Class} associated with the current test or container, if available. */ Optional> getTestClass(); /** * Get the {@link Method} associated with the current test or container, if available. */ Optional getTestMethod(); } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInstance.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.api.parallel.Execution; /** * {@code @TestInstance} is a type-level annotation that is used to configure * the {@linkplain Lifecycle lifecycle} of test instances for the annotated * test class or test interface. * *

If {@code @TestInstance} is not explicitly declared on a test class or * on a test interface implemented by a test class, the lifecycle mode will * implicitly default to {@link Lifecycle#PER_METHOD PER_METHOD}. Note, however, * that an explicit lifecycle mode is inherited within a test class * hierarchy. In addition, the default lifecycle mode may be overridden * via the {@value Lifecycle#DEFAULT_LIFECYCLE_PROPERTY_NAME} configuration * parameter which can be supplied via the {@code Launcher} API, build tools * (e.g., Gradle and Maven), a JVM system property, or the JUnit Platform * configuration file (i.e., a file named {@code junit-platform.properties} in * the root of the class path). Consult the User Guide for further information. * *

Use Cases

*

Setting the test instance lifecycle mode to {@link Lifecycle#PER_CLASS * PER_CLASS} enables the following features. *

    *
  • Shared test instance state between test methods in a given test class * as well as between non-static {@link BeforeAll @BeforeAll} and * {@link AfterAll @AfterAll} methods in the test class.
  • *
  • Declaration of non-static {@code @BeforeAll} and {@code @AfterAll} methods * in top-level or {@link Nested @Nested} test classes.
  • *
  • Declaration of {@code @BeforeAll} and {@code @AfterAll} on interface * {@code default} methods.
  • *
  • Simplified declaration of non-static {@code @BeforeAll} and {@code @AfterAll} * lifecycle methods as well as {@code @MethodSource} factory methods in test classes * implemented with the Kotlin programming language.
  • *
* *

{@code @TestInstance} may also be used as a meta-annotation in order to * create a custom composed annotation that inherits the semantics * of {@code @TestInstance}. * *

Parallel Execution

*

Using the {@link Lifecycle#PER_CLASS PER_CLASS} lifecycle mode disables * parallel execution unless the test class or test method is annotated with * {@link Execution @Execution(CONCURRENT)}. * * @since 5.0 * @see Nested @Nested * @see Execution @Execution */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @API(status = STABLE, since = "5.0") public @interface TestInstance { /** * Enumeration of test instance lifecycle modes. * * @see #PER_METHOD * @see #PER_CLASS */ enum Lifecycle { /** * When using this mode, a new test instance will be created once per * test class or class template. * *

For {@link Nested @Nested}

test classes declared inside an * enclosing {@link ClassTemplate @ClassTemplate}, an instance of the * {@code @Nested} class will be created for each invocation of the * {@code @ClassTemplate}. * * @see #PER_METHOD */ PER_CLASS, /** * When using this mode, a new test instance will be created for each * test method, test factory method, or test template method. * *

This mode is analogous to the behavior found in JUnit versions 1 * through 4. * * @see #PER_CLASS */ PER_METHOD; /** * Property name used to set the default test instance lifecycle mode: * {@value} * *

Supported Values

* *

Supported values include names of enum constants defined in * {@link org.junit.jupiter.api.TestInstance.Lifecycle}, ignoring case. * *

If not specified, the default is "per_method" which corresponds to * {@code @TestInstance(Lifecycle.PER_METHOD)}. * * @since 5.0 * @see org.junit.jupiter.api.TestInstance */ @API(status = STABLE, since = "5.9") public static final String DEFAULT_LIFECYCLE_PROPERTY_NAME = "junit.jupiter.testinstance.lifecycle.default"; } /** * The test instance lifecycle mode to use. */ Lifecycle value(); } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestMethodOrder.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.api.parallel.Execution; /** * {@code @TestMethodOrder} is a type-level annotation that is used to configure * a {@link #value MethodOrderer} for the test methods of the annotated * test class or test interface. * *

In this context, the term "test method" refers to any method annotated with * {@code @Test}, {@code @RepeatedTest}, {@code @ParameterizedTest}, * {@code @TestFactory}, or {@code @TestTemplate}. * *

If {@code @TestMethodOrder} is not explicitly declared on a test class, * inherited from a parent class, declared on a test interface implemented by * a test class, or inherited from an {@linkplain Class#getEnclosingClass() enclosing * class}, test methods will be ordered using a default algorithm that is * deterministic but intentionally nonobvious. * *

As an alternative to {@code @TestMethodOrder}, a global {@link MethodOrderer} * can be configured for the entire test suite via the * {@value MethodOrderer#DEFAULT_ORDER_PROPERTY_NAME} configuration parameter. See * the User Guide for details. Note, however, that a {@code @TestClassOrder} * declaration always overrides a global {@code ClassOrderer}. * *

Example Usage

* *

The following demonstrates how to guarantee that test methods are executed * in the order specified via the {@link Order @Order} annotation. * *

 * {@literal @}TestMethodOrder(MethodOrderer.OrderAnnotation.class)
 * class OrderedTests {
 *
 *     {@literal @}Test
 *     {@literal @}Order(1)
 *     void nullValues() {}
 *
 *     {@literal @}Test
 *     {@literal @}Order(2)
 *     void emptyValues() {}
 *
 *     {@literal @}Test
 *     {@literal @}Order(3)
 *     void validValues() {}
 * }
* *

Parallel Execution

*

Using a {@link MethodOrderer} disables parallel execution unless the test * class or test method is annotated with * {@link Execution @Execution(CONCURRENT)}. * * @since 5.4 * @see MethodOrderer * @see TestClassOrder */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @API(status = STABLE, since = "5.7") public @interface TestMethodOrder { /** * The {@link MethodOrderer} to use. * * @see MethodOrderer * @see MethodOrderer.MethodName * @see MethodOrderer.DisplayName * @see MethodOrderer.OrderAnnotation * @see MethodOrderer.Random */ Class value(); } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestReporter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Map; import java.util.stream.Stream; import org.apiguardian.api.API; import org.junit.jupiter.api.function.ThrowingConsumer; import org.junit.platform.commons.util.Preconditions; /** * Parameters of type {@code TestReporter} can be injected into * {@link BeforeEach @BeforeEach} and {@link AfterEach @AfterEach} lifecycle * methods as well as methods annotated with {@link Test @Test}, * {@link RepeatedTest @RepeatedTest}, * {@link org.junit.jupiter.params.ParameterizedTest @ParameterizedTest}, * {@link TestFactory @TestFactory}, etc. * *

Within such methods the injected {@code TestReporter} can be used to * publish report entries for the current container or test to the * reporting infrastructure. * * @since 5.0 * @see #publishEntry(Map) * @see #publishEntry(String, String) */ @FunctionalInterface @API(status = STABLE, since = "5.0") public interface TestReporter { /** * Publish the supplied map of key-value pairs as a report entry. * * @param map the key-value pairs to be published; never {@code null}; * keys and values within entries in the map also must not be * {@code null} or blank * @see #publishEntry(String, String) * @see #publishEntry(String) */ void publishEntry(Map map); /** * Publish the supplied key-value pair as a report entry. * * @param key the key of the entry to publish; never {@code null} or blank * @param value the value of the entry to publish; never {@code null} or blank * @see #publishEntry(Map) * @see #publishEntry(String) */ default void publishEntry(String key, String value) { Preconditions.notBlank(key, "key must not be null or blank"); Preconditions.notBlank(value, "value must not be null or blank"); publishEntry(Map.of(key, value)); } /** * Publish the supplied value as a report entry. * *

This method delegates to {@link #publishEntry(String, String)}, * supplying {@code "value"} as the key and the supplied {@code value} * argument as the value. * * @param value the value to be published; never {@code null} or blank * @since 5.3 * @see #publishEntry(Map) * @see #publishEntry(String, String) */ @API(status = STABLE, since = "5.3") default void publishEntry(String value) { publishEntry("value", value); } /** * Publish the supplied file and attach it to the current test or container. * *

The file will be copied to the report output directory replacing any * potentially existing file with the same name. * * @param file the file to be published; never {@code null} * @param mediaType the media type of the file; never {@code null}; use * {@link org.junit.jupiter.api.extension.MediaType#APPLICATION_OCTET_STREAM} * if unknown * @since 5.12 * @deprecated Use {@link #publishFile(Path, MediaType)} instead. */ @Deprecated(since = "5.14", forRemoval = true) @API(status = DEPRECATED, since = "5.14") @SuppressWarnings("removal") default void publishFile(Path file, org.junit.jupiter.api.extension.MediaType mediaType) { Preconditions.notNull(mediaType, "mediaType must not be null"); publishFile(file, MediaType.parse(mediaType.toString())); } /** * Publish the supplied file and attach it to the current test or container. * *

The file will be copied to the report output directory replacing any * potentially existing file with the same name. * * @param file the file to be published; never {@code null} * @param mediaType the media type of the file; never {@code null}; use * {@link MediaType#APPLICATION_OCTET_STREAM} if unknown * @since 5.14 */ @API(status = MAINTAINED, since = "5.14") default void publishFile(Path file, MediaType mediaType) { Preconditions.notNull(file, "file must not be null"); Preconditions.notNull(mediaType, "mediaType must not be null"); Preconditions.condition(Files.exists(file), () -> "file must exist: " + file); Preconditions.condition(Files.isRegularFile(file), () -> "file must be a regular file: " + file); publishFile(file.getFileName().toString(), mediaType, path -> Files.copy(file, path, REPLACE_EXISTING)); } /** * Publish the supplied directory and attach it to the current test or * container. * *

The entire directory will be copied to the report output directory * replacing any potentially existing files with the same name. * * @param directory the directory to be published; never {@code null} * @since 5.12 */ @API(status = MAINTAINED, since = "5.13.3") default void publishDirectory(Path directory) { Preconditions.notNull(directory, "directory must not be null"); Preconditions.condition(Files.exists(directory), () -> "directory must exist: " + directory); Preconditions.condition(Files.isDirectory(directory), () -> "path must represent a directory: " + directory); publishDirectory(directory.getFileName().toString(), path -> { try (Stream stream = Files.walk(directory)) { stream.forEach(source -> { Path destination = path.resolve(directory.relativize(source)); try { if (Files.isDirectory(source)) { Files.createDirectories(destination); } else { Files.copy(source, destination, REPLACE_EXISTING); } } catch (IOException e) { throw new UncheckedIOException("Failed to copy files to the output directory", e); } }); } }); } /** * Publish a file with the supplied name and media type written by the supplied * action and attach it to the current test or container. * *

The {@link Path} passed to the supplied action will be relative to the * report output directory, but it is up to the action to write the file. * * @param name the name of the file to be published; never {@code null} or * blank and must not contain any path separators * @param mediaType the media type of the file; never {@code null}; use * {@link org.junit.jupiter.api.extension.MediaType#APPLICATION_OCTET_STREAM} * if unknown * @param action the action to be executed to write the file; never {@code null} * @since 5.12 * @deprecated Use {@link #publishFile(String, MediaType, ThrowingConsumer)} instead. */ @Deprecated(since = "5.14", forRemoval = true) @API(status = DEPRECATED, since = "5.14") @SuppressWarnings("removal") default void publishFile(String name, org.junit.jupiter.api.extension.MediaType mediaType, ThrowingConsumer action) { Preconditions.notNull(mediaType, "mediaType must not be null"); publishFile(name, MediaType.parse(mediaType.toString()), action); } /** * Publish a file with the supplied name and media type written by the supplied * action and attach it to the current test or container. * *

The {@link Path} passed to the supplied action will be relative to the * report output directory, but it is up to the action to write the file. * * @param name the name of the file to be published; never {@code null} or * blank and must not contain any path separators * @param mediaType the media type of the file; never {@code null}; use * {@link MediaType#APPLICATION_OCTET_STREAM} if unknown * @param action the action to be executed to write the file; never {@code null} * @since 5.14 */ @API(status = MAINTAINED, since = "5.14") default void publishFile(String name, MediaType mediaType, ThrowingConsumer action) { throw new UnsupportedOperationException(); } /** * Publish a directory with the supplied name written by the supplied action * and attach it to the current test or container. * *

The {@link Path} passed to the supplied action will be relative to the * report output directory and will point to an existing directory, but it is * up to the action to write files to the directory. * * @param name the name of the directory to be published; never {@code null} * or blank and must not contain any path separators * @param action the action to be executed to write to the directory; never * {@code null} * @since 5.12 */ @API(status = MAINTAINED, since = "5.13.3") default void publishDirectory(String name, ThrowingConsumer action) { throw new UnsupportedOperationException(); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestTemplate.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.platform.commons.annotation.Testable; /** * {@code @TestTemplate} is used to signal that the annotated method is a * test template method. * *

In contrast to {@link Test @Test} methods, a test template is not itself * a test case but rather a template for test cases. As such, it is designed to * be invoked multiple times depending on the number of {@linkplain * org.junit.jupiter.api.extension.TestTemplateInvocationContext invocation * contexts} returned by the registered {@linkplain * org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider * providers}. Must be used together with at least one provider. Otherwise, * execution will fail. * *

Each invocation of a test template method behaves like the execution of * a regular {@link Test @Test} method with full support for the same lifecycle * callbacks and extensions. * *

{@code @TestTemplate} methods must not be {@code private} or {@code static} * and must return {@code void}. * *

{@code @TestTemplate} methods may optionally declare parameters to be * resolved by {@link org.junit.jupiter.api.extension.ParameterResolver * ParameterResolvers}. * *

{@code @TestTemplate} may also be used as a meta-annotation in order to * create a custom composed annotation that inherits the semantics * of {@code @TestTemplate}. * *

Inheritance

* *

{@code @TestTemplate} methods are inherited from superclasses as long as * they are not overridden according to the visibility rules of the Java * language. Similarly, {@code @TestTemplate} methods declared as interface * default methods are inherited as long as they are not overridden. * *

Test Execution Order

* *

By default, test methods will be ordered using an algorithm that is * deterministic but intentionally nonobvious. This ensures that subsequent runs * of a test suite execute test methods in the same order, thereby allowing for * repeatable builds. In this context, a test method is any instance * method that is directly annotated or meta-annotated with {@code @Test}, * {@code @RepeatedTest}, {@code @ParameterizedTest}, {@code @TestFactory}, or * {@code @TestTemplate}. * *

Although true unit tests typically should not rely on the order * in which they are executed, there are times when it is necessary to enforce * a specific test method execution order — for example, when writing * integration tests or functional tests where the sequence of * the tests is important, especially in conjunction with * {@link TestInstance @TestInstance(Lifecycle.PER_CLASS)}. * *

To control the order in which test methods are executed, annotate your * test class or test interface with {@link TestMethodOrder @TestMethodOrder} * and specify the desired {@link MethodOrderer} implementation. * * @since 5.0 * @see Test * @see ClassTemplate * @see org.junit.jupiter.api.extension.TestTemplateInvocationContext * @see org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = STABLE, since = "5.0") @Testable public @interface TestTemplate { } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/Timeout.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.concurrent.TimeUnit; import org.apiguardian.api.API; /** * {@code @Timeout} is used to define a timeout for a method or all testable * methods within one class and its {@link Nested @Nested} classes. * *

This annotation may also be used on lifecycle methods annotated with * {@link BeforeAll @BeforeAll}, {@link BeforeEach @BeforeEach}, * {@link AfterEach @AfterEach}, or {@link AfterAll @AfterAll}. * *

Applying this annotation to a test class has the same effect as applying * it to all testable methods, i.e. all methods annotated or meta-annotated with * {@link Test @Test}, {@link TestFactory @TestFactory}, or * {@link TestTemplate @TestTemplate}, but not to its lifecycle methods. * *

Default Timeouts

* *

If this annotation is not present, no timeout will be used unless a * default timeout is defined via one of the following configuration parameters: * *

*
{@value #DEFAULT_TIMEOUT_PROPERTY_NAME}
*
Default timeout for all testable and lifecycle methods
*
{@value #DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME}
*
Default timeout for all testable methods
*
{@value #DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME}
*
Default timeout for {@link Test @Test} methods
*
{@value #DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME}
*
Default timeout for {@link TestTemplate @TestTemplate} methods
*
{@value #DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME}
*
Default timeout for {@link TestFactory @TestFactory} methods
*
{@value #DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME}
*
Default timeout for all lifecycle methods
*
{@value #DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME}
*
Default timeout for {@link BeforeAll @BeforeAll} methods
*
{@value #DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME}
*
Default timeout for {@link BeforeEach @BeforeEach} methods
*
{@value #DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME}
*
Default timeout for {@link AfterEach @AfterEach} methods
*
{@value #DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME}
*
Default timeout for {@link AfterAll @AfterAll} methods
*
* *

More specific configuration parameters override less specific ones. For * example, {@value #DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME} * overrides {@value #DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME} * which overrides {@value #DEFAULT_TIMEOUT_PROPERTY_NAME}. * *

Supported Values

* *

Values for timeouts must be in the following, case-insensitive format: * {@code [ns|μs|ms|s|m|h|d]}. The space between the number and the * unit may be omitted. Specifying no unit is equivalent to using seconds. * * * * * * * * * * * * *
Timeout configuration via configuration parameter vs. annotation
Value Equivalent annotation
{@code 42} {@code @Timeout(42)}
{@code 42 ns} {@code @Timeout(value = 42, unit = NANOSECONDS)}
{@code 42 μs} {@code @Timeout(value = 42, unit = MICROSECONDS)}
{@code 42 ms} {@code @Timeout(value = 42, unit = MILLISECONDS)}
{@code 42 s} {@code @Timeout(value = 42, unit = SECONDS)}
{@code 42 m} {@code @Timeout(value = 42, unit = MINUTES)}
{@code 42 h} {@code @Timeout(value = 42, unit = HOURS)}
{@code 42 d} {@code @Timeout(value = 42, unit = DAYS)}
* *

Disabling Timeouts

* *

You may use the {@value #TIMEOUT_MODE_PROPERTY_NAME} configuration * parameter to explicitly enable or disable timeouts. * *

Supported values: *

    *
  • {@code enabled}: enables timeouts *
  • {@code disabled}: disables timeouts *
  • {@code disabled_on_debug}: disables timeouts while debugging *
* * @since 5.5 */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @API(status = STABLE, since = "5.7") public @interface Timeout { /** * Property name used to set the default timeout for all testable and * lifecycle methods: {@value}. * *

The value of this property will be used unless overridden by a more * specific property or a {@link Timeout @Timeout} * annotation present on the method or on an enclosing test class (for * testable methods). * *

Please refer to the class * description for the definition of supported values. * * @since 5.5 */ @API(status = STABLE, since = "5.9") String DEFAULT_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.default"; /** * Property name used to set the default timeout for all testable methods: * {@value}. * *

The value of this property will be used unless overridden by a more * specific property or a {@link Timeout @Timeout} * annotation present on the testable method or on an enclosing test class. * *

This property overrides the {@value #DEFAULT_TIMEOUT_PROPERTY_NAME} * property. * *

Please refer to the class * description for the definition of supported values. * * @since 5.5 */ @API(status = STABLE, since = "5.9") String DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.testable.method.default"; /** * Property name used to set the default timeout for all {@link Test @Test} * methods: {@value}. * *

The value of this property will be used unless overridden by a * {@link Timeout @Timeout} annotation present on the {@link Test @Test} * method or on an enclosing test class. * *

This property overrides the * {@value #DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME} property. * *

Please refer to the class * description for the definition of supported values. * * @since 5.5 */ @API(status = STABLE, since = "5.9") String DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.test.method.default"; /** * Property name used to set the default timeout for all * {@link TestTemplate @TestTemplate} methods: {@value}. * *

The value of this property will be used unless overridden by a * {@link Timeout @Timeout} annotation present on the * {@link TestTemplate @TestTemplate} method or on an enclosing test class. * *

This property overrides the * {@value #DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME} property. * *

Please refer to the class * description for the definition of supported values. * * @since 5.5 */ @API(status = STABLE, since = "5.9") String DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.testtemplate.method.default"; /** * Property name used to set the default timeout for all * {@link TestFactory @TestFactory} methods: {@value}. * *

The value of this property will be used unless overridden by a * {@link Timeout @Timeout} annotation present on the * {@link TestFactory @TestFactory} method or on an enclosing test class. * *

This property overrides the * {@value #DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME} property. * *

Please refer to the class * description for the definition of supported values. * * @since 5.5 */ @API(status = STABLE, since = "5.9") String DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.testfactory.method.default"; /** * Property name used to set the default timeout for all lifecycle methods: * {@value}. * *

The value of this property will be used unless overridden by a more * specific property or a {@link Timeout @Timeout} annotation present on the * lifecycle method. * *

This property overrides the {@value #DEFAULT_TIMEOUT_PROPERTY_NAME} * property. * *

Please refer to the class * description for the definition of supported values. * * @since 5.5 */ @API(status = STABLE, since = "5.9") String DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.lifecycle.method.default"; /** * Property name used to set the default timeout for all * {@link BeforeAll @BeforeAll} methods: {@value}. * *

The value of this property will be used unless overridden by a * {@link Timeout @Timeout} annotation present on the * {@link BeforeAll @BeforeAll} method. * *

This property overrides the * {@value #DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME} property. * *

Please refer to the class * description for the definition of supported values. * * @since 5.5 */ @API(status = STABLE, since = "5.9") String DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.beforeall.method.default"; /** * Property name used to set the default timeout for all * {@link BeforeEach @BeforeEach} methods: {@value}. * *

The value of this property will be used unless overridden by a * {@link Timeout @Timeout} annotation present on the * {@link BeforeEach @BeforeEach} method. * *

This property overrides the * {@value #DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME} property. * *

Please refer to the class * description for the definition of supported values. * * @since 5.5 */ @API(status = STABLE, since = "5.9") String DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.beforeeach.method.default"; /** * Property name used to set the default timeout for all * {@link AfterEach @AfterEach} methods: {@value}. * *

The value of this property will be used unless overridden by a * {@link Timeout @Timeout} annotation present on the * {@link AfterEach @AfterEach} method. * *

This property overrides the * {@value #DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME} property. * *

Please refer to the class * description for the definition of supported values. * * @since 5.5 */ @API(status = STABLE, since = "5.9") String DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.aftereach.method.default"; /** * Property name used to set the default timeout for all * {@link AfterAll @AfterAll} methods: {@value}. * *

The value of this property will be used unless overridden by a * {@link Timeout @Timeout} annotation present on the * {@link AfterAll @AfterAll} method. * *

This property overrides the * {@value #DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME} property. * *

Please refer to the class * description for the definition of supported values. * * @since 5.5 */ @API(status = STABLE, since = "5.9") String DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.afterall.method.default"; /** * Property name used to configure whether timeouts are applied to tests: * {@value}. * *

The value of this property will be used to toggle whether * {@link Timeout @Timeout} is applied to tests.

* *

Supported timeout mode values (case insensitive):

*
    *
  • {@code ENABLED}: enables timeouts *
  • {@code DISABLED}: disables timeouts *
  • {@code DISABLED_ON_DEBUG}: disables timeouts while debugging *
* *

If not specified, the default is {@code ENABLED}. * * @since 5.6 */ @API(status = STABLE, since = "5.9") String TIMEOUT_MODE_PROPERTY_NAME = "junit.jupiter.execution.timeout.mode"; /** * Property name used to set the default thread mode for all testable and * lifecycle methods: {@value}. * *

The value of this property will be used unless overridden by a * {@link Timeout @Timeout} annotation present on the method or on an * enclosing test class (for testable methods). * *

The supported values are {@code SAME_THREAD} or * {@code SEPARATE_THREAD}, ignoring case. If none is provided, * {@code SAME_THREAD} is used as default. * * @since 5.9 * @see #threadMode() */ @API(status = MAINTAINED, since = "5.13.3") String DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME = "junit.jupiter.execution.timeout.thread.mode.default"; /** * The duration of this timeout. * * @return timeout duration; must be a positive number */ long value(); /** * The time unit of this timeout. * * @return time unit * @see TimeUnit */ TimeUnit unit() default TimeUnit.SECONDS; /** * The thread mode of this timeout. * * @return thread mode * @since 5.9 * @see ThreadMode * @see #DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME */ @API(status = STABLE, since = "5.11") ThreadMode threadMode() default ThreadMode.INFERRED; /** * {@code ThreadMode} is used to define whether test code should be executed * in the thread of the calling code or in a separate thread. * * @since 5.9 */ @API(status = STABLE, since = "5.11") enum ThreadMode { /** * The thread mode is determined using the parameter configured in property * {@value Timeout#DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME}. */ INFERRED, /** * The test code is executed in the thread of the calling code. */ SAME_THREAD, /** * The test code is executed in a different thread than that of the calling code. Furthermore, * execution of the test code will be preemptively aborted if the timeout is exceeded. See the * {@linkplain Assertions Preemptive Timeouts} section of the class-level * Javadoc for a discussion of possible undesirable side effects. */ SEPARATE_THREAD, } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractJreCondition.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static java.util.function.Predicate.isEqual; import java.lang.annotation.Annotation; import java.util.Arrays; import java.util.function.Function; import java.util.stream.IntStream; import org.junit.platform.commons.util.Preconditions; /** * Abstract base class for {@link EnabledOnJreCondition} and * {@link DisabledOnJreCondition}. * * @since 5.12 */ abstract class AbstractJreCondition extends BooleanExecutionCondition { static final String ENABLED_ON_CURRENT_JRE = // "Enabled on JRE version: " + System.getProperty("java.version"); static final String DISABLED_ON_CURRENT_JRE = // "Disabled on JRE version: " + System.getProperty("java.version"); AbstractJreCondition(Class annotationType, Function customDisabledReason) { super(annotationType, ENABLED_ON_CURRENT_JRE, DISABLED_ON_CURRENT_JRE, customDisabledReason); } protected final IntStream validatedVersions(JRE[] jres, int[] versions) { String annotationName = super.annotationType.getSimpleName(); Preconditions.condition(jres.length > 0 || versions.length > 0, () -> "You must declare at least one JRE or version in @" + annotationName); Preconditions.condition(Arrays.stream(jres).noneMatch(isEqual(JRE.UNDEFINED)), () -> "JRE.UNDEFINED is not supported in @" + annotationName); Arrays.stream(versions).min().ifPresent(version -> Preconditions.condition(version >= JRE.MINIMUM_VERSION, () -> "Version [%d] in @%s must be greater than or equal to %d".formatted(version, annotationName, JRE.MINIMUM_VERSION))); return IntStream.concat(// Arrays.stream(jres).mapToInt(JRE::version), // Arrays.stream(versions) // ).distinct(); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractJreRangeCondition.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.junit.jupiter.api.condition.AbstractJreCondition.DISABLED_ON_CURRENT_JRE; import static org.junit.jupiter.api.condition.AbstractJreCondition.ENABLED_ON_CURRENT_JRE; import java.lang.annotation.Annotation; import java.util.function.Function; import org.junit.platform.commons.util.Preconditions; /** * Abstract base class for {@link EnabledForJreRangeCondition} and * {@link DisabledForJreRangeCondition}. * * @since 5.12 */ abstract class AbstractJreRangeCondition extends BooleanExecutionCondition { private static final JRE DEFAULT_MINIMUM_JRE = JRE.JAVA_17; @SuppressWarnings("deprecation") private static final JRE DEFAULT_MAXIMUM_JRE = JRE.OTHER; AbstractJreRangeCondition(Class annotationType, Function customDisabledReason) { super(annotationType, ENABLED_ON_CURRENT_JRE, DISABLED_ON_CURRENT_JRE, customDisabledReason); } protected final boolean isCurrentVersionWithinRange(JRE minJre, JRE maxJre, int minVersion, int maxVersion) { String annotationName = super.annotationType.getSimpleName(); boolean minJreSet = minJre != JRE.UNDEFINED; boolean maxJreSet = maxJre != JRE.UNDEFINED; boolean minVersionSet = minVersion != JRE.UNDEFINED_VERSION; boolean maxVersionSet = maxVersion != JRE.UNDEFINED_VERSION; // Users must choose between JRE enum constants and version numbers. Preconditions.condition(!minJreSet || !minVersionSet, () -> "@%s's minimum value must be configured with either a JRE enum constant or numeric version, but not both".formatted( annotationName)); Preconditions.condition(!maxJreSet || !maxVersionSet, () -> "@%s's maximum value must be configured with either a JRE enum constant or numeric version, but not both".formatted( annotationName)); // Users must supply valid values for minVersion and maxVersion. Preconditions.condition(!minVersionSet || (minVersion >= JRE.MINIMUM_VERSION), () -> "@%s's minVersion [%d] must be greater than or equal to %d".formatted(annotationName, minVersion, JRE.MINIMUM_VERSION)); Preconditions.condition(!maxVersionSet || (maxVersion >= JRE.MINIMUM_VERSION), () -> "@%s's maxVersion [%d] must be greater than or equal to %d".formatted(annotationName, maxVersion, JRE.MINIMUM_VERSION)); // Now that we have checked the basic preconditions, we need to ensure that we are // using valid JRE enum constants. if (!minJreSet) { minJre = DEFAULT_MINIMUM_JRE; } if (!maxJreSet) { maxJre = DEFAULT_MAXIMUM_JRE; } int min = (minVersionSet ? minVersion : minJre.version()); int max = (maxVersionSet ? maxVersion : maxJre.version()); // Finally, we need to validate the effective minimum and maximum values. Preconditions.condition((min != DEFAULT_MINIMUM_JRE.version() || max != DEFAULT_MAXIMUM_JRE.version()), () -> "You must declare a non-default value for the minimum or maximum value in @" + annotationName); Preconditions.condition(min <= max, () -> "@%s's minimum value [%d] must be less than or equal to its maximum value [%d]".formatted( annotationName, min, max)); return JRE.isCurrentVersionWithinRange(min, max); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractOsBasedExecutionCondition.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import java.lang.annotation.Annotation; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.ExtensionContext; /** * Base class for OS-based {@link ExecutionCondition} implementations. * * @since 5.9 */ abstract class AbstractOsBasedExecutionCondition implements ExecutionCondition { static final String CURRENT_ARCHITECTURE = System.getProperty("os.arch"); static final String CURRENT_OS = System.getProperty("os.name"); private final Class annotationType; AbstractOsBasedExecutionCondition(Class annotationType) { this.annotationType = annotationType; } @Override public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { return findAnnotation(context.getElement(), this.annotationType) // .map(this::evaluateExecutionCondition) // .orElseGet(this::enabledByDefault); } abstract ConditionEvaluationResult evaluateExecutionCondition(A annotation); String createReason(boolean enabled, boolean osSpecified, boolean archSpecified) { StringBuilder reason = new StringBuilder() // .append(enabled ? "Enabled" : "Disabled") // .append(osSpecified ? " on operating system: " : " on architecture: "); if (osSpecified && archSpecified) { reason.append("%s (%s)".formatted(CURRENT_OS, CURRENT_ARCHITECTURE)); } else if (osSpecified) { reason.append(CURRENT_OS); } else { reason.append(CURRENT_ARCHITECTURE); } return reason.toString(); } private ConditionEvaluationResult enabledByDefault() { String reason = "@%s is not present".formatted(this.annotationType.getSimpleName()); return enabled(reason); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractRepeatableAnnotationCondition.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.junit.platform.commons.support.AnnotationSupport.findRepeatableAnnotations; import java.lang.annotation.Annotation; import java.lang.annotation.Repeatable; import java.lang.reflect.AnnotatedElement; import java.util.Optional; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; /** * Abstract base class for {@link ExecutionCondition} implementations that support * {@linkplain Repeatable repeatable} annotations. * * @param the type of repeatable annotation supported by this {@code ExecutionCondition} * @since 5.6 */ abstract class AbstractRepeatableAnnotationCondition implements ExecutionCondition { private final Logger logger = LoggerFactory.getLogger(getClass()); private final Class annotationType; AbstractRepeatableAnnotationCondition(Class annotationType) { this.annotationType = annotationType; } @Override public final ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { Optional optionalElement = context.getElement(); if (optionalElement.isPresent()) { AnnotatedElement annotatedElement = optionalElement.get(); // @formatter:off return findRepeatableAnnotations(annotatedElement, this.annotationType).stream() .map(annotation -> { ConditionEvaluationResult result = evaluate(annotation); logResult(annotation, annotatedElement, result); return result; }) .filter(ConditionEvaluationResult::isDisabled) .findFirst() .orElse(getNoDisabledConditionsEncounteredResult()); // @formatter:on } return getNoDisabledConditionsEncounteredResult(); } protected abstract ConditionEvaluationResult evaluate(A annotation); protected abstract ConditionEvaluationResult getNoDisabledConditionsEncounteredResult(); private void logResult(A annotation, AnnotatedElement annotatedElement, ConditionEvaluationResult result) { logger.trace(() -> "Evaluation of %s on [%s] resulted in: %s".formatted(annotation, annotatedElement, result)); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/BooleanExecutionCondition.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled; import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import java.lang.annotation.Annotation; import java.util.function.Function; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.ExtensionContext; abstract class BooleanExecutionCondition implements ExecutionCondition { protected final Class annotationType; private final String enabledReason; private final String disabledReason; private final Function customDisabledReason; BooleanExecutionCondition(Class annotationType, String enabledReason, String disabledReason, Function customDisabledReason) { this.annotationType = annotationType; this.enabledReason = enabledReason; this.disabledReason = disabledReason; this.customDisabledReason = customDisabledReason; } @Override public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { return findAnnotation(context.getElement(), this.annotationType) // .map(annotation -> isEnabled(annotation) ? enabled(this.enabledReason) : disabled(this.disabledReason, this.customDisabledReason.apply(annotation))) // .orElseGet(this::enabledByDefault); } abstract boolean isEnabled(A annotation); private ConditionEvaluationResult enabledByDefault() { String reason = "@%s is not present".formatted(this.annotationType.getSimpleName()); return enabled(reason); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledForJreRange.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ExtendWith; /** * {@code @DisabledForJreRange} is used to signal that the annotated test class * or test method is disabled for a specific range of Java Runtime * Environment (JRE) versions. * *

Version ranges can be specified as {@link JRE} enum constants via * {@link #min min} and {@link #max max} or as integers via * {@link #minVersion minVersion} and {@link #maxVersion maxVersion}. * *

When applied at the class level, all test methods within that class will * be disabled on the same specified JRE versions. * *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. * Consequently, if you wish to apply the same semantics to a subclass, this * annotation must be redeclared on the subclass. * *

If a test method is disabled via this annotation, that prevents execution * of the test method and method-level lifecycle callbacks such as * {@code @BeforeEach} methods, {@code @AfterEach} methods, and corresponding * extension APIs. However, that does not prevent the test class from being * instantiated, and it does not prevent the execution of class-level lifecycle * callbacks such as {@code @BeforeAll} methods, {@code @AfterAll} methods, and * corresponding extension APIs. * *

This annotation may be used as a meta-annotation in order to create a * custom composed annotation that inherits the semantics of this * annotation. * *

Warning

* *

This annotation can only be declared once on an * {@link java.lang.reflect.AnnotatedElement AnnotatedElement} (i.e., test * interface, test class, or test method). If this annotation is directly * present, indirectly present, or meta-present multiple times on a given * element, only the first such annotation discovered by JUnit will be used; * any additional declarations will be silently ignored. Note, however, that * this annotation may be used in conjunction with other {@code @Enabled*} or * {@code @Disabled*} annotations in this package. * * @since 5.6 * @see JRE * @see org.junit.jupiter.api.condition.EnabledIf * @see org.junit.jupiter.api.condition.DisabledIf * @see org.junit.jupiter.api.condition.EnabledOnOs * @see org.junit.jupiter.api.condition.DisabledOnOs * @see org.junit.jupiter.api.condition.EnabledOnJre * @see org.junit.jupiter.api.condition.DisabledOnJre * @see org.junit.jupiter.api.condition.EnabledForJreRange * @see org.junit.jupiter.api.condition.EnabledInNativeImage * @see org.junit.jupiter.api.condition.DisabledInNativeImage * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable * @see org.junit.jupiter.api.Disabled */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @ExtendWith(DisabledForJreRangeCondition.class) @API(status = STABLE, since = "5.6") @SuppressWarnings("exports") public @interface DisabledForJreRange { /** * Java Runtime Environment version which is used as the lower boundary for * the version range that determines if the annotated class or method should * be disabled, specified as a {@link JRE} enum constant. * *

If a {@code JRE} enum constant does not exist for a particular JRE * version, you can specify the minimum version via * {@link #minVersion() minVersion} instead. * *

Defaults to {@link JRE#UNDEFINED UNDEFINED}, which will be interpreted * as {@link JRE#JAVA_17 JAVA_17} if the {@link #minVersion() minVersion} is * not set. * * @see JRE * @see #minVersion() */ JRE min() default JRE.UNDEFINED; /** * Java Runtime Environment version which is used as the upper boundary for * the version range that determines if the annotated class or method should * be disabled, specified as a {@link JRE} enum constant. * *

If a {@code JRE} enum constant does not exist for a particular JRE * version, you can specify the maximum version via * {@link #maxVersion() maxVersion} instead. * *

Defaults to {@link JRE#UNDEFINED UNDEFINED}, which will be interpreted * as {@link JRE#OTHER OTHER} if the {@link #maxVersion() maxVersion} is not * set. * * @see JRE * @see #maxVersion() */ JRE max() default JRE.UNDEFINED; /** * Java Runtime Environment version which is used as the lower boundary for * the version range that determines if the annotated class or method should * be disabled, specified as an integer. * *

If a {@code JRE} enum constant exists for the particular JRE version, * you can specify the minimum version via {@link #min() min} instead. * *

Defaults to {@code -1} to signal that {@link #min() min} should be used * instead. * * @since 5.12 * @see #min() * @see JRE#version() * @see Runtime.Version#feature() */ @API(status = MAINTAINED, since = "5.13.3") int minVersion() default -1; /** * Java Runtime Environment version which is used as the upper boundary for * the version range that determines if the annotated class or method should * be disabled, specified as an integer. * *

If a {@code JRE} enum constant exists for the particular JRE version, * you can specify the maximum version via {@link #max() max} instead. * *

Defaults to {@code -1} to signal that {@link #max() max} should be used * instead. * * @since 5.12 * @see #max() * @see JRE#version() * @see Runtime.Version#feature() */ @API(status = MAINTAINED, since = "5.13.3") int maxVersion() default -1; /** * Custom reason to provide if the test or container is disabled. * *

If a custom reason is supplied, it will be combined with the default * reason for this annotation. If a custom reason is not supplied, the default * reason will be used. * * @since 5.7 */ @API(status = STABLE, since = "5.7") String disabledReason() default ""; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledForJreRangeCondition.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import org.junit.jupiter.api.extension.ExecutionCondition; /** * {@link ExecutionCondition} for {@link DisabledForJreRange @DisabledForJreRange}. * * @since 5.6 * @see DisabledForJreRange */ class DisabledForJreRangeCondition extends AbstractJreRangeCondition { DisabledForJreRangeCondition() { super(DisabledForJreRange.class, DisabledForJreRange::disabledReason); } @Override boolean isEnabled(DisabledForJreRange range) { return !isCurrentVersionWithinRange(range.min(), range.max(), range.minVersion(), range.maxVersion()); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIf.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ExtendWith; /** * {@code @DisabledIf} is used to signal that the annotated test class or test * method is disabled if the provided {@linkplain #value() condition} * evaluates to {@code true}. * *

When applied at the class level, all test methods within that class will * be disabled on the same condition. * *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. * Consequently, if you wish to apply the same semantics to a subclass, this * annotation must be redeclared on the subclass. * *

If a test method is disabled via this annotation, that prevents execution * of the test method and method-level lifecycle callbacks such as * {@code @BeforeEach} methods, {@code @AfterEach} methods, and corresponding * extension APIs. However, that does not prevent the test class from being * instantiated, and it does not prevent the execution of class-level lifecycle * callbacks such as {@code @BeforeAll} methods, {@code @AfterAll} methods, and * corresponding extension APIs. * *

This annotation may be used as a meta-annotation in order to create a * custom composed annotation that inherits the semantics of this * annotation. * *

Warning

* * This annotation can only be declared once on an * {@link java.lang.reflect.AnnotatedElement AnnotatedElement} (i.e., test * interface, test class, or test method). If this annotation is directly * present, indirectly present, or meta-present multiple times on a given * element, only the first such annotation discovered by JUnit will be used; * any additional declarations will be silently ignored. Note, however, that * this annotation may be used in conjunction with other {@code @Enabled*} or * {@code @Disabled*} annotations in this package. * * @since 5.7 * @see org.junit.jupiter.api.condition.EnabledIf * @see org.junit.jupiter.api.condition.EnabledOnOs * @see org.junit.jupiter.api.condition.DisabledOnOs * @see org.junit.jupiter.api.condition.EnabledOnJre * @see org.junit.jupiter.api.condition.DisabledOnJre * @see org.junit.jupiter.api.condition.EnabledForJreRange * @see org.junit.jupiter.api.condition.DisabledForJreRange * @see org.junit.jupiter.api.condition.EnabledInNativeImage * @see org.junit.jupiter.api.condition.DisabledInNativeImage * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable * @see org.junit.jupiter.api.Disabled */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @ExtendWith(DisabledIfCondition.class) @API(status = STABLE, since = "5.7") @SuppressWarnings("exports") public @interface DisabledIf { /** * The name of a method within the test class or in an external class to use * as a condition for the test's or container's execution. * *

Condition methods must be static if located outside the test class or * if {@code @DisabledIf} is used at the class level. * *

A condition method in an external class must be referenced by its * fully qualified method name — for example, * {@code com.example.Conditions#isEncryptionSupported}. */ String value(); /** * Custom reason to provide if the test or container is disabled. * *

If a custom reason is supplied, it will be combined with the default * reason for this annotation. If a custom reason is not supplied, the default * reason will be used. */ String disabledReason() default ""; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfCondition.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import org.junit.jupiter.api.extension.ExecutionCondition; /** * {@link ExecutionCondition} for {@link DisabledIf @DisabledIf}. * * @since 5.7 * @see DisabledIf */ class DisabledIfCondition extends MethodBasedCondition { DisabledIfCondition() { super(DisabledIf.class, DisabledIf::value, DisabledIf::disabledReason); } @Override protected boolean isEnabled(boolean methodResult) { return !methodResult; } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariable.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ExtendWith; /** * {@code @DisabledIfEnvironmentVariable} is used to signal that the annotated test * class or test method is disabled if the value of the specified * {@linkplain #named environment variable} matches the specified * {@linkplain #matches regular expression}. * *

When declared at the class level, the result will apply to all test methods * within that class as well. * *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. * Consequently, if you wish to apply the same semantics to a subclass, this * annotation must be redeclared on the subclass. * *

If a test method is disabled via this annotation, that prevents execution * of the test method and method-level lifecycle callbacks such as * {@code @BeforeEach} methods, {@code @AfterEach} methods, and corresponding * extension APIs. However, that does not prevent the test class from being * instantiated, and it does not prevent the execution of class-level lifecycle * callbacks such as {@code @BeforeAll} methods, {@code @AfterAll} methods, and * corresponding extension APIs. * *

If the specified environment variable is undefined, the presence of this * annotation will have no effect on whether or not the class or method * is disabled. * *

This annotation may be used as a meta-annotation in order to create a * custom composed annotation that inherits the semantics of this * annotation. * *

This annotation is a {@linkplain Repeatable repeatable} annotation and may * be declared multiple times on an {@link java.lang.reflect.AnnotatedElement * AnnotatedElement} such as a test interface, test class, or test method. * Specifically, this annotation will be found if it is directly present, * indirectly present, or meta-present on a given element. * * @since 5.1 * @see org.junit.jupiter.api.condition.EnabledIf * @see org.junit.jupiter.api.condition.DisabledIf * @see org.junit.jupiter.api.condition.EnabledOnOs * @see org.junit.jupiter.api.condition.DisabledOnOs * @see org.junit.jupiter.api.condition.EnabledOnJre * @see org.junit.jupiter.api.condition.DisabledOnJre * @see org.junit.jupiter.api.condition.EnabledForJreRange * @see org.junit.jupiter.api.condition.DisabledForJreRange * @see org.junit.jupiter.api.condition.EnabledInNativeImage * @see org.junit.jupiter.api.condition.DisabledInNativeImage * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable * @see org.junit.jupiter.api.Disabled */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Repeatable(DisabledIfEnvironmentVariables.class) @ExtendWith(DisabledIfEnvironmentVariableCondition.class) @API(status = STABLE, since = "5.1") @SuppressWarnings("exports") public @interface DisabledIfEnvironmentVariable { /** * The name of the environment variable to retrieve. * * @return the environment variable name; never blank * @see System#getenv(String) */ String named(); /** * A regular expression that will be used to match against the retrieved * value of the {@link #named} environment variable. * * @return the regular expression; never blank * @see String#matches(String) * @see java.util.regex.Pattern */ String matches(); /** * Custom reason to provide if the test or container is disabled. * *

If a custom reason is supplied, it will be combined with the default * reason for this annotation. If a custom reason is not supplied, the default * reason will be used. * * @since 5.7 */ @API(status = STABLE, since = "5.7") String disabledReason() default ""; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableCondition.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled; import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.platform.commons.util.Preconditions; /** * {@link ExecutionCondition} for {@link DisabledIfEnvironmentVariable @DisabledIfEnvironmentVariable}. * * @since 5.1 * @see DisabledIfEnvironmentVariable */ class DisabledIfEnvironmentVariableCondition extends AbstractRepeatableAnnotationCondition { private static final ConditionEvaluationResult ENABLED = ConditionEvaluationResult.enabled( "No @DisabledIfEnvironmentVariable conditions resulting in 'disabled' execution encountered"); DisabledIfEnvironmentVariableCondition() { super(DisabledIfEnvironmentVariable.class); } @Override protected ConditionEvaluationResult getNoDisabledConditionsEncounteredResult() { return ENABLED; } @Override protected ConditionEvaluationResult evaluate(DisabledIfEnvironmentVariable annotation) { String name = annotation.named().strip(); String regex = annotation.matches(); Preconditions.notBlank(name, () -> "The 'named' attribute must not be blank in " + annotation); Preconditions.notBlank(regex, () -> "The 'matches' attribute must not be blank in " + annotation); String actual = getEnvironmentVariable(name); // Nothing to match against? if (actual == null) { return enabled("Environment variable [%s] does not exist".formatted(name)); } if (actual.matches(regex)) { return disabled("Environment variable [%s] with value [%s] matches regular expression [%s]".formatted(name, actual, regex), annotation.disabledReason()); } // else return enabled("Environment variable [%s] with value [%s] does not match regular expression [%s]".formatted( name, actual, regex)); } /** * Get the value of the named environment variable. * *

The default implementation delegates to * {@link System#getenv(String)}. Can be overridden in a subclass for * testing purposes. */ protected @Nullable String getEnvironmentVariable(String name) { return System.getenv(name); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariables.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @DisabledIfEnvironmentVariables} is a container for one or more * {@link DisabledIfEnvironmentVariable @DisabledIfEnvironmentVariable} declarations. * *

Note, however, that use of the {@code @DisabledIfEnvironmentVariables} container * is completely optional since {@code @DisabledIfEnvironmentVariable} is a {@linkplain * java.lang.annotation.Repeatable repeatable} annotation. * *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. * Consequently, if you wish to apply the same semantics to a subclass, this * annotation must be redeclared on the subclass. * * @since 5.6 * @see DisabledIfEnvironmentVariable * @see java.lang.annotation.Repeatable */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = STABLE, since = "5.6") public @interface DisabledIfEnvironmentVariables { /** * An array of one or more {@link DisabledIfEnvironmentVariable @DisabledIfEnvironmentVariable} * declarations. */ DisabledIfEnvironmentVariable[] value(); } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperties.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @DisabledIfSystemProperties} is a container for one or more * {@link DisabledIfSystemProperty @DisabledIfSystemProperty} declarations. * *

Note, however, that use of the {@code @DisabledIfSystemProperties} container * is completely optional since {@code @DisabledIfSystemProperty} is a {@linkplain * java.lang.annotation.Repeatable repeatable} annotation. * *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. * Consequently, if you wish to apply the same semantics to a subclass, this * annotation must be redeclared on the subclass. * * @since 5.6 * @see DisabledIfSystemProperty * @see java.lang.annotation.Repeatable */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = STABLE, since = "5.6") public @interface DisabledIfSystemProperties { /** * An array of one or more {@link DisabledIfSystemProperty @DisabledIfSystemProperty} * declarations. */ DisabledIfSystemProperty[] value(); } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperty.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ExtendWith; /** * {@code @DisabledIfSystemProperty} is used to signal that the annotated test * class or test method is disabled if the value of the specified * {@linkplain #named system property} matches the specified * {@linkplain #matches regular expression}. * *

When declared at the class level, the result will apply to all test methods * within that class as well. * *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. * Consequently, if you wish to apply the same semantics to a subclass, this * annotation must be redeclared on the subclass. * *

If a test method is disabled via this annotation, that prevents execution * of the test method and method-level lifecycle callbacks such as * {@code @BeforeEach} methods, {@code @AfterEach} methods, and corresponding * extension APIs. However, that does not prevent the test class from being * instantiated, and it does not prevent the execution of class-level lifecycle * callbacks such as {@code @BeforeAll} methods, {@code @AfterAll} methods, and * corresponding extension APIs. * *

If the specified system property is undefined, the presence of this * annotation will have no effect on whether or not the class or method * is disabled. * *

This annotation may be used as a meta-annotation in order to create a * custom composed annotation that inherits the semantics of this * annotation. * *

This annotation is a {@linkplain Repeatable repeatable} annotation and may * be declared multiple times on an {@link java.lang.reflect.AnnotatedElement * AnnotatedElement} such as a test interface, test class, or test method. * Specifically, this annotation will be found if it is directly present, * indirectly present, or meta-present on a given element. * * @since 5.1 * @see org.junit.jupiter.api.condition.EnabledIf * @see org.junit.jupiter.api.condition.DisabledIf * @see org.junit.jupiter.api.condition.EnabledOnOs * @see org.junit.jupiter.api.condition.DisabledOnOs * @see org.junit.jupiter.api.condition.EnabledOnJre * @see org.junit.jupiter.api.condition.DisabledOnJre * @see org.junit.jupiter.api.condition.EnabledForJreRange * @see org.junit.jupiter.api.condition.DisabledForJreRange * @see org.junit.jupiter.api.condition.EnabledInNativeImage * @see org.junit.jupiter.api.condition.DisabledInNativeImage * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable * @see org.junit.jupiter.api.Disabled */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Repeatable(DisabledIfSystemProperties.class) @ExtendWith(DisabledIfSystemPropertyCondition.class) @API(status = STABLE, since = "5.1") @SuppressWarnings("exports") public @interface DisabledIfSystemProperty { /** * The name of the JVM system property to retrieve. * * @return the system property name; never blank * @see System#getProperty(String) */ String named(); /** * A regular expression that will be used to match against the retrieved * value of the {@link #named} JVM system property. * * @return the regular expression; never blank * @see String#matches(String) * @see java.util.regex.Pattern */ String matches(); /** * Custom reason to provide if the test or container is disabled. * *

If a custom reason is supplied, it will be combined with the default * reason for this annotation. If a custom reason is not supplied, the default * reason will be used. * * @since 5.7 */ @API(status = STABLE, since = "5.7") String disabledReason() default ""; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyCondition.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled; import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.platform.commons.util.Preconditions; /** * {@link ExecutionCondition} for {@link DisabledIfSystemProperty @DisabledIfSystemProperty}. * * @since 5.1 * @see DisabledIfSystemProperty */ class DisabledIfSystemPropertyCondition extends AbstractRepeatableAnnotationCondition { private static final ConditionEvaluationResult ENABLED = ConditionEvaluationResult.enabled( "No @DisabledIfSystemProperty conditions resulting in 'disabled' execution encountered"); DisabledIfSystemPropertyCondition() { super(DisabledIfSystemProperty.class); } @Override protected ConditionEvaluationResult getNoDisabledConditionsEncounteredResult() { return ENABLED; } @Override protected ConditionEvaluationResult evaluate(DisabledIfSystemProperty annotation) { String name = annotation.named().strip(); String regex = annotation.matches(); Preconditions.notBlank(name, () -> "The 'named' attribute must not be blank in " + annotation); Preconditions.notBlank(regex, () -> "The 'matches' attribute must not be blank in " + annotation); String actual = System.getProperty(name); // Nothing to match against? if (actual == null) { return enabled("System property [%s] does not exist".formatted(name)); } if (actual.matches(regex)) { return disabled( "System property [%s] with value [%s] matches regular expression [%s]".formatted(name, actual, regex), annotation.disabledReason()); } // else return enabled("System property [%s] with value [%s] does not match regular expression [%s]".formatted(name, actual, regex)); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledInNativeImage.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @DisabledInNativeImage} is used to signal that the annotated test class * or test method is disabled when executing within a GraalVM native * image. * *

When applied at the class level, all test methods within that class will * be disabled within a native image. * *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. * Consequently, if you wish to apply the same semantics to a subclass, this * annotation must be redeclared on the subclass. * *

If a test method is disabled via this annotation, that prevents execution * of the test method and method-level lifecycle callbacks such as * {@code @BeforeEach} methods, {@code @AfterEach} methods, and corresponding * extension APIs. However, that does not prevent the test class from being * instantiated, and it does not prevent the execution of class-level lifecycle * callbacks such as {@code @BeforeAll} methods, {@code @AfterAll} methods, and * corresponding extension APIs. * *

This annotation may be used as a meta-annotation in order to create a * custom composed annotation that inherits the semantics of this * annotation. * *

Technical Details

* *

JUnit detects whether tests are executing within a GraalVM native image by * checking for the presence of the {@code org.graalvm.nativeimage.imagecode} * system property (see * org.graalvm.nativeimage.ImageInfo * for details). The GraalVM compiler sets the property to {@code buildtime} while * compiling a native image; the property is set to {@code runtime} while a native * image is executing; and the Gradle and Maven plug-ins in the GraalVM * Native Build Tools * project set the property to {@code agent} while executing tests with the GraalVM * tracing agent. * * @since 5.9.1 * @see org.junit.jupiter.api.condition.EnabledIf * @see org.junit.jupiter.api.condition.DisabledIf * @see org.junit.jupiter.api.condition.EnabledOnOs * @see org.junit.jupiter.api.condition.DisabledOnOs * @see org.junit.jupiter.api.condition.EnabledOnJre * @see org.junit.jupiter.api.condition.DisabledOnJre * @see org.junit.jupiter.api.condition.EnabledForJreRange * @see org.junit.jupiter.api.condition.DisabledForJreRange * @see org.junit.jupiter.api.condition.EnabledInNativeImage * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable * @see org.junit.jupiter.api.Disabled */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @DisabledIfSystemProperty(named = "org.graalvm.nativeimage.imagecode", matches = ".+", // disabledReason = "Currently executing within a GraalVM native image") @API(status = STABLE, since = "5.9.1") public @interface DisabledInNativeImage { } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnJre.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ExtendWith; /** * {@code @DisabledOnJre} is used to signal that the annotated test class or * test method is disabled on one or more specified Java Runtime * Environment (JRE) versions. * *

Versions can be specified as {@link JRE} enum constants via * {@link #value() value} or as integers via {@link #versions() versions}. * *

When applied at the class level, all test methods within that class * will be disabled on the same specified JRE versions. * *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. * Consequently, if you wish to apply the same semantics to a subclass, this * annotation must be redeclared on the subclass. * *

If a test method is disabled via this annotation, that prevents execution * of the test method and method-level lifecycle callbacks such as * {@code @BeforeEach} methods, {@code @AfterEach} methods, and corresponding * extension APIs. However, that does not prevent the test class from being * instantiated, and it does not prevent the execution of class-level lifecycle * callbacks such as {@code @BeforeAll} methods, {@code @AfterAll} methods, and * corresponding extension APIs. * *

This annotation may be used as a meta-annotation in order to create a * custom composed annotation that inherits the semantics of this * annotation. * *

Warning

* *

This annotation can only be declared once on an * {@link java.lang.reflect.AnnotatedElement AnnotatedElement} such as a test * interface, test class, or test method. If this annotation is directly * present, indirectly present, or meta-present multiple times on a given * element, only the first such annotation discovered by JUnit will be used; * any additional declarations will be silently ignored. Note, however, that * this annotation may be used in conjunction with other {@code @Enabled*} or * {@code @Disabled*} annotations in this package. * * @since 5.1 * @see JRE * @see org.junit.jupiter.api.condition.EnabledIf * @see org.junit.jupiter.api.condition.DisabledIf * @see org.junit.jupiter.api.condition.EnabledOnOs * @see org.junit.jupiter.api.condition.DisabledOnOs * @see org.junit.jupiter.api.condition.EnabledOnJre * @see org.junit.jupiter.api.condition.EnabledForJreRange * @see org.junit.jupiter.api.condition.DisabledForJreRange * @see org.junit.jupiter.api.condition.EnabledInNativeImage * @see org.junit.jupiter.api.condition.DisabledInNativeImage * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable * @see org.junit.jupiter.api.Disabled */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @ExtendWith(DisabledOnJreCondition.class) @API(status = STABLE, since = "5.1") @SuppressWarnings("exports") public @interface DisabledOnJre { /** * Java Runtime Environment versions on which the annotated class or method * should be disabled, specified as {@link JRE} enum constants. * *

If a {@code JRE} enum constant does not exist for a particular JRE * version, you can specify the version via {@link #versions() versions} * instead. * * @see JRE * @see #versions() */ JRE[] value() default {}; /** * Java Runtime Environment versions on which the annotated class or method * should be disabled, specified as integers. * *

If a {@code JRE} enum constant exists for a particular JRE version, you * can specify the version via {@link #value() value} instead. * * @since 5.12 * @see #value() * @see JRE#version() * @see Runtime.Version#feature() */ @API(status = MAINTAINED, since = "5.13.3") int[] versions() default {}; /** * Custom reason to provide if the test or container is disabled. * *

If a custom reason is supplied, it will be combined with the default * reason for this annotation. If a custom reason is not supplied, the default * reason will be used. * * @since 5.7 */ @API(status = STABLE, since = "5.7") String disabledReason() default ""; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnJreCondition.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import org.junit.jupiter.api.extension.ExecutionCondition; /** * {@link ExecutionCondition} for {@link DisabledOnJre @DisabledOnJre}. * * @since 5.1 * @see DisabledOnJre */ class DisabledOnJreCondition extends AbstractJreCondition { DisabledOnJreCondition() { super(DisabledOnJre.class, DisabledOnJre::disabledReason); } @Override boolean isEnabled(DisabledOnJre annotation) { return validatedVersions(annotation.value(), annotation.versions()).noneMatch(JRE::isCurrentVersion); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnOs.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ExtendWith; /** * {@code @DisabledOnOs} is used to signal that the annotated test class or * test method is disabled on one or more specified * {@linkplain #value operating systems} or on one or more specified * {@linkplain #architectures architectures} * *

If operating systems and architectures are specified, the annotated * test class or test method is disabled if both conditions apply. * *

When applied at the class level, all test methods within that class * will be disabled on the same specified operating systems, architectures, or * the specified combinations of both. * *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. * Consequently, if you wish to apply the same semantics to a subclass, this * annotation must be redeclared on the subclass. * *

If a test method is disabled via this annotation, that prevents execution * of the test method and method-level lifecycle callbacks such as * {@code @BeforeEach} methods, {@code @AfterEach} methods, and corresponding * extension APIs. However, that does not prevent the test class from being * instantiated, and it does not prevent the execution of class-level lifecycle * callbacks such as {@code @BeforeAll} methods, {@code @AfterAll} methods, and * corresponding extension APIs. * *

This annotation may be used as a meta-annotation in order to create a * custom composed annotation that inherits the semantics of this * annotation. * *

Warning

* *

This annotation can only be declared once on an * {@link java.lang.reflect.AnnotatedElement AnnotatedElement} such as a test * interface, test class, or test method. If this annotation is directly * present, indirectly present, or meta-present multiple times on a given * element, only the first such annotation discovered by JUnit will be used; * any additional declarations will be silently ignored. Note, however, that * this annotation may be used in conjunction with other {@code @Enabled*} or * {@code @Disabled*} annotations in this package. * * @since 5.1 * @see OS * @see org.junit.jupiter.api.condition.EnabledIf * @see org.junit.jupiter.api.condition.DisabledIf * @see org.junit.jupiter.api.condition.EnabledOnOs * @see org.junit.jupiter.api.condition.EnabledOnJre * @see org.junit.jupiter.api.condition.DisabledOnJre * @see org.junit.jupiter.api.condition.EnabledForJreRange * @see org.junit.jupiter.api.condition.DisabledForJreRange * @see org.junit.jupiter.api.condition.EnabledInNativeImage * @see org.junit.jupiter.api.condition.DisabledInNativeImage * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable * @see org.junit.jupiter.api.Disabled */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @ExtendWith(DisabledOnOsCondition.class) @API(status = STABLE, since = "5.1") @SuppressWarnings("exports") public @interface DisabledOnOs { /** * Operating systems on which the annotated class or method should be * disabled. * * @see OS */ OS[] value() default {}; /** * Architectures on which the annotated class or method should be disabled. * *

Each architecture will be compared to the value returned from * {@code System.getProperty("os.arch")}, ignoring case. * * @since 5.9 */ @API(status = STABLE, since = "5.9") String[] architectures() default {}; /** * Custom reason to provide if the test or container is disabled. * *

If a custom reason is supplied, it will be combined with the default * reason for this annotation. If a custom reason is not supplied, the default * reason will be used. * * @since 5.7 */ @API(status = STABLE, since = "5.7") String disabledReason() default ""; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnOsCondition.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import java.util.Arrays; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.platform.commons.util.Preconditions; /** * {@link ExecutionCondition} for {@link DisabledOnOs @DisabledOnOs}. * * @since 5.1 * @see DisabledOnOs */ class DisabledOnOsCondition extends AbstractOsBasedExecutionCondition { DisabledOnOsCondition() { super(DisabledOnOs.class); } @Override ConditionEvaluationResult evaluateExecutionCondition(DisabledOnOs annotation) { boolean osSpecified = annotation.value().length > 0; boolean archSpecified = annotation.architectures().length > 0; Preconditions.condition(osSpecified || archSpecified, "You must declare at least one OS or architecture in @DisabledOnOs"); boolean enabled = isEnabledBasedOnOs(annotation) || isEnabledBasedOnArchitecture(annotation); String reason = createReason(enabled, osSpecified, archSpecified); return enabled ? ConditionEvaluationResult.enabled(reason) : ConditionEvaluationResult.disabled(reason, annotation.disabledReason()); } private boolean isEnabledBasedOnOs(DisabledOnOs annotation) { OS[] operatingSystems = annotation.value(); if (operatingSystems.length == 0) { return false; } return Arrays.stream(operatingSystems).noneMatch(OS::isCurrentOs); } private boolean isEnabledBasedOnArchitecture(DisabledOnOs annotation) { String[] architectures = annotation.architectures(); if (architectures.length == 0) { return false; } return Arrays.stream(architectures).noneMatch(CURRENT_ARCHITECTURE::equalsIgnoreCase); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledForJreRange.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ExtendWith; /** * {@code @EnabledForJreRange} is used to signal that the annotated test class or * test method is only enabled for a specific range of Java Runtime * Environment (JRE) versions. * *

Version ranges can be specified as {@link JRE} enum constants via * {@link #min min} and {@link #max max} or as integers via * {@link #minVersion minVersion} and {@link #maxVersion maxVersion}. * *

When applied at the class level, all test methods within that class will * be enabled on the same specified JRE versions. * *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. * Consequently, if you wish to apply the same semantics to a subclass, this * annotation must be redeclared on the subclass. * *

If a test method is disabled via this annotation, that prevents execution * of the test method and method-level lifecycle callbacks such as * {@code @BeforeEach} methods, {@code @AfterEach} methods, and corresponding * extension APIs. However, that does not prevent the test class from being * instantiated, and it does not prevent the execution of class-level lifecycle * callbacks such as {@code @BeforeAll} methods, {@code @AfterAll} methods, and * corresponding extension APIs. * *

This annotation may be used as a meta-annotation in order to create a * custom composed annotation that inherits the semantics of this * annotation. * *

Warning

* *

This annotation can only be declared once on an * {@link java.lang.reflect.AnnotatedElement AnnotatedElement} (i.e., test * interface, test class, or test method). If this annotation is directly * present, indirectly present, or meta-present multiple times on a given * element, only the first such annotation discovered by JUnit will be used; * any additional declarations will be silently ignored. Note, however, that * this annotation may be used in conjunction with other {@code @Enabled*} or * {@code @Disabled*} annotations in this package. * * @since 5.6 * @see JRE * @see org.junit.jupiter.api.condition.EnabledIf * @see org.junit.jupiter.api.condition.DisabledIf * @see org.junit.jupiter.api.condition.EnabledOnOs * @see org.junit.jupiter.api.condition.DisabledOnOs * @see org.junit.jupiter.api.condition.EnabledOnJre * @see org.junit.jupiter.api.condition.DisabledOnJre * @see org.junit.jupiter.api.condition.DisabledForJreRange * @see org.junit.jupiter.api.condition.EnabledInNativeImage * @see org.junit.jupiter.api.condition.DisabledInNativeImage * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable * @see org.junit.jupiter.api.Disabled */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @ExtendWith(EnabledForJreRangeCondition.class) @API(status = STABLE, since = "5.6") @SuppressWarnings("exports") public @interface EnabledForJreRange { /** * Java Runtime Environment version which is used as the lower boundary for * the version range that determines if the annotated class or method should * be enabled, specified as a {@link JRE} enum constant. * *

If a {@code JRE} enum constant does not exist for a particular JRE * version, you can specify the minimum version via * {@link #minVersion() minVersion} instead. * *

Defaults to {@link JRE#UNDEFINED UNDEFINED}, which will be interpreted * as {@link JRE#JAVA_17 JAVA_17} if the {@link #minVersion() minVersion} is * not set. * * @see JRE * @see #minVersion() */ JRE min() default JRE.UNDEFINED; /** * Java Runtime Environment version which is used as the upper boundary for * the version range that determines if the annotated class or method should * be enabled, specified as a {@link JRE} enum constant. * *

If a {@code JRE} enum constant does not exist for a particular JRE * version, you can specify the maximum version via * {@link #maxVersion() maxVersion} instead. * *

Defaults to {@link JRE#UNDEFINED UNDEFINED}, which will be interpreted * as {@link JRE#OTHER OTHER} if the {@link #maxVersion() maxVersion} is not * set. * * @see JRE * @see #maxVersion() */ JRE max() default JRE.UNDEFINED; /** * Java Runtime Environment version which is used as the lower boundary for * the version range that determines if the annotated class or method should * be enabled, specified as an integer. * *

If a {@code JRE} enum constant exists for the particular JRE version, * you can specify the minimum version via {@link #min() min} instead. * *

Defaults to {@code -1} to signal that {@link #min() min} should be used * instead. * * @since 5.12 * @see #min() * @see JRE#version() * @see Runtime.Version#feature() */ @API(status = MAINTAINED, since = "5.13.3") int minVersion() default -1; /** * Java Runtime Environment version which is used as the upper boundary for * the version range that determines if the annotated class or method should * be enabled, specified as an integer. * *

If a {@code JRE} enum constant exists for the particular JRE version, * you can specify the maximum version via {@link #max() max} instead. * *

Defaults to {@code -1} to signal that {@link #max() max} should be used * instead. * * @since 5.12 * @see #max() * @see JRE#version() * @see Runtime.Version#feature() */ @API(status = MAINTAINED, since = "5.13.3") int maxVersion() default -1; /** * Custom reason to provide if the test or container is disabled. * *

If a custom reason is supplied, it will be combined with the default * reason for this annotation. If a custom reason is not supplied, the default * reason will be used. * * @since 5.7 */ @API(status = STABLE, since = "5.7") String disabledReason() default ""; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledForJreRangeCondition.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import org.junit.jupiter.api.extension.ExecutionCondition; /** * {@link ExecutionCondition} for {@link EnabledForJreRange @EnabledForJreRange}. * * @since 5.6 * @see EnabledForJreRange */ class EnabledForJreRangeCondition extends AbstractJreRangeCondition { EnabledForJreRangeCondition() { super(EnabledForJreRange.class, EnabledForJreRange::disabledReason); } @Override boolean isEnabled(EnabledForJreRange range) { return isCurrentVersionWithinRange(range.min(), range.max(), range.minVersion(), range.maxVersion()); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIf.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ExtendWith; /** * {@code @EnabledIf} is used to signal that the annotated test class or test * method is only enabled if the provided {@linkplain #value() condition} * evaluates to {@code true}. * *

When applied at the class level, all test methods within that class will * be enabled on the same condition. * *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. * Consequently, if you wish to apply the same semantics to a subclass, this * annotation must be redeclared on the subclass. * *

If a test method is disabled via this annotation, that prevents execution * of the test method and method-level lifecycle callbacks such as * {@code @BeforeEach} methods, {@code @AfterEach} methods, and corresponding * extension APIs. However, that does not prevent the test class from being * instantiated, and it does not prevent the execution of class-level lifecycle * callbacks such as {@code @BeforeAll} methods, {@code @AfterAll} methods, and * corresponding extension APIs. * *

This annotation may be used as a meta-annotation in order to create a * custom composed annotation that inherits the semantics of this * annotation. * *

Warning

* * This annotation can only be declared once on an * {@link java.lang.reflect.AnnotatedElement AnnotatedElement} (i.e., test * interface, test class, or test method). If this annotation is directly * present, indirectly present, or meta-present multiple times on a given * element, only the first such annotation discovered by JUnit will be used; * any additional declarations will be silently ignored. Note, however, that * this annotation may be used in conjunction with other {@code @Enabled*} or * {@code @Disabled*} annotations in this package. * * @since 5.7 * @see org.junit.jupiter.api.condition.DisabledIf * @see org.junit.jupiter.api.condition.EnabledOnOs * @see org.junit.jupiter.api.condition.DisabledOnOs * @see org.junit.jupiter.api.condition.EnabledOnJre * @see org.junit.jupiter.api.condition.DisabledOnJre * @see org.junit.jupiter.api.condition.EnabledForJreRange * @see org.junit.jupiter.api.condition.DisabledForJreRange * @see org.junit.jupiter.api.condition.EnabledInNativeImage * @see org.junit.jupiter.api.condition.DisabledInNativeImage * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable * @see org.junit.jupiter.api.Disabled */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @ExtendWith(EnabledIfCondition.class) @API(status = STABLE, since = "5.7") @SuppressWarnings("exports") public @interface EnabledIf { /** * The name of a method within the test class or in an external class to use * as a condition for the test's or container's execution. * *

Condition methods must be static if located outside the test class or * if {@code @EnabledIf} is used at the class level. * *

A condition method in an external class must be referenced by its * fully qualified method name — for example, * {@code com.example.Conditions#isEncryptionSupported}. */ String value(); /** * Custom reason to provide if the test or container is disabled. * *

If a custom reason is supplied, it will be combined with the default * reason for this annotation. If a custom reason is not supplied, the default * reason will be used. */ String disabledReason() default ""; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfCondition.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import org.junit.jupiter.api.extension.ExecutionCondition; /** * {@link ExecutionCondition} for {@link EnabledIf @EnabledIf}. * * @since 5.7 * @see EnabledIf */ class EnabledIfCondition extends MethodBasedCondition { EnabledIfCondition() { super(EnabledIf.class, EnabledIf::value, EnabledIf::disabledReason); } @Override protected boolean isEnabled(boolean methodResult) { return methodResult; } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariable.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ExtendWith; /** * {@code @EnabledIfEnvironmentVariable} is used to signal that the annotated test * class or test method is only enabled if the value of the specified * {@linkplain #named environment variable} matches the specified * {@linkplain #matches regular expression}. * *

When declared at the class level, the result will apply to all test methods * within that class as well. * *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. * Consequently, if you wish to apply the same semantics to a subclass, this * annotation must be redeclared on the subclass. * *

If a test method is disabled via this annotation, that prevents execution * of the test method and method-level lifecycle callbacks such as * {@code @BeforeEach} methods, {@code @AfterEach} methods, and corresponding * extension APIs. However, that does not prevent the test class from being * instantiated, and it does not prevent the execution of class-level lifecycle * callbacks such as {@code @BeforeAll} methods, {@code @AfterAll} methods, and * corresponding extension APIs. * *

If the specified environment variable is undefined, the annotated class or * method will be disabled. * *

This annotation may be used as a meta-annotation in order to create a * custom composed annotation that inherits the semantics of this * annotation. * *

This annotation is a {@linkplain Repeatable repeatable} annotation and may * be declared multiple times on an {@link java.lang.reflect.AnnotatedElement * AnnotatedElement} such as a test interface, test class, or test method. * Specifically, this annotation will be found if it is directly present, * indirectly present, or meta-present on a given element. * * @since 5.1 * @see org.junit.jupiter.api.condition.EnabledIf * @see org.junit.jupiter.api.condition.DisabledIf * @see org.junit.jupiter.api.condition.EnabledOnOs * @see org.junit.jupiter.api.condition.DisabledOnOs * @see org.junit.jupiter.api.condition.EnabledOnJre * @see org.junit.jupiter.api.condition.DisabledOnJre * @see org.junit.jupiter.api.condition.EnabledForJreRange * @see org.junit.jupiter.api.condition.DisabledForJreRange * @see org.junit.jupiter.api.condition.EnabledInNativeImage * @see org.junit.jupiter.api.condition.DisabledInNativeImage * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable * @see org.junit.jupiter.api.Disabled */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Repeatable(EnabledIfEnvironmentVariables.class) @ExtendWith(EnabledIfEnvironmentVariableCondition.class) @API(status = STABLE, since = "5.1") @SuppressWarnings("exports") public @interface EnabledIfEnvironmentVariable { /** * The name of the environment variable to retrieve. * * @return the environment variable name; never blank * @see System#getenv(String) */ String named(); /** * A regular expression that will be used to match against the retrieved * value of the {@link #named} environment variable. * * @return the regular expression; never blank * @see String#matches(String) * @see java.util.regex.Pattern */ String matches(); /** * Custom reason to provide if the test or container is disabled. * *

If a custom reason is supplied, it will be combined with the default * reason for this annotation. If a custom reason is not supplied, the default * reason will be used. * * @since 5.7 */ @API(status = STABLE, since = "5.7") String disabledReason() default ""; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableCondition.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled; import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.platform.commons.util.Preconditions; /** * {@link ExecutionCondition} for {@link EnabledIfEnvironmentVariable @EnabledIfEnvironmentVariable}. * * @since 5.1 * @see EnabledIfEnvironmentVariable */ class EnabledIfEnvironmentVariableCondition extends AbstractRepeatableAnnotationCondition { private static final ConditionEvaluationResult ENABLED = ConditionEvaluationResult.enabled( "No @EnabledIfEnvironmentVariable conditions resulting in 'disabled' execution encountered"); EnabledIfEnvironmentVariableCondition() { super(EnabledIfEnvironmentVariable.class); } @Override protected ConditionEvaluationResult getNoDisabledConditionsEncounteredResult() { return ENABLED; } @Override protected ConditionEvaluationResult evaluate(EnabledIfEnvironmentVariable annotation) { String name = annotation.named().strip(); String regex = annotation.matches(); Preconditions.notBlank(name, () -> "The 'named' attribute must not be blank in " + annotation); Preconditions.notBlank(regex, () -> "The 'matches' attribute must not be blank in " + annotation); String actual = getEnvironmentVariable(name); // Nothing to match against? if (actual == null) { return disabled("Environment variable [%s] does not exist".formatted(name), annotation.disabledReason()); } if (actual.matches(regex)) { return enabled("Environment variable [%s] with value [%s] matches regular expression [%s]".formatted(name, actual, regex)); } return disabled("Environment variable [%s] with value [%s] does not match regular expression [%s]".formatted( name, actual, regex), annotation.disabledReason()); } /** * Get the value of the named environment variable. * *

The default implementation delegates to * {@link System#getenv(String)}. Can be overridden in a subclass for * testing purposes. */ protected @Nullable String getEnvironmentVariable(String name) { return System.getenv(name); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariables.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @EnabledIfEnvironmentVariables} is a container for one or more * {@link EnabledIfEnvironmentVariable @EnabledIfEnvironmentVariable} declarations. * *

Note, however, that use of the {@code @EnabledIfEnvironmentVariables} container * is completely optional since {@code @EnabledIfEnvironmentVariable} is a {@linkplain * java.lang.annotation.Repeatable repeatable} annotation. * *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. * Consequently, if you wish to apply the same semantics to a subclass, this * annotation must be redeclared on the subclass. * * @since 5.6 * @see EnabledIfEnvironmentVariable * @see java.lang.annotation.Repeatable */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = STABLE, since = "5.6") public @interface EnabledIfEnvironmentVariables { /** * An array of one or more {@link EnabledIfEnvironmentVariable @EnabledIfEnvironmentVariable} * declarations. */ EnabledIfEnvironmentVariable[] value(); } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperties.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @EnabledIfSystemProperties} is a container for one or more * {@link EnabledIfSystemProperty @EnabledIfSystemProperty} declarations. * *

Note, however, that use of the {@code @EnabledIfSystemProperties} container * is completely optional since {@code @EnabledIfSystemProperty} is a {@linkplain * java.lang.annotation.Repeatable repeatable} annotation. * *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. * Consequently, if you wish to apply the same semantics to a subclass, this * annotation must be redeclared on the subclass. * * @since 5.6 * @see EnabledIfSystemProperty * @see java.lang.annotation.Repeatable */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = STABLE, since = "5.6") public @interface EnabledIfSystemProperties { /** * An array of one or more {@link EnabledIfSystemProperty @EnabledIfSystemProperty} * declarations. */ EnabledIfSystemProperty[] value(); } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperty.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ExtendWith; /** * {@code @EnabledIfSystemProperty} is used to signal that the annotated test * class or test method is only enabled if the value of the specified * {@linkplain #named system property} matches the specified * {@linkplain #matches regular expression}. * *

When declared at the class level, the result will apply to all test methods * within that class as well. * *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. * Consequently, if you wish to apply the same semantics to a subclass, this * annotation must be redeclared on the subclass. * *

If a test method is disabled via this annotation, that prevents execution * of the test method and method-level lifecycle callbacks such as * {@code @BeforeEach} methods, {@code @AfterEach} methods, and corresponding * extension APIs. However, that does not prevent the test class from being * instantiated, and it does not prevent the execution of class-level lifecycle * callbacks such as {@code @BeforeAll} methods, {@code @AfterAll} methods, and * corresponding extension APIs. * *

If the specified system property is undefined, the annotated class or * method will be disabled. * *

This annotation may be used as a meta-annotation in order to create a * custom composed annotation that inherits the semantics of this * annotation. * *

This annotation is a {@linkplain Repeatable repeatable} annotation and may * be declared multiple times on an {@link java.lang.reflect.AnnotatedElement * AnnotatedElement} such as a test interface, test class, or test method. * Specifically, this annotation will be found if it is directly present, * indirectly present, or meta-present on a given element. * * @since 5.1 * @see org.junit.jupiter.api.condition.EnabledIf * @see org.junit.jupiter.api.condition.DisabledIf * @see org.junit.jupiter.api.condition.EnabledOnOs * @see org.junit.jupiter.api.condition.DisabledOnOs * @see org.junit.jupiter.api.condition.EnabledOnJre * @see org.junit.jupiter.api.condition.DisabledOnJre * @see org.junit.jupiter.api.condition.EnabledForJreRange * @see org.junit.jupiter.api.condition.DisabledForJreRange * @see org.junit.jupiter.api.condition.EnabledInNativeImage * @see org.junit.jupiter.api.condition.DisabledInNativeImage * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable * @see org.junit.jupiter.api.Disabled */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Repeatable(EnabledIfSystemProperties.class) @ExtendWith(EnabledIfSystemPropertyCondition.class) @API(status = STABLE, since = "5.1") @SuppressWarnings("exports") public @interface EnabledIfSystemProperty { /** * The name of the JVM system property to retrieve. * * @return the system property name; never blank * @see System#getProperty(String) */ String named(); /** * A regular expression that will be used to match against the retrieved * value of the {@link #named} JVM system property. * * @return the regular expression; never blank * @see String#matches(String) * @see java.util.regex.Pattern */ String matches(); /** * Custom reason to provide if the test or container is disabled. * *

If a custom reason is supplied, it will be combined with the default * reason for this annotation. If a custom reason is not supplied, the default * reason will be used. * * @since 5.7 */ @API(status = STABLE, since = "5.7") String disabledReason() default ""; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyCondition.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled; import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.platform.commons.util.Preconditions; /** * {@link ExecutionCondition} for {@link EnabledIfSystemProperty @EnabledIfSystemProperty}. * * @since 5.1 * @see EnabledIfSystemProperty */ class EnabledIfSystemPropertyCondition extends AbstractRepeatableAnnotationCondition { private static final ConditionEvaluationResult ENABLED = ConditionEvaluationResult.enabled( "No @EnabledIfSystemProperty conditions resulting in 'disabled' execution encountered"); EnabledIfSystemPropertyCondition() { super(EnabledIfSystemProperty.class); } @Override protected ConditionEvaluationResult getNoDisabledConditionsEncounteredResult() { return ENABLED; } @Override protected ConditionEvaluationResult evaluate(EnabledIfSystemProperty annotation) { String name = annotation.named().strip(); String regex = annotation.matches(); Preconditions.notBlank(name, () -> "The 'named' attribute must not be blank in " + annotation); Preconditions.notBlank(regex, () -> "The 'matches' attribute must not be blank in " + annotation); String actual = System.getProperty(name); // Nothing to match against? if (actual == null) { return disabled("System property [%s] does not exist".formatted(name), annotation.disabledReason()); } if (actual.matches(regex)) { return enabled( "System property [%s] with value [%s] matches regular expression [%s]".formatted(name, actual, regex)); } return disabled("System property [%s] with value [%s] does not match regular expression [%s]".formatted(name, actual, regex), annotation.disabledReason()); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledInNativeImage.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @EnabledInNativeImage} is used to signal that the annotated test class * or test method is only enabled when executing within a GraalVM native * image. * *

When applied at the class level, all test methods within that class will * be enabled within a native image. * *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. * Consequently, if you wish to apply the same semantics to a subclass, this * annotation must be redeclared on the subclass. * *

If a test method is disabled via this annotation, that prevents execution * of the test method and method-level lifecycle callbacks such as * {@code @BeforeEach} methods, {@code @AfterEach} methods, and corresponding * extension APIs. However, that does not prevent the test class from being * instantiated, and it does not prevent the execution of class-level lifecycle * callbacks such as {@code @BeforeAll} methods, {@code @AfterAll} methods, and * corresponding extension APIs. * *

This annotation may be used as a meta-annotation in order to create a * custom composed annotation that inherits the semantics of this * annotation. * *

Technical Details

* *

JUnit detects whether tests are executing within a GraalVM native image by * checking for the presence of the {@code org.graalvm.nativeimage.imagecode} * system property (see * org.graalvm.nativeimage.ImageInfo * for details). The GraalVM compiler sets the property to {@code buildtime} while * compiling a native image; the property is set to {@code runtime} while a native * image is executing; and the Gradle and Maven plug-ins in the GraalVM * Native Build Tools * project set the property to {@code agent} while executing tests with the GraalVM * tracing agent. * * @since 5.9.1 * @see org.junit.jupiter.api.condition.EnabledIf * @see org.junit.jupiter.api.condition.DisabledIf * @see org.junit.jupiter.api.condition.EnabledOnOs * @see org.junit.jupiter.api.condition.DisabledOnOs * @see org.junit.jupiter.api.condition.EnabledOnJre * @see org.junit.jupiter.api.condition.DisabledOnJre * @see org.junit.jupiter.api.condition.EnabledForJreRange * @see org.junit.jupiter.api.condition.DisabledForJreRange * @see org.junit.jupiter.api.condition.DisabledInNativeImage * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable * @see org.junit.jupiter.api.Disabled */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @EnabledIfSystemProperty(named = "org.graalvm.nativeimage.imagecode", matches = ".+", // disabledReason = "Not currently executing within a GraalVM native image") @API(status = STABLE, since = "5.9.1") public @interface EnabledInNativeImage { } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnJre.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ExtendWith; /** * {@code @EnabledOnJre} is used to signal that the annotated test class or * test method is only enabled on one or more specified Java Runtime * Environment (JRE) versions. * *

Versions can be specified as {@link JRE} enum constants via * {@link #value() value} or as integers via {@link #versions() versions}. * *

When applied at the class level, all test methods within that class * will be enabled on the same specified JRE versions. * *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. * Consequently, if you wish to apply the same semantics to a subclass, this * annotation must be redeclared on the subclass. * *

If a test method is disabled via this annotation, that prevents execution * of the test method and method-level lifecycle callbacks such as * {@code @BeforeEach} methods, {@code @AfterEach} methods, and corresponding * extension APIs. However, that does not prevent the test class from being * instantiated, and it does not prevent the execution of class-level lifecycle * callbacks such as {@code @BeforeAll} methods, {@code @AfterAll} methods, and * corresponding extension APIs. * *

This annotation may be used as a meta-annotation in order to create a * custom composed annotation that inherits the semantics of this * annotation. * *

Warning

* *

This annotation can only be declared once on an * {@link java.lang.reflect.AnnotatedElement AnnotatedElement} such as a test * interface, test class, or test method. If this annotation is directly * present, indirectly present, or meta-present multiple times on a given * element, only the first such annotation discovered by JUnit will be used; * any additional declarations will be silently ignored. Note, however, that * this annotation may be used in conjunction with other {@code @Enabled*} or * {@code @Disabled*} annotations in this package. * * @since 5.1 * @see JRE * @see org.junit.jupiter.api.condition.EnabledIf * @see org.junit.jupiter.api.condition.DisabledIf * @see org.junit.jupiter.api.condition.EnabledOnOs * @see org.junit.jupiter.api.condition.DisabledOnOs * @see org.junit.jupiter.api.condition.DisabledOnJre * @see org.junit.jupiter.api.condition.EnabledForJreRange * @see org.junit.jupiter.api.condition.DisabledForJreRange * @see org.junit.jupiter.api.condition.EnabledInNativeImage * @see org.junit.jupiter.api.condition.DisabledInNativeImage * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable * @see org.junit.jupiter.api.Disabled */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @ExtendWith(EnabledOnJreCondition.class) @API(status = STABLE, since = "5.1") @SuppressWarnings("exports") public @interface EnabledOnJre { /** * Java Runtime Environment versions on which the annotated class or method * should be enabled, specified as {@link JRE} enum constants. * *

If a {@code JRE} enum constant does not exist for a particular JRE * version, you can specify the version via {@link #versions() versions} * instead. * * @see JRE * @see #versions() */ JRE[] value() default {}; /** * Java Runtime Environment versions on which the annotated class or method * should be enabled, specified as integers. * *

If a {@code JRE} enum constant exists for a particular JRE version, you * can specify the version via {@link #value() value} instead. * * @since 5.12 * @see #value() * @see JRE#version() * @see Runtime.Version#feature() */ @API(status = MAINTAINED, since = "5.13.3") int[] versions() default {}; /** * Custom reason to provide if the test or container is disabled. * *

If a custom reason is supplied, it will be combined with the default * reason for this annotation. If a custom reason is not supplied, the default * reason will be used. * * @since 5.7 */ @API(status = STABLE, since = "5.7") String disabledReason() default ""; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnJreCondition.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import org.junit.jupiter.api.extension.ExecutionCondition; /** * {@link ExecutionCondition} for {@link EnabledOnJre @EnabledOnJre}. * * @since 5.1 * @see EnabledOnJre */ class EnabledOnJreCondition extends AbstractJreCondition { EnabledOnJreCondition() { super(EnabledOnJre.class, EnabledOnJre::disabledReason); } @Override boolean isEnabled(EnabledOnJre annotation) { return validatedVersions(annotation.value(), annotation.versions()).anyMatch(JRE::isCurrentVersion); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnOs.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ExtendWith; /** * {@code @EnabledOnOs} is used to signal that the annotated test class or * test method is only enabled on one or more specified * {@linkplain #value operating systems} or one or more specified * {@linkplain #architectures architectures}. * *

If operating systems and architectures are specified, the annotated * test class or test method is enabled if both conditions apply. * *

When applied at the class level, all test methods within that class * will be enabled on the same specified operating systems, architectures, or * the specified combinations of both. * *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. * Consequently, if you wish to apply the same semantics to a subclass, this * annotation must be redeclared on the subclass. * *

If a test method is disabled via this annotation, that prevents execution * of the test method and method-level lifecycle callbacks such as * {@code @BeforeEach} methods, {@code @AfterEach} methods, and corresponding * extension APIs. However, that does not prevent the test class from being * instantiated, and it does not prevent the execution of class-level lifecycle * callbacks such as {@code @BeforeAll} methods, {@code @AfterAll} methods, and * corresponding extension APIs. * *

This annotation may be used as a meta-annotation in order to create a * custom composed annotation that inherits the semantics of this * annotation. * *

Warning

* *

This annotation can only be declared once on an * {@link java.lang.reflect.AnnotatedElement AnnotatedElement} such as a test * interface, test class, or test method. If this annotation is directly * present, indirectly present, or meta-present multiple times on a given * element, only the first such annotation discovered by JUnit will be used; * any additional declarations will be silently ignored. Note, however, that * this annotation may be used in conjunction with other {@code @Enabled*} or * {@code @Disabled*} annotations in this package. * * @since 5.1 * @see OS * @see org.junit.jupiter.api.condition.EnabledIf * @see org.junit.jupiter.api.condition.DisabledIf * @see org.junit.jupiter.api.condition.DisabledOnOs * @see org.junit.jupiter.api.condition.EnabledOnJre * @see org.junit.jupiter.api.condition.DisabledOnJre * @see org.junit.jupiter.api.condition.EnabledForJreRange * @see org.junit.jupiter.api.condition.DisabledForJreRange * @see org.junit.jupiter.api.condition.EnabledInNativeImage * @see org.junit.jupiter.api.condition.DisabledInNativeImage * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable * @see org.junit.jupiter.api.Disabled */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @ExtendWith(EnabledOnOsCondition.class) @API(status = STABLE, since = "5.1") @SuppressWarnings("exports") public @interface EnabledOnOs { /** * Operating systems on which the annotated class or method should be * enabled. * * @see OS */ OS[] value() default {}; /** * Architectures on which the annotated class or method should be enabled. * *

Each architecture will be compared to the value returned from * {@code System.getProperty("os.arch")}, ignoring case. * * @since 5.9 */ @API(status = STABLE, since = "5.9") String[] architectures() default {}; /** * Custom reason to provide if the test or container is disabled. * *

If a custom reason is supplied, it will be combined with the default * reason for this annotation. If a custom reason is not supplied, the default * reason will be used. * * @since 5.7 */ @API(status = STABLE, since = "5.7") String disabledReason() default ""; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnOsCondition.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import java.util.Arrays; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.platform.commons.util.Preconditions; /** * {@link ExecutionCondition} for {@link EnabledOnOs @EnabledOnOs}. * * @since 5.1 * @see EnabledOnOs */ class EnabledOnOsCondition extends AbstractOsBasedExecutionCondition { EnabledOnOsCondition() { super(EnabledOnOs.class); } @Override ConditionEvaluationResult evaluateExecutionCondition(EnabledOnOs annotation) { boolean osSpecified = annotation.value().length > 0; boolean archSpecified = annotation.architectures().length > 0; Preconditions.condition(osSpecified || archSpecified, "You must declare at least one OS or architecture in @EnabledOnOs"); boolean enabled = isEnabledBasedOnOs(annotation) && isEnabledBasedOnArchitecture(annotation); String reason = createReason(enabled, osSpecified, archSpecified); return enabled ? ConditionEvaluationResult.enabled(reason) : ConditionEvaluationResult.disabled(reason, annotation.disabledReason()); } private boolean isEnabledBasedOnOs(EnabledOnOs annotation) { OS[] operatingSystems = annotation.value(); if (operatingSystems.length == 0) { return true; } return Arrays.stream(operatingSystems).anyMatch(OS::isCurrentOs); } private boolean isEnabledBasedOnArchitecture(EnabledOnOs annotation) { String[] architectures = annotation.architectures(); if (architectures.length == 0) { return true; } return Arrays.stream(architectures).anyMatch(CURRENT_ARCHITECTURE::equalsIgnoreCase); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/MethodBasedCondition.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled; import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.Optional; import java.util.function.Function; import java.util.function.Supplier; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.ClassLoaderUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.commons.util.StringUtils; /** * @since 5.7 */ abstract class MethodBasedCondition implements ExecutionCondition { private final Class annotationType; private final Function methodName; private final Function customDisabledReason; MethodBasedCondition(Class annotationType, Function methodName, Function customDisabledReason) { this.annotationType = annotationType; this.methodName = methodName; this.customDisabledReason = customDisabledReason; } @Override public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { Optional annotation = findAnnotation(context.getElement(), this.annotationType); return annotation // .map(this.methodName) // .map(methodName -> getConditionMethod(methodName, context)) // .map(method -> invokeConditionMethod(method, context)) // .map(methodResult -> buildConditionEvaluationResult(methodResult, annotation.get())) // .orElseGet(this::enabledByDefault); } // package-private for testing Method getConditionMethod(String fullyQualifiedMethodName, ExtensionContext context) { Class testClass = context.getRequiredTestClass(); if (!fullyQualifiedMethodName.contains("#")) { return findMethod(testClass, fullyQualifiedMethodName); } String[] methodParts = ReflectionUtils.parseFullyQualifiedMethodName(fullyQualifiedMethodName); String className = methodParts[0]; String methodName = methodParts[1]; ClassLoader classLoader = ClassLoaderUtils.getClassLoader(testClass); Class clazz = ReflectionSupport.tryToLoadClass(className, classLoader).getNonNullOrThrow( cause -> new JUnitException("Could not load class [%s]".formatted(className), cause)); return findMethod(clazz, methodName); } private Method findMethod(Class clazz, String methodName) { return ReflectionSupport.findMethod(clazz, methodName) // .orElseGet(() -> ReflectionUtils.getRequiredMethod(clazz, methodName, ExtensionContext.class)); } private boolean invokeConditionMethod(Method method, ExtensionContext context) { Preconditions.condition(method.getReturnType() == boolean.class, () -> "Method [%s] must return a boolean".formatted(method)); Preconditions.condition(acceptsExtensionContextOrNoArguments(method), () -> "Method [%s] must accept either an ExtensionContext or no arguments".formatted(method)); Object testInstance = context.getTestInstance().orElse(null); return invokeMethod(method, context, testInstance); } @SuppressWarnings("DataFlowIssue") private static boolean invokeMethod(Method method, ExtensionContext context, @Nullable Object testInstance) { if (method.getParameterCount() == 0) { return (boolean) ReflectionSupport.invokeMethod(method, testInstance); } return (boolean) ReflectionSupport.invokeMethod(method, testInstance, context); } private boolean acceptsExtensionContextOrNoArguments(Method method) { int parameterCount = method.getParameterCount(); return parameterCount == 0 || (parameterCount == 1 && method.getParameterTypes()[0] == ExtensionContext.class); } private ConditionEvaluationResult buildConditionEvaluationResult(boolean methodResult, A annotation) { Supplier defaultReason = () -> "@%s(\"%s\") evaluated to %s".formatted( this.annotationType.getSimpleName(), this.methodName.apply(annotation), methodResult); if (isEnabled(methodResult)) { return enabled(defaultReason.get()); } String customReason = this.customDisabledReason.apply(annotation); return StringUtils.isNotBlank(customReason) ? disabled(customReason) : disabled(defaultReason.get()); } protected abstract boolean isEnabled(boolean methodResult); private ConditionEvaluationResult enabledByDefault() { return enabled("@%s is not present".formatted(this.annotationType.getSimpleName())); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/OS.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.apiguardian.api.API.Status.STABLE; import java.util.Locale; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.StringUtils; /** * Enumeration of common operating systems used for testing Java applications. * *

If the current operating system cannot be detected — for example, * if the {@code os.name} JVM system property is undefined — then none * of the constants defined in this enum will be considered to be the * {@linkplain #isCurrentOs current operating system}. * * @since 5.1 * @see #AIX * @see #FREEBSD * @see #LINUX * @see #MAC * @see #OPENBSD * @see #SOLARIS * @see #WINDOWS * @see #OTHER * @see EnabledOnOs * @see DisabledOnOs */ @API(status = STABLE, since = "5.1") public enum OS { /** * IBM AIX operating system. * * @since 5.3 */ @API(status = STABLE, since = "5.3") AIX, /** * FreeBSD operating system. * * @since 5.9 */ @API(status = STABLE, since = "5.9") FREEBSD, /** * Linux-based operating system. */ LINUX, /** * Apple Macintosh operating system (e.g., macOS). */ MAC, /** * OpenBSD operating system. * * @since 5.9 */ @API(status = STABLE, since = "5.9") OPENBSD, /** * Oracle Solaris operating system. */ SOLARIS, /** * Microsoft Windows operating system. */ WINDOWS, /** * An operating system other than {@link #AIX}, {@link #FREEBSD}, {@link #LINUX}, * {@link #MAC}, {@link #OPENBSD}, {@link #SOLARIS}, or {@link #WINDOWS}. */ OTHER; private static final Logger logger = LoggerFactory.getLogger(OS.class); private static final @Nullable OS CURRENT_OS = determineCurrentOs(); /** * {@return the current operating system, if known; otherwise, {@code null}} * * @since 5.9 */ @API(status = STABLE, since = "5.10") public static @Nullable OS current() { return CURRENT_OS; } private static @Nullable OS determineCurrentOs() { return parse(System.getProperty("os.name")); } static @Nullable OS parse(String osName) { if (StringUtils.isBlank(osName)) { logger.debug( () -> "JVM system property 'os.name' is undefined. It is therefore not possible to detect the current OS."); // null signals that the current OS is "unknown" return null; } osName = osName.toLowerCase(Locale.ENGLISH); if (osName.contains("aix")) { return AIX; } if (osName.contains("freebsd")) { return FREEBSD; } if (osName.contains("linux")) { return LINUX; } if (osName.contains("mac")) { return MAC; } if (osName.contains("openbsd")) { return OPENBSD; } if (osName.contains("sunos") || osName.contains("solaris")) { return SOLARIS; } if (osName.contains("win")) { return WINDOWS; } return OTHER; } /** * {@return {@code true} if this {@code OS} is known to be the * operating system on which the current JVM is executing} */ public boolean isCurrentOs() { return this == CURRENT_OS; } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Annotation-based conditions for enabling or disabling tests in JUnit Jupiter. */ @NullMarked package org.junit.jupiter.api.condition; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterAllCallback.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; /** * {@code AfterAllCallback} defines the API for {@link Extension Extensions} * that wish to provide additional behavior to test containers once * after all tests in the container have been executed. * *

Concrete implementations often implement {@link BeforeAllCallback} as well. * *

Extensions that implement {@code AfterAllCallback} must be registered at * the class level. * *

Constructor Requirements

* *

Consult the documentation in {@link Extension} for details on * constructor requirements. * *

Wrapping Behavior

* *

JUnit Jupiter guarantees wrapping behavior for multiple * registered extensions that implement lifecycle callbacks such as * {@link BeforeAllCallback}, {@link AfterAllCallback}, * {@link BeforeClassTemplateInvocationCallback}, * {@link AfterClassTemplateInvocationCallback}, {@link BeforeEachCallback}, * {@link AfterEachCallback}, {@link BeforeTestExecutionCallback}, and * {@link AfterTestExecutionCallback}. * *

That means that, given two extensions {@code Extension1} and * {@code Extension2} with {@code Extension1} registered before * {@code Extension2}, any "before" callbacks implemented by {@code Extension1} * are guaranteed to execute before any "before" callbacks implemented by * {@code Extension2}. Similarly, given the two same two extensions registered * in the same order, any "after" callbacks implemented by {@code Extension1} * are guaranteed to execute after any "after" callbacks implemented by * {@code Extension2}. {@code Extension1} is therefore said to wrap * {@code Extension2}. * * @since 5.0 * @see org.junit.jupiter.api.AfterAll * @see BeforeAllCallback * @see BeforeEachCallback * @see AfterEachCallback * @see BeforeTestExecutionCallback * @see AfterTestExecutionCallback * @see BeforeClassTemplateInvocationCallback * @see AfterClassTemplateInvocationCallback */ @FunctionalInterface @API(status = STABLE, since = "5.0") public interface AfterAllCallback extends Extension { /** * Callback that is invoked once after all tests in the current * container. * * @param context the current extension context; never {@code null} */ void afterAll(ExtensionContext context) throws Exception; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterClassTemplateInvocationCallback.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import org.apiguardian.api.API; import org.junit.jupiter.api.ClassTemplate; /** * {@code AfterClassTemplateInvocationCallback} defines the API for * {@link Extension Extensions} that wish to provide additional behavior * once after each invocation of a * {@link ClassTemplate @ClassTemplate}. * *

Concrete implementations often implement * {@link BeforeClassTemplateInvocationCallback} as well. * *

Constructor Requirements

* *

Consult the documentation in {@link Extension} for details on * constructor requirements. * *

Wrapping Behavior

* *

JUnit Jupiter guarantees wrapping behavior for multiple * registered extensions that implement lifecycle callbacks such as * {@link BeforeAllCallback}, {@link AfterAllCallback}, * {@link BeforeClassTemplateInvocationCallback}, * {@link AfterClassTemplateInvocationCallback}, {@link BeforeEachCallback}, * {@link AfterEachCallback}, {@link BeforeTestExecutionCallback}, and * {@link AfterTestExecutionCallback}. * *

That means that, given two extensions {@code Extension1} and * {@code Extension2} with {@code Extension1} registered before * {@code Extension2}, any "before" callbacks implemented by {@code Extension1} * are guaranteed to execute before any "before" callbacks implemented by * {@code Extension2}. Similarly, given the two same two extensions registered * in the same order, any "after" callbacks implemented by {@code Extension1} * are guaranteed to execute after any "after" callbacks implemented by * {@code Extension2}. {@code Extension1} is therefore said to wrap * {@code Extension2}. * * @since 5.13 * @see ClassTemplate * @see BeforeClassTemplateInvocationCallback * @see BeforeAllCallback * @see AfterAllCallback * @see BeforeEachCallback * @see AfterEachCallback * @see BeforeTestExecutionCallback * @see AfterTestExecutionCallback */ @FunctionalInterface @API(status = EXPERIMENTAL, since = "6.0") public interface AfterClassTemplateInvocationCallback extends Extension { /** * Callback that is invoked after each invocation of a class * template. * * @param context the current extension context; never {@code null} */ void afterClassTemplateInvocation(ExtensionContext context) throws Exception; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterEachCallback.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; /** * {@code AfterEachCallback} defines the API for {@link Extension Extensions} * that wish to provide additional behavior to tests after an individual test * and any user-defined teardown methods (e.g., * {@link org.junit.jupiter.api.AfterEach @AfterEach} methods) for that test * have been executed. * *

Concrete implementations often implement {@link BeforeEachCallback} as well. * If you do not wish to have your callbacks wrapped around user-defined * setup and teardown methods, implement {@link BeforeTestExecutionCallback} and * {@link AfterTestExecutionCallback} instead of {@link BeforeEachCallback} and * {@link AfterEachCallback}. * *

Constructor Requirements

* *

Consult the documentation in {@link Extension} for details on * constructor requirements. * *

Wrapping Behavior

* *

JUnit Jupiter guarantees wrapping behavior for multiple * registered extensions that implement lifecycle callbacks such as * {@link BeforeAllCallback}, {@link AfterAllCallback}, * {@link BeforeClassTemplateInvocationCallback}, * {@link AfterClassTemplateInvocationCallback}, {@link BeforeEachCallback}, * {@link AfterEachCallback}, {@link BeforeTestExecutionCallback}, and * {@link AfterTestExecutionCallback}. * *

That means that, given two extensions {@code Extension1} and * {@code Extension2} with {@code Extension1} registered before * {@code Extension2}, any "before" callbacks implemented by {@code Extension1} * are guaranteed to execute before any "before" callbacks implemented by * {@code Extension2}. Similarly, given the two same two extensions registered * in the same order, any "after" callbacks implemented by {@code Extension1} * are guaranteed to execute after any "after" callbacks implemented by * {@code Extension2}. {@code Extension1} is therefore said to wrap * {@code Extension2}. * * @since 5.0 * @see org.junit.jupiter.api.AfterEach * @see BeforeEachCallback * @see BeforeTestExecutionCallback * @see AfterTestExecutionCallback * @see BeforeAllCallback * @see AfterAllCallback * @see BeforeClassTemplateInvocationCallback * @see AfterClassTemplateInvocationCallback */ @FunctionalInterface @API(status = STABLE, since = "5.0") public interface AfterEachCallback extends Extension { /** * Callback that is invoked after an individual test and any * user-defined teardown methods for that test have been executed. * * @param context the current extension context; never {@code null} */ void afterEach(ExtensionContext context) throws Exception; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterTestExecutionCallback.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; /** * {@code AfterTestExecutionCallback} defines the API for {@link Extension * Extensions} that wish to provide additional behavior to tests * immediately after an individual test has been executed but * before any user-defined teardown methods (e.g., * {@link org.junit.jupiter.api.AfterEach @AfterEach} methods) have been executed * for that test. * *

Concrete implementations often implement {@link BeforeTestExecutionCallback} * as well. If you wish to have your callbacks wrapped around user-defined * setup and teardown methods, implement {@link BeforeEachCallback} and * {@link AfterEachCallback} instead of {@link BeforeTestExecutionCallback} and * {@link AfterTestExecutionCallback}. * *

Constructor Requirements

* *

Consult the documentation in {@link Extension} for details on * constructor requirements. * *

Wrapping Behavior

* *

JUnit Jupiter guarantees wrapping behavior for multiple * registered extensions that implement lifecycle callbacks such as * {@link BeforeAllCallback}, {@link AfterAllCallback}, * {@link BeforeClassTemplateInvocationCallback}, * {@link AfterClassTemplateInvocationCallback}, {@link BeforeEachCallback}, * {@link AfterEachCallback}, {@link BeforeTestExecutionCallback}, and * {@link AfterTestExecutionCallback}. * *

That means that, given two extensions {@code Extension1} and * {@code Extension2} with {@code Extension1} registered before * {@code Extension2}, any "before" callbacks implemented by {@code Extension1} * are guaranteed to execute before any "before" callbacks implemented by * {@code Extension2}. Similarly, given the two same two extensions registered * in the same order, any "after" callbacks implemented by {@code Extension1} * are guaranteed to execute after any "after" callbacks implemented by * {@code Extension2}. {@code Extension1} is therefore said to wrap * {@code Extension2}. * * @since 5.0 * @see org.junit.jupiter.api.Test * @see BeforeTestExecutionCallback * @see BeforeEachCallback * @see AfterEachCallback * @see BeforeAllCallback * @see AfterAllCallback * @see BeforeClassTemplateInvocationCallback * @see AfterClassTemplateInvocationCallback */ @FunctionalInterface @API(status = STABLE, since = "5.0") public interface AfterTestExecutionCallback extends Extension { /** * Callback that is invoked immediately after an individual test has * been executed but before any user-defined teardown methods have been * executed for that test. * * @param context the current extension context; never {@code null} */ void afterTestExecution(ExtensionContext context) throws Exception; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AnnotatedElementContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.MAINTAINED; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.util.List; import java.util.Optional; import org.apiguardian.api.API; import org.junit.platform.commons.support.AnnotationSupport; /** * {@code AnnotatedElementContext} encapsulates the context in which an * {@link #getAnnotatedElement() AnnotatedElement} is declared. * *

For example, an {@code AnnotatedElementContext} is used in * {@link org.junit.jupiter.api.io.TempDirFactory TempDirFactory} to allow inspecting * the field or parameter the {@link org.junit.jupiter.api.io.TempDir TempDir} * annotation is declared on. * *

This interface is not intended to be implemented by clients. * * @since 5.10 */ @API(status = MAINTAINED, since = "5.13.3") public interface AnnotatedElementContext { /** * Get the {@link AnnotatedElement} for this context. * *

WARNING

*

When searching for annotations on the annotated element in this context, * favor {@link #isAnnotated(Class)}, {@link #findAnnotation(Class)}, and * {@link #findRepeatableAnnotations(Class)} over methods in the * {@link AnnotatedElement} API due to a bug in {@code javac} on JDK versions prior * to JDK 9. * * @return the annotated element; never {@code null} */ AnnotatedElement getAnnotatedElement(); /** * Determine if an annotation of {@code annotationType} is either * present or meta-present on the {@link AnnotatedElement} for * this context. * *

Note: This method does not find repeatable annotations. * To check for repeatable annotations, use {@link #findRepeatableAnnotations(Class)} * and verify that the returned list is not empty. * *

WARNING

*

Favor the use of this method over directly invoking * {@link AnnotatedElement#isAnnotationPresent(Class)} due to a bug in {@code javac} * on JDK versions prior to JDK 9. * * @param annotationType the annotation type to search for; never {@code null} * @return {@code true} if the annotation is present or meta-present * @see #findAnnotation(Class) * @see #findRepeatableAnnotations(Class) */ default boolean isAnnotated(Class annotationType) { return AnnotationSupport.isAnnotated(getAnnotatedElement(), annotationType); } /** * Find the first annotation of {@code annotationType} that is either * present or meta-present on the {@link AnnotatedElement} for * this context. * *

WARNING

*

Favor the use of this method over directly invoking annotation lookup * methods in the {@link AnnotatedElement} API due to a bug in {@code javac} on JDK * versions prior to JDK 9. * * @param the annotation type * @param annotationType the annotation type to search for; never {@code null} * @return an {@code Optional} containing the annotation; never {@code null} but * potentially empty * @see #isAnnotated(Class) * @see #findRepeatableAnnotations(Class) */ default Optional findAnnotation(Class annotationType) { return AnnotationSupport.findAnnotation(getAnnotatedElement(), annotationType); } /** * Find all repeatable {@linkplain Annotation annotations} of * {@code annotationType} that are either present or * meta-present on the {@link AnnotatedElement} for this context. * *

WARNING

*

Favor the use of this method over directly invoking annotation lookup * methods in the {@link AnnotatedElement} API due to a bug in {@code javac} on JDK * versions prior to JDK 9. * * @param the annotation type * @param annotationType the repeatable annotation type to search for; never * {@code null} * @return the list of all such annotations found; neither {@code null} nor * mutable, but potentially empty * @see #isAnnotated(Class) * @see #findAnnotation(Class) * @see java.lang.annotation.Repeatable */ default List findRepeatableAnnotations(Class annotationType) { return AnnotationSupport.findRepeatableAnnotations(getAnnotatedElement(), annotationType); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeAllCallback.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; /** * {@code BeforeAllCallback} defines the API for {@link Extension Extensions} * that wish to provide additional behavior to test containers once * before all tests in the container have been executed. * *

Concrete implementations often implement {@link AfterAllCallback} as well. * *

Extensions that implement {@code BeforeAllCallback} must be registered at * the class level. * *

Constructor Requirements

* *

Consult the documentation in {@link Extension} for details on * constructor requirements. * *

Wrapping Behavior

* *

JUnit Jupiter guarantees wrapping behavior for multiple * registered extensions that implement lifecycle callbacks such as * {@link BeforeAllCallback}, {@link AfterAllCallback}, * {@link BeforeClassTemplateInvocationCallback}, * {@link AfterClassTemplateInvocationCallback}, {@link BeforeEachCallback}, * {@link AfterEachCallback}, {@link BeforeTestExecutionCallback}, and * {@link AfterTestExecutionCallback}. * *

That means that, given two extensions {@code Extension1} and * {@code Extension2} with {@code Extension1} registered before * {@code Extension2}, any "before" callbacks implemented by {@code Extension1} * are guaranteed to execute before any "before" callbacks implemented by * {@code Extension2}. Similarly, given the two same two extensions registered * in the same order, any "after" callbacks implemented by {@code Extension1} * are guaranteed to execute after any "after" callbacks implemented by * {@code Extension2}. {@code Extension1} is therefore said to wrap * {@code Extension2}. * * @since 5.0 * @see org.junit.jupiter.api.BeforeAll * @see AfterAllCallback * @see BeforeEachCallback * @see AfterEachCallback * @see BeforeTestExecutionCallback * @see AfterTestExecutionCallback * @see BeforeClassTemplateInvocationCallback * @see AfterClassTemplateInvocationCallback */ @FunctionalInterface @API(status = STABLE, since = "5.0") public interface BeforeAllCallback extends Extension { /** * Callback that is invoked once before all tests in the current * container. * * @param context the current extension context; never {@code null} */ void beforeAll(ExtensionContext context) throws Exception; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeClassTemplateInvocationCallback.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import org.apiguardian.api.API; import org.junit.jupiter.api.ClassTemplate; /** * {@code BeforeClassTemplateInvocationCallback} defines the API for * {@link Extension Extensions} that wish to provide additional behavior * once before each invocation of a * {@link ClassTemplate @ClassTemplate}. * *

Concrete implementations often implement * {@link AfterClassTemplateInvocationCallback} as well. * *

Constructor Requirements

* *

Consult the documentation in {@link Extension} for details on * constructor requirements. * *

Wrapping Behavior

* *

JUnit Jupiter guarantees wrapping behavior for multiple * registered extensions that implement lifecycle callbacks such as * {@link BeforeAllCallback}, {@link AfterAllCallback}, * {@link BeforeClassTemplateInvocationCallback}, * {@link AfterClassTemplateInvocationCallback}, {@link BeforeEachCallback}, * {@link AfterEachCallback}, {@link BeforeTestExecutionCallback}, and * {@link AfterTestExecutionCallback}. * *

That means that, given two extensions {@code Extension1} and * {@code Extension2} with {@code Extension1} registered before * {@code Extension2}, any "before" callbacks implemented by {@code Extension1} * are guaranteed to execute before any "before" callbacks implemented by * {@code Extension2}. Similarly, given the two same two extensions registered * in the same order, any "after" callbacks implemented by {@code Extension1} * are guaranteed to execute after any "after" callbacks implemented by * {@code Extension2}. {@code Extension1} is therefore said to wrap * {@code Extension2}. * * @since 5.13 * @see ClassTemplate * @see AfterClassTemplateInvocationCallback * @see BeforeAllCallback * @see AfterAllCallback * @see BeforeEachCallback * @see AfterEachCallback * @see BeforeTestExecutionCallback * @see AfterTestExecutionCallback */ @FunctionalInterface @API(status = EXPERIMENTAL, since = "6.0") public interface BeforeClassTemplateInvocationCallback extends Extension { /** * Callback that is invoked before each invocation of a class * template. * * @param context the current extension context; never {@code null} */ void beforeClassTemplateInvocation(ExtensionContext context) throws Exception; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeEachCallback.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; /** * {@code BeforeEachCallback} defines the API for {@link Extension Extensions} * that wish to provide additional behavior to tests before an individual test * and any user-defined setup methods (e.g., * {@link org.junit.jupiter.api.BeforeEach @BeforeEach} methods) for that test * have been executed. * *

Concrete implementations often implement {@link AfterEachCallback} as well. * If you do not wish to have your callbacks wrapped around user-defined * setup and teardown methods, implement {@link BeforeTestExecutionCallback} and * {@link AfterTestExecutionCallback} instead of {@link BeforeEachCallback} and * {@link AfterEachCallback}. * *

Constructor Requirements

* *

Consult the documentation in {@link Extension} for details on * constructor requirements. * *

Wrapping Behavior

* *

JUnit Jupiter guarantees wrapping behavior for multiple * registered extensions that implement lifecycle callbacks such as * {@link BeforeAllCallback}, {@link AfterAllCallback}, * {@link BeforeClassTemplateInvocationCallback}, * {@link AfterClassTemplateInvocationCallback}, {@link BeforeEachCallback}, * {@link AfterEachCallback}, {@link BeforeTestExecutionCallback}, and * {@link AfterTestExecutionCallback}. * *

That means that, given two extensions {@code Extension1} and * {@code Extension2} with {@code Extension1} registered before * {@code Extension2}, any "before" callbacks implemented by {@code Extension1} * are guaranteed to execute before any "before" callbacks implemented by * {@code Extension2}. Similarly, given the two same two extensions registered * in the same order, any "after" callbacks implemented by {@code Extension1} * are guaranteed to execute after any "after" callbacks implemented by * {@code Extension2}. {@code Extension1} is therefore said to wrap * {@code Extension2}. * * @since 5.0 * @see org.junit.jupiter.api.BeforeEach * @see AfterEachCallback * @see BeforeTestExecutionCallback * @see AfterTestExecutionCallback * @see BeforeAllCallback * @see AfterAllCallback * @see BeforeClassTemplateInvocationCallback * @see AfterClassTemplateInvocationCallback */ @FunctionalInterface @API(status = STABLE, since = "5.0") public interface BeforeEachCallback extends Extension { /** * Callback that is invoked before an individual test and any * user-defined setup methods for that test have been executed. * * @param context the current extension context; never {@code null} */ void beforeEach(ExtensionContext context) throws Exception; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeTestExecutionCallback.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; /** * {@code BeforeTestExecutionCallback} defines the API for {@link Extension * Extensions} that wish to provide additional behavior to tests * immediately before an individual test is executed but after * any user-defined setup methods (e.g., * {@link org.junit.jupiter.api.BeforeEach @BeforeEach} methods) have been * executed for that test. * *

Concrete implementations often implement {@link AfterTestExecutionCallback} * as well. If you wish to have your callbacks wrapped around user-defined * setup and teardown methods, implement {@link BeforeEachCallback} and * {@link AfterEachCallback} instead of {@link BeforeTestExecutionCallback} and * {@link AfterTestExecutionCallback}. * *

Constructor Requirements

* *

Consult the documentation in {@link Extension} for details on * constructor requirements. * *

Wrapping Behavior

* *

JUnit Jupiter guarantees wrapping behavior for multiple * registered extensions that implement lifecycle callbacks such as * {@link BeforeAllCallback}, {@link AfterAllCallback}, * {@link BeforeClassTemplateInvocationCallback}, * {@link AfterClassTemplateInvocationCallback}, {@link BeforeEachCallback}, * {@link AfterEachCallback}, {@link BeforeTestExecutionCallback}, and * {@link AfterTestExecutionCallback}. * *

That means that, given two extensions {@code Extension1} and * {@code Extension2} with {@code Extension1} registered before * {@code Extension2}, any "before" callbacks implemented by {@code Extension1} * are guaranteed to execute before any "before" callbacks implemented by * {@code Extension2}. Similarly, given the two same two extensions registered * in the same order, any "after" callbacks implemented by {@code Extension1} * are guaranteed to execute after any "after" callbacks implemented by * {@code Extension2}. {@code Extension1} is therefore said to wrap * {@code Extension2}. * * @since 5.0 * @see org.junit.jupiter.api.Test * @see AfterTestExecutionCallback * @see BeforeEachCallback * @see AfterEachCallback * @see BeforeAllCallback * @see AfterAllCallback * @see BeforeClassTemplateInvocationCallback * @see AfterClassTemplateInvocationCallback */ @FunctionalInterface @API(status = STABLE, since = "5.0") public interface BeforeTestExecutionCallback extends Extension { /** * Callback that is invoked immediately before an individual test is * executed but after any user-defined setup methods have been executed * for that test. * * @param context the current extension context; never {@code null} */ void beforeTestExecution(ExtensionContext context) throws Exception; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ClassTemplateInvocationContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static java.util.Collections.emptyList; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import java.util.List; import org.apiguardian.api.API; import org.junit.jupiter.api.ClassTemplate; /** * {@code ClassTemplateInvocationContext} represents the context of * a single invocation of a {@link ClassTemplate @ClassTemplate}. * *

Each context is provided by a {@link ClassTemplateInvocationContextProvider}. * * @since 5.13 * @see ClassTemplate * @see ClassTemplateInvocationContextProvider */ @API(status = EXPERIMENTAL, since = "6.0") public interface ClassTemplateInvocationContext { /** * Get the display name for this invocation. * *

The supplied {@code invocationIndex} is incremented by the framework * with each class template invocation. Thus, in the case of multiple active * {@linkplain ClassTemplateInvocationContextProvider providers}, only the * first active provider receives indices starting with {@code 1}. * *

The default implementation returns the supplied {@code invocationIndex} * wrapped in brackets — for example, {@code [1]}, {@code [42]}, etc. * * @param invocationIndex the index of this invocation (1-based). * @return the display name for this invocation; never {@code null} or blank */ default String getDisplayName(int invocationIndex) { return "[" + invocationIndex + "]"; } /** * Get additional {@linkplain Extension extensions} for this invocation. * *

The extensions provided by this method will only be used for this * invocation of the class template. Thus, it does not make sense to return * an extension that needs to perform some action at the container level, * such as an implementation of {@link BeforeAllCallback}. * *

The default implementation returns an empty list. * * @return additional extensions for this invocation; never {@code null} * or containing {@code null} elements, but potentially empty */ default List getAdditionalExtensions() { return emptyList(); } /** * Prepare the imminent invocation of the class template. * *

This may be used, for example, to store entries in the * {@link ExtensionContext.Store Store} to benefit from its cleanup support * or for retrieval by other extensions. * * @param context the invocation-level extension context */ default void prepareInvocation(ExtensionContext context) { } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ClassTemplateInvocationContextProvider.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import java.util.stream.Stream; import org.apiguardian.api.API; import org.junit.jupiter.api.ClassTemplate; /** * {@code ClassTemplateInvocationContextProvider} defines the API for * {@link Extension Extensions} that wish to provide one or multiple contexts * for the invocation of a {@link ClassTemplate @ClassTemplate}. * *

This extension API makes it possible to execute a class template in * different contexts — for example, with different parameters, by * preparing the test class instance differently, or multiple times without * modifying the context. * *

This interface defines two main methods: {@link #supportsClassTemplate} and * {@link #provideClassTemplateInvocationContexts}. The former is called by the * framework to determine whether this extension wants to act on a class * template that is about to be executed. If so, the latter is called and must * return a {@link Stream} of {@link ClassTemplateInvocationContext} instances. * Otherwise, this provider is ignored for the execution of the current class * template. * *

A provider that has returned {@code true} from its {@link #supportsClassTemplate} * method is called active. When multiple providers are active for a class * template, the {@code Streams} returned by their * {@link #provideClassTemplateInvocationContexts} methods will be chained, and * the class template method will be invoked using the contexts of all active * providers. * *

An active provider may return zero invocation contexts from its * {@link #provideClassTemplateInvocationContexts} method if it overrides * {@link #mayReturnZeroClassTemplateInvocationContexts} to return {@code true}. * *

Constructor Requirements

* *

Consult the documentation in {@link Extension} for details on constructor * requirements. * * @since 5.13 * @see ClassTemplate * @see ClassTemplateInvocationContext */ @API(status = EXPERIMENTAL, since = "6.0") public interface ClassTemplateInvocationContextProvider extends Extension { /** * Determine if this provider supports providing invocation contexts for the * class template represented by the supplied {@code context}. * * @param context the extension context for the class template * about to be invoked; never {@code null} * @return {@code true} if this provider can provide invocation contexts * @see #provideClassTemplateInvocationContexts * @see ExtensionContext */ boolean supportsClassTemplate(ExtensionContext context); /** * Provide {@linkplain ClassTemplateInvocationContext invocation contexts} * for the class template represented by the supplied {@code context}. * *

This method is only called by the framework if * {@link #supportsClassTemplate} previously returned {@code true} for the * same {@link ExtensionContext}; this method is allowed to return an empty * {@code Stream} but not {@code null}. * *

The returned {@code Stream} will be properly closed by calling * {@link Stream#close()}, making it safe to use a resource such as * {@link java.nio.file.Files#lines(java.nio.file.Path) Files.lines()}. * * @param context the extension context for the class template about to be * invoked; never {@code null} * @return a {@code Stream} of {@code ClassTemplateInvocationContext} * instances for the invocation of the class template; never {@code null} * @throws TemplateInvocationValidationException if validation fails while * providing or closing the {@link Stream} * @see #supportsClassTemplate * @see ExtensionContext */ Stream provideClassTemplateInvocationContexts(ExtensionContext context); /** * Signal that this provider may provide zero * {@linkplain ClassTemplateInvocationContext invocation contexts} for * the class template represented by the supplied {@code context}. * *

If this method returns {@code false} (which is the default) and the * provider returns an empty stream from * {@link #provideClassTemplateInvocationContexts}, this will be considered * an execution error. Override this method to return {@code true} to ignore * the absence of invocation contexts for this provider. * * @param context the extension context for the class template * about to be invoked; never {@code null} * @return {@code true} to allow zero contexts, {@code false} to fail * execution in case of zero contexts */ default boolean mayReturnZeroClassTemplateInvocationContexts(ExtensionContext context) { return false; } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ClassTemplateInvocationLifecycleMethod.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.INTERNAL; import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * Internal marker annotation for lifecycle methods specific to implementations * of {@link ClassTemplateInvocationContextProvider}. * * @since 5.13 */ @API(status = INTERNAL, since = "5.13") @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface ClassTemplateInvocationLifecycleMethod { /** * The corresponding {@link org.junit.jupiter.api.ClassTemplate}-derived * annotation class. */ Class classTemplateAnnotation(); /** * The actual lifecycle method annotation class. */ Class lifecycleMethodAnnotation(); } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.STABLE; import java.util.Optional; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.StringUtils; import org.junit.platform.commons.util.ToStringBuilder; /** * The result of evaluating an {@link ExecutionCondition}. * * @since 5.0 */ @API(status = STABLE, since = "5.0") public class ConditionEvaluationResult { /** * Factory for creating enabled results. * * @param reason the reason why the container or test should be enabled; may * be {@code null} or blank if the reason is unknown * @return an enabled {@code ConditionEvaluationResult} with the given reason * or an empty reason if the reason is unknown * @see StringUtils#isBlank(String) */ public static ConditionEvaluationResult enabled(@Nullable String reason) { return new ConditionEvaluationResult(true, reason); } /** * Factory for creating disabled results. * * @param reason the reason why the container or test should be disabled; may * be {@code null} or blank if the reason is unknown * @return a disabled {@code ConditionEvaluationResult} with the given reason * or an empty reason if the reason is unknown * @see StringUtils#isBlank(String) */ public static ConditionEvaluationResult disabled(@Nullable String reason) { return new ConditionEvaluationResult(false, reason); } /** * Factory for creating disabled results with custom reasons * added by the user. * *

If non-blank default and custom reasons are provided, they will be * concatenated using the format: "reason ==> customReason". * * @param reason the default reason why the container or test should be disabled; * may be {@code null} or blank if the default reason is unknown * @param customReason the custom reason why the container or test should be * disabled; may be {@code null} or blank if the custom reason is unknown * @return a disabled {@code ConditionEvaluationResult} with the given reason(s) * or an empty reason if the reasons are unknown * @since 5.7 * @see StringUtils#isBlank(String) */ @API(status = STABLE, since = "5.7") public static ConditionEvaluationResult disabled(@Nullable String reason, @Nullable String customReason) { if (StringUtils.isBlank(reason)) { return disabled(customReason); } if (StringUtils.isBlank(customReason)) { return disabled(reason); } return disabled("%s ==> %s".formatted(reason.strip(), customReason.strip())); } private final boolean enabled; private final Optional reason; private ConditionEvaluationResult(boolean enabled, @Nullable String reason) { this.enabled = enabled; this.reason = StringUtils.isNotBlank(reason) ? Optional.of(reason.strip()) : Optional.empty(); } /** * Whether the container or test should be disabled. * * @return {@code true} if the container or test should be disabled */ public boolean isDisabled() { return !this.enabled; } /** * Get the reason why the container or test should be enabled or disabled, * if available. */ public Optional getReason() { return this.reason; } @Override public String toString() { // @formatter:off return new ToStringBuilder(this) .append("enabled", this.enabled) .append("reason", this.reason.orElse("")) .toString(); // @formatter:on } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/DynamicTestInvocationContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; import org.junit.jupiter.api.function.Executable; /** * {@code DynamicTestInvocationContext} represents the context of a * single invocation of a {@linkplain org.junit.jupiter.api.DynamicTest * dynamic test}. * * @since 5.8 * @see org.junit.jupiter.api.DynamicTest */ @API(status = STABLE, since = "5.11") public interface DynamicTestInvocationContext { /** * Get the {@code Executable} of this dynamic test invocation context. * * @return the executable of the dynamic test invocation, never {@code null} */ Executable getExecutable(); } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExecutableInvoker.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.STABLE; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; /** * {@code ExecutableInvoker} allows invoking methods and constructors * with support for dynamic resolution of parameters via * {@link ParameterResolver ParameterResolvers}. * * @since 5.9 */ @API(status = STABLE, since = "5.11") public interface ExecutableInvoker { /** * Invoke the supplied {@code static} method with dynamic parameter resolution. * * @param method the method to invoke and resolve parameters for * @see #invoke(Method, Object) */ default @Nullable Object invoke(Method method) { return invoke(method, null); } /** * Invoke the supplied method with dynamic parameter resolution. * * @param method the method to invoke and resolve parameters for * @param target the target on which the executable will be invoked; * can be {@code null} for {@code static} methods */ @Nullable Object invoke(Method method, @Nullable Object target); /** * Invoke the supplied top-level constructor with dynamic parameter resolution. * * @param constructor the constructor to invoke and resolve parameters for * @see #invoke(Constructor, Object) */ default T invoke(Constructor constructor) { return invoke(constructor, null); } /** * Invoke the supplied constructor with the supplied outer instance and * dynamic parameter resolution. * *

Use this method when invoking the constructor for an inner class. * * @param constructor the constructor to invoke and resolve parameters for * @param outerInstance the outer instance to supply as the first argument * to the constructor; must be {@code null} for top-level classes * or {@code static} nested classes */ T invoke(Constructor constructor, @Nullable Object outerInstance); } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExecutionCondition.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; /** * {@code ExecutionCondition} defines the {@link Extension} API for * programmatic, conditional test execution. * *

An {@code ExecutionCondition} is * {@linkplain #evaluateExecutionCondition(ExtensionContext) evaluated} * to determine if a given container or test should be executed based on the * supplied {@link ExtensionContext}. * *

If an {@code ExecutionCondition} {@linkplain ConditionEvaluationResult#disabled * disables} a test method, that prevents execution of the test method and * method-level lifecycle callbacks such as {@code @BeforeEach} methods, * {@code @AfterEach} methods, and corresponding extension APIs. However, that * does not prevent the test class from being instantiated, and it does not prevent * the execution of class-level lifecycle callbacks such as {@code @BeforeAll} * methods, {@code @AfterAll} methods, and corresponding extension APIs. * *

Constructor Requirements

* *

Consult the documentation in {@link Extension} for details on * constructor requirements. * * @since 5.0 * @see org.junit.jupiter.api.Disabled * @see org.junit.jupiter.api.condition.EnabledIf * @see org.junit.jupiter.api.condition.DisabledIf * @see org.junit.jupiter.api.condition.EnabledOnOs * @see org.junit.jupiter.api.condition.DisabledOnOs * @see org.junit.jupiter.api.condition.EnabledOnJre * @see org.junit.jupiter.api.condition.DisabledOnJre * @see org.junit.jupiter.api.condition.EnabledForJreRange * @see org.junit.jupiter.api.condition.DisabledForJreRange * @see org.junit.jupiter.api.condition.EnabledInNativeImage * @see org.junit.jupiter.api.condition.DisabledInNativeImage * @see org.junit.jupiter.api.condition.EnabledIfSystemProperty * @see org.junit.jupiter.api.condition.DisabledIfSystemProperty * @see org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable * @see org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable */ @FunctionalInterface @API(status = STABLE, since = "5.0") public interface ExecutionCondition extends Extension { /** * Evaluate this condition for the supplied {@link ExtensionContext}. * *

An {@linkplain ConditionEvaluationResult#enabled enabled} result * indicates that the container or test should be executed; whereas, a * {@linkplain ConditionEvaluationResult#disabled disabled} result * indicates that the container or test should not be executed. * * @param context the current extension context; never {@code null} * @return the result of evaluating this condition; never {@code null} */ ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context); } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtendWith.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @ExtendWith} is a {@linkplain Repeatable repeatable} annotation that * is used to register {@linkplain Extension extensions} for the annotated test * class, test interface, test method, parameter, or field. * *

Annotated parameters are supported in test class constructors, in test * methods, and in {@code @BeforeAll}, {@code @AfterAll}, {@code @BeforeEach}, * and {@code @AfterEach} lifecycle methods. * *

{@code @ExtendWith} fields may be either {@code static} or non-static. * *

Inheritance

* *

{@code @ExtendWith} fields are inherited from superclasses. Furthermore, * {@code @ExtendWith} fields from superclasses will be registered before * {@code @ExtendWith} fields in subclasses unless {@code @Order} is used to * alter that behavior (see below). * *

Registration Order

* *

When {@code @ExtendWith} is present on a test class, test interface, or * test method or on a parameter in a test method or lifecycle method, the * corresponding extensions will be registered in the order in which the * {@code @ExtendWith} annotations are discovered. For example, if a test class * is annotated with {@code @ExtendWith(A.class)} and then with * {@code @ExtendWith(B.class)}, extension {@code A} will be registered before * extension {@code B}. * *

By default, if multiple extensions are registered on fields via * {@code @ExtendWith}, they will be ordered using an algorithm that is * deterministic but intentionally nonobvious. This ensures that subsequent runs * of a test suite execute extensions in the same order, thereby allowing for * repeatable builds. However, there are times when extensions need to be * registered in an explicit order. To achieve that, you can annotate * {@code @ExtendWith} fields with {@link org.junit.jupiter.api.Order @Order}. * Any {@code @ExtendWith} field not annotated with {@code @Order} will be * ordered using the {@link org.junit.jupiter.api.Order#DEFAULT default} order * value. Note that {@code @RegisterExtension} fields can also be ordered with * {@code @Order}, relative to {@code @ExtendWith} fields and other * {@code @RegisterExtension} fields. * *

Supported Extension APIs

* *
    *
  • {@link ExecutionCondition}
  • *
  • {@link InvocationInterceptor}
  • *
  • {@link BeforeAllCallback}
  • *
  • {@link AfterAllCallback}
  • *
  • {@link BeforeEachCallback}
  • *
  • {@link AfterEachCallback}
  • *
  • {@link BeforeTestExecutionCallback}
  • *
  • {@link AfterTestExecutionCallback}
  • *
  • {@link TestInstanceFactory}
  • *
  • {@link TestInstancePostProcessor}
  • *
  • {@link TestInstancePreConstructCallback}
  • *
  • {@link TestInstancePreDestroyCallback}
  • *
  • {@link ParameterResolver}
  • *
  • {@link LifecycleMethodExecutionExceptionHandler}
  • *
  • {@link TestExecutionExceptionHandler}
  • *
  • {@link TestTemplateInvocationContextProvider}
  • *
  • {@link TestWatcher}
  • *
* * @since 5.0 * @see RegisterExtension * @see Extension */ @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Repeatable(Extensions.class) @API(status = STABLE, since = "5.0") public @interface ExtendWith { /** * An array of one or more {@link Extension} classes to register. */ Class[] value(); } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/Extension.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; /** * Marker interface for all extensions. * *

An {@code Extension} can be registered declaratively via * {@link ExtendWith @ExtendWith}, programmatically via * {@link RegisterExtension @RegisterExtension}, or automatically via * the {@link java.util.ServiceLoader} mechanism. For details on the latter, * consult the User Guide. * *

Constructor Requirements

* *

Extension implementations must have a default constructor if * registered via {@code @ExtendWith} or the {@code ServiceLoader}. When * registered via {@code @ExtendWith} the default constructor is not required * to be {@code public}. When registered via the {@code ServiceLoader} the * default constructor must be {@code public}. When registered via * {@code @RegisterExtension} the extension's constructors typically must be * {@code public} unless the extension provides {@code static} factory methods * or a builder API as an alternative to constructors. * * @since 5.0 */ @API(status = STABLE, since = "5.0") public interface Extension { } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionConfigurationException.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.STABLE; import java.io.Serial; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; /** * Thrown if an error is encountered regarding the configuration of an * extension. * * @since 5.0 */ @API(status = STABLE, since = "5.0") public class ExtensionConfigurationException extends JUnitException { @Serial private static final long serialVersionUID = 1L; public ExtensionConfigurationException(String message) { super(message); } public ExtensionConfigurationException(String message, @Nullable Throwable cause) { super(message, cause); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Function; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.MediaType; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.function.ThrowingConsumer; import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.Preconditions; /** * {@code ExtensionContext} encapsulates the context in which the * current test or container is being executed. * *

{@link Extension Extensions} are provided an instance of * {@code ExtensionContext} to perform their work. * *

This interface is not intended to be implemented by clients. * * @since 5.0 * @see Store * @see Namespace */ @API(status = STABLE, since = "5.0") public interface ExtensionContext { /** * Get the parent extension context, if available. * * @return an {@code Optional} containing the parent; never {@code null} but * potentially empty * @see #getRoot() */ Optional getParent(); /** * Get the root {@code ExtensionContext}. * * @return the root extension context; never {@code null} but potentially * this {@code ExtensionContext} * @see #getParent() */ ExtensionContext getRoot(); /** * Get the unique ID of the current test or container. * * @return the unique ID of the test or container; never {@code null} or blank */ String getUniqueId(); /** * Get the display name for the current test or container. * *

The display name is either a default name or a custom name configured * via {@link org.junit.jupiter.api.DisplayName @DisplayName}. * *

For details on default display names consult the Javadoc for * {@link org.junit.jupiter.api.TestInfo#getDisplayName()}. * *

Note that display names are typically used for test reporting in IDEs * and build tools and may contain spaces, special characters, and even emoji. * * @return the display name of the test or container; never {@code null} or blank */ String getDisplayName(); /** * Get the set of all tags for the current test or container. * *

Tags may be declared directly on the test element or inherited * from an outer context. * * @return the set of tags for the test or container; never {@code null} but * potentially empty */ Set getTags(); /** * Get the {@link AnnotatedElement} corresponding to the current extension * context, if available. * *

For example, if the current extension context encapsulates a test * class, test method, test factory method, or test template method, the * annotated element will be the corresponding {@link Class} or {@link Method} * reference. * *

Favor this method over more specific methods whenever the * {@code AnnotatedElement} API suits the task at hand — for example, * when looking up annotations regardless of concrete element type. * * @return an {@code Optional} containing the {@code AnnotatedElement}; * never {@code null} but potentially empty * @see #getTestClass() * @see #getTestMethod() */ Optional getElement(); /** * Get the {@link Class} associated with the current test or container, * if available. * * @return an {@code Optional} containing the class; never {@code null} but * potentially empty * @see #getRequiredTestClass() */ Optional> getTestClass(); /** * Get the required {@link Class} associated with the current test * or container. * *

Use this method as an alternative to {@link #getTestClass()} for use * cases in which the test class is required to be present. * * @return the test class; never {@code null} * @throws PreconditionViolationException if the test class is not present * in this {@code ExtensionContext} */ default Class getRequiredTestClass() { return Preconditions.notNull(getTestClass().orElse(null), "Illegal state: required test class is not present in the current ExtensionContext"); } /** * Get the enclosing test classes of the current test or container. * *

This method is useful to look up annotations on nested test classes * and their enclosing runtime types: * *

{@code
	 * AnnotationSupport.findAnnotation(
	 *     extensionContext.getRequiredTestClass(),
	 *     MyAnnotation.class,
	 *     extensionContext.getEnclosingTestClasses()
	 * );
	 * }
* * @return an empty list if there is no class associated with the current * test or container or when it is not nested; otherwise, a list containing * the enclosing test classes in order from outermost to innermost; never * {@code null} * * @since 5.12.1 * @see org.junit.platform.commons.support.AnnotationSupport#findAnnotation(Class, Class, List) */ @API(status = MAINTAINED, since = "5.13.3") List> getEnclosingTestClasses(); /** * Get the {@link Lifecycle} of the {@linkplain #getTestInstance() test * instance} associated with the current test or container, if available. * * @return an {@code Optional} containing the test instance {@code Lifecycle}; * never {@code null} but potentially empty * @since 5.1 * @see org.junit.jupiter.api.TestInstance {@code @TestInstance} */ @API(status = STABLE, since = "5.1") Optional getTestInstanceLifecycle(); /** * Get the test instance associated with the current test or container, * if available. * * @return an {@code Optional} containing the test instance; never * {@code null} but potentially empty * @see #getRequiredTestInstance() * @see #getTestInstances() */ Optional getTestInstance(); /** * Get the required test instance associated with the current test * or container. * *

Use this method as an alternative to {@link #getTestInstance()} for use * cases in which the test instance is required to be present. * * @return the test instance; never {@code null} * @throws PreconditionViolationException if the test instance is not present * in this {@code ExtensionContext} * * @see #getRequiredTestInstances() */ default Object getRequiredTestInstance() { return Preconditions.notNull(getTestInstance().orElse(null), "Illegal state: required test instance is not present in the current ExtensionContext"); } /** * Get the test instances associated with the current test or container, * if available. * *

While top-level tests only have a single test instance, nested tests * have one additional instance for each enclosing test class. * * @return an {@code Optional} containing the test instances; never * {@code null} but potentially empty * @since 5.4 * @see #getRequiredTestInstances() */ @API(status = STABLE, since = "5.7") Optional getTestInstances(); /** * Get the required test instances associated with the current test * or container. * *

Use this method as an alternative to {@link #getTestInstances()} for use * cases in which the test instances are required to be present. * * @return the test instances; never {@code null} * @throws PreconditionViolationException if the test instances are not present * in this {@code ExtensionContext} * @since 5.4 */ @API(status = STABLE, since = "5.7") default TestInstances getRequiredTestInstances() { return Preconditions.notNull(getTestInstances().orElse(null), "Illegal state: required test instances are not present in the current ExtensionContext"); } /** * Get the {@link Method} associated with the current test, if available. * * @return an {@code Optional} containing the method; never {@code null} but * potentially empty * @see #getRequiredTestMethod() */ Optional getTestMethod(); /** * Get the required {@link Method} associated with the current test * or container. * *

Use this method as an alternative to {@link #getTestMethod()} for use * cases in which the test method is required to be present. * * @return the test method; never {@code null} * @throws PreconditionViolationException if the test method is not present * in this {@code ExtensionContext} */ default Method getRequiredTestMethod() { return Preconditions.notNull(getTestMethod().orElse(null), "Illegal state: required test method is not present in the current ExtensionContext"); } /** * Get the exception that was thrown during execution of the test or container * associated with this {@code ExtensionContext}, if available. * *

This method is typically used for logging and tracing purposes. If you * wish to actually handle an exception thrown during test execution, * implement the {@link TestExecutionExceptionHandler} API. * *

Unlike the exception passed to a {@code TestExecutionExceptionHandler}, * an execution exception returned by this method can be any * exception thrown during the invocation of a {@code @Test} method, its * surrounding {@code @BeforeEach} and {@code @AfterEach} methods, or a * test-level {@link Extension}. Similarly, if this {@code ExtensionContext} * represents a test class, the execution exception returned by * this method can be any exception thrown in a {@code @BeforeAll} or * {@code AfterAll} method or a class-level {@link Extension}. * *

Note, however, that this method will never return an exception * swallowed by a {@code TestExecutionExceptionHandler}. Furthermore, if * multiple exceptions have been thrown during test execution, the exception * returned by this method will be the first such exception with all * additional exceptions {@linkplain Throwable#addSuppressed(Throwable) * suppressed} in the first one. * * @return an {@code Optional} containing the exception thrown; never * {@code null} but potentially empty if test execution has not (yet) * resulted in an exception */ Optional getExecutionException(); /** * Get the configuration parameter stored under the specified {@code key}. * *

If no such key is present in the {@code ConfigurationParameters} for * the JUnit Platform, an attempt will be made to look up the value as a * JVM system property. If no such system property exists, an attempt will * be made to look up the value in the JUnit Platform properties file. * * @param key the key to look up; never {@code null} or blank * @return an {@code Optional} containing the value; never {@code null} * but potentially empty * * @since 5.1 * @see System#getProperty(String) * @see org.junit.platform.engine.ConfigurationParameters */ @API(status = STABLE, since = "5.1") Optional getConfigurationParameter(String key); /** * Get and transform the configuration parameter stored under the specified * {@code key} using the specified {@code transformer}. * *

If no such key is present in the {@code ConfigurationParameters} for * the JUnit Platform, an attempt will be made to look up the value as a * JVM system property. If no such system property exists, an attempt will * be made to look up the value in the JUnit Platform properties file. * *

In case the transformer throws an exception, it will be wrapped in a * {@link org.junit.platform.commons.JUnitException} with a helpful message. * * @param key the key to look up; never {@code null} or blank * @param transformer the transformer to apply in case a value is found; * never {@code null} * @return an {@code Optional} containing the value; never {@code null} * but potentially empty * * @since 5.7 * @see System#getProperty(String) * @see org.junit.platform.engine.ConfigurationParameters */ @API(status = STABLE, since = "5.10") Optional getConfigurationParameter(String key, Function transformer); /** * Publish a map of key-value pairs to be consumed by an * {@code org.junit.platform.engine.EngineExecutionListener} in order to * supply additional information to the reporting infrastructure. * * @param map the key-value pairs to be published; never {@code null}; * keys and values within entries in the map also must not be * {@code null} or blank * @see #publishReportEntry(String, String) * @see #publishReportEntry(String) * @see org.junit.platform.engine.EngineExecutionListener#reportingEntryPublished */ void publishReportEntry(Map map); /** * Publish the specified key-value pair to be consumed by an * {@code org.junit.platform.engine.EngineExecutionListener} in order to * supply additional information to the reporting infrastructure. * * @param key the key of the published pair; never {@code null} or blank * @param value the value of the published pair; never {@code null} or blank * @see #publishReportEntry(Map) * @see #publishReportEntry(String) * @see org.junit.platform.engine.EngineExecutionListener#reportingEntryPublished */ default void publishReportEntry(String key, String value) { Preconditions.notBlank(key, "key must not be null or blank"); Preconditions.notBlank(value, "value must not be null or blank"); publishReportEntry(Map.of(key, value)); } /** * Publish the specified value to be consumed by an * {@code org.junit.platform.engine.EngineExecutionListener} in order to * supply additional information to the reporting infrastructure. * *

This method delegates to {@link #publishReportEntry(String, String)}, * supplying {@code "value"} as the key and the supplied {@code value} * argument as the value. * * @param value the value to be published; never {@code null} or blank * @since 5.3 * @see #publishReportEntry(Map) * @see #publishReportEntry(String, String) * @see org.junit.platform.engine.EngineExecutionListener#reportingEntryPublished */ @API(status = STABLE, since = "5.3") default void publishReportEntry(String value) { publishReportEntry("value", value); } /** * Publish a file with the supplied name written by the supplied action and * attach it to the current test or container. * *

The file will be resolved in the report output directory prior to * invoking the supplied action. * * @param name the name of the file to be published; never {@code null} or * blank and must not contain any path separators * @param mediaType the media type of the file; never {@code null}; use * {@link org.junit.jupiter.api.extension.MediaType#APPLICATION_OCTET_STREAM} * if unknown * @param action the action to be executed to write the file; never {@code null} * @since 5.12 * @see org.junit.platform.engine.EngineExecutionListener#fileEntryPublished * @deprecated Use * {@link #publishFile(String, MediaType, ThrowingConsumer)} * instead. */ @Deprecated(since = "5.14", forRemoval = true) @API(status = DEPRECATED, since = "5.14") @SuppressWarnings("removal") default void publishFile(String name, org.junit.jupiter.api.extension.MediaType mediaType, ThrowingConsumer action) { Preconditions.notNull(mediaType, "mediaType must not be null"); publishFile(name, MediaType.parse(mediaType.toString()), action); } /** * Publish a file with the supplied name written by the supplied action and * attach it to the current test or container. * *

The file will be resolved in the report output directory prior to * invoking the supplied action. * * @param name the name of the file to be attached; never {@code null} or * blank and must not contain any path separators * @param mediaType the media type of the file; never {@code null}; use * {@link MediaType#APPLICATION_OCTET_STREAM} if unknown * @param action the action to be executed to write the file; never {@code null} * @since 5.14 * @see org.junit.platform.engine.EngineExecutionListener#fileEntryPublished */ @API(status = MAINTAINED, since = "5.14") void publishFile(String name, MediaType mediaType, ThrowingConsumer action); /** * Publish a directory with the supplied name written by the supplied action * and attach it to the current test or container. * *

The directory will be resolved and created in the report output directory * prior to invoking the supplied action, if it does not already exist. * * @param name the name of the directory to be published; never {@code null} * or blank and must not contain any path separators * @param action the action to be executed to write to the directory; never * {@code null} * @since 5.12 * @see org.junit.platform.engine.EngineExecutionListener#fileEntryPublished */ @API(status = MAINTAINED, since = "5.13.3") void publishDirectory(String name, ThrowingConsumer action); /** * Get the {@link Store} for the supplied {@link Namespace}. * *

Use {@code getStore(Namespace.GLOBAL)} to get the default, global {@link Namespace}. * *

A store is bound to its extension context lifecycle. When an extension * context lifecycle ends it closes its associated store. All stored values * that are instances of {@link ExtensionContext.Store.CloseableResource} are * notified by invoking their {@code close()} methods. * * @param namespace the {@code Namespace} to get the store for; never {@code null} * @return the store in which to put and get objects for other invocations * working in the same namespace; never {@code null} * @see Namespace#GLOBAL * @see #getStore(StoreScope, Namespace) */ Store getStore(Namespace namespace); /** * Returns the store for supplied scope and namespace. * *

If {@code scope} is * {@link StoreScope#EXTENSION_CONTEXT EXTENSION_CONTEXT}, the store behaves * exactly like the one returned by {@link #getStore(Namespace)}. If the * {@code scope} is {@link StoreScope#LAUNCHER_SESSION LAUNCHER_SESSION} or * {@link StoreScope#EXECUTION_REQUEST EXECUTION_REQUEST}, all stored values * that are instances of {@link AutoCloseable} are notified by invoking * their {@code close()} methods when the scope is closed. * * @since 5.13 * @see StoreScope * @see #getStore(Namespace) */ @API(status = EXPERIMENTAL, since = "6.0") Store getStore(StoreScope scope, Namespace namespace); /** * Get the {@link ExecutionMode} associated with the current test or container. * * @return the {@code ExecutionMode} of the test; never {@code null} * * @since 5.8.1 * @see org.junit.jupiter.api.parallel.ExecutionMode {@code @ExecutionMode} */ @API(status = STABLE, since = "5.8.1") ExecutionMode getExecutionMode(); /** * Get an {@link ExecutableInvoker} to invoke methods and constructors * with support for dynamic resolution of parameters. * * @since 5.9 */ @API(status = STABLE, since = "5.11") ExecutableInvoker getExecutableInvoker(); /** * {@code Store} provides methods for extensions to save and retrieve data. */ interface Store { /** * Classes implementing this interface indicate that they want to {@link #close} * some underlying resource or resources when the enclosing {@link Store Store} * is closed. * *

Note that the {@code CloseableResource} API is only honored for * objects stored within an extension context {@link Store Store}. * *

The resources stored in a {@link Store Store} are closed in the * inverse order they were added in. * * @since 5.1 * @deprecated Please extend {@code AutoCloseable} directly. */ @Deprecated(since = "5.13") @API(status = DEPRECATED, since = "5.13") interface CloseableResource { /** * Close underlying resources. * * @throws Throwable any throwable will be caught and rethrown */ void close() throws Throwable; } /** * Get the value that is stored under the supplied {@code key}. * *

If no value is stored in the current {@link ExtensionContext} * for the supplied {@code key}, ancestors of the context will be queried * for a value with the same {@code key} in the {@code Namespace} used * to create this store. * *

For greater type safety, consider using {@link #get(Object, Class)} * instead. * * @param key the key; never {@code null} * @return the value; potentially {@code null} * @see #get(Object, Class) * @see #getOrDefault(Object, Class, Object) */ @Nullable Object get(Object key); /** * Get the value of the specified required type that is stored under * the supplied {@code key}. * *

If no value is stored in the current {@link ExtensionContext} * for the supplied {@code key}, ancestors of the context will be queried * for a value with the same {@code key} in the {@code Namespace} used * to create this store. * * @param key the key; never {@code null} * @param requiredType the required type of the value; never {@code null} * @param the value type * @return the value; potentially {@code null} * @see #get(Object) * @see #getOrDefault(Object, Class, Object) */ @Nullable V get(Object key, Class requiredType); /** * Get the value of the specified required type that is stored under * the supplied {@code key}, or the supplied {@code defaultValue} if no * value is found for the supplied {@code key} in this store or in an * ancestor. * *

If no value is stored in the current {@link ExtensionContext} * for the supplied {@code key}, ancestors of the context will be queried * for a value with the same {@code key} in the {@code Namespace} used * to create this store. * * @param key the key; never {@code null} * @param requiredType the required type of the value; never {@code null} * @param defaultValue the default value; never {@code null} * @param the value type * @return the value; never {@code null} * @since 5.5 * @see #get(Object, Class) */ @API(status = STABLE, since = "5.5") default V getOrDefault(Object key, Class requiredType, V defaultValue) { V value = get(key, requiredType); return (value != null ? value : defaultValue); } /** * Get the object of type {@code type} that is present in this * {@code Store} (keyed by {@code type}); and otherwise invoke * the default constructor for {@code type} to generate the object, * store it, and return it. * *

This method is a shortcut for the following, where {@code X} is * the type of object we wish to retrieve from the store. * *

		 * X x = store.computeIfAbsent(X.class, key -> new X(), X.class);
		 * // Equivalent to:
		 * // X x = store.computeIfAbsent(X.class);
		 * 
* *

See {@link #computeIfAbsent(Object, Function, Class)} for further * details. * *

If {@code type} implements {@link CloseableResource} or * {@link AutoCloseable} (unless the * {@code junit.jupiter.extensions.store.close.autocloseable.enabled} * configuration parameter is set to {@code false}), then the {@code close()} * method will be invoked on the stored object when the store is closed. * * @param type the type of object to retrieve; never {@code null} * @param the key and value type * @return the object; never {@code null} * @since 5.1 * @see #computeIfAbsent(Class) * @see #computeIfAbsent(Object, Function) * @see #computeIfAbsent(Object, Function, Class) * @see CloseableResource * @see AutoCloseable * @deprecated Please use {@link #computeIfAbsent(Class)} instead. */ @Deprecated(since = "6.0") @API(status = DEPRECATED, since = "6.0") default V getOrComputeIfAbsent(Class type) { return computeIfAbsent(type); } /** * Return the object of type {@code type} if it is present and not * {@code null} in this {@code Store} (keyed by {@code type}); * otherwise, invoke the default constructor for {@code type} to * generate the object, store it, and return it. * *

This method is a shortcut for the following, where {@code X} is * the type of object we wish to retrieve from the store. * *

		 * X x = store.computeIfAbsent(X.class, key -> new X(), X.class);
		 * // Equivalent to:
		 * // X x = store.computeIfAbsent(X.class);
		 * 
* *

See {@link #computeIfAbsent(Object, Function, Class)} for further * details. * *

If {@code type} implements {@link CloseableResource} or * {@link AutoCloseable} (unless the * {@code junit.jupiter.extensions.store.close.autocloseable.enabled} * configuration parameter is set to {@code false}), then the {@code close()} * method will be invoked on the stored object when the store is closed. * * @param type the type of object to retrieve; never {@code null} * @param the key and value type * @return the object; never {@code null} * @since 6.0 * @see #computeIfAbsent(Object, Function) * @see #computeIfAbsent(Object, Function, Class) * @see CloseableResource * @see AutoCloseable */ @API(status = MAINTAINED, since = "6.0") default V computeIfAbsent(Class type) { return computeIfAbsent(type, ReflectionSupport::newInstance, type); } /** * Get the value that is stored under the supplied {@code key}. * *

If no value is stored in the current {@link ExtensionContext} * for the supplied {@code key}, ancestors of the context will be queried * for a value with the same {@code key} in the {@code Namespace} used * to create this store. If no value is found for the supplied {@code key}, * a new value will be computed by the {@code defaultCreator} (given * the {@code key} as input), stored, and returned. * *

For greater type safety, consider using * {@link #computeIfAbsent(Object, Function, Class)} instead. * *

If the created value is an instance of {@link CloseableResource} or * {@link AutoCloseable} (unless the * {@code junit.jupiter.extensions.store.close.autocloseable.enabled} * configuration parameter is set to {@code false}), then the {@code close()} * method will be invoked on the stored object when the store is closed. * * @param key the key; never {@code null} * @param defaultCreator the function called with the supplied {@code key} * to create a new value; never {@code null} but may return {@code null} * @param the key type * @param the value type * @return the value; potentially {@code null} * @see #computeIfAbsent(Class) * @see #computeIfAbsent(Object, Function) * @see #computeIfAbsent(Object, Function, Class) * @see CloseableResource * @see AutoCloseable * @deprecated Please use {@link #computeIfAbsent(Object, Function)} instead. */ @Deprecated(since = "6.0") @API(status = DEPRECATED, since = "6.0") @Nullable Object getOrComputeIfAbsent(K key, Function defaultCreator); /** * Return the value of the specified required type that is stored under * the supplied {@code key}. * *

If no value is stored in the current {@link ExtensionContext} * for the supplied {@code key}, ancestors of the context will be queried * for a value with the same {@code key} in the {@code Namespace} used * to create this store. If no value is found for the supplied {@code key} * or the value is {@code null}, a new value will be computed by the * {@code defaultCreator} (given the {@code key} as input), stored, and * returned. * *

For greater type safety, consider using * {@link #computeIfAbsent(Object, Function, Class)} instead. * *

If the created value is an instance of {@link CloseableResource} or * {@link AutoCloseable} (unless the * {@code junit.jupiter.extensions.store.close.autocloseable.enabled} * configuration parameter is set to {@code false}), then the {@code close()} * method will be invoked on the stored object when the store is closed. * * @param key the key; never {@code null} * @param defaultCreator the function called with the supplied {@code key} * to create a new value; never {@code null} and must not return * {@code null} * @param the key type * @param the value type * @return the value; never {@code null} * @since 6.0 * @see #computeIfAbsent(Class) * @see #computeIfAbsent(Object, Function, Class) * @see CloseableResource * @see AutoCloseable */ @API(status = MAINTAINED, since = "6.0") Object computeIfAbsent(K key, Function defaultCreator); /** * Get the value of the specified required type that is stored under the * supplied {@code key}. * *

If no value is stored in the current {@link ExtensionContext} * for the supplied {@code key}, ancestors of the context will be queried * for a value with the same {@code key} in the {@code Namespace} used * to create this store. If no value is found for the supplied {@code key}, * a new value will be computed by the {@code defaultCreator} (given * the {@code key} as input), stored, and returned. * *

If the created value implements {@link CloseableResource} or * {@link AutoCloseable} (unless the * {@code junit.jupiter.extensions.store.close.autocloseable.enabled} * configuration parameter is set to {@code false}), then the {@code close()} * method will be invoked on the stored object when the store is closed. * * @param key the key; never {@code null} * @param defaultCreator the function called with the supplied {@code key} * to create a new value; never {@code null} but may return {@code null} * @param requiredType the required type of the value; never {@code null} * @param the key type * @param the value type * @return the value; potentially {@code null} * @see #computeIfAbsent(Class) * @see #computeIfAbsent(Object, Function) * @see #computeIfAbsent(Object, Function, Class) * @see CloseableResource * @see AutoCloseable * @deprecated Please use {@link #computeIfAbsent(Object, Function, Class)} instead. */ @Deprecated(since = "6.0") @API(status = DEPRECATED, since = "6.0") @Nullable V getOrComputeIfAbsent(K key, Function defaultCreator, Class requiredType); /** * Get the value of the specified required type that is stored under the * supplied {@code key}. * *

If no value is stored in the current {@link ExtensionContext} * for the supplied {@code key}, ancestors of the context will be queried * for a value with the same {@code key} in the {@code Namespace} used * to create this store. If no value is found for the supplied {@code key} * or the value is {@code null}, a new value will be computed by the * {@code defaultCreator} (given the {@code key} as input), stored, and * returned. * *

If the created value is an instance of {@link CloseableResource} or * {@link AutoCloseable} (unless the * {@code junit.jupiter.extensions.store.close.autocloseable.enabled} * configuration parameter is set to {@code false}), then the {@code close()} * method will be invoked on the stored object when the store is closed. * * @param key the key; never {@code null} * @param defaultCreator the function called with the supplied {@code key} * to create a new value; never {@code null} and must not return * {@code null} * @param requiredType the required type of the value; never {@code null} * @param the key type * @param the value type * @return the value; never {@code null} * @since 6.0 * @see #computeIfAbsent(Class) * @see #computeIfAbsent(Object, Function) * @see CloseableResource * @see AutoCloseable */ @API(status = MAINTAINED, since = "6.0") V computeIfAbsent(K key, Function defaultCreator, Class requiredType); /** * Store a {@code value} for later retrieval under the supplied {@code key}. * *

A stored {@code value} is visible in child {@link ExtensionContext * ExtensionContexts} for the store's {@code Namespace} unless they * overwrite it. * *

If the {@code value} is an instance of {@link CloseableResource} or * {@link AutoCloseable} (unless the * {@code junit.jupiter.extensions.store.close.autocloseable.enabled} * configuration parameter is set to {@code false}), then the {@code close()} * method will be invoked on the stored object when the store is closed. * * @param key the key under which the value should be stored; never * {@code null} * @param value the value to store; may be {@code null} * @see CloseableResource * @see AutoCloseable */ void put(Object key, @Nullable Object value); /** * Remove the value that was previously stored under the supplied {@code key}. * *

The value will only be removed in the current {@link ExtensionContext}, * not in ancestors. In addition, the {@link CloseableResource} and {@link AutoCloseable} * API will not be honored for values that are manually removed via this method. * *

For greater type safety, consider using {@link #remove(Object, Class)} * instead. * * @param key the key; never {@code null} * @return the previous value or {@code null} if no value was present * for the specified key * @see #remove(Object, Class) */ @Nullable Object remove(Object key); /** * Remove the value of the specified required type that was previously stored * under the supplied {@code key}. * *

The value will only be removed in the current {@link ExtensionContext}, * not in ancestors. In addition, the {@link CloseableResource} and {@link AutoCloseable} * API will not be honored for values that are manually removed via this method. * * @param key the key; never {@code null} * @param requiredType the required type of the value; never {@code null} * @param the value type * @return the previous value or {@code null} if no value was present * for the specified key * @see #remove(Object) */ @Nullable V remove(Object key, Class requiredType); } /** * A {@code Namespace} is used to provide a scope for data saved by * extensions within a {@link Store}. * *

Storing data in custom namespaces allows extensions to avoid accidentally * mixing data between extensions or across different invocations within the * lifecycle of a single extension. */ final class Namespace { /** * The default, global namespace which allows access to stored data from * all extensions. */ public static final Namespace GLOBAL = Namespace.create(new Object()); /** * Create a namespace which restricts access to data to all extensions * which use the same sequence of {@code parts} for creating a namespace. * *

The order of the {@code parts} is significant. * *

Internally the {@code parts} are compared using {@link Object#equals(Object)}. */ public static Namespace create(Object... parts) { Preconditions.notEmpty(parts, "parts array must not be null or empty"); Preconditions.containsNoNullElements(parts, "individual parts must not be null"); return new Namespace(List.of(parts)); } private final List parts; private Namespace(List parts) { this.parts = List.copyOf(parts); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Namespace that = (Namespace) o; return this.parts.equals(that.parts); } @Override public int hashCode() { return this.parts.hashCode(); } /** * Create a new namespace by appending the supplied {@code parts} to the * existing sequence of parts in this namespace. * * @return new namespace; never {@code null} * @since 5.8 */ @API(status = STABLE, since = "5.10") public Namespace append(Object... parts) { Preconditions.notEmpty(parts, "parts array must not be null or empty"); Preconditions.containsNoNullElements(parts, "individual parts must not be null"); ArrayList newParts = new ArrayList<>(this.parts.size() + parts.length); newParts.addAll(this.parts); Collections.addAll(newParts, parts); return new Namespace(newParts); } @API(status = INTERNAL, since = "5.13") public List getParts() { return parts; } } /** * {@code StoreScope} is an enumeration of the different scopes for * {@link Store} instances. * * @since 5.13 * @see #getStore(StoreScope, Namespace) */ @API(status = EXPERIMENTAL, since = "6.0") enum StoreScope { /** * The store is scoped to the current {@code LauncherSession}. * *

Any data that is stored in a {@code Store} with this scope will be * available throughout the entire launcher session. Therefore, it may * be used to inject values from registered * {@code LauncherSessionListener} implementations, to share data across * multiple executions of the Jupiter engine within the same session, or * even to share data across multiple engines. * * @see org.junit.platform.launcher.LauncherSession#getStore() * @see org.junit.platform.launcher.LauncherSessionListener */ LAUNCHER_SESSION, /** * The store is scoped to the current {@code ExecutionRequest} of the * JUnit Platform {@code Launcher}. * *

Any data that is stored in a {@code Store} with this scope will be * available for the duration of the current execution request. * Therefore, it may be used to share data across multiple engines. * * @see org.junit.platform.engine.ExecutionRequest#getStore() */ EXECUTION_REQUEST, /** * The store is scoped to the current {@code ExtensionContext}. * *

Any data that is stored in a {@code Store} with this scope will be * bound to the current extension context lifecycle. */ EXTENSION_CONTEXT } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContextException.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.io.Serial; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExtensionContext.Store; import org.junit.platform.commons.JUnitException; /** * Thrown if an error is encountered regarding the use of an * {@link ExtensionContext} or {@link Store}. * * @since 5.0 */ @API(status = STABLE, since = "5.0") public class ExtensionContextException extends JUnitException { @Serial private static final long serialVersionUID = 1L; @SuppressWarnings("unused") public ExtensionContextException(@Nullable String message) { super(message); } @API(status = MAINTAINED, since = "5.13.3") public ExtensionContextException(@Nullable String message, Throwable cause) { super(message, cause); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/Extensions.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @Extensions} is a container for one or more {@code @ExtendWith} * declarations. * *

Note, however, that use of the {@code @Extensions} container is completely * optional since {@code @ExtendWith} is a {@linkplain java.lang.annotation.Repeatable * repeatable} annotation. * * @since 5.0 * @see ExtendWith * @see java.lang.annotation.Repeatable */ @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @API(status = STABLE, since = "5.0") public @interface Extensions { /** * An array of one or more {@link ExtendWith @ExtendWith} declarations. */ ExtendWith[] value(); } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/InvocationInterceptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.STABLE; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; 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.Test; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestTemplate; /** * {@code InvocationInterceptor} defines the API for {@link Extension * Extensions} that wish to intercept calls to test code. * *

Invocation Contract

* *

Each method in this class must call {@link Invocation#proceed()} or {@link * Invocation#skip()} exactly once on the supplied invocation. Otherwise, the * enclosing test or container will be reported as failed. * *

The default implementation calls {@link Invocation#proceed() * proceed()} on the supplied {@linkplain Invocation invocation}. * *

Constructor Requirements

* *

Consult the documentation in {@link Extension} for details on * constructor requirements. * *

{@code ExtensionContext} Scope

* *

As of JUnit Jupiter 5.12, this API participates in the * {@link TestInstantiationAwareExtension} contract. Implementations of this API * may therefore choose to override * {@link TestInstantiationAwareExtension#getTestInstantiationExtensionContextScope(ExtensionContext) * getTestInstantiationExtensionContextScope(ExtensionContext)}. See * {@link #interceptTestClassConstructor(Invocation, ReflectiveInvocationContext, ExtensionContext)} * for details. * * @since 5.5 * @see Invocation * @see ReflectiveInvocationContext * @see ExtensionContext */ @API(status = STABLE, since = "5.10") public interface InvocationInterceptor extends TestInstantiationAwareExtension { /** * Intercept the invocation of a test class constructor. * *

Note that the test class may not have been initialized * (static initialization) when this method is invoked. * *

By default, the supplied {@link ExtensionContext} represents the test * class that's about to be constructed. Extensions may override * {@link #getTestInstantiationExtensionContextScope} to return * {@link ExtensionContextScope#TEST_METHOD TEST_METHOD} in order to change * the scope of the {@code ExtensionContext} to the test method, unless the * {@link TestInstance.Lifecycle#PER_CLASS PER_CLASS} lifecycle is used. * Changing the scope makes test-specific data available to the * implementation of this method and allows keeping state on the test level * by using the provided {@link ExtensionContext.Store Store} instance. * * @param invocation the invocation that is being intercepted; never * {@code null} * @param invocationContext the context of the invocation that is being * intercepted; never {@code null} * @param extensionContext the current extension context; never {@code null} * @param the result type * @return the result of the invocation; never {@code null} * @throws Throwable in case of failure */ default T interceptTestClassConstructor(Invocation invocation, ReflectiveInvocationContext> invocationContext, ExtensionContext extensionContext) throws Throwable { return invocation.proceed(); } /** * Intercept the invocation of a {@link BeforeAll @BeforeAll} method. * * @param invocation the invocation that is being intercepted; never * {@code null} * @param invocationContext the context of the invocation that is being * intercepted; never {@code null} * @param extensionContext the current extension context; never {@code null} * @throws Throwable in case of failures */ default void interceptBeforeAllMethod(Invocation<@Nullable Void> invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { invocation.proceed(); } /** * Intercept the invocation of a {@link BeforeEach @BeforeEach} method. * * @param invocation the invocation that is being intercepted; never * {@code null} * @param invocationContext the context of the invocation that is being * intercepted; never {@code null} * @param extensionContext the current extension context; never {@code null} * @throws Throwable in case of failures */ default void interceptBeforeEachMethod(Invocation<@Nullable Void> invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { invocation.proceed(); } /** * Intercept the invocation of a {@link Test @Test} method. * * @param invocation the invocation that is being intercepted; never * {@code null} * @param invocationContext the context of the invocation that is being * intercepted; never {@code null} * @param extensionContext the current extension context; never {@code null} * @throws Throwable in case of failures */ default void interceptTestMethod(Invocation<@Nullable Void> invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { invocation.proceed(); } /** * Intercept the invocation of a {@link TestFactory @TestFactory} method, * such as a {@link org.junit.jupiter.api.RepeatedTest @RepeatedTest} or * {@code @ParameterizedTest} method. * * @param invocation the invocation that is being intercepted; never * {@code null} * @param invocationContext the context of the invocation that is being * intercepted; never {@code null} * @param extensionContext the current extension context; never {@code null} * @param the result type * @return the result of the invocation; potentially {@code null} * @throws Throwable in case of failures */ default T interceptTestFactoryMethod(Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { return invocation.proceed(); } /** * Intercept the invocation of a {@link TestTemplate @TestTemplate} method. * * @param invocation the invocation that is being intercepted; never * {@code null} * @param invocationContext the context of the invocation that is being * intercepted; never {@code null} * @param extensionContext the current extension context; never {@code null} * @throws Throwable in case of failures */ default void interceptTestTemplateMethod(Invocation<@Nullable Void> invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { invocation.proceed(); } /** * Intercept the invocation of a {@link DynamicTest}. * * @param invocation the invocation that is being intercepted; never * {@code null} * @param invocationContext the context of the invocation that is being * intercepted; never {@code null} * @param extensionContext the current extension context; never {@code null} * @throws Throwable in case of failures */ @API(status = STABLE, since = "5.11") default void interceptDynamicTest(Invocation<@Nullable Void> invocation, DynamicTestInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { invocation.proceed(); } /** * Intercept the invocation of an {@link AfterEach @AfterEach} method. * * @param invocation the invocation that is being intercepted; never * {@code null} * @param invocationContext the context of the invocation that is being * intercepted; never {@code null} * @param extensionContext the current extension context; never {@code null} * @throws Throwable in case of failures */ default void interceptAfterEachMethod(Invocation<@Nullable Void> invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { invocation.proceed(); } /** * Intercept the invocation of an {@link AfterAll @AfterAll} method. * * @param invocation the invocation that is being intercepted; never * {@code null} * @param invocationContext the context of the invocation that is being * intercepted; never {@code null} * @param extensionContext the current extension context; never {@code null} * @throws Throwable in case of failures */ default void interceptAfterAllMethod(Invocation<@Nullable Void> invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { invocation.proceed(); } /** * An invocation that returns a result and may throw a {@link Throwable}. * *

This interface is not intended to be implemented by clients. * * @param the result type * @since 5.5 */ @API(status = STABLE, since = "5.10") interface Invocation { /** * Proceed with this invocation. * * @return the result of this invocation; potentially {@code null}. * @throws Throwable in case the invocation failed */ T proceed() throws Throwable; /** * Explicitly skip this invocation. * *

This allows to bypass the check that {@link #proceed()} must be * called at least once. The default implementation does nothing. */ @API(status = STABLE, since = "5.10") default void skip() { // do nothing } } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/LifecycleMethodExecutionExceptionHandler.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; /** * {@code LifecycleMethodExecutionExceptionHandler} defines the API for * {@link Extension Extensions} that wish to handle exceptions thrown during * the execution of {@code @BeforeAll}, {@code @BeforeEach}, {@code @AfterEach}, * and {@code @AfterAll} lifecycle methods. * *

Common use cases include swallowing an exception if it's anticipated, * logging errors, or rolling back a transaction in certain error scenarios. * *

Implementations of this extension API must be registered at the class level * if exceptions thrown from {@code @BeforeAll} or {@code @AfterAll} methods are * to be handled. When registered at the test level, only exceptions thrown from * {@code @BeforeEach} or {@code @AfterEach} methods will be handled. * *

Constructor Requirements

* *

Consult the documentation in {@link Extension} for details on constructor * requirements. * *

Implementation Guidelines

* *

An implementation of an exception handler method defined in this API must * perform one of the following. * *

    *
  1. Rethrow the supplied {@code Throwable} as is, which is the default implementation.
  2. *
  3. Swallow the supplied {@code Throwable}, thereby preventing propagation.
  4. *
  5. Throw a new exception, potentially wrapping the supplied {@code Throwable}.
  6. *
* *

If the supplied {@code Throwable} is swallowed by a handler method, subsequent * handler methods for the same lifecycle will not be invoked; otherwise, the * corresponding handler method of the next registered * {@code LifecycleMethodExecutionExceptionHandler} (if there is one) will be * invoked with any {@link Throwable} thrown by the previous handler. * * @since 5.5 * @see TestExecutionExceptionHandler */ @API(status = STABLE, since = "5.10") public interface LifecycleMethodExecutionExceptionHandler extends Extension { /** * Handle the supplied {@link Throwable} that was thrown during execution of * a {@code @BeforeAll} lifecycle method. * *

Please refer to the class-level Javadoc for * Implementation Guidelines. * * @param context the current extension context; never {@code null} * @param throwable the {@code Throwable} to handle; never {@code null} */ default void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { throw throwable; } /** * Handle the supplied {@link Throwable} that was thrown during execution of * a {@code @BeforeEach} lifecycle method. * *

Please refer to the class-level Javadoc for * Implementation Guidelines. * * @param context the current extension context; never {@code null} * @param throwable the {@code Throwable} to handle; never {@code null} */ default void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { throw throwable; } /** * Handle the supplied {@link Throwable} that was thrown during execution of * a {@code @AfterEach} lifecycle method. * *

Please refer to the class-level Javadoc for * Implementation Guidelines. * * @param context the current extension context; never {@code null} * @param throwable the {@code Throwable} to handle; never {@code null} */ default void handleAfterEachMethodExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { throw throwable; } /** * Handle the supplied {@link Throwable} that was thrown during execution of * a {@code @AfterAll} lifecycle method. * *

Please refer to the class-level Javadoc for * Implementation Guidelines. * * @param context the current extension context; never {@code null} * @param throwable the {@code Throwable} to handle; never {@code null} */ default void handleAfterAllMethodExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { throw throwable; } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/MediaType.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static java.nio.charset.StandardCharsets.UTF_8; import static org.apiguardian.api.API.Status.DEPRECATED; import java.nio.charset.Charset; import java.nio.file.Path; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.Preconditions; /** * Represents a media type as defined by * RFC 2045. * * @since 5.12 * @see org.junit.jupiter.api.TestReporter#publishFile(Path, MediaType) * @see org.junit.jupiter.api.TestReporter#publishFile(String, MediaType, org.junit.jupiter.api.function.ThrowingConsumer) * @see ExtensionContext#publishFile(String, MediaType, org.junit.jupiter.api.function.ThrowingConsumer) * @deprecated Use {@link org.junit.jupiter.api.MediaType} instead. */ @Deprecated(since = "5.14", forRemoval = true) @API(status = DEPRECATED, since = "5.14") public final class MediaType extends org.junit.jupiter.api.MediaType { /** * The {@code text/plain} media type. */ public static final MediaType TEXT_PLAIN = create("text", "plain"); /** * The {@code text/plain; charset=UTF-8} media type. */ public static final MediaType TEXT_PLAIN_UTF_8 = create("text", "plain", UTF_8); /** * The {@code application/json} media type. */ public static final MediaType APPLICATION_JSON = create("application", "json"); /** * The {@code application/json; charset=UTF-8} media type. * @deprecated Use {@link #APPLICATION_JSON} instead. */ @Deprecated(since = "5.14") @API(status = DEPRECATED, since = "5.14") public static final MediaType APPLICATION_JSON_UTF_8 = create("application", "json", UTF_8); /** * The {@code application/octet-stream} media type. */ public static final MediaType APPLICATION_OCTET_STREAM = create("application", "octet-stream"); /** * The {@code image/jpeg} media type. */ public static final MediaType IMAGE_JPEG = create("image", "jpeg"); /** * The {@code image/png} media type. */ public static final MediaType IMAGE_PNG = create("image", "png"); /** * Parse the given media type value. * *

Must be valid according to * RFC 2045. * * @param value the media type value to parse; never {@code null} or blank * @return the parsed media type * @throws PreconditionViolationException if the value is not a valid media type */ public static MediaType parse(String value) { return new MediaType(value); } /** * Create a media type with the given type and subtype. * * @param type the type; never {@code null} or blank * @param subtype the subtype; never {@code null} or blank * @return the media type */ public static MediaType create(String type, String subtype) { return new MediaType(type, subtype, null); } /** * Create a media type with the given type, subtype, and charset. * * @param type the type; never {@code null} or blank * @param subtype the subtype; never {@code null} or blank * @param charset the charset; never {@code null} * @return the media type */ public static MediaType create(String type, String subtype, Charset charset) { Preconditions.notNull(charset, "charset must not be null"); return new MediaType(type, subtype, charset); } private MediaType(String type, String subtype, @Nullable Charset charset) { super(type, subtype, charset); } private MediaType(String value) { super(value); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Executable; import java.lang.reflect.Parameter; import java.util.List; import java.util.Optional; import org.apiguardian.api.API; import org.junit.platform.commons.util.AnnotationUtils; /** * {@code ParameterContext} encapsulates the context in which an * {@link #getDeclaringExecutable Executable} will be invoked for a given * {@link #getParameter Parameter}. * *

A {@code ParameterContext} is used to support parameter resolution via * a {@link ParameterResolver}. * * @since 5.0 * @see ParameterResolver * @see java.lang.reflect.Parameter * @see java.lang.reflect.Executable * @see java.lang.reflect.Method * @see java.lang.reflect.Constructor */ @API(status = STABLE, since = "5.0") public interface ParameterContext extends AnnotatedElementContext { /** * Get the {@link Parameter} for this context. * *

WARNING

*

When searching for annotations on the parameter in this context, * favor {@link #isAnnotated(Class)}, {@link #findAnnotation(Class)}, and * {@link #findRepeatableAnnotations(Class)} over methods in the * {@link Parameter} API due to a bug in {@code javac} on JDK versions prior * to JDK 9. * * @return the parameter; never {@code null} * @see #getIndex() */ Parameter getParameter(); /** * Get the index of the {@link Parameter} for this context within the * parameter list of the {@link #getDeclaringExecutable Executable} that * declares the parameter. * * @return the index of the parameter * @see #getParameter() * @see Executable#getParameters() */ int getIndex(); /** * Get the {@link Executable} (i.e., the {@link java.lang.reflect.Method} or * {@link java.lang.reflect.Constructor}) that declares the {@code Parameter} * for this context. * * @return the declaring {@code Executable}; never {@code null} * @see Parameter#getDeclaringExecutable() */ default Executable getDeclaringExecutable() { return getParameter().getDeclaringExecutable(); } /** * Get the target on which the {@link #getDeclaringExecutable Executable} * that declares the {@link #getParameter Parameter} for this context will * be invoked, if available. * * @return an {@link Optional} containing the target on which the * {@code Executable} will be invoked; never {@code null} but will be * empty if the {@code Executable} is a constructor or a * {@code static} method. */ Optional getTarget(); /** * {@inheritDoc} * @since 5.10 */ @API(status = MAINTAINED, since = "5.13.3") @Override default AnnotatedElement getAnnotatedElement() { return getParameter(); } /** * Determine if an annotation of {@code annotationType} is either * present or meta-present on the {@link Parameter} for * this context. * *

WARNING

*

Favor the use of this method over directly invoking * {@link Parameter#isAnnotationPresent(Class)} due to a bug in {@code javac} * on JDK versions prior to JDK 9. * * @param annotationType the annotation type to search for; never {@code null} * @return {@code true} if the annotation is present or meta-present * @since 5.1.1 * @see #findAnnotation(Class) * @see #findRepeatableAnnotations(Class) */ @API(status = STABLE, since = "5.10") @Override default boolean isAnnotated(Class annotationType) { return AnnotationUtils.isAnnotated(getParameter(), getIndex(), annotationType); } /** * Find the first annotation of {@code annotationType} that is either * present or meta-present on the {@link Parameter} for * this context. * *

WARNING

*

Favor the use of this method over directly invoking annotation lookup * methods in the {@link Parameter} API due to a bug in {@code javac} on JDK * versions prior to JDK 9. * * @param the annotation type * @param annotationType the annotation type to search for; never {@code null} * @return an {@code Optional} containing the annotation; never {@code null} but * potentially empty * @since 5.1.1 * @see #isAnnotated(Class) * @see #findRepeatableAnnotations(Class) */ @API(status = STABLE, since = "5.10") @Override default Optional findAnnotation(Class annotationType) { return AnnotationUtils.findAnnotation(getParameter(), getIndex(), annotationType); } /** * Find all repeatable {@linkplain Annotation annotations} of * {@code annotationType} that are either present or * meta-present on the {@link Parameter} for this context. * *

WARNING

*

Favor the use of this method over directly invoking annotation lookup * methods in the {@link Parameter} API due to a bug in {@code javac} on JDK * versions prior to JDK 9. * * @param the annotation type * @param annotationType the repeatable annotation type to search for; never * {@code null} * @return the list of all such annotations found; neither {@code null} nor * mutable, but potentially empty * @since 5.1.1 * @see #isAnnotated(Class) * @see #findAnnotation(Class) * @see java.lang.annotation.Repeatable */ @API(status = STABLE, since = "5.10") @Override default List findRepeatableAnnotations(Class annotationType) { return AnnotationUtils.findRepeatableAnnotations(getParameter(), getIndex(), annotationType); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterResolutionException.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.STABLE; import java.io.Serial; import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.Preconditions; /** * Thrown if an error is encountered in the configuration or execution of a * {@link ParameterResolver}. * * @since 5.0 * @see ParameterResolver */ @API(status = STABLE, since = "5.0") public class ParameterResolutionException extends JUnitException { @Serial private static final long serialVersionUID = 1L; /** * Construct a {@code ParameterResolutionException} with the supplied message. * * @param message the message; never {@code null} */ public ParameterResolutionException(String message) { super(Preconditions.notNull(message, "message must not be null")); } /** * Construct a {@code ParameterResolutionException} with the supplied message * and cause. * * @param message the message; never {@code null} * @param cause the cause; never {@code null} */ public ParameterResolutionException(String message, Throwable cause) { super(Preconditions.notNull(message, "message must not be null"), Preconditions.notNull(cause, "cause must not be null")); } /** * Get the message, never {@code null}. */ @Override public String getMessage() { String message = super.getMessage(); if (message == null) { throw new IllegalStateException("message must not be null"); } return message; } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterResolver.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.STABLE; import java.lang.reflect.Parameter; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.TestInstance; /** * {@code ParameterResolver} defines the API for {@link Extension Extensions} * that wish to dynamically resolve arguments for {@linkplain Parameter parameters} * at runtime. * *

If a constructor for a test class or a * {@link org.junit.jupiter.api.Test @Test}, * {@link org.junit.jupiter.api.BeforeEach @BeforeEach}, * {@link org.junit.jupiter.api.AfterEach @AfterEach}, * {@link org.junit.jupiter.api.BeforeAll @BeforeAll}, or * {@link org.junit.jupiter.api.AfterAll @AfterAll} method declares a parameter, * an argument for the parameter must be resolved at runtime by a * {@code ParameterResolver}. * *

By default, when the methods in this interface are called for a test class * constructor, the supplied {@link ExtensionContext} represents the test * class that's about to be instantiated. Extensions may override * {@link #getTestInstantiationExtensionContextScope} to return * {@link ExtensionContextScope#TEST_METHOD TEST_METHOD} in order to change * the scope of the {@code ExtensionContext} to the test method, unless the * {@link TestInstance.Lifecycle#PER_CLASS PER_CLASS} lifecycle is used. * Changing the scope makes test-specific data available to the * implementation of this method and allows keeping state on the test level * by using the provided {@link ExtensionContext.Store Store} instance. * *

Constructor Requirements

* *

Consult the documentation in {@link Extension} for details on * constructor requirements. * *

{@code ExtensionContext} Scope

* *

As of JUnit Jupiter 5.12, this API participates in the * {@link TestInstantiationAwareExtension} contract. Implementations of this API * may therefore choose to override * {@link TestInstantiationAwareExtension#getTestInstantiationExtensionContextScope(ExtensionContext) * getTestInstantiationExtensionContextScope(ExtensionContext)} to require a * test-method scoped {@code ExtensionContext}. * * @since 5.0 * @see #supportsParameter(ParameterContext, ExtensionContext) * @see #resolveParameter(ParameterContext, ExtensionContext) * @see ParameterContext * @see TestInstanceFactory * @see TestInstancePostProcessor * @see TestInstancePreDestroyCallback */ @API(status = STABLE, since = "5.0") public interface ParameterResolver extends TestInstantiationAwareExtension { /** * Determine if this resolver supports resolution of an argument for the * {@link Parameter} in the supplied {@link ParameterContext} for the supplied * {@link ExtensionContext}. * *

The {@link java.lang.reflect.Method} or {@link java.lang.reflect.Constructor} * in which the parameter is declared can be retrieved via * {@link ParameterContext#getDeclaringExecutable()}. * * @param parameterContext the context for the parameter for which an argument should * be resolved; never {@code null} * @param extensionContext the extension context for the {@code Executable} * about to be invoked; never {@code null} * @return {@code true} if this resolver can resolve an argument for the parameter * @see #resolveParameter * @see ParameterContext */ boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException; /** * Resolve an argument for the {@link Parameter} in the supplied {@link ParameterContext} * for the supplied {@link ExtensionContext}. * *

This method is only called by the framework if {@link #supportsParameter} * previously returned {@code true} for the same {@link ParameterContext} * and {@link ExtensionContext}. * *

The {@link java.lang.reflect.Method} or {@link java.lang.reflect.Constructor} * in which the parameter is declared can be retrieved via * {@link ParameterContext#getDeclaringExecutable()}. * * @param parameterContext the context for the parameter for which an argument should * be resolved; never {@code null} * @param extensionContext the extension context for the {@code Executable} * about to be invoked; never {@code null} * @return the resolved argument for the parameter; may only be {@code null} if the * parameter type is not a primitive * @see #supportsParameter * @see ParameterContext */ @Nullable Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/PreInterruptCallback.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.MAINTAINED; import org.apiguardian.api.API; /** * {@code PreInterruptCallback} defines the API for {@link Extension * Extensions} that wish to be called prior to invocations of * {@link Thread#interrupt()} by the {@link org.junit.jupiter.api.Timeout} * extension. * *

JUnit registers a default implementation that dumps the stacks of all * {@linkplain Thread threads} to {@code System.out} if the * {@value #THREAD_DUMP_ENABLED_PROPERTY_NAME} configuration parameter is set to * {@code true}. * * @since 5.12 * @see org.junit.jupiter.api.Timeout */ @API(status = MAINTAINED, since = "5.13.3") public interface PreInterruptCallback extends Extension { /** * Property name used to enable dumping the stack of all * {@linkplain Thread threads} to {@code System.out} when a timeout has occurred. * *

This behavior is disabled by default. * * @since 5.12 */ @API(status = MAINTAINED, since = "5.13.3") String THREAD_DUMP_ENABLED_PROPERTY_NAME = "junit.jupiter.execution.timeout.threaddump.enabled"; /** * Callback that is invoked before a {@link Thread} is interrupted with * {@link Thread#interrupt()}. * *

Note: There is no guarantee on which {@link Thread} this callback will be * executed. * * @param preInterruptContext the context with the target {@link Thread}, which will get interrupted. * @param extensionContext the extension context for the callback; never {@code null} * @since 5.12 * @see PreInterruptContext */ @API(status = MAINTAINED, since = "5.13.3") void beforeThreadInterrupt(PreInterruptContext preInterruptContext, ExtensionContext extensionContext) throws Exception; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/PreInterruptContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.MAINTAINED; import org.apiguardian.api.API; /** * {@code PreInterruptContext} encapsulates the context in which an * {@link PreInterruptCallback#beforeThreadInterrupt(PreInterruptContext, ExtensionContext) beforeThreadInterrupt} method is called. * * @since 5.12 * @see PreInterruptCallback */ @API(status = MAINTAINED, since = "5.13.3") public interface PreInterruptContext { /** * Get the {@link Thread} which will be interrupted. * * @return the Thread; never {@code null} * @since 5.12 */ @API(status = MAINTAINED, since = "5.13.3") Thread getThreadToInterrupt(); } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ReflectiveInvocationContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.STABLE; import java.lang.reflect.Executable; import java.util.List; import java.util.Optional; import org.apiguardian.api.API; /** * {@code ReflectiveInvocationContext} encapsulates the context of * a reflective invocation of an executable (method or constructor). * *

This interface is not intended to be implemented by clients. * * @since 5.5 */ @API(status = STABLE, since = "5.10") public interface ReflectiveInvocationContext { /** * Get the target class of this invocation context. * *

If this invocation context represents an instance method, this * method returns the class of the object the method will be invoked on, * not the class it is declared in. Otherwise, if this invocation * represents a static method or constructor, this method returns the * class the method or constructor is declared in. * * @return the target class of this invocation context; never * {@code null} */ Class getTargetClass(); /** * Get the method or constructor of this invocation context. * * @return the executable of this invocation context; never {@code null} */ T getExecutable(); /** * Get the arguments of the executable in this invocation context. * * @return the arguments of the executable in this invocation context; * immutable and never {@code null} */ List getArguments(); /** * Get the target object of this invocation context, if available. * *

If this invocation context represents an instance method, this * method returns the object the method will be invoked on. Otherwise, * if this invocation context represents a static method or * constructor, this method returns {@link Optional#empty() empty()}. * * @return the target of the executable of this invocation context; never * {@code null} but potentially empty */ Optional getTarget(); } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/RegisterExtension.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @RegisterExtension} is used to register an {@link Extension} via a * field in a test class. * *

In contrast to {@link ExtendWith @ExtendWith} which is used to register * extensions declaratively, {@code @RegisterExtension} can be used to * register an extension programmatically — for example, in order * to pass arguments to the extension's constructor, {@code static} factory * method, or builder API. * *

{@code @RegisterExtension} fields must not be {@code null} (when evaluated) * but may be either {@code static} or non-static. * *

Static Fields

* *

If a {@code @RegisterExtension} field is {@code static}, the extension * will be registered after extensions that are registered at the class level * via {@code @ExtendWith}. Such static extensions are not limited in * which extension APIs they can implement. Extensions registered via static * fields may therefore implement class-level and instance-level extension APIs * such as {@link BeforeAllCallback}, {@link AfterAllCallback}, * {@link TestInstanceFactory}, {@link TestInstancePostProcessor} and * {@link TestInstancePreDestroyCallback} as well as method-level extension APIs * such as {@link BeforeEachCallback}, etc. * *

Instance Fields

* *

If a {@code @RegisterExtension} field is non-static (i.e., an instance * field), the extension will be registered after the test class has been * instantiated and after all {@link TestInstancePostProcessor * TestInstancePostProcessors} have been given a chance to post-process the * test instance (potentially injecting the instance of the extension to be * used into the annotated field). Thus, if such an instance extension * implements class-level or instance-level extension APIs such as * {@link BeforeAllCallback}, {@link AfterAllCallback}, * {@link TestInstanceFactory}, or {@link TestInstancePostProcessor} those APIs * will not be honored. By default, an instance extension will be registered * after extensions that are registered at the method level via * {@code @ExtendWith}; however, if the test class is configured with * {@link org.junit.jupiter.api.TestInstance.Lifecycle @TestInstance(Lifecycle.PER_CLASS)} * semantics, an instance extension will be registered before extensions * that are registered at the method level via {@code @ExtendWith}. * *

Inheritance

* *

{@code @RegisterExtension} fields are inherited from superclasses. * Furthermore, {@code @RegisterExtension} fields from superclasses will be * registered before {@code @RegisterExtension} fields in subclasses unless * {@code @Order} is used to alter that behavior (see below). * *

Registration Order

* *

By default, if multiple extensions are registered via * {@code @RegisterExtension}, they will be ordered using an algorithm that is * deterministic but intentionally nonobvious. This ensures that subsequent runs * of a test suite execute extensions in the same order, thereby allowing for * repeatable builds. However, there are times when extensions need to be * registered in an explicit order. To achieve that, you can annotate * {@code @RegisterExtension} fields with {@link org.junit.jupiter.api.Order @Order}. * Any {@code @RegisterExtension} field not annotated with {@code @Order} will be * ordered using the {@link org.junit.jupiter.api.Order#DEFAULT default} order * value. Note that {@code @ExtendWith} fields can also be ordered with * {@code @Order}, relative to {@code @RegisterExtension} fields and other * {@code @ExtendWith} fields. * *

Example Usage

* *

In the following example, the {@code docs} field in the test class is * initialized programmatically by supplying a custom {@code lookUpDocsDir()} * method to a {@code static} factory method in the {@code DocumentationExtension}. * The configured {@code DocumentationExtension} will be automatically registered * as an extension. In addition, test methods can access the instance of the * extension via the {@code docs} field if necessary. * *

 * class DocumentationTests {
 *
 *     static Path lookUpDocsDir() {
 *         // return path to docs dir
 *     }
 *
 *     {@literal @}RegisterExtension
 *     DocumentationExtension docs =
 *         DocumentationExtension.forPath(lookUpDocsDir());
 *
 *     {@literal @}Test
 *     void generateDocumentation() {
 *         // use docs ...
 *     }
 * }
* *

Supported Extension APIs

* *
    *
  • {@link ExecutionCondition}
  • *
  • {@link InvocationInterceptor}
  • *
  • {@link BeforeAllCallback}
  • *
  • {@link AfterAllCallback}
  • *
  • {@link BeforeEachCallback}
  • *
  • {@link AfterEachCallback}
  • *
  • {@link BeforeTestExecutionCallback}
  • *
  • {@link AfterTestExecutionCallback}
  • *
  • {@link TestInstanceFactory}
  • *
  • {@link TestInstancePostProcessor}
  • *
  • {@link TestInstancePreConstructCallback}
  • *
  • {@link TestInstancePreDestroyCallback}
  • *
  • {@link ParameterResolver}
  • *
  • {@link LifecycleMethodExecutionExceptionHandler}
  • *
  • {@link TestExecutionExceptionHandler}
  • *
  • {@link TestTemplateInvocationContextProvider}
  • *
  • {@link TestWatcher}
  • *
* * @since 5.1 * @see ExtendWith @ExtendWith * @see Extension * @see org.junit.jupiter.api.Order @Order */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = STABLE, since = "5.1") public @interface RegisterExtension { } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TemplateInvocationValidationException.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import java.io.Serial; import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; /** * {@code TemplateInvocationValidationException} is an exception thrown by a * {@link TestTemplateInvocationContextProvider} or * {@link ClassTemplateInvocationContextProvider} if a validation fails when * while providing or closing {@link java.util.stream.Stream} of invocation * contexts. * * @since 5.13 */ @API(status = EXPERIMENTAL, since = "6.0") public class TemplateInvocationValidationException extends JUnitException { @Serial private static final long serialVersionUID = 1L; public TemplateInvocationValidationException(String message) { super(message); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestExecutionExceptionHandler.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; /** * {@code TestExecutionExceptionHandler} defines the API for {@link Extension * Extensions} that wish to handle exceptions thrown during test execution. * *

In this context, test execution refers to the physical * invocation of a {@code @Test} method and not to any test-level extensions * or callbacks. * *

Common use cases include swallowing an exception if it's anticipated * or rolling back a transaction in certain error scenarios. * *

Constructor Requirements

* *

Consult the documentation in {@link Extension} for details on * constructor requirements. * * @since 5.0 * @see LifecycleMethodExecutionExceptionHandler */ @FunctionalInterface @API(status = STABLE, since = "5.0") public interface TestExecutionExceptionHandler extends Extension { /** * Handle the supplied {@link Throwable throwable}. * *

Implementors must perform one of the following. *

    *
  1. Swallow the supplied {@code throwable}, thereby preventing propagation.
  2. *
  3. Rethrow the supplied {@code throwable} as is.
  4. *
  5. Throw a new exception, potentially wrapping the supplied {@code throwable}.
  6. *
* *

If the supplied {@code throwable} is swallowed, subsequent * {@code TestExecutionExceptionHandlers} will not be invoked; otherwise, * the next registered {@code TestExecutionExceptionHandler} (if there is * one) will be invoked with any {@link Throwable} thrown by this handler. * *

Note that the {@link ExtensionContext#getExecutionException() execution * exception} in the supplied {@code ExtensionContext} will not * contain the {@code Throwable} thrown during invocation of the corresponding * {@code @Test} method. * * @param context the current extension context; never {@code null} * @param throwable the {@code Throwable} to handle; never {@code null} */ void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstanceFactory.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; import org.junit.jupiter.api.TestInstance; /** * {@code TestInstanceFactory} defines the API for {@link Extension * Extensions} that wish to {@linkplain #createTestInstance create} test instances. * *

Common use cases include creating test instances with special construction * requirements or acquiring the test instance from a dependency injection * framework. * *

Extensions that implement {@code TestInstanceFactory} must be registered * at the class level. * *

Warning

* *

Only one {@code TestInstanceFactory} is allowed to be registered for any * given test class. Registering multiple factories for any single test class * will result in an exception being thrown for all tests in that class, in any * subclass, and in any nested class. Note that any {@code TestInstanceFactory} * registered in a {@linkplain Class#getSuperclass() superclass} or * {@linkplain Class#getEnclosingClass() enclosing} class (i.e., in the case of * a {@code @Nested} test class) is inherited. It is therefore the * user's responsibility to ensure that only a single {@code TestInstanceFactory} * is registered for any specific test class. * *

Constructor Requirements

* *

Consult the documentation in {@link Extension} for details on * constructor requirements. * *

{@code ExtensionContext} Scope

* *

As of JUnit Jupiter 5.12, this API participates in the * {@link TestInstantiationAwareExtension} contract. Implementations of this API * may therefore choose to override * {@link TestInstantiationAwareExtension#getTestInstantiationExtensionContextScope(ExtensionContext) * getTestInstantiationExtensionContextScope(ExtensionContext)} to require a * test-method scoped {@code ExtensionContext}. See * {@link #createTestInstance(TestInstanceFactoryContext, ExtensionContext)} for * further details. * * @since 5.3 * @see #createTestInstance(TestInstanceFactoryContext, ExtensionContext) * @see TestInstanceFactoryContext * @see TestInstancePostProcessor * @see TestInstancePreDestroyCallback * @see ParameterResolver */ @FunctionalInterface @API(status = STABLE, since = "5.7") public interface TestInstanceFactory extends TestInstantiationAwareExtension { /** * Callback for creating a test instance for the supplied context. * *

By default, the supplied {@link ExtensionContext} represents the test * class that's about to be instantiated. Extensions may override * {@link #getTestInstantiationExtensionContextScope} to return * {@link ExtensionContextScope#TEST_METHOD TEST_METHOD} in order to change * the scope of the {@code ExtensionContext} to the test method, unless the * {@link TestInstance.Lifecycle#PER_CLASS PER_CLASS} lifecycle is used. * Changing the scope makes test-specific data available to the * implementation of this method and allows keeping state on the test level * by using the provided {@link ExtensionContext.Store Store} instance. * *

Note: the {@code ExtensionContext} supplied to a * {@code TestInstanceFactory} will always return an empty * {@link java.util.Optional} value from * {@link ExtensionContext#getTestInstance() getTestInstance()} since the * test instance cannot exist before it has been created by a * {@code TestInstanceFactory} or the framework itself. * * @param factoryContext the context for the test class to be instantiated; * never {@code null} * @param extensionContext the current extension context; never {@code null} * @return the test instance; never {@code null} * @throws TestInstantiationException if an error occurs while creating the * test instance */ Object createTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext) throws TestInstantiationException; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstanceFactoryContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.STABLE; import java.util.Optional; import org.apiguardian.api.API; /** * {@code TestInstanceFactoryContext} encapsulates the context in which * a {@linkplain #getTestClass test class} is to be instantiated by a * {@link TestInstanceFactory}. * * @since 5.3 * @see TestInstanceFactory */ @API(status = STABLE, since = "5.7") public interface TestInstanceFactoryContext { /** * Get the test class for this context. * * @return the test class to be instantiated; never {@code null} */ Class getTestClass(); /** * Get the instance of the outer class, if available. * *

The returned {@link Optional} will be empty unless the * current {@linkplain #getTestClass() test class} is a * {@link org.junit.jupiter.api.Nested @Nested} test class. * * @return an {@code Optional} containing the outer test instance; never * {@code null} but potentially empty * @see org.junit.jupiter.api.Nested */ Optional getOuterInstance(); } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePostProcessor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; import org.junit.jupiter.api.TestInstance; /** * {@code TestInstancePostProcessor} defines the API for {@link Extension * Extensions} that wish to post-process test instances. * *

Common use cases include injecting dependencies into the test * instance, invoking custom initialization methods on the test instance, * etc. * *

Extensions that implement {@code TestInstancePostProcessor} must be * registered at the class level, {@linkplain ExtendWith declaratively} via a * field of the test class, or {@linkplain RegisterExtension programmatically} * via a static field of the test class. * *

Constructor Requirements

* *

Consult the documentation in {@link Extension} for details on * constructor requirements. * *

{@code ExtensionContext} Scope

* *

As of JUnit Jupiter 5.12, this API participates in the * {@link TestInstantiationAwareExtension} contract. Implementations of this API * may therefore choose to override * {@link TestInstantiationAwareExtension#getTestInstantiationExtensionContextScope(ExtensionContext) * getTestInstantiationExtensionContextScope(ExtensionContext)} to require a * test-method scoped {@code ExtensionContext}. See * {@link #postProcessTestInstance(Object, ExtensionContext)} for further details. * * @since 5.0 * @see #postProcessTestInstance(Object, ExtensionContext) * @see TestInstancePreDestroyCallback * @see TestInstanceFactory * @see ParameterResolver */ @FunctionalInterface @API(status = STABLE, since = "5.0") public interface TestInstancePostProcessor extends TestInstantiationAwareExtension { /** * Callback for post-processing the supplied test instance. * *

By default, the supplied {@link ExtensionContext} represents the test * class that's being post-processed. Extensions may override * {@link #getTestInstantiationExtensionContextScope} to return * {@link ExtensionContextScope#TEST_METHOD TEST_METHOD} in order to change * the scope of the {@code ExtensionContext} to the test method, unless the * {@link TestInstance.Lifecycle#PER_CLASS PER_CLASS} lifecycle is used. * Changing the scope makes test-specific data available to the * implementation of this method and allows keeping state on the test level * by using the provided {@link ExtensionContext.Store Store} instance. * *

Note: the {@code ExtensionContext} supplied to a * {@code TestInstancePostProcessor} will always return an empty * {@link java.util.Optional} value from {@link ExtensionContext#getTestInstance() * getTestInstance()}. A {@code TestInstancePostProcessor} should therefore * only attempt to process the supplied {@code testInstance}. * * @param testInstance the instance to post-process; never {@code null} * @param context the current extension context; never {@code null} */ void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePreConstructCallback.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; /** * {@code TestInstancePreConstructCallback} defines the API for {@link Extension * Extensions} that wish to be invoked prior to creation of test instances. * *

This extension is a symmetric counterpart to {@link TestInstancePreDestroyCallback}. * The use cases for this extension may include preparing context-sensitive arguments * that are injected into the instance's constructor. * *

Extensions that implement {@code TestInstancePreConstructCallback} must be * registered at the class level if the test class is configured with * {@link Lifecycle @TestInstance(Lifecycle.PER_CLASS)} semantics. If the test * class is configured with * {@link Lifecycle @TestInstance(Lifecycle.PER_METHOD)} semantics, * {@code TestInstancePreConstructCallback} extensions may be registered at the * class level or at the method level. In the latter case, the extension will * only be applied to the test method for which it is registered. * *

Constructor Requirements

* *

Consult the documentation in {@link Extension} for details on constructor * requirements. * *

{@code ExtensionContext} Scope

* *

As of JUnit Jupiter 5.12, this API participates in the * {@link TestInstantiationAwareExtension} contract. Implementations of this API * may therefore choose to override * {@link TestInstantiationAwareExtension#getTestInstantiationExtensionContextScope(ExtensionContext) * getTestInstantiationExtensionContextScope(ExtensionContext)} to require a * test-method scoped {@code ExtensionContext}. See * {@link #preConstructTestInstance(TestInstanceFactoryContext, ExtensionContext)} * for further details. * * @since 5.9 * @see TestInstancePreDestroyCallback * @see TestInstanceFactory * @see ParameterResolver */ @FunctionalInterface @API(status = STABLE, since = "5.11") public interface TestInstancePreConstructCallback extends TestInstantiationAwareExtension { /** * Callback invoked prior to test instances being constructed. * *

By default, the supplied {@link ExtensionContext} represents the test * class that's about to be constructed. Extensions may override * {@link #getTestInstantiationExtensionContextScope} to return * {@link ExtensionContextScope#TEST_METHOD TEST_METHOD} in order to change * the scope of the {@code ExtensionContext} to the test method, unless the * {@link TestInstance.Lifecycle#PER_CLASS PER_CLASS} lifecycle is used. * Changing the scope makes test-specific data available to the * implementation of this method and allows keeping state on the test level * by using the provided {@link ExtensionContext.Store Store} instance. * * @param factoryContext the context for the test instance about to be instantiated; * never {@code null} * @param context the current extension context; never {@code null} */ void preConstructTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext context) throws Exception; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePreDestroyCallback.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.STABLE; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.function.Consumer; import org.apiguardian.api.API; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.TestInstance.Lifecycle; /** * {@code TestInstancePreDestroyCallback} defines the API for {@link Extension * Extensions} that wish to process test instances after they have been * used in tests but before they are destroyed. * *

Common use cases include releasing resources that have been created for * the test instance, invoking custom clean-up methods on the test instance, etc. * *

Extensions that implement {@code TestInstancePreDestroyCallback} must be * registered at the class level if the test class is configured with * {@link Lifecycle @TestInstance(Lifecycle.PER_CLASS)} * semantics. If the test class is configured with * {@link Lifecycle @TestInstance(Lifecycle.PER_METHOD)} * semantics, {@code TestInstancePreDestroyCallback} extensions may be registered * at the class level or at the method level. In the latter case, the * {@code TestInstancePreDestroyCallback} extension will only be applied to the * test method for which it is registered. * *

A symmetric {@link TestInstancePreConstructCallback} extension defines a callback * hook that is invoked prior to any test class instances being constructed. * *

Constructor Requirements

* *

Consult the documentation in {@link Extension} for details on constructor * requirements. * * @since 5.6 * @see #preDestroyTestInstance(ExtensionContext) * @see TestInstancePostProcessor * @see TestInstancePreConstructCallback * @see TestInstanceFactory * @see ParameterResolver */ @FunctionalInterface @API(status = STABLE, since = "5.7") public interface TestInstancePreDestroyCallback extends Extension { /** * Callback for processing test instances before they are destroyed. * *

Contrary to {@link TestInstancePostProcessor#postProcessTestInstance} * this method is only called once for each {@link ExtensionContext} even if * there are multiple test instances about to be destroyed in case of * {@link Nested @Nested} tests. Please use the provided * {@link #preDestroyTestInstances(ExtensionContext, Consumer)} utility * method to ensure that all test instances are handled. * * @param context the current extension context; never {@code null} * @see ExtensionContext#getTestInstance() * @see ExtensionContext#getRequiredTestInstance() * @see ExtensionContext#getTestInstances() * @see ExtensionContext#getRequiredTestInstances() * @see #preDestroyTestInstances(ExtensionContext, Consumer) */ void preDestroyTestInstance(ExtensionContext context) throws Exception; /** * Utility method for processing all test instances of an * {@link ExtensionContext} that are not present in any of its parent * contexts. * *

This method should be called in order to implement this interface * correctly since it ensures that the right test instances are processed * regardless of the used {@linkplain Lifecycle lifecycle}. The supplied * callback is called once per test instance that is about to be destroyed * starting with the innermost one. * *

This method is intended to be called from an implementation of * {@link #preDestroyTestInstance(ExtensionContext)} like this: * *

{@code class MyExtension implements TestInstancePreDestroyCallback {
	 *    @Override
	 *    public void preDestroyTestInstance(ExtensionContext context) {
	 *        TestInstancePreDestroyCallback.preDestroyTestInstances(context, testInstance -> {
	 *            // custom logic that processes testInstance
	 *        });
	 *    }
	 *}}
* * @param context the current extension context; never {@code null} * @param callback the callback to be invoked for every test instance of the * current extension context that is about to be destroyed; never * {@code null} * @since 5.7.1 */ @API(status = STABLE, since = "5.10") static void preDestroyTestInstances(ExtensionContext context, Consumer callback) { List destroyedInstances = new ArrayList<>(context.getRequiredTestInstances().getAllInstances()); for (Optional current = context.getParent(); current.isPresent(); current = current.get().getParent()) { current.get().getTestInstances().map(TestInstances::getAllInstances).ifPresent( destroyedInstances::removeAll); } Collections.reverse(destroyedInstances); destroyedInstances.forEach(callback); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstances.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.STABLE; import java.util.List; import java.util.Optional; import org.apiguardian.api.API; /** * {@code TestInstances} encapsulates the test instances of a test. * *

While top-level tests only have a single test instance, nested tests * have one additional instance for each enclosing test class. * * @since 5.4 * @see ExtensionContext#getTestInstances() * @see ExtensionContext#getRequiredTestInstances() */ @API(status = STABLE, since = "5.7") public interface TestInstances { /** * Get the innermost test instance. * *

The innermost instance is the one closest to the test method. * * @return the innermost test instance; never {@code null} */ Object getInnermostInstance(); /** * Get the enclosing test instances, excluding the innermost test instance, * ordered from outermost to innermost. * * @return the enclosing test instances; never {@code null} or containing * {@code null}, but potentially empty */ List getEnclosingInstances(); /** * Get all test instances, ordered from outermost to innermost. * * @return all test instances; never {@code null}, containing {@code null}, * or empty */ List getAllInstances(); /** * Find the first test instance that is an instance of the supplied required * type, checking from innermost to outermost. * * @param requiredType the type to search for * @return the first test instance of the required type; never {@code null} * but potentially empty */ Optional findInstance(Class requiredType); } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationAwareExtension.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.MAINTAINED; import org.apiguardian.api.API; /** * {@code TestInstantiationAwareExtension} defines the API for {@link Extension * Extensions} that are aware of or influence the instantiation of test classes. * *

This interface is not intended to be implemented directly. Instead, extensions * should implement one of the sub-interfaces listed below. * *

    *
  • {@link InvocationInterceptor}
  • *
  • {@link ParameterResolver}
  • *
  • {@link TestInstancePreConstructCallback}
  • *
  • {@link TestInstancePostProcessor}
  • *
  • {@link TestInstanceFactory}
  • *
* *

See {@link #getTestInstantiationExtensionContextScope(ExtensionContext)} for * further details. * * @since 5.12 */ @API(status = MAINTAINED, since = "5.13.3") public interface TestInstantiationAwareExtension extends Extension { /** * Determine whether this extension should receive a test-method scoped * {@link ExtensionContext} during the instantiation of test classes or * processing of test instances. * *

If an extension returns {@link ExtensionContextScope#TEST_METHOD TEST_METHOD} * from this method, methods defined in the following extension APIs will be * called with a test-method scoped {@code ExtensionContext} instead of a * test-class scoped context. Note, however, that a test-class scoped context * will always be supplied if the * {@link org.junit.jupiter.api.TestInstance.Lifecycle#PER_CLASS PER_CLASS} * test instance lifecycle is used. * *

    *
  • {@link InvocationInterceptor}: only the * {@link InvocationInterceptor#interceptTestClassConstructor * interceptTestClassConstructor(...)} method
  • *
  • {@link ParameterResolver}: only when resolving constructor parameters
  • *
  • {@link TestInstancePreConstructCallback}
  • *
  • {@link TestInstancePostProcessor}
  • *
  • {@link TestInstanceFactory}
  • *
* *

When a test-method scoped {@code ExtensionContext} is supplied, implementations * of the above extension APIs will observe the following differences. * *

    *
  • * {@link ExtensionContext#getElement() getElement()} may refer to the * test method. *
  • *
  • * {@link ExtensionContext#getTestClass() getTestClass()} may refer to a * nested test class. *
      *
    • * For {@link TestInstancePostProcessor}, use {@code testInstance.getClass()} * to get the test class associated with the supplied instance. *
    • *
    • * For {@link TestInstanceFactory} and {@link TestInstancePreConstructCallback}, * use {@link TestInstanceFactoryContext#getTestClass()} to get the * class under construction. *
    • *
    • * For {@link ParameterResolver}, when resolving a parameter for a * constructor, ensure that the * {@link ParameterContext#getDeclaringExecutable() Executable} is a * {@link java.lang.reflect.Constructor Constructor}, and then use * {@code constructor.getDeclaringClass()} to get the test class * associated with the constructor. *
    • *
    *
  • *
  • * {@link ExtensionContext#getTestMethod() getTestMethod()} is no longer * empty, unless the * {@link org.junit.jupiter.api.TestInstance.Lifecycle#PER_CLASS PER_CLASS} * test instance lifecycle is used. *
  • *
  • * If the extension adds a {@link ExtensionContext.Store.CloseableResource * CloseableResource} or {@link AutoCloseable} to the * {@link ExtensionContext.Store Store} (unless the * {@code junit.jupiter.extensions.store.close.autocloseable.enabled} * configuration parameter is set to {@code false}), then the resource will * be closed just after the instance is destroyed. *
  • *
  • * Extensions can now access data previously stored by a * {@link TestTemplateInvocationContext}, unless the * {@link org.junit.jupiter.api.TestInstance.Lifecycle#PER_CLASS PER_CLASS} * test instance lifecycle is used. *
  • *
* *

Note: The behavior which is enabled by returning * {@link ExtensionContextScope#TEST_METHOD TEST_METHOD} from this method * will become the default in future versions of JUnit. To ensure forward * compatibility, extension authors are therefore advised to opt into this * feature, even if they do not require the new functionality. * * @implNote There are no guarantees about how often this method will be called. * Therefore, implementations should be idempotent and avoid side effects. * If computation of the return value is costly, implementations may wish to * cache the result in the {@link ExtensionContext.Store Store} of the supplied * {@code ExtensionContext}. * @param rootContext the root extension context to allow inspection of * configuration parameters; never {@code null} * @since 5.12 */ @API(status = MAINTAINED, since = "5.13.3") default ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { return ExtensionContextScope.DEFAULT; } /** * {@code ExtensionContextScope} is used to define the scope of the * {@link ExtensionContext} supplied to an extension during the instantiation * of test classes or processing of test instances. * * @since 5.12 * @see TestInstantiationAwareExtension#getTestInstantiationExtensionContextScope(ExtensionContext) */ @API(status = MAINTAINED, since = "5.13.3") enum ExtensionContextScope { /** * The extension should receive an {@link ExtensionContext} for the * the default scope. * *

The default scope is determined by the configuration parameter * {@link #DEFAULT_SCOPE_PROPERTY_NAME}. If not specified, extensions * will receive an {@link ExtensionContext} scoped to the test class. * * @deprecated This behavior will be removed from future versions of * JUnit, and {@link #TEST_METHOD} will become the default. * * @see #DEFAULT_SCOPE_PROPERTY_NAME */ @Deprecated(since = "5.12") // @API(status = DEPRECATED, since = "5.12") DEFAULT, /** * The extension should receive an {@link ExtensionContext} scoped to * the test method. * *

Note, however, that a test-class scoped context will always be * supplied if the * {@link org.junit.jupiter.api.TestInstance.Lifecycle#PER_CLASS PER_CLASS} * test instance lifecycle is used. */ TEST_METHOD; /** * Property name used to set the default extension context scope: {@value} * *

Supported Values

* *

Supported values include names of enum constants defined in this * class, ignoring case. * * @see #DEFAULT */ public static final String DEFAULT_SCOPE_PROPERTY_NAME = "junit.jupiter.extensions.testinstantiation.extensioncontextscope.default"; } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationException.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.STABLE; import java.io.Serial; import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; /** * Thrown if an error is encountered during the execution of * a {@link TestInstanceFactory}. * * @since 5.3 */ @API(status = STABLE, since = "5.10") public class TestInstantiationException extends JUnitException { @Serial private static final long serialVersionUID = 1L; public TestInstantiationException(String message) { super(message); } public TestInstantiationException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static java.util.Collections.emptyList; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import java.util.List; import org.apiguardian.api.API; /** * {@code TestTemplateInvocationContext} represents the context of a * single invocation of a {@linkplain org.junit.jupiter.api.TestTemplate test * template}. * *

Each context is provided by a {@link TestTemplateInvocationContextProvider}. * * @since 5.0 * @see org.junit.jupiter.api.TestTemplate * @see TestTemplateInvocationContextProvider */ @API(status = STABLE, since = "5.0") public interface TestTemplateInvocationContext { /** * Get the display name for this invocation. * *

The supplied {@code invocationIndex} is incremented by the framework * with each test template invocation. Thus, in the case of multiple active * {@linkplain TestTemplateInvocationContextProvider providers}, only the * first active provider receives indices starting with {@code 1}. * *

The default implementation returns the supplied {@code invocationIndex} * wrapped in brackets — for example, {@code [1]}, {@code [42]}, etc. * * @param invocationIndex the index of this invocation (1-based). * @return the display name for this invocation; never {@code null} or blank */ default String getDisplayName(int invocationIndex) { return "[" + invocationIndex + "]"; } /** * Get additional {@linkplain Extension extensions} for this invocation. * *

The extensions provided by this method will only be used for this * invocation of the test template. Thus, it does not make sense to return * an extension that needs to perform some action at the container level, * such as an implementation of {@link BeforeAllCallback}. * *

The default implementation returns an empty list. * * @return additional extensions for this invocation; never {@code null} * or containing {@code null} elements, but potentially empty */ default List getAdditionalExtensions() { return emptyList(); } /** * Prepare the imminent invocation of the test template. * *

This may be used, for example, to store entries in the * {@link ExtensionContext.Store Store} to benefit from its cleanup support * or for retrieval by other extensions. * * @param context the invocation-level extension context * @since 5.13 */ @API(status = EXPERIMENTAL, since = "6.0") default void prepareInvocation(ExtensionContext context) { } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.util.stream.Stream; import org.apiguardian.api.API; /** * {@code TestTemplateInvocationContextProvider} defines the API for * {@link Extension Extensions} that wish to provide one or multiple contexts * for the invocation of a * {@link org.junit.jupiter.api.TestTemplate @TestTemplate} method. * *

This extension API makes it possible to execute a test template in * different contexts — for example, with different parameters, by * preparing the test class instance differently, or multiple times without * modifying the context. * *

This interface defines two main methods: {@link #supportsTestTemplate} and * {@link #provideTestTemplateInvocationContexts}. The former is called by the * framework to determine whether this extension wants to act on a test template * that is about to be executed. If so, the latter is called and must return a * {@link Stream} of {@link TestTemplateInvocationContext} instances. Otherwise, * this provider is ignored for the execution of the current test template. * *

A provider that has returned {@code true} from its {@link #supportsTestTemplate} * method is called active. When multiple providers are active for a * test template method, the {@code Streams} returned by their * {@link #provideTestTemplateInvocationContexts} methods will be chained, and * the test template method will be invoked using the contexts of all active * providers. * *

An active provider may return zero invocation contexts from its * {@link #provideTestTemplateInvocationContexts} method if it overrides * {@link #mayReturnZeroTestTemplateInvocationContexts} to return {@code true}. * *

Constructor Requirements

* *

Consult the documentation in {@link Extension} for details on constructor * requirements. * * @since 5.0 * @see org.junit.jupiter.api.TestTemplate * @see TestTemplateInvocationContext */ @API(status = STABLE, since = "5.0") public interface TestTemplateInvocationContextProvider extends Extension { /** * Determine if this provider supports providing invocation contexts for the * test template method represented by the supplied {@code context}. * * @param context the extension context for the test template method about * to be invoked; never {@code null} * @return {@code true} if this provider can provide invocation contexts * @see #provideTestTemplateInvocationContexts * @see ExtensionContext */ boolean supportsTestTemplate(ExtensionContext context); /** * Provide {@linkplain TestTemplateInvocationContext invocation contexts} * for the test template method represented by the supplied {@code context}. * *

This method is only called by the framework if {@link #supportsTestTemplate} * previously returned {@code true} for the same {@link ExtensionContext}; * this method is allowed to return an empty {@code Stream} but not {@code null}. * *

The returned {@code Stream} will be properly closed by calling * {@link Stream#close()}, making it safe to use a resource such as * {@link java.nio.file.Files#lines(java.nio.file.Path) Files.lines()}. * * @param context the extension context for the test template method about * to be invoked; never {@code null} * @return a {@code Stream} of {@code TestTemplateInvocationContext} * instances for the invocation of the test template method; never {@code null} * @throws TemplateInvocationValidationException if validation fails while * providing or closing the {@link Stream} * @see #supportsTestTemplate * @see ExtensionContext */ Stream provideTestTemplateInvocationContexts(ExtensionContext context); /** * Signal that this provider may provide zero * {@linkplain TestTemplateInvocationContext invocation contexts} for the test * template method represented by the supplied {@code context}. * *

If this method returns {@code false} (which is the default) and the * provider returns an empty stream from * {@link #provideTestTemplateInvocationContexts}, this will be considered * an execution error. Override this method to return {@code true} to ignore * the absence of invocation contexts for this provider. * * @param context the extension context for the test template method about * to be invoked; never {@code null} * @return {@code true} to allow zero contexts, {@code false} to fail * execution in case of zero contexts * * @since 5.12 */ @API(status = MAINTAINED, since = "5.13.3") default boolean mayReturnZeroTestTemplateInvocationContexts(ExtensionContext context) { return false; } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestWatcher.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.apiguardian.api.API.Status.STABLE; import java.util.Optional; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; /** * {@code TestWatcher} defines the API for {@link Extension Extensions} that * wish to process test results. * *

The methods in this API are called after a test has been skipped or * executed. Any {@link ExtensionContext.Store.CloseableResource CloseableResource} * objects stored in the {@link ExtensionContext.Store Store} of the supplied * {@link ExtensionContext} will have already been closed before * methods in this API are invoked. * *

Please note that this API is currently only used to report the results of * {@link org.junit.jupiter.api.Test @Test} methods and * {@link org.junit.jupiter.api.TestTemplate @TestTemplate} methods (e.g., * {@code @RepeatedTest} and {@code @ParameterizedTest}). Moreover, if there is a * failure at the class level — for example, an exception thrown by a * {@code @BeforeAll} method — no test results will be reported. Similarly, * if the test class is disabled via an {@link ExecutionCondition} — for * example, {@code @Disabled} — no test results will be reported. * *

Extensions implementing this interface can be registered at the class level, * instance level, or method level. When registered at the class level, a * {@code TestWatcher} will be invoked for any contained test method including * those in {@link org.junit.jupiter.api.Nested @Nested} classes. When registered * at the method level, a {@code TestWatcher} will only be invoked for the test * method for which it was registered. * *

WARNING: If a {@code TestWatcher} is registered via a * non-static (instance) field — for example, using * {@link RegisterExtension @RegisterExtension} — and the test class is * configured with * {@link org.junit.jupiter.api.TestInstance @TestInstance(Lifecycle.PER_METHOD)} * semantics (which is the default lifecycle mode), the {@code TestWatcher} will * not be invoked with events for {@code @TestTemplate} methods * (such as {@code @RepeatedTest} and {@code @ParameterizedTest}). To ensure that * a {@code TestWatcher} is invoked for all test methods in a given class, it is * therefore recommended that the {@code TestWatcher} be registered at the class * level with {@link ExtendWith @ExtendWith} or via a {@code static} field with * {@code @RegisterExtension} or {@code @ExtendWith}. * *

Exception Handling

* *

In contrast to other {@link Extension} APIs, a {@code TestWatcher} is not * permitted to adversely influence the execution of tests. Consequently, any * exception thrown by a method in the {@code TestWatcher} API will be logged at * {@code WARNING} level and will not be allowed to propagate or fail test * execution. * * @since 5.4 */ @API(status = STABLE, since = "5.7") public interface TestWatcher extends Extension { /** * Invoked after a disabled test has been skipped. * *

The default implementation does nothing. Concrete implementations can * override this method as appropriate. * * @param context the current extension context; never {@code null} * @param reason the reason the test is disabled; never {@code null} but * potentially empty */ default void testDisabled(ExtensionContext context, Optional reason) { /* no-op */ } /** * Invoked after a test has completed successfully. * *

The default implementation does nothing. Concrete implementations can * override this method as appropriate. * * @param context the current extension context; never {@code null} */ default void testSuccessful(ExtensionContext context) { /* no-op */ } /** * Invoked after a test has been aborted. * *

The default implementation does nothing. Concrete implementations can * override this method as appropriate. * * @param context the current extension context; never {@code null} * @param cause the throwable responsible for the test being aborted; may be {@code null} */ default void testAborted(ExtensionContext context, @Nullable Throwable cause) { /* no-op */ } /** * Invoked after a test has failed. * *

The default implementation does nothing. Concrete implementations can * override this method as appropriate. * * @param context the current extension context; never {@code null} * @param cause the throwable that caused test failure; may be {@code null} */ default void testFailed(ExtensionContext context, @Nullable Throwable cause) { /* no-op */ } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * JUnit Jupiter API for writing extensions. */ @NullMarked package org.junit.jupiter.api.extension; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolver.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension.support; import static org.apiguardian.api.API.Status.STABLE; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.platform.commons.util.Preconditions; /** * {@link ParameterResolver} adapter which resolves a parameter based on its exact type. * * @param the type of the parameter supported by this {@code ParameterResolver} * @since 5.6 */ @API(status = STABLE, since = "5.10") public abstract class TypeBasedParameterResolver implements ParameterResolver { private final Type supportedParameterType; public TypeBasedParameterResolver() { this.supportedParameterType = enclosedTypeOfParameterResolver(); } @Override public final boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return this.supportedParameterType.equals(getParameterType(parameterContext)); } @Override public abstract T resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException; private Type getParameterType(ParameterContext parameterContext) { return parameterContext.getParameter().getParameterizedType(); } private Type enclosedTypeOfParameterResolver() { ParameterizedType typeBasedParameterResolverSuperclass = Preconditions.notNull( findTypeBasedParameterResolverSuperclass(getClass()), () -> """ Failed to discover parameter type supported by %s; \ potentially caused by lacking parameterized type in class declaration.""".formatted( getClass().getName())); return typeBasedParameterResolverSuperclass.getActualTypeArguments()[0]; } private @Nullable ParameterizedType findTypeBasedParameterResolverSuperclass(Class clazz) { Class superclass = clazz.getSuperclass(); // Abort? if (superclass == null || superclass == Object.class) { return null; } Type genericSuperclass = clazz.getGenericSuperclass(); if (genericSuperclass instanceof ParameterizedType type) { Type rawType = type.getRawType(); if (rawType == TypeBasedParameterResolver.class) { return type; } } return findTypeBasedParameterResolverSuperclass(superclass); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * JUnit Jupiter API support for writing extensions. */ @NullMarked package org.junit.jupiter.api.extension.support; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/Executable.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.function; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; /** * {@code Executable} is a functional interface that can be used to * implement any generic block of code that potentially throws a * {@link Throwable}. * *

The {@code Executable} interface is similar to {@link java.lang.Runnable}, * except that an {@code Executable} can throw any kind of exception. * *

Rationale for throwing {@code Throwable} instead of {@code Exception}

* *

Although Java applications typically throw exceptions that are instances * of {@link java.lang.Exception}, {@link java.lang.RuntimeException}, * {@link java.lang.Error}, or {@link java.lang.AssertionError} (in testing * scenarios), there may be use cases where an {@code Executable} needs to * explicitly throw a {@code Throwable}. In order to support such specialized * use cases, {@link #execute()} is declared to throw {@code Throwable}. * * @since 5.0 * @see org.junit.jupiter.api.Assertions#assertAll(Executable...) * @see org.junit.jupiter.api.Assertions#assertAll(String, Executable...) * @see org.junit.jupiter.api.Assertions#assertThrows(Class, Executable) * @see org.junit.jupiter.api.Assumptions#assumingThat(java.util.function.BooleanSupplier, Executable) * @see org.junit.jupiter.api.DynamicTest#dynamicTest(String, Executable) * @see ThrowingConsumer * @see ThrowingSupplier */ @FunctionalInterface @API(status = STABLE, since = "5.0") public interface Executable { void execute() throws Throwable; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingConsumer.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.function; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; /** * {@code ThrowingConsumer} is a functional interface that can be used to * implement any generic block of code that consumes an argument and * potentially throws a {@link Throwable}. * *

The {@code ThrowingConsumer} interface is similar to * {@link java.util.function.Consumer}, except that a {@code ThrowingConsumer} * can throw any kind of exception, including checked exceptions. * *

Rationale for throwing {@code Throwable} instead of {@code Exception}

* *

Although Java applications typically throw exceptions that are instances * of {@link java.lang.Exception}, {@link java.lang.RuntimeException}, * {@link java.lang.Error}, or {@link java.lang.AssertionError} (in testing * scenarios), there may be use cases where a {@code ThrowingConsumer} needs to * explicitly throw a {@code Throwable}. In order to support such specialized * use cases, {@link #accept} is declared to throw {@code Throwable}. * * @param the type of argument consumed * @since 5.0 * @see java.util.function.Consumer * @see org.junit.jupiter.api.DynamicTest#stream * @see Executable * @see ThrowingSupplier */ @FunctionalInterface @API(status = STABLE, since = "5.0") public interface ThrowingConsumer { /** * Consume the supplied argument, potentially throwing an exception. * * @param t the argument to consume */ void accept(T t) throws Throwable; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingSupplier.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.function; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; /** * {@code ThrowingSupplier} is a functional interface that can be used to * implement any generic block of code that returns an object and * potentially throws a {@link Throwable}. * *

The {@code ThrowingSupplier} interface is similar to * {@link java.util.function.Supplier}, except that a {@code ThrowingSupplier} * can throw any kind of exception, including checked exceptions. * *

Rationale for throwing {@code Throwable} instead of {@code Exception}

* *

Although Java applications typically throw exceptions that are instances * of {@link Exception}, {@link RuntimeException}, * {@link Error}, or {@link AssertionError} (in testing * scenarios), there may be use cases where a {@code ThrowingSupplier} needs to * explicitly throw a {@code Throwable}. In order to support such specialized * use cases, {@link #get} is declared to throw {@code Throwable}. * * @param the type of argument supplied * @since 5.0 * @see java.util.function.Supplier * @see org.junit.jupiter.api.Assertions#assertTimeout(java.time.Duration, ThrowingSupplier) * @see org.junit.jupiter.api.Assertions#assertTimeoutPreemptively(java.time.Duration, ThrowingSupplier) * @see Executable * @see ThrowingConsumer */ @FunctionalInterface @API(status = STABLE, since = "5.0") public interface ThrowingSupplier { /** * Get a result, potentially throwing an exception. * * @return a result */ T get() throws Throwable; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Functional interfaces used within JUnit Jupiter. */ @NullMarked package org.junit.jupiter.api.function; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/CleanupMode.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.io; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; /** * Enumeration of cleanup modes for {@link TempDir @TempDir}. * *

When a test with a temporary directory completes, it might be useful in * some cases to be able to view the contents of the temporary directory used by * the test. {@code CleanupMode} allows you to control how a {@code TempDir} * is cleaned up. * * @since 5.9 * @see TempDir */ @API(status = STABLE, since = "5.11") public enum CleanupMode { /** * Use the default cleanup mode. * * @see TempDir#DEFAULT_CLEANUP_MODE_PROPERTY_NAME */ DEFAULT, /** * Always clean up a temporary directory after the test has completed. */ ALWAYS, /** * Only clean up a temporary directory if the test completed successfully. */ ON_SUCCESS, /** * Never clean up a temporary directory after the test has completed. */ NEVER } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/DefaultDeletionResult.java ================================================ /* * Copyright 2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.io; import static java.util.Comparator.comparing; import static java.util.stream.Collectors.joining; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Optional; import org.junit.jupiter.api.io.TempDirDeletionStrategy.DeletionException; import org.junit.jupiter.api.io.TempDirDeletionStrategy.DeletionFailure; import org.junit.jupiter.api.io.TempDirDeletionStrategy.DeletionResult; import org.junit.platform.commons.util.Preconditions; record DefaultDeletionResult(Path rootDir, List failures) implements DeletionResult { DefaultDeletionResult(Path rootDir, List failures) { this.rootDir = rootDir; this.failures = List.copyOf(failures); } @Override public Optional toException() { if (isSuccessful()) { return Optional.empty(); } var joinedPaths = failures().stream() // .map(DeletionFailure::path) // .sorted() // .distinct() // .map(path -> relativizeSafely(rootDir(), path).toString()) // .map(path -> path.isEmpty() ? "" : path) // .collect(joining(", ")); var exception = new DeletionException("Failed to delete temp directory " + rootDir().toAbsolutePath() + ". The following paths could not be deleted (see suppressed exceptions for details): " + joinedPaths); failures().stream() // .sorted(comparing(DeletionFailure::path)) // .map(DeletionFailure::cause) // .forEach(exception::addSuppressed); return Optional.of(exception); } private static Path relativizeSafely(Path rootDir, Path path) { try { return rootDir.relativize(path); } catch (IllegalArgumentException e) { return path; } } static final class Builder implements DeletionResult.Builder { private final Path rootDir; private final List failures = new ArrayList<>(); Builder(Path rootDir) { this.rootDir = rootDir; } @Override public Builder addFailure(Path path, Exception cause) { Preconditions.notNull(path, "path must not be null"); Preconditions.notNull(cause, "cause must not be null"); failures.add(new DefaultDeletionFailure(path, cause)); return this; } @Override public DefaultDeletionResult build() { return new DefaultDeletionResult(rootDir, failures); } } record DefaultDeletionFailure(Path path, Exception cause) implements DeletionFailure { } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDir.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.io; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.io.File; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.nio.file.Path; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ExtensionConfigurationException; import org.junit.jupiter.api.extension.ParameterResolutionException; /** * {@code @TempDir} can be used to annotate a field in a test class or a parameter * in a test class constructor, lifecycle method, or test method of type * {@link Path} or {@link File} that should be resolved into a temporary directory. * *

Creation

* *

The temporary directory is only created if a field in a test class or a * parameter in a test class constructor, lifecycle method, or test method is * annotated with {@code @TempDir}. An {@link ExtensionConfigurationException} or * a {@link ParameterResolutionException} will be thrown in one of the following * cases: * *

    *
  • If the field type or parameter type is neither {@code Path} nor {@code File}.
  • *
  • If a field is declared as {@code final}.
  • *
  • If the temporary directory cannot be created.
  • *
  • If the field type or parameter type is {@code File} and a custom * {@link TempDir#factory() factory} is used, which creates a temporary * directory that does not belong to the * {@linkplain java.nio.file.FileSystems#getDefault() default file system}. *
  • *
* *

Scope

* *

By default, a separate temporary directory is created for every declaration * of the {@code @TempDir} annotation. For better isolation when using * {@link org.junit.jupiter.api.TestInstance.Lifecycle#PER_METHOD @TestInstance(Lifecycle.PER_METHOD)} * semantics, you can annotate an instance field or a parameter in the test class * constructor with {@code @TempDir} so that each test method uses a separate * temporary directory. Alternatively, if you want to share a temporary directory * across all tests in a test class, you should declare the annotation on a * {@code static} field or on a parameter of a * {@link org.junit.jupiter.api.BeforeAll @BeforeAll} method. * *

Cleanup/Deletion

* *

By default, when the end of the scope of a temporary directory is reached, * — when the test method or class has finished execution — JUnit will * attempt to clean up the temporary directory by recursively deleting all files * and directories in the temporary directory and, finally, the temporary directory * itself. * *

Two attributes allow customizing when (see {@link #cleanup()}) * and how (see {@link #deletionStrategy()}) to clean up. * *

The {@link #cleanup} attribute allows you to configure the {@link CleanupMode}. * If the cleanup mode is set to {@link CleanupMode#NEVER NEVER}, the temporary * directory will not be cleaned up after the test completes. If the cleanup mode is * set to {@link CleanupMode#ON_SUCCESS ON_SUCCESS}, the temporary directory will * only be cleaned up if the test completes successfully. By default, the * {@link CleanupMode#ALWAYS ALWAYS} clean up mode will be used, but this can be * configured globally by setting the {@value #DEFAULT_CLEANUP_MODE_PROPERTY_NAME} * configuration parameter. * *

The {@link #deletionStrategy()} attribute defines the strategy for * performing the cleanup and dealing with errors such as undeletable files. * By default, the {@link TempDirDeletionStrategy.Standard Standard} strategy is * used which will cause a test or test class to fail in case deletion of a file * or directory fails. This can be configured globally by setting the * {@value #DEFAULT_DELETION_STRATEGY_PROPERTY_NAME} configuration parameter. * * @since 5.4 */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = STABLE, since = "5.10") public @interface TempDir { /** * Property name used to set the default temporary directory factory class name: * {@value} * *

Supported Values

* *

Supported values include fully qualified class names for types that * implement {@link TempDirFactory}. * *

If not specified, the default is {@link TempDirFactory.Standard}. * * @since 5.10 */ @API(status = MAINTAINED, since = "5.13.3") String DEFAULT_FACTORY_PROPERTY_NAME = "junit.jupiter.tempdir.factory.default"; /** * Factory for the temporary directory. * *

Defaults to {@link TempDirFactory.Standard}. * *

As an alternative to setting this attribute, a global * {@link TempDirFactory} can be configured for the entire test suite via * the {@value #DEFAULT_FACTORY_PROPERTY_NAME} configuration parameter. * See the User Guide for details. Note, however, that a {@code @TempDir} * declaration with a custom {@code factory} always overrides a global * {@code TempDirFactory}. * * @return the type of {@code TempDirFactory} to use * @since 5.10 * @see TempDirFactory */ @API(status = MAINTAINED, since = "5.13.3") Class factory() default TempDirFactory.class; /** * Property name used to configure the default {@link CleanupMode}: {@value} * *

Supported values include names of enum constants defined in * {@link CleanupMode}, ignoring case. * *

If this configuration parameter is not set, {@link CleanupMode#ALWAYS} * will be used as the default. * * @since 5.9 */ @API(status = MAINTAINED, since = "5.13.3") String DEFAULT_CLEANUP_MODE_PROPERTY_NAME = "junit.jupiter.tempdir.cleanup.mode.default"; /** * In which cases the temporary directory gets cleaned up after the test completes. * * @since 5.9 */ @API(status = STABLE, since = "5.11") CleanupMode cleanup() default CleanupMode.DEFAULT; /** * Property name used to set the default deletion strategy class name: * {@value} * *

Supported Values

* *

Supported values include fully qualified class names for types that * implement {@link TempDirDeletionStrategy}. * *

If not specified, the default is {@link TempDirDeletionStrategy.Standard}. * * @since 6.1 */ @API(status = EXPERIMENTAL, since = "6.1") String DEFAULT_DELETION_STRATEGY_PROPERTY_NAME = "junit.jupiter.tempdir.deletion.strategy.default"; /** * Deletion strategy for the temporary directory. * *

Defaults to {@link TempDirDeletionStrategy.Standard}. * *

As an alternative to setting this attribute, a global * {@link TempDirDeletionStrategy} can be configured for the entire test * suite via the {@value #DEFAULT_DELETION_STRATEGY_PROPERTY_NAME} * configuration parameter. See the User Guide for details. Note, however, * that a {@code @TempDir} declaration with a custom * {@code deletionStrategy} always overrides a global * {@code TempDirDeletionStrategy}. * * @return the type of {@code TempDirDeletionStrategy} to use * @since 6.1 * @see TempDirDeletionStrategy */ @API(status = EXPERIMENTAL, since = "6.1") Class deletionStrategy() default TempDirDeletionStrategy.class; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDirDeletionStrategy.java ================================================ /* * Copyright 2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.io; import static java.nio.file.FileVisitResult.CONTINUE; import static java.nio.file.FileVisitResult.SKIP_SUBTREE; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.INTERNAL; import java.io.File; import java.io.IOException; import java.io.Serial; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Field; import java.lang.reflect.Parameter; import java.nio.file.DirectoryNotEmptyException; import java.nio.file.FileSystems; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.DosFileAttributeView; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.function.BiConsumer; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.AnnotatedElementContext; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.ClassUtils; import org.junit.platform.commons.util.Preconditions; /** * {@code TempDirDeletionStrategy} defines the SPI for deleting temporary * directories programmatically. * *

A deletion strategy controls how a temporary directory is cleaned up * when the end of its scope is reached. * *

Implementations must provide a no-args constructor. * *

A {@link TempDirDeletionStrategy} can be configured globally * for the entire test suite via the * {@value TempDir#DEFAULT_DELETION_STRATEGY_PROPERTY_NAME} configuration * parameter (see the User Guide for details) or locally for a test * class field or method parameter via the {@link TempDir @TempDir} annotation. * * @since 6.1 * @see TempDir @TempDir */ @API(status = EXPERIMENTAL, since = "6.1") public interface TempDirDeletionStrategy { /** * Delete the supplied temporary directory and all of its contents. * *

Depending on the used {@link TempDirFactory}, the supplied * {@link Path} may or may not be associated with the * {@linkplain java.nio.file.FileSystems#getDefault() default FileSystem}. * * @param tempDir the temporary directory to delete; never {@code null} * @param elementContext the context of the field or parameter where * {@code @TempDir} is declared; never {@code null} * @param extensionContext the current extension context; never {@code null} * @return a {@link DeletionResult}, potentially containing failures for * {@link Path Paths} that could not be deleted or no failures if deletion * was successful; never {@code null} * @throws IOException in case of general failures */ DeletionResult delete(Path tempDir, AnnotatedElementContext elementContext, ExtensionContext extensionContext) throws IOException; /** * A {@link TempDirDeletionStrategy} that delegates to {@link Standard} but * suppresses deletion failures by logging a warning instead of propagating * them. */ final class IgnoreFailures implements TempDirDeletionStrategy { private static final Logger logger = LoggerFactory.getLogger(IgnoreFailures.class); private final TempDirDeletionStrategy delegate; /** * Create a new {@code IgnoreFailures} strategy that delegates to * {@link Standard}. */ public IgnoreFailures() { this(Standard.INSTANCE); } IgnoreFailures(TempDirDeletionStrategy delegate) { this.delegate = delegate; } @Override public DeletionResult delete(Path tempDir, AnnotatedElementContext elementContext, ExtensionContext extensionContext) throws IOException { var result = delegate.delete(tempDir, elementContext, extensionContext); result.toException().ifPresent(ex -> logWarning(elementContext, ex)); return DeletionResult.builder(tempDir).build(); } private void logWarning(AnnotatedElementContext elementContext, DeletionException exception) { logger.warn(exception, () -> "Failed to delete all temporary files for %s".formatted( descriptionFor(elementContext.getAnnotatedElement()))); } @API(status = INTERNAL, since = "6.1") public static String descriptionFor(AnnotatedElement annotatedElement) { if (annotatedElement instanceof Field field) { return "field " + field.getDeclaringClass().getSimpleName() + "." + field.getName(); } if (annotatedElement instanceof Parameter parameter) { Executable executable = parameter.getDeclaringExecutable(); return "parameter '" + parameter.getName() + "' in " + descriptionFor(executable); } throw new IllegalStateException("Unsupported AnnotatedElement type for @TempDir: " + annotatedElement); } private static String descriptionFor(Executable executable) { boolean isConstructor = executable instanceof Constructor; String type = isConstructor ? "constructor" : "method"; String name = isConstructor ? executable.getDeclaringClass().getSimpleName() : executable.getName(); return "%s %s(%s)".formatted(type, name, ClassUtils.nullSafeToString(Class::getSimpleName, executable.getParameterTypes())); } } /** * Standard {@link TempDirDeletionStrategy} implementation that recursively * deletes all files and directories within the temporary directory. * *

Symbolic and other types of links, such as junctions on Windows, are * not followed. A warning is logged when deleting a link that targets a * location outside the temporary directory. * *

If a file or directory cannot be deleted, its permissions are reset * and deletion is attempted again. If deletion still fails, the path is * scheduled for deletion on JVM exit via * {@link java.io.File#deleteOnExit()}, if it belongs to the default file * system. */ final class Standard implements TempDirDeletionStrategy { /** * The singleton instance of {@code Standard}. */ public static final Standard INSTANCE = new Standard(); private static final Logger logger = LoggerFactory.getLogger(Standard.class); private Standard() { } @Override public DeletionResult delete(Path tempDir, AnnotatedElementContext elementContext, ExtensionContext extensionContext) throws IOException { return delete(tempDir, Files::delete); } // package-private for testing DeletionResult delete(Path tempDir, FileOperations fileOperations) throws IOException { var result = DeletionResult.builder(tempDir); delete(tempDir, fileOperations, (path, cause) -> { result.addFailure(path, cause); tryToDeleteOnExit(path); }); return result.build(); } private void delete(Path tempDir, FileOperations fileOperations, BiConsumer failureHandler) throws IOException { Set retriedPaths = new HashSet<>(); Path rootRealPath = tempDir.toRealPath(); tryToResetPermissions(tempDir); Files.walkFileTree(tempDir, new SimpleFileVisitor<>() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { logger.trace(() -> "preVisitDirectory: " + dir); if (isLinkWithTargetOutsideTempDir(dir)) { warnAboutLinkWithTargetOutsideTempDir("link", dir); delete(dir, fileOperations); return SKIP_SUBTREE; } if (!dir.equals(tempDir)) { tryToResetPermissions(dir); } return CONTINUE; } @Override public FileVisitResult visitFileFailed(Path file, IOException exc) { logger.trace(exc, () -> "visitFileFailed: " + file); if (exc instanceof NoSuchFileException && !Files.exists(file, LinkOption.NOFOLLOW_LINKS)) { return CONTINUE; } // IOException includes `AccessDeniedException` thrown by non-readable or non-executable flags resetPermissionsAndTryToDeleteAgain(file, exc); return CONTINUE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) throws IOException { logger.trace(() -> "visitFile: " + file); if (Files.isSymbolicLink(file) && isLinkWithTargetOutsideTempDir(file)) { warnAboutLinkWithTargetOutsideTempDir("symbolic link", file); } delete(file, fileOperations); return CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, @Nullable IOException exc) { logger.trace(exc, () -> "postVisitDirectory: " + dir); delete(dir, fileOperations); return CONTINUE; } private boolean isLinkWithTargetOutsideTempDir(Path path) { // While `Files.walkFileTree` does not follow symbolic links, it may follow other links // such as "junctions" on Windows try { return !path.toRealPath().startsWith(rootRealPath); } catch (IOException e) { logger.trace(e, () -> "Failed to determine real path for " + path + "; assuming it is not a link"); return false; } } private void warnAboutLinkWithTargetOutsideTempDir(String linkType, Path file) throws IOException { Path realPath = file.toRealPath(); logger.warn(() -> """ Deleting %s from location inside of temp dir (%s) \ to location outside of temp dir (%s) but not the target file/directory""".formatted( linkType, file, realPath)); } private void delete(Path path, FileOperations fileOperations) { try { deleteWithLogging(path, fileOperations); } catch (NoSuchFileException ignore) { // ignore } catch (DirectoryNotEmptyException exception) { failureHandler.accept(path, exception); } catch (IOException exception) { // IOException includes `AccessDeniedException` thrown by non-readable or non-executable flags resetPermissionsAndTryToDeleteAgain(path, exception); } } private void resetPermissionsAndTryToDeleteAgain(Path path, IOException exception) { boolean notYetRetried = retriedPaths.add(path); if (notYetRetried) { try { tryToResetPermissions(path); if (Files.isDirectory(path)) { Files.walkFileTree(path, this); } else { deleteWithLogging(path, fileOperations); } } catch (Exception suppressed) { exception.addSuppressed(suppressed); failureHandler.accept(path, exception); } } else { failureHandler.accept(path, exception); } } }); } private void deleteWithLogging(Path file, FileOperations fileOperations) throws IOException { logger.trace(() -> "Attempting to delete " + file); try { fileOperations.delete(file); logger.trace(() -> "Successfully deleted " + file); } catch (IOException e) { logger.trace(e, () -> "Failed to delete " + file); throw e; } } @SuppressWarnings("ResultOfMethodCallIgnored") private static void tryToResetPermissions(Path path) { File file; try { file = path.toFile(); } catch (UnsupportedOperationException ignore) { // Might happen when the `TempDirFactory` uses a custom `FileSystem` return; } file.setReadable(true); file.setWritable(true); if (Files.isDirectory(path)) { file.setExecutable(true); } DosFileAttributeView dos = Files.getFileAttributeView(path, DosFileAttributeView.class); if (dos != null) { try { dos.setReadOnly(false); } catch (IOException ignore) { // nothing we can do } } } @SuppressWarnings("EmptyCatch") private static void tryToDeleteOnExit(Path path) { try { if (FileSystems.getDefault().equals(path.getFileSystem())) { path.toFile().deleteOnExit(); } } catch (UnsupportedOperationException ignore) { } } // For testing only interface FileOperations { void delete(Path path) throws IOException; } } /** * Represents the result of a {@link TempDirDeletionStrategy#delete} operation, * including any paths that could not be deleted. */ sealed interface DeletionResult permits DefaultDeletionResult { /** * Create a new {@link Builder} for the supplied root directory. * * @param rootDir the root temporary directory; never {@code null} * @return a new {@code Builder}; never {@code null} */ static Builder builder(Path rootDir) { return new DefaultDeletionResult.Builder(Preconditions.notNull(rootDir, "rootDir must not be null")); } /** * Return the root temporary directory of this deletion operation. * * @return the root directory; never {@code null} */ Path rootDir(); /** * Return the list of failures that occurred during deletion. * * @return the list of failures; never {@code null} */ List failures(); /** * Return {@code true} if the deletion was successful, i.e., no * {@linkplain #failures() failures} were recorded. */ default boolean isSuccessful() { return failures().isEmpty(); } /** * Convert this result to a {@link DeletionException} summarizing all * failures. * *

Must only be called if {@link #isSuccessful()} returns * {@code false}. * * @return an {@link DeletionException}, if the deletion * {@linkplain #isSuccessful() was successful; otherwise, empty}; never * {@code null} */ Optional toException(); /** * Builder for {@link DeletionResult}. */ sealed interface Builder permits DefaultDeletionResult.Builder { /** * Record a failure for the supplied path. * * @param path the path that could not be deleted; never {@code null} * @param cause the exception that caused the failure; never {@code null} * @return this builder; never {@code null} */ Builder addFailure(Path path, Exception cause); /** * Build the {@link DeletionResult}. * * @return a new {@link DeletionResult}; never {@code null} */ DeletionResult build(); } } /** * Represents a single failure that occurred while attempting to delete a * path during a {@link TempDirDeletionStrategy#delete} operation. */ sealed interface DeletionFailure permits DefaultDeletionResult.DefaultDeletionFailure { /** * Return the path that could not be deleted. * * @return the path; never {@code null} */ Path path(); /** * Return the exception that caused the failure. * * @return the cause; never {@code null} */ Exception cause(); } /** * Exception thrown when one or more paths in a temporary directory could * not be deleted by a {@link TempDirDeletionStrategy}. */ final class DeletionException extends JUnitException { @Serial private static final long serialVersionUID = 1L; DeletionException(String message) { super(message, null, true, false); } } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDirFactory.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.io; import static org.apiguardian.api.API.Status.MAINTAINED; import java.io.Closeable; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.AnnotatedElementContext; import org.junit.jupiter.api.extension.ExtensionContext; /** * {@code TempDirFactory} defines the SPI for creating temporary directories * programmatically. * *

A temporary directory factory is typically used to gain control over the * temporary directory creation, like defining the parent directory or the file * system that should be used. * *

Implementations must provide a no-args constructor and should not make any * assumptions regarding when and how many times they are instantiated, but they * can assume that {@link #createTempDirectory(AnnotatedElementContext, ExtensionContext)} * and {@link #close()} will both be called once per instance, in this order, * and from the same thread. * *

A {@link TempDirFactory} can be configured globally for the * entire test suite via the {@value TempDir#DEFAULT_FACTORY_PROPERTY_NAME} * configuration parameter (see the User Guide for details) or locally * for a test class field or method parameter via the {@link TempDir @TempDir} * annotation. * * @since 5.10 * @see TempDir @TempDir */ @FunctionalInterface @API(status = MAINTAINED, since = "5.13.3") public interface TempDirFactory extends Closeable { /** * Create a new temporary directory. * *

Depending on the implementation, the resulting {@link Path} may or may * not be associated with the {@link java.nio.file.FileSystems#getDefault() * default FileSystem}. * * @param elementContext the context of the field or parameter where * {@code @TempDir} is declared; never {@code null} * @param extensionContext the current extension context; never {@code null} * @return the path to the newly created temporary directory; never {@code null} * @throws Exception in case of failures */ Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) throws Exception; /** * {@inheritDoc} */ @Override default void close() throws IOException { } /** * Standard {@link TempDirFactory} implementation which delegates to * {@link Files#createTempDirectory} using {@code "junit-"} as prefix. * *

The created temporary directory is always created in the default * file system with the system's default temporary directory as its parent. * * @see Files#createTempDirectory(java.lang.String, java.nio.file.attribute.FileAttribute[]) */ class Standard implements TempDirFactory { public static final TempDirFactory INSTANCE = new Standard(); private static final String TEMP_DIR_PREFIX = "junit-"; public Standard() { } @Override public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) throws IOException { return Files.createTempDirectory(TEMP_DIR_PREFIX); } } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * IO-related support in JUnit Jupiter. */ @NullMarked package org.junit.jupiter.api.io; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * JUnit Jupiter API for writing tests. */ @NullMarked package org.junit.jupiter.api; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Execution.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.parallel; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.TestInstance; /** * {@code @Execution} is used to configure the parallel execution * {@linkplain #value mode} of a test class or test method. * *

This annotation is {@linkplain Inherited inherited} within class hierarchies. * *

Default Execution Mode

* *

If this annotation is not present, {@link ExecutionMode#SAME_THREAD} is * used unless a default execution mode is defined via one of the following * configuration parameters: * *

*
{@value #DEFAULT_EXECUTION_MODE_PROPERTY_NAME}
*
Default execution mode for all classes and tests
*
{@value #DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME}
*
Default execution mode for top-level classes
*
* *

{@value #DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME} overrides * {@value #DEFAULT_EXECUTION_MODE_PROPERTY_NAME} for top-level classes. * *

The default execution mode is not applied to classes that use the * {@link TestInstance.Lifecycle#PER_CLASS PER_CLASS} lifecycle or a * {@link MethodOrderer}. In both cases, test methods in such test classes are * only executed concurrently if the {@code @Execution(CONCURRENT)} annotation * is present on the test class or method. * * @see Isolated * @see ResourceLock * @since 5.3 */ @API(status = STABLE, since = "5.10") @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) @Inherited public @interface Execution { /** * Property name used to set the default test execution mode: {@value} * *

This setting is only effective if parallel execution is enabled. * *

Supported Values

* *

Supported values include names of enum constants defined in * {@link ExecutionMode}, ignoring case. * *

If not specified, the default is "same_thread" which corresponds to * {@code @Execution(ExecutionMode.SAME_THREAD)}. * * @since 5.4 */ @API(status = MAINTAINED, since = "5.13.3") String DEFAULT_EXECUTION_MODE_PROPERTY_NAME = "junit.jupiter.execution.parallel.mode.default"; /** * Property name used to set the default test execution mode for top-level * classes: {@value} * *

This setting is only effective if parallel execution is enabled. * *

Supported Values

* *

Supported values include names of enum constants defined in * {@link ExecutionMode}, ignoring case. * *

If not specified, it will be resolved into the same value as * {@link #DEFAULT_EXECUTION_MODE_PROPERTY_NAME}. * * @since 5.4 */ @API(status = MAINTAINED, since = "5.13.3") String DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME = "junit.jupiter.execution.parallel.mode.classes.default"; /** * The required/preferred execution mode. * * @see ExecutionMode */ ExecutionMode value(); /** * The reason for using the selected execution mode. * *

This is for informational purposes only. * * @since 5.10 */ @API(status = STABLE, since = "5.10") String reason() default ""; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ExecutionMode.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.parallel; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; /** * Supported execution modes for parallel test execution. * * @since 5.3 * @see #SAME_THREAD * @see #CONCURRENT */ @API(status = STABLE, since = "5.10") public enum ExecutionMode { /** * Force execution in same thread as the parent node. * * @see #CONCURRENT */ SAME_THREAD, /** * Allow concurrent execution with any other node. * * @see #SAME_THREAD */ CONCURRENT } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Isolated.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.parallel; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @Isolated} is used to declare that the annotated test class should be * executed in isolation from other test classes. * *

When a test class is run in isolation, no other test class is executed * concurrently. This can be used to enable parallel test execution for the * entire test suite while running some tests in isolation (e.g. if they modify * some global resource). * * @since 5.7 * @see ExecutionMode * @see ResourceLock */ @API(status = STABLE, since = "5.10") @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited @ResourceLock(Resources.GLOBAL) public @interface Isolated { /** * The reason this test class needs to run in isolation. * *

The supplied string is currently not reported in any way but can be * used for documentation purposes. */ String value() default ""; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceAccessMode.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.parallel; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; /** * The access mode required by a test class or method for a given resource. * * @since 5.3 * @see ResourceLock * @see ResourceLocksProvider.Lock */ @API(status = STABLE, since = "5.10") public enum ResourceAccessMode { /** * Require read and write access to the resource. */ READ_WRITE, /** * Require only read access to the resource. */ READ } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLock.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.parallel; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.api.ClassTemplate; /** * {@code @ResourceLock} is used to declare that the annotated test class or test * method requires access to a shared resource identified by a key. * *

The resource key is specified via {@link #value}. In addition, {@link #mode} * allows one to specify whether the annotated test class or test method requires * {@link ResourceAccessMode#READ_WRITE READ_WRITE} or * {@link ResourceAccessMode#READ READ} access to the resource. In the former case, * execution of the annotated element will occur while no other test class or * test method that uses the shared resource is being executed. In the latter case, * the annotated element may be executed concurrently with other test classes or * methods that also require {@code READ} access but not at the same time as any * other test that requires {@code READ_WRITE} access. * *

This guarantee extends to lifecycle methods of a test class or method. For * example, if a test method is annotated with {@code @ResourceLock} the lock * will be acquired before any {@link org.junit.jupiter.api.BeforeEach @BeforeEach} * methods are executed and released after all * {@link org.junit.jupiter.api.AfterEach @AfterEach} methods have been executed. * *

This annotation can be repeated to declare the use of multiple shared resources. * *

Uniqueness of a shared resource is determined by both the {@link #value()} * and the {@link #mode()}. Duplicated shared resources do not cause errors. * *

This annotation is {@linkplain Inherited inherited} within class hierarchies. * *

Since JUnit Jupiter 5.12, this annotation supports adding shared resources * dynamically at runtime via {@link #providers}. Resources declared "statically" * using {@link #value()} and {@link #mode()} are combined with "dynamic" resources * added via {@link #providers()}. For example, declaring resource "A" via * {@code @ResourceLock("A")} and resource "B" via a provider returning * {@code new Lock("B")} will result in two shared resources "A" and "B". * *

Since JUnit Jupiter 5.12, this annotation supports declaring "static" * shared resources for direct child nodes via the {@link #target()} * attribute. Using {@link ResourceLockTarget#CHILDREN} in a class-level annotation * has the same semantics as adding an annotation with the same {@link #value()} * and {@link #mode()} to each test method and nested test class declared in the * annotated class. This may improve parallelization when a test class declares a * {@link ResourceAccessMode#READ READ} lock, but only a few methods hold * {@link ResourceAccessMode#READ_WRITE READ_WRITE} lock. Note that * {@code target = CHILDREN} means that {@link #value()} and {@link #mode()} no * longer apply to a node declaring the annotation. However, the {@link #providers()} * attribute remains applicable, and the target of "dynamic" shared resources added * via implementations of {@link ResourceLocksProvider} is not changed. * *

Shared resources declared on or provided for methods or nested test * classes in a {@link ClassTemplate @ClassTemplate} are propagated as if they * were declared on the outermost enclosing {@code @ClassTemplate} class itself. * * @see Isolated * @see Resources * @see ResourceAccessMode * @see ResourceLockTarget * @see ResourceLocks * @see ResourceLocksProvider * @since 5.3 */ @API(status = STABLE, since = "5.10") @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) @Inherited @Repeatable(ResourceLocks.class) public @interface ResourceLock { /** * The resource key. * *

Defaults to an empty string. * * @see Resources * @see ResourceLocksProvider.Lock#getKey() */ String value() default ""; /** * The resource access mode. * *

Defaults to {@link ResourceAccessMode#READ_WRITE READ_WRITE}. * * @see ResourceAccessMode * @see ResourceLocksProvider.Lock#getAccessMode() */ ResourceAccessMode mode() default ResourceAccessMode.READ_WRITE; /** * An array of one or more classes implementing {@link ResourceLocksProvider}. * *

Defaults to an empty array. * * @see ResourceLocksProvider.Lock * @since 5.12 */ @API(status = MAINTAINED, since = "5.13.3") Class[] providers() default {}; /** * The target of a resource created from {@link #value()} and {@link #mode()}. * *

Defaults to {@link ResourceLockTarget#SELF SELF}. * *

Note that using {@link ResourceLockTarget#CHILDREN} in a method-level * annotation results in an exception. * * @see ResourceLockTarget * @since 5.12 */ @API(status = MAINTAINED, since = "5.13.3") ResourceLockTarget target() default ResourceLockTarget.SELF; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLockTarget.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.parallel; import static org.apiguardian.api.API.Status.MAINTAINED; import org.apiguardian.api.API; /** * {@code ResourceLockTarget} is used to define the target of a shared resource. * * @since 5.12 * @see ResourceLock#target() */ @API(status = MAINTAINED, since = "5.13.3") public enum ResourceLockTarget { /** * Add a shared resource to the current node. */ SELF, /** * Add a shared resource to the direct children of the current node. * *

Examples of "parent - child" relationships in the context of * {@code ResourceLockTarget}: *

    *
  • test class: test methods and nested test classes * declared in the test class are children of the test class.
  • *
  • nested test class: test methods and nested test * classes declared in the nested class are children of the nested test class. *
  • *
  • test method: test methods are not considered to have * children. Using {@code CHILDREN} for a test method results in an * exception.
  • *
*/ CHILDREN } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocks.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.parallel; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @ResourceLocks} is a container for one or more * {@link ResourceLock @ResourceLock} declarations. * *

Note, however, that use of the {@code @ResourceLocks} container is * completely optional since {@code @ResourceLock} is a * {@linkplain java.lang.annotation.Repeatable repeatable} annotation. * *

This annotation is {@linkplain Inherited inherited} within class hierarchies. * * @see ResourceLock * @since 5.3 */ @API(status = STABLE, since = "5.10") @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) @Inherited public @interface ResourceLocks { /** * An array of one or more {@linkplain ResourceLock @ResourceLock} declarations. */ ResourceLock[] value(); } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.parallel; import static java.util.Collections.emptySet; import static org.apiguardian.api.API.Status.MAINTAINED; import java.lang.reflect.Method; import java.util.List; import java.util.Objects; import java.util.Set; import org.apiguardian.api.API; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; /** * A {@code ResourceLocksProvider} is used to programmatically add shared resources * to a test class or its test methods dynamically at runtime. * *

Each shared resource is represented by an instance of {@link Lock}. * *

Adding shared resources via this API has the same semantics as declaring * them declaratively via {@link ResourceLock @ResourceLock(value, mode)}, but for * some use cases the programmatic approach may be more flexible and less verbose. * *

Implementations must provide a no-args constructor. * * @since 5.12 * @see ResourceLock#providers() * @see Resources * @see ResourceAccessMode * @see Lock */ @API(status = MAINTAINED, since = "5.13.3") public interface ResourceLocksProvider { /** * Add shared resources for a test class. * *

Invoked in case a test class or its parent class is annotated with * {@code @ResourceLock(providers)}. * * @apiNote Adding {@linkplain Lock a shared resource} via this method has * the same semantics as annotating a test class with an analogous * {@code @ResourceLock(value, mode)} declaration. * * @param testClass a test class for which to add shared resources * @return a set of {@link Lock}; may be empty */ default Set provideForClass(Class testClass) { return emptySet(); } /** * Add shared resources for a * {@link org.junit.jupiter.api.Nested @Nested} test class. * *

Invoked in case: *

    *
  • an enclosing test class of any level or its parent class is * annotated with {@code @ResourceLock(providers = ...)}.
  • *
  • a nested test class or its parent class is annotated with * {@code @ResourceLock(providers = ...)}.
  • *
* * @apiNote Adding {@linkplain Lock a shared resource} via this method has * the same semantics as annotating a nested test class with an analogous * {@code @ResourceLock(value, mode)} declaration. * * @implNote The classes supplied as {@code enclosingInstanceTypes} may * differ from the classes returned from invocations of * {@link Class#getEnclosingClass()} — for example, when a nested test * class is inherited from a superclass. * * @param enclosingInstanceTypes the runtime types of the enclosing * instances for the test class, ordered from outermost to innermost, * excluding {@code testClass}; never {@code null} * @param testClass a nested test class for which to add shared resources * @return a set of {@link Lock}; may be empty * @see org.junit.jupiter.api.Nested @Nested */ default Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { return emptySet(); } /** * Add shared resources for a test method. * *

Invoked in case: *

    *
  • an enclosing test class of any level or its parent class is * annotated with {@code @ResourceLock(providers)}.
  • *
  • a test method is annotated with {@code @ResourceLock(providers)}.
  • *
* * @apiNote Adding {@linkplain Lock a shared resource} with this method * has the same semantics as annotating a test method * with analogous {@code @ResourceLock(value, mode)}. * * @implNote The classes supplied as {@code enclosingInstanceTypes} may * differ from the classes returned from invocations of * {@link Class#getEnclosingClass()} — for example, when a nested test * class is inherited from a superclass. Similarly, the class instance * supplied as {@code testClass} may differ from the class returned by * {@code testMethod.getDeclaringClass()} — for example, when a test * method is inherited from a superclass. * * @param enclosingInstanceTypes the runtime types of the enclosing * instances for the test class, ordered from outermost to innermost, * excluding {@code testClass}; never {@code null} * @param testClass the test class or {@link org.junit.jupiter.api.Nested @Nested} * test class that contains the {@code testMethod} * @param testMethod a test method for which to add shared resources * @return a set of {@link Lock}; may be empty * @see org.junit.jupiter.api.Nested @Nested */ default Set provideForMethod(List> enclosingInstanceTypes, Class testClass, Method testMethod) { return emptySet(); } /** * {@code Lock} represents a shared resource. * *

Each resource is identified by a {@linkplain #getKey() key}. In addition, * the {@linkplain #getAccessMode() access mode} allows one to specify whether * a test class or test method requires {@link ResourceAccessMode#READ_WRITE * READ_WRITE} or {@link ResourceAccessMode#READ READ} access to the resource. * * @apiNote {@link #getKey()} and {@link #getAccessMode()} have the same * semantics as {@link ResourceLock#value()} and {@link ResourceLock#mode()} * respectively. * * @since 5.12 * @see Isolated * @see Resources * @see ResourceAccessMode * @see ResourceLock * @see ResourceLocksProvider */ final class Lock { private final String key; private final ResourceAccessMode accessMode; /** * Create a new {@code Lock} with {@link ResourceAccessMode#READ_WRITE}. * * @param key the identifier of the resource; never {@code null} or blank * @see ResourceLock#value() */ public Lock(String key) { this(key, ResourceAccessMode.READ_WRITE); } /** * Create a new {@code Lock}. * * @param key the identifier of the resource; never {@code null} or blank * @param accessMode the lock mode to use to synchronize access to the * resource; never {@code null} * @see ResourceLock#value() * @see ResourceLock#mode() */ public Lock(String key, ResourceAccessMode accessMode) { this.key = Preconditions.notBlank(key, "key must not be null or blank"); this.accessMode = Preconditions.notNull(accessMode, "accessMode must not be null"); } /** * Get the key for this lock. * * @see ResourceLock#value() */ public String getKey() { return this.key; } /** * Get the access mode for this lock. * * @see ResourceLock#mode() */ public ResourceAccessMode getAccessMode() { return this.accessMode; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Lock that = (Lock) o; return this.key.equals(that.key) && this.accessMode == that.accessMode; } @Override public int hashCode() { return Objects.hash(this.key, this.accessMode); } @Override public String toString() { return new ToStringBuilder(this) // .append("key", this.key) // .append("accessMode", this.accessMode) // .toString(); } } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Resources.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.parallel; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; /** * Common resource names for synchronizing test execution. * * @since 5.3 * @see ResourceLock * @see ResourceLocksProvider.Lock */ @API(status = STABLE, since = "5.10") public class Resources { /** * Represents Java's system properties: {@value} * * @see System#getProperties() * @see System#setProperties(java.util.Properties) */ public static final String SYSTEM_PROPERTIES = "java.lang.System.properties"; /** * Represents the standard output stream of the current process: {@value} * * @see System#out * @see System#setOut(java.io.PrintStream) */ public static final String SYSTEM_OUT = "java.lang.System.out"; /** * Represents the standard error stream of the current process: {@value} * * @see System#err * @see System#setErr(java.io.PrintStream) */ public static final String SYSTEM_ERR = "java.lang.System.err"; /** * Represents the default locale for the current instance of the JVM: * {@value} * * @since 5.4 * @see java.util.Locale#setDefault(java.util.Locale) */ @API(status = STABLE, since = "5.10") public static final String LOCALE = "java.util.Locale.default"; /** * Represents the default time zone for the current instance of the JVM: * {@value} * * @since 5.4 * @see java.util.TimeZone#setDefault(java.util.TimeZone) */ @API(status = STABLE, since = "5.10") public static final String TIME_ZONE = "java.util.TimeZone.default"; /** * Represents the global resource lock: {@value} * * @since 5.8 * @see Isolated * @see org.junit.platform.engine.support.hierarchical.ExclusiveResource */ @API(status = STABLE, since = "5.10") public static final String GLOBAL = "org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_KEY"; private Resources() { /* no-op */ } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * JUnit Jupiter API for influencing parallel test execution. */ @NullMarked package org.junit.jupiter.api.parallel; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/timeout/PreemptiveTimeoutUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.timeout; import static java.util.Objects.requireNonNullElse; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.commons.util.ExceptionUtils.throwAsUncheckedException; import java.io.Serial; import java.time.Duration; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.function.ThrowingSupplier; import org.junit.platform.commons.JUnitException; /** * Internal utilities for executing code with a preemptive timeout. * * @since 6.0 */ @API(status = INTERNAL, since = "6.0") public class PreemptiveTimeoutUtils { /** * Assert that execution of the supplied {@code supplier} * completes before the given {@code timeout} is exceeded. * *

See the {@linkplain Assertions Preemptive Timeouts} section of the * class-level Javadoc for further details. * *

If the assertion passes then the {@code supplier}'s result is returned. * *

In the case the assertion does not pass, the supplied * {@link TimeoutFailureFactory} is invoked to create an exception which is * then thrown. * *

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. */ public static T executeWithPreemptiveTimeout(Duration timeout, ThrowingSupplier supplier, @Nullable Supplier<@Nullable String> messageSupplier, TimeoutFailureFactory failureFactory) throws E { AtomicReference threadReference = new AtomicReference<>(); ExecutorService executorService = Executors.newSingleThreadExecutor(new TimeoutThreadFactory()); try { Future future = submitTask(supplier, threadReference, executorService); return resolveFutureAndHandleException(future, timeout, messageSupplier, threadReference::get, failureFactory); } finally { executorService.shutdownNow(); } } private static Future submitTask(ThrowingSupplier supplier, AtomicReference threadReference, ExecutorService executorService) { return executorService.submit(() -> { try { threadReference.set(Thread.currentThread()); return supplier.get(); } catch (Throwable throwable) { throw throwAsUncheckedException(throwable); } }); } private static T resolveFutureAndHandleException(Future future, Duration timeout, @Nullable Supplier<@Nullable String> messageSupplier, Supplier<@Nullable Thread> threadSupplier, TimeoutFailureFactory failureFactory) throws E, RuntimeException { try { return future.get(timeout.toMillis(), TimeUnit.MILLISECONDS); } catch (TimeoutException ex) { Thread thread = threadSupplier.get(); ExecutionTimeoutException cause = null; if (thread != null) { cause = new ExecutionTimeoutException("Execution timed out in thread " + thread.getName()); cause.setStackTrace(thread.getStackTrace()); } throw failureFactory.createTimeoutFailure(timeout, messageSupplier, cause, thread); } catch (ExecutionException ex) { throw throwAsUncheckedException(requireNonNullElse(ex.getCause(), ex)); } catch (Throwable ex) { throw throwAsUncheckedException(ex); } } private PreemptiveTimeoutUtils() { } /** * Factory for timeout failures. * * @param The type of error or exception created * @since 5.9.1 * @see PreemptiveTimeoutUtils#executeWithPreemptiveTimeout(Duration, ThrowingSupplier, Supplier, TimeoutFailureFactory) */ public interface TimeoutFailureFactory { /** * Create a failure for the given timeout, message, and cause. * * @return timeout failure; never {@code null} */ T createTimeoutFailure(Duration timeout, @Nullable Supplier<@Nullable String> messageSupplier, @Nullable Throwable cause, @Nullable Thread testThread); } private static class ExecutionTimeoutException extends JUnitException { @Serial private static final long serialVersionUID = 1L; ExecutionTimeoutException(String message) { super(message); } } /** * The thread factory used for preemptive timeout. * *

The factory creates threads with meaningful names, helpful for debugging * purposes. */ private static class TimeoutThreadFactory implements ThreadFactory { private static final AtomicInteger threadNumber = new AtomicInteger(1); @Override public Thread newThread(Runnable r) { return new Thread(r, "junit-timeout-thread-" + threadNumber.getAndIncrement()); } } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/timeout/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Internal JUnit Jupiter timeout utilities. * *

DISCLAIMER

* *

These utilities are intended solely for usage within the JUnit framework * itself. Any usage by external parties is not supported. * Use at your own risk! */ @NullMarked package org.junit.jupiter.api.timeout; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/util/ClearSystemProperty.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.util; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ExtendWith; /** * {@code @ClearSystemProperty} is an annotation that is used to clear the value * of a JVM system property for test execution. * *

The key of the system property must be specified via the {@link #key() key} * attribute. * *

{@code @ClearSystemProperty} can be used on the method and on the class level. * It is {@linkplain Repeatable repeatable} and {@link Inherited @Inherited} from * higher-level containers. If a class is annotated, the configured property will * be cleared before every test inside that class. After a test has completed, * the original value of the system property will be restored. * *

During * parallel test execution, all tests annotated with * {@link SetSystemProperty @SetSystemProperty}, * {@link ClearSystemProperty @ClearSystemProperty}, * {@link ReadsSystemProperty @ReadsSystemProperty}, and * {@link WritesSystemProperty @WritesSystemProperty} are scheduled in a way that * guarantees correctness under mutation of shared global state. * *

For further details and examples, see the documentation on all JVM system * property annotations in the * * User Guide. * * @since 6.1 * @see SetSystemProperty @SetSystemProperty * @see RestoreSystemProperties @RestoreSystemProperties */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD, ElementType.TYPE }) @Inherited @Repeatable(ClearSystemProperty.ClearSystemProperties.class) @WritesSystemProperty @ExtendWith(SystemPropertiesExtension.class) @API(status = EXPERIMENTAL, since = "6.1") @SuppressWarnings("exports") public @interface ClearSystemProperty { /** * The key of the system property to clear. */ String key(); /** * {@code @ClearSystemProperties} is a container for one or more * {@code ClearSystemProperty} declarations. */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD, ElementType.TYPE }) @Inherited @WritesSystemProperty @API(status = EXPERIMENTAL, since = "6.1") @interface ClearSystemProperties { /** * An array of one or more {@link ClearSystemProperty @ClearSystemProperty} * declarations. */ ClearSystemProperty[] value(); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/util/DefaultLocale.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.util; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.Locale; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ExtendWith; /** * {@code @DefaultLocale} is a JUnit Jupiter extension for changing the value * returned by {@link Locale#getDefault()} for a test execution. * *

The {@link Locale} to set as the default locale can be * configured in several ways: * *

    *
  • using a {@link Locale#forLanguageTag(String) language tag}
  • *
  • using a {@link Locale.Builder Locale.Builder} together with *
      *
    • a language
    • *
    • a language and a country
    • *
    • a language, a country, and a variant
    • *
    *
  • *
* *

Please keep in mind that the {@code Locale.Builder} does a syntax check, * if you use a variant. The given string must match the BCP 47 (or more * detailed RFC 5646) syntax. * *

If a language tag is set, none of the other fields must be set. Otherwise, an * {@link org.junit.jupiter.api.extension.ExtensionConfigurationException} will * be thrown. Specifying a {@link #country()} but no {@link #language()}, or a * {@link #variant()} but no {@link #country()} and {@link #language()} will * also cause an {@code ExtensionConfigurationException}. After the annotated * element has been executed, the default {@code Locale} will be restored to * its original value. * *

{@code @DefaultLocale} can be used on the method and on the class level. It * is inherited from higher-level containers, but can only be used once per method * or class. If a class is annotated, the configured {@code Locale} will be the * default {@code Locale} for all tests inside that class. Any method level * configurations will override the class level default {@code Locale}. * *

During * parallel test execution, * all tests annotated with {@link DefaultLocale @DefaultLocale}, * {@link ReadsDefaultLocale @ReadsDefaultLocale}, and * {@link WritesDefaultLocale @WritesDefaultLocale} are scheduled in a way that guarantees * correctness under mutation of shared global state. * *

For more details and examples, see the * User Guide. * * @since 6.1 * @see Locale#getDefault() * @see ReadsDefaultLocale * @see WritesDefaultLocale * @see DefaultTimeZone */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD, ElementType.TYPE }) @Inherited @WritesDefaultLocale @API(status = STABLE, since = "6.1") @ExtendWith(DefaultLocaleExtension.class) @SuppressWarnings("exports") public @interface DefaultLocale { /** * A language tag string as specified by IETF BCP 47. See * {@link Locale#forLanguageTag(String)} for more information about valid * language tag values. */ String value() default ""; /** * An ISO 639 alpha-2 or alpha-3 language code, or a language subtag up to * 8 characters in length. See the {@link Locale} class * description about valid language values. */ String language() default ""; /** * An ISO 3166 alpha-2 country code or a UN M.49 numeric-3 area code. See * the {@link Locale} class description about valid country * values. */ String country() default ""; /** * An IETF BCP 47 language string that matches the * RFC 5646 * syntax. It's validated by the {@code Locale.Builder}, using * {@code sun.util.locale.LanguageTag#isVariant}. */ String variant() default ""; /** * A class implementing {@link LocaleProvider} to be used for custom * {@code Locale} resolution. This is mutually exclusive with other * properties, if any other property is given a value it will result in an * {@link org.junit.jupiter.api.extension.ExtensionConfigurationException}. */ Class localeProvider() default NullLocaleProvider.class; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/util/DefaultLocaleExtension.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.util; import java.util.Locale; import java.util.Optional; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionConfigurationException; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Namespace; import org.junit.platform.commons.support.AnnotationSupport; import org.junit.platform.commons.support.ReflectionSupport; /** * @since 6.1 */ final class DefaultLocaleExtension implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback { private static final Namespace NAMESPACE = Namespace.create(DefaultLocaleExtension.class); private static final String CUSTOM_KEY = "CustomLocale"; private static final String DEFAULT_KEY = "DefaultLocale"; @Override public void beforeAll(ExtensionContext context) { createLocaleFromAnnotation(context) // .ifPresent(locale -> store(context, CUSTOM_KEY, locale)); } @Override public void beforeEach(ExtensionContext context) { createLocaleFromAnnotation(context) // .or(() -> load(context, CUSTOM_KEY)) // .ifPresent(locale -> setDefaultLocale(context, locale)); } private void setDefaultLocale(ExtensionContext context, Locale customLocale) { store(context, DEFAULT_KEY, Locale.getDefault()); Locale.setDefault(customLocale); } private static Optional createLocaleFromAnnotation(ExtensionContext context) { return AnnotationSupport.findAnnotation(context.getElement(), DefaultLocale.class) // .map(DefaultLocaleExtension::createLocale); } private static Locale createLocale(DefaultLocale annotation) { if (!annotation.value().isEmpty()) { return createFromLanguageTag(annotation); } else if (!annotation.language().isEmpty()) { return createFromParts(annotation); } else { return getFromProvider(annotation); } } private static Locale createFromLanguageTag(DefaultLocale annotation) { if (!annotation.language().isEmpty() || !annotation.country().isEmpty() || !annotation.variant().isEmpty() || annotation.localeProvider() != NullLocaleProvider.class) { throw new ExtensionConfigurationException( "@DefaultLocale can only be used with language tag if language, country, variant and provider are not set"); } return Locale.forLanguageTag(annotation.value()); } private static Locale createFromParts(DefaultLocale annotation) { if (annotation.localeProvider() != NullLocaleProvider.class) throw new ExtensionConfigurationException( "@DefaultLocale can only be used with language tag if provider is not set"); String language = annotation.language(); String country = annotation.country(); String variant = annotation.variant(); if (!language.isEmpty() && !country.isEmpty() && !variant.isEmpty()) { return JupiterLocaleUtils.createLocale(language, country, variant); } else if (!language.isEmpty() && !country.isEmpty()) { return JupiterLocaleUtils.createLocale(language, country); } else if (!language.isEmpty() && variant.isEmpty()) { return JupiterLocaleUtils.createLocale(language); } else { throw new ExtensionConfigurationException( "@DefaultLocale not configured correctly. When not using a language tag, specify either" + " language, or language and country, or language and country and variant."); } } private static Locale getFromProvider(DefaultLocale annotation) { if (!annotation.country().isEmpty() || !annotation.variant().isEmpty()) throw new ExtensionConfigurationException( "@DefaultLocale can only be used with a provider if value, language, country and variant are not set."); var providerClass = annotation.localeProvider(); LocaleProvider provider; try { provider = ReflectionSupport.newInstance(providerClass); } catch (Exception exception) { throw new ExtensionConfigurationException( "LocaleProvider instance could not be constructed because of an exception", exception); } return invoke(provider); } @SuppressWarnings("ConstantValue") private static Locale invoke(LocaleProvider provider) { var locale = provider.get(); if (locale == null) { throw new ExtensionConfigurationException("LocaleProvider instance returned with null"); } return locale; } @Override public void afterEach(ExtensionContext context) { load(context, DEFAULT_KEY).ifPresent(Locale::setDefault); } private static void store(ExtensionContext context, String key, Locale value) { getStore(context).put(key, value); } private static Optional load(ExtensionContext context, String key) { return Optional.ofNullable(getStore(context).get(key, Locale.class)); } private static ExtensionContext.Store getStore(ExtensionContext context) { return context.getStore(NAMESPACE); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/util/DefaultTimeZone.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.util; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.TimeZone; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ExtendWith; /** * {@code @DefaultTimeZone} is a JUnit Jupiter extension for changing the value * returned by {@link TimeZone#getDefault()} for a test execution. * *

The {@link TimeZone} to set as the default {@code TimeZone} is configured * by specifying the {@code TimeZone} ID as defined by * {@link TimeZone#getTimeZone(String)}. After the annotated element has been * executed, the default {@code TimeZone} will be restored to its original * value. * *

{@code @DefaultTimeZone} can be used on the method and on the class * level. It is inherited from higher-level containers, but can only be used * once per method or class. If a class is annotated, the configured * {@code TimeZone} will be the default {@code TimeZone} for all tests inside * that class. Any method level configurations will override the class level * default {@code TimeZone}. * *

During * parallel test execution, * all tests annotated with {@link DefaultTimeZone @DefaultTimeZone}, * {@link ReadsDefaultTimeZone @ReadsDefaultTimeZone}, and * {@link WritesDefaultTimeZone @WritesDefaultTimeZone} are scheduled in a way that * guarantees correctness under mutation of shared global state. * *

For more details and examples, see the * User Guide. * * @since 6.1 * @see TimeZone#getDefault() * @see ReadsDefaultTimeZone * @see WritesDefaultTimeZone * @see DefaultLocale */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD, ElementType.TYPE }) @Inherited @WritesDefaultTimeZone @API(status = STABLE, since = "6.1") @ExtendWith(DefaultTimeZoneExtension.class) @SuppressWarnings("exports") public @interface DefaultTimeZone { /** * The ID for a {@code TimeZone}, either an abbreviation such as "PST", a * full name such as "America/Los_Angeles", or a custom ID such as * "GMT-8:00". Note that the support of abbreviations is for JDK 1.1.x * compatibility only and full names should be used. */ String value() default ""; /** * A class implementing {@link TimeZoneProvider} to be used for custom {@code TimeZone} resolution. * This is mutually exclusive with other properties, if any other property is given a value it * will result in an {@link org.junit.jupiter.api.extension.ExtensionConfigurationException}. */ Class timeZoneProvider() default NullTimeZoneProvider.class; } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/util/DefaultTimeZoneExtension.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.util; import java.util.Optional; import java.util.TimeZone; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionConfigurationException; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Namespace; import org.junit.platform.commons.support.AnnotationSupport; import org.junit.platform.commons.support.ReflectionSupport; /** * @since 6.1 */ final class DefaultTimeZoneExtension implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback { private static final Namespace NAMESPACE = Namespace.create(DefaultTimeZoneExtension.class); private static final String CUSTOM_KEY = "CustomTimeZone"; private static final String DEFAULT_KEY = "DefaultTimeZone"; @Override public void beforeAll(ExtensionContext context) { createTimeZoneFromAnnotation(context) // .ifPresent(timeZone -> store(context, CUSTOM_KEY, timeZone)); } @Override public void beforeEach(ExtensionContext context) { createTimeZoneFromAnnotation(context) // .or(() -> load(context, CUSTOM_KEY)) // .ifPresent(timeZone -> setDefaultTimeZone(context, timeZone)); } private void setDefaultTimeZone(ExtensionContext context, TimeZone customTimeZone) { store(context, DEFAULT_KEY, TimeZone.getDefault()); TimeZone.setDefault(customTimeZone); } private static Optional createTimeZoneFromAnnotation(ExtensionContext context) { return AnnotationSupport.findAnnotation(context.getElement(), DefaultTimeZone.class) // .map(DefaultTimeZoneExtension::createTimeZone); } private static TimeZone createTimeZone(DefaultTimeZone annotation) { validateCorrectConfiguration(annotation); if (!annotation.value().isEmpty()) { return createTimeZoneFromZoneId(annotation.value()); } else { return createTimeZoneFromProvider(annotation.timeZoneProvider()); } } private static void validateCorrectConfiguration(DefaultTimeZone annotation) { boolean noValue = annotation.value().isEmpty(); boolean noProvider = annotation.timeZoneProvider() == NullTimeZoneProvider.class; if (noValue == noProvider) { throw new ExtensionConfigurationException( "Either a valid time zone id or a TimeZoneProvider must be provided to " + DefaultTimeZone.class.getSimpleName()); } } private static TimeZone createTimeZoneFromZoneId(String timeZoneId) { TimeZone configuredTimeZone = TimeZone.getTimeZone(timeZoneId); // TimeZone::getTimeZone returns with GMT as fallback if the given ID cannot be understood if (configuredTimeZone.equals(TimeZone.getTimeZone("GMT")) && !"GMT".equals(timeZoneId)) { throw new ExtensionConfigurationException(""" @DefaultTimeZone not configured correctly. Could not find the specified time zone + '%s'. Please use correct identifiers, e.g. "GMT" for Greenwich Mean Time. """.formatted(timeZoneId)); } return configuredTimeZone; } private static TimeZone createTimeZoneFromProvider(Class providerClass) { try { TimeZoneProvider provider = ReflectionSupport.newInstance(providerClass); return Optional.ofNullable(provider.get()).orElse(TimeZone.getTimeZone("GMT")); } catch (Exception exception) { throw new ExtensionConfigurationException("Could not instantiate TimeZoneProvider because of exception", exception); } } @Override public void afterEach(ExtensionContext context) { load(context, DEFAULT_KEY).ifPresent(TimeZone::setDefault); } private static void store(ExtensionContext context, String key, TimeZone value) { getStore(context).put(key, value); } private static Optional load(ExtensionContext context, String key) { return Optional.ofNullable(getStore(context).get(key, TimeZone.class)); } private static ExtensionContext.Store getStore(ExtensionContext context) { return context.getStore(NAMESPACE); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/util/JupiterLocaleUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.util; import java.util.Locale; /** * Utility class to create {@code Locale}. * * @since 6.1 */ final class JupiterLocaleUtils { private JupiterLocaleUtils() { // private constructor to prevent instantiation of utility class } public static Locale createLocale(String language, String country, String variant) { return new Locale.Builder().setLanguage(language).setRegion(country).setVariant(variant).build(); } public static Locale createLocale(String language, String country) { return new Locale.Builder().setLanguage(language).setRegion(country).build(); } public static Locale createLocale(String language) { return new Locale.Builder().setLanguage(language).build(); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/util/JupiterPropertyUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.util; import java.util.List; import java.util.Properties; import java.util.Set; import org.junit.jupiter.api.extension.ExtensionConfigurationException; import org.junit.jupiter.api.extension.ExtensionContext; final class JupiterPropertyUtils { private JupiterPropertyUtils() { /* no-op */ } static Properties cloneWithoutDefaults(ExtensionContext context, Properties properties) { // Custom implementations have to implement clone correctly. if (properties.getClass() == Properties.class) { throwIfHasObservableDefaults(context, properties); } return (Properties) properties.clone(); } private static void throwIfHasObservableDefaults(ExtensionContext context, Properties properties) { Set keySet = properties.keySet(); // A best effort check. List defaultPropertyNames = properties.stringPropertyNames().stream() // .filter(propertyName -> !keySet.contains(propertyName)) // .toList(); if (!defaultPropertyNames.isEmpty()) { throw new ExtensionConfigurationException(""" SystemPropertiesExtension was configured to restore the system properties by [%s]. \ However, it was not possible to create an accurate snapshot of the system properties \ using Properties::clone, because default properties were present: %s""" // .formatted(context.getElement().orElseThrow(), defaultPropertyNames)); } } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/util/LocaleProvider.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.util; import static org.apiguardian.api.API.Status.STABLE; import java.util.Locale; import java.util.function.Supplier; import org.apiguardian.api.API; /** * Custom {@link Locale} provider for use with * {@link DefaultLocale#localeProvider()}. * * @since 6.1 */ @API(status = STABLE, since = "6.1") public interface LocaleProvider extends Supplier { } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/util/NullLocaleProvider.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.util; /** * @since 6.1 */ interface NullLocaleProvider extends LocaleProvider { } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/util/NullTimeZoneProvider.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.util; /** * @since 6.1 */ interface NullTimeZoneProvider extends TimeZoneProvider { } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/util/ReadsDefaultLocale.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.util; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.api.parallel.ResourceAccessMode; import org.junit.jupiter.api.parallel.ResourceLock; import org.junit.jupiter.api.parallel.Resources; /** * Marks tests that read the default locale but don't use the * {@link DefaultLocale @DefaultLocale} extension themselves. * *

During * parallel test execution, * all tests annotated with {@link DefaultLocale @DefaultLocale}, * {@link ReadsDefaultLocale @ReadsDefaultLocale}, and * {@link WritesDefaultLocale} are scheduled in a way that guarantees * correctness under mutation of shared global state. * *

For more details and examples, see the * User Guide. * * @since 6.1 * @see DefaultLocale * @see WritesDefaultLocale */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) @Inherited @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) @API(status = STABLE, since = "6.1") public @interface ReadsDefaultLocale { } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/util/ReadsDefaultTimeZone.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.util; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.api.parallel.ResourceAccessMode; import org.junit.jupiter.api.parallel.ResourceLock; import org.junit.jupiter.api.parallel.Resources; /** * Marks tests that read the default time zone but don't use the * {@link DefaultTimeZone @DefaultTimeZone} extension themselves. * *

During * parallel test execution, * all tests annotated with {@link DefaultTimeZone @DefaultTimeZone}, * {@link ReadsDefaultTimeZone @ReadsDefaultTimeZone}, and * {@link WritesDefaultTimeZone @WritesDefaultTimeZone} are scheduled in a way that * guarantees correctness under mutation of shared global state. * *

For more details and examples, see the * User Guide. * * @since 6.1 * @see DefaultTimeZone * @see WritesDefaultTimeZone */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) @Inherited @ResourceLock(value = Resources.TIME_ZONE, mode = ResourceAccessMode.READ) @API(status = STABLE, since = "6.1") public @interface ReadsDefaultTimeZone { } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/util/ReadsSystemProperty.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.util; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.api.parallel.ResourceAccessMode; import org.junit.jupiter.api.parallel.ResourceLock; import org.junit.jupiter.api.parallel.Resources; /** * {@code @ReadsSystemProperty} marks tests that read system properties but do * not use the JVM system properties extensions themselves. * *

During * parallel test execution, all tests annotated with * {@link SetSystemProperty @SetSystemProperty}, * {@link ClearSystemProperty @ClearSystemProperty}, * {@link ReadsSystemProperty @ReadsSystemProperty}, and * {@link WritesSystemProperty @WritesSystemProperty} are scheduled in a way that * guarantees correctness under mutation of shared global state. * *

For further details and examples, see the documentation on all JVM system * property annotations in the * * User Guide. * * @since 6.1 * @see WritesSystemProperty @WritesSystemProperty */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) @Inherited @ResourceLock(value = Resources.SYSTEM_PROPERTIES, mode = ResourceAccessMode.READ) @API(status = EXPERIMENTAL, since = "6.1") public @interface ReadsSystemProperty { } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/util/RestoreSystemProperties.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.util; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.Properties; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ExtendWith; /** * {@code @RestoreSystemProperties} is an annotation that is used to restore the * entire set of JVM system properties to its original state, or the state of the * higher-level container, after execution of the annotated element has completed. * *

Use this annotation when there is a need to programmatically modify system * properties in a test method or in {@code @BeforeAll} / {@code @BeforeEach} * lifecycle methods. To set or clear a system property, consider * {@link SetSystemProperty @SetSystemProperty} or * {@link ClearSystemProperty @ClearSystemProperty} instead. * *

{@code @RestoreSystemProperties} can be used on the method and on the class * level. * *

When declared on a test method, a snapshot of all JVM system properties is * stored prior to that test. The snapshot is created before any {@code @BeforeEach} * lifecycle methods in scope and before any {@link SetSystemProperty @SetSystemProperty} * or {@link ClearSystemProperty @ClearSystemProperty} annotations on that method. * After the test, system properties are restored from the snapshot after all * {@code @AfterEach} lifecycle methods have completed. * *

When placed on a test class, a snapshot of all JVM system properties is stored * prior to any {@code @BeforeAll} lifecycle methods in scope and before any * {@link SetSystemProperty @SetSystemProperty} or * {@link ClearSystemProperty @ClearSystemProperty} annotations on that class. * After the test class completes, system properties are restored from the snapshot * after any {@code @AfterAll} lifecycle methods have completed. In addition, a * class-level annotation is inherited by each test method just as if each one were * annotated with {@code RestoreSystemProperties}. * *

During * parallel test execution, all tests annotated with * {@link SetSystemProperty @SetSystemProperty}, * {@link ClearSystemProperty @ClearSystemProperty}, * {@link ReadsSystemProperty @ReadsSystemProperty}, and * {@link WritesSystemProperty @WritesSystemProperty} are scheduled in a way that * guarantees correctness under mutation of shared global state. * *

For further details and examples, see the documentation on all JVM system * property annotations in the * * User Guide. * *

Note: The snapshot of the properties object is created using * {@link Properties#clone()}. However, this cloned properties object will not * include any default values from the original properties object. Consequently, * this extension will make a best effort attempt to detect default values and * fail if any are detected. For classes that extend {@code Properties}, it is * assumed that {@code clone()} is implemented with sufficient fidelity. * * @since 6.1 * @see ClearSystemProperty @ClearSystemProperty * @see SetSystemProperty @SetSystemProperty */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD, ElementType.TYPE }) @Inherited @WritesSystemProperty @ExtendWith(SystemPropertiesExtension.class) @API(status = EXPERIMENTAL, since = "6.1") @SuppressWarnings("exports") public @interface RestoreSystemProperties { } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/util/SetSystemProperty.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.util; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ExtendWith; /** * {@code @SetSystemProperty} is an annotation that is used to set the value of * a JVM system property for test execution. * *

The key and value of the system property must be specified via the * {@link #key() key} and {@link #value() value} attributes. * *

This annotation can be used both at the type level and the method level. * It is {@linkplain Repeatable repeatable} and {@link Inherited @Inherited} from * higher-level containers — for example, a test method inherits a * {@code @SetSystemProperty} declaration from the test class in which the test * is declared. If this annotation is declared on a test class (or implemented * interface), the configured property will be set before every test inside that * class. After a test has completed, the original value of the system property * (or the value configured via {@code @SetSystemProperty} at a higher level) will * be restored. Note that method-level configuration always overrides class-level * configuration. * *

During * parallel test execution, all tests annotated with * {@link SetSystemProperty @SetSystemProperty}, * {@link ClearSystemProperty @ClearSystemProperty}, * {@link ReadsSystemProperty @ReadsSystemProperty}, and * {@link WritesSystemProperty @WritesSystemProperty} are scheduled in a way that * guarantees correctness under mutation of shared global state. * *

For further details and examples, see the documentation on all JVM system * property annotations in the * * User Guide. * * @since 6.1 * @see ClearSystemProperty @ClearSystemProperty * @see RestoreSystemProperties @RestoreSystemProperties */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD, ElementType.TYPE }) @Inherited @Repeatable(SetSystemProperty.SetSystemProperties.class) @WritesSystemProperty @ExtendWith(SystemPropertiesExtension.class) @API(status = EXPERIMENTAL, since = "6.1") @SuppressWarnings("exports") public @interface SetSystemProperty { /** * The key of the system property to set. */ String key(); /** * The value of the system property to set. */ String value(); /** * {@code @SetSystemProperties} is a container for one or more * {@code SetSystemProperty} declarations. */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD, ElementType.TYPE }) @Inherited @WritesSystemProperty @API(status = EXPERIMENTAL, since = "6.1") @interface SetSystemProperties { /** * An array of one or more {@link SetSystemProperty @SetSystemProperty} * declarations. */ SetSystemProperty[] value(); } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/util/SystemPropertiesExtension.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.util; import static java.util.stream.Collectors.toSet; import static org.junit.platform.commons.support.AnnotationSupport.findRepeatableAnnotations; import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated; import static org.junit.platform.commons.util.CollectionUtils.forEachInReverseOrder; import java.lang.reflect.AnnotatedElement; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Properties; import java.util.Set; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionConfigurationException; import org.junit.jupiter.api.extension.ExtensionContext; /** * {@code Extension} which provides support for the following annotations. * *

    *
  • {@link SetSystemProperty @SetSystemProperty}
  • *
  • {@link ClearSystemProperty @ClearSystemProperty}
  • *
  • {@link RestoreSystemProperties @RestoreSystemProperties}
  • *
* * @since 6.1 */ final class SystemPropertiesExtension implements BeforeEachCallback, AfterEachCallback, BeforeAllCallback, AfterAllCallback { /** * Prepare for entering a context that must be restorable. * *

The context is prepared by pre-emptively swapping out the current * System properties with a snapshot. The original system properties are * restored after the test. * * @return the original {@link System#getProperties} object */ Properties prepareToEnterRestorableContext(ExtensionContext context) { var current = System.getProperties(); var clone = JupiterPropertyUtils.cloneWithoutDefaults(context, current); System.setProperties(clone); return current; } /** * Prepare to exit a restorable context. * *

The entry environment will be restored to the state passed in as {@code Properties}. * * @param properties a non-null {@code Properties} that contains all entries * of the entry environment */ void prepareToExitRestorableContext(Properties properties) { System.setProperties(properties); } @Override public void beforeAll(ExtensionContext context) { applyForAllContexts(context); } @Override public void beforeEach(ExtensionContext context) { applyForAllContexts(context); } private void applyForAllContexts(ExtensionContext originalContext) { var allContexts = findAllExtensionContexts(originalContext); var restoreAnnotationContext = findFirstRestoreAnnotationContext(allContexts); restoreAnnotationContext.ifPresent(annotatedElementContext -> { var properties = this.prepareToEnterRestorableContext(annotatedElementContext); storeCompleteBackup(originalContext, properties); }); // we have to apply the annotations from the outermost to the innermost context. forEachInReverseOrder(allContexts, currentContext -> clearAndSetEntries(currentContext, originalContext, restoreAnnotationContext.isEmpty())); } private Optional findFirstRestoreAnnotationContext(List contexts) { return contexts.stream() // .filter(context -> isAnnotated(context.getElement(), RestoreSystemProperties.class)) // .findFirst(); } private void clearAndSetEntries(ExtensionContext currentContext, ExtensionContext originalContext, boolean doIncrementalBackup) { currentContext.getElement().ifPresent(element -> { var entriesToClear = findEntriesToClear(element); var entriesToSet = findEntriesToSet(element); preventClearAndSetSameEntries(element, entriesToClear, entriesToSet.keySet()); if (entriesToClear.isEmpty() && entriesToSet.isEmpty()) { return; } // Only backup original values if we didn't already do bulk storage of the original state if (doIncrementalBackup) { storeIncrementalBackup(originalContext, entriesToClear, entriesToSet.keySet()); } // For consistency don't use Properties::setProperty or System.setProperty here var properties = System.getProperties(); entriesToClear.forEach(properties::remove); properties.putAll(entriesToSet); }); } private Set findEntriesToClear(AnnotatedElement element) { return findRepeatableAnnotations(element, ClearSystemProperty.class).stream() // .map(ClearSystemProperty::key) // // already distinct due to findRepeatableAnnotations .collect(toSet()); } private Map findEntriesToSet(AnnotatedElement element) { var entries = new HashMap(); var duplicatePropertyNames = new HashSet(); findRepeatableAnnotations(element, SetSystemProperty.class) // .forEach(annotation -> { var key = annotation.key(); if (entries.put(key, annotation.value()) != null) { duplicatePropertyNames.add(key); } }); requireUniqueEntries(element, duplicatePropertyNames); return entries; } private void preventClearAndSetSameEntries(AnnotatedElement element, Set entriesToClear, Set entriesToSet) { requireUniqueEntries(element, // entriesToClear.stream() // .filter(entriesToSet::contains) // .collect(toSet())); } private static void requireUniqueEntries(AnnotatedElement annotatedElement, Set duplicatePropertNames) { if (duplicatePropertNames.isEmpty()) { return; } throw new ExtensionConfigurationException( "SystemPropertyExtension was configured to set/clear %s [%s] more than once by [%s]." // .formatted( // duplicatePropertNames.size() == 1 ? "property" : "properties", // String.join(", ", duplicatePropertNames), // annotatedElement // )); } private void storeIncrementalBackup(ExtensionContext context, Collection entriesToClear, Collection entriesToSet) { var backup = new EntriesBackup(entriesToClear, entriesToSet); getStore(context).put(getStoreKey(context, BackupType.INCREMENTAL), backup); } private void storeCompleteBackup(ExtensionContext context, Properties backup) { getStore(context).put(getStoreKey(context, BackupType.COMPLETE), backup); } /** * Restore the complete original state of the entries as they were prior to * this {@code ExtensionContext}, if the complete state was initially stored * in a before all/each event. * * @param context the {@code ExtensionContext} which may have a bulk backup stored * @return true if a complete backup exists and was used to restore, false if not */ private boolean restoreOriginalCompleteBackup(ExtensionContext context) { var backup = getCompleteBackup(context); if (backup != null) { prepareToExitRestorableContext(backup); return true; } // No complete backup - false will let the caller know to continue w/ an incremental restore return false; } private @Nullable Properties getCompleteBackup(ExtensionContext context) { var key = getStoreKey(context, BackupType.COMPLETE); return getStore(context).get(key, Properties.class); } @Override public void afterEach(ExtensionContext context) { restoreForAllContexts(context); } @Override public void afterAll(ExtensionContext context) { restoreForAllContexts(context); } private void restoreForAllContexts(ExtensionContext originalContext) { // Try a complete restore first if (!restoreOriginalCompleteBackup(originalContext)) { // A complete backup is not available, so restore incrementally from innermost to outermost findAllExtensionContexts(originalContext).forEach(__ -> restoreOriginalIncrementalBackup(originalContext)); } } private void restoreOriginalIncrementalBackup(ExtensionContext originalContext) { var backup = getIncrementalBackup(originalContext); if (backup != null) { backup.restoreBackup(); } } private static List findAllExtensionContexts(ExtensionContext context) { var contexts = new ArrayList(); do { contexts.add(context); context = context.getParent().orElse(null); } while (context != null); return contexts; } private @Nullable EntriesBackup getIncrementalBackup(ExtensionContext originalContext) { var key = getStoreKey(originalContext, BackupType.INCREMENTAL); return getStore(originalContext).get(key, EntriesBackup.class); } private ExtensionContext.Store getStore(ExtensionContext context) { return context.getStore(ExtensionContext.Namespace.create(getClass())); } private StoreKey getStoreKey(ExtensionContext context, BackupType type) { return new StoreKey(context.getUniqueId(), type); } private record StoreKey(String uniqueId, BackupType type) { } private enum BackupType { /** * Store entry is for an incremental backup object. */ INCREMENTAL, /** * Store entry is for a complete backup object. */ COMPLETE } private static final class EntriesBackup { private final Set entriesToClear = new HashSet<>(); private final Map entriesToSet = new HashMap<>(); EntriesBackup(Collection entriesToClear, Collection entriesToSet) { var properties = System.getProperties(); Stream.concat(entriesToClear.stream(), entriesToSet.stream()).forEach(entry -> { // Do not use Properties::getProperty or System.getProperty here, since // this would prevent backing up non-string values. Object backup = properties.get(entry); if (backup == null) { this.entriesToClear.add(entry); } else { this.entriesToSet.put(entry, backup); } }); } void restoreBackup() { // We can't use Properties::setProperty or System.setProperty here, since // this would prevent restoring non-string values. var properties = System.getProperties(); entriesToClear.forEach(properties::remove); properties.putAll(entriesToSet); } } } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/util/TimeZoneProvider.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.util; import static org.apiguardian.api.API.Status.STABLE; import java.util.TimeZone; import java.util.function.Supplier; import org.apiguardian.api.API; /** * Custom {@link TimeZone} provider for use with * {@link DefaultTimeZone#timeZoneProvider()}. * * @since 6.1 */ @API(status = STABLE, since = "6.1") public interface TimeZoneProvider extends Supplier { } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/util/WritesDefaultLocale.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.util; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.api.parallel.ResourceAccessMode; import org.junit.jupiter.api.parallel.ResourceLock; import org.junit.jupiter.api.parallel.Resources; /** * Marks tests that write the default locale but don't use the * {@link DefaultLocale @DefaultLocale} extension themselves. * *

During * parallel test execution, * all tests annotated with {@link DefaultLocale @DefaultLocale}, * {@link ReadsDefaultLocale @ReadsDefaultLocale}, and * {@link WritesDefaultLocale} are scheduled in a way that guarantees * correctness under mutation of shared global state. * *

For more details and examples, see the * User Guide. * * @since 6.1 * @see DefaultLocale * @see ReadsDefaultLocale */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) @Inherited @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ_WRITE) @API(status = STABLE, since = "6.1") public @interface WritesDefaultLocale { } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/util/WritesDefaultTimeZone.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.util; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.api.parallel.ResourceAccessMode; import org.junit.jupiter.api.parallel.ResourceLock; import org.junit.jupiter.api.parallel.Resources; /** * Marks tests that write the default time zone but don't use the * {@link DefaultTimeZone @DefaultTimeZone} extension themselves. * *

During * parallel test execution, * all tests annotated with {@link DefaultTimeZone @DefaultTimeZone}, * {@link ReadsDefaultTimeZone @ReadsDefaultTimeZone}, and * {@link WritesDefaultTimeZone @WritesDefaultTimeZone} are scheduled in a way that * guarantees correctness under mutation of shared global state. * *

For more details and examples, see the * User Guide. * * @since 6.1 * @see DefaultTimeZone * @see WritesDefaultTimeZone */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) @Inherited @ResourceLock(value = Resources.TIME_ZONE, mode = ResourceAccessMode.READ_WRITE) @API(status = STABLE, since = "6.1") public @interface WritesDefaultTimeZone { } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/util/WritesSystemProperty.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.util; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.api.parallel.ResourceAccessMode; import org.junit.jupiter.api.parallel.ResourceLock; import org.junit.jupiter.api.parallel.Resources; /** * {@code @WritesSystemProperty} marks tests that write system properties but do * not use the JVM system properties extensions themselves. * *

During * parallel test execution, all tests annotated with * {@link SetSystemProperty @SetSystemProperty}, * {@link ClearSystemProperty @ClearSystemProperty}, * {@link ReadsSystemProperty @ReadsSystemProperty}, and * {@link WritesSystemProperty @WritesSystemProperty} are scheduled in a way that * guarantees correctness under mutation of shared global state. * *

For further details and examples, see the documentation on all JVM system * property annotations in the * * User Guide. * * @since 6.1 * @see ReadsSystemProperty @ReadsSystemProperty */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) @Inherited @ResourceLock(value = Resources.SYSTEM_PROPERTIES, mode = ResourceAccessMode.READ_WRITE) @API(status = EXPERIMENTAL, since = "6.1") public @interface WritesSystemProperty { } ================================================ FILE: junit-jupiter-api/src/main/java/org/junit/jupiter/api/util/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * {@code java.util}-related support in JUnit Jupiter. * * @see org.junit.jupiter.api.util.DefaultLocale * @see org.junit.jupiter.api.util.DefaultTimeZone * @see org.junit.jupiter.api.util.SetSystemProperty */ @NullMarked package org.junit.jupiter.api.util; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ @file:API(status = STABLE, since = "5.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 import java.util.stream.Stream import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind.AT_MOST_ONCE import kotlin.contracts.InvocationKind.EXACTLY_ONCE import kotlin.contracts.contract /** * @see Assertions.fail */ fun fail( message: String?, throwable: Throwable? = null ): Nothing = Assertions.fail(message, throwable) /** * @see Assertions.fail */ @OptIn(ExperimentalContracts::class) @API(status = MAINTAINED, since = "5.13.3") @JvmName("fail_nonNullableLambda") fun fail(message: () -> String): Nothing { contract { callsInPlace(message, EXACTLY_ONCE) } return Assertions.fail(message()) } /** * @see Assertions.fail */ fun fail(message: (() -> String)?): Nothing = Assertions.fail(message ?: { null }) /** * @see Assertions.fail */ fun fail(throwable: Throwable?): Nothing = Assertions.fail(throwable) /** * [Stream] of functions to be executed. */ private typealias ExecutableStream = Stream<() -> Unit> private fun ExecutableStream.convert() = map { Executable(it) } /** * @see Assertions.assertAll */ fun assertAll(executables: ExecutableStream) = Assertions.assertAll(executables.convert()) /** * @see Assertions.assertAll */ fun assertAll( heading: String?, executables: ExecutableStream ) = Assertions.assertAll(heading, executables.convert()) /** * [Collection] of functions to be executed. */ private typealias ExecutableCollection = Collection<() -> Unit> private fun ExecutableCollection.convert() = map { Executable(it) } /** * @see Assertions.assertAll */ fun assertAll(executables: ExecutableCollection) = Assertions.assertAll(executables.convert()) /** * @see Assertions.assertAll */ fun assertAll( heading: String?, executables: ExecutableCollection ) = Assertions.assertAll(heading, executables.convert()) /** * @see Assertions.assertAll */ fun assertAll(vararg executables: () -> Unit) = assertAll(executables.toList().stream()) /** * @see Assertions.assertAll */ fun assertAll( heading: String?, vararg executables: () -> Unit ) = assertAll(heading, executables.toList().stream()) /** * Example usage: * ```kotlin * val nullableString: String? = ... * * assertNull(nullableString) * * // The compiler won't allow even safe calls, since nullableString is always null. * // nullableString?.isNotEmpty() * ``` * @see Assertions.assertNull */ @OptIn(ExperimentalContracts::class) @API(status = MAINTAINED, since = "5.13.3") fun assertNull(actual: Any?) { contract { returns() implies (actual == null) } Assertions.assertNull(actual) } /** * Example usage: * ```kotlin * val nullableString: String? = ... * * assertNull(nullableString, "Should be nullable") * * // The compiler won't allow even safe calls, since nullableString is always null. * // nullableString?.isNotEmpty() * ``` * @see Assertions.assertNull */ @OptIn(ExperimentalContracts::class) @API(status = MAINTAINED, since = "5.13.3") fun assertNull( actual: Any?, message: String ) { contract { returns() implies (actual == null) } Assertions.assertNull(actual, message) } /** * Example usage: * ```kotlin * val nullableString: String? = ... * * assertNull(nullableString) { "Should be nullable" } * * // The compiler won't allow even safe calls, since nullableString is always null. * // nullableString?.isNotEmpty() * ``` * @see Assertions.assertNull */ @OptIn(ExperimentalContracts::class) @API(status = MAINTAINED, since = "5.13.3") fun assertNull( actual: Any?, messageSupplier: () -> String ) { contract { returns() implies (actual == null) callsInPlace(messageSupplier, AT_MOST_ONCE) } Assertions.assertNull(actual, messageSupplier) } /** * Example usage: * ```kotlin * val nullableString: String? = ... * * assertNotNull(nullableString) * * // The compiler smart casts nullableString to a non-nullable object. * assertTrue(nullableString.isNotEmpty()) * ``` * @see Assertions.assertNotNull */ @OptIn(ExperimentalContracts::class) @API(status = MAINTAINED, since = "5.13.3") fun assertNotNull(actual: Any?) { contract { returns() implies (actual != null) } Assertions.assertNotNull(actual) } /** * Example usage: * ```kotlin * val nullableString: String? = ... * * assertNotNull(nullableString, "Should be non-nullable") * * // The compiler smart casts nullableString to a non-nullable object. * assertTrue(nullableString.isNotEmpty()) * ``` * @see Assertions.assertNotNull */ @OptIn(ExperimentalContracts::class) @API(status = MAINTAINED, since = "5.13.3") fun assertNotNull( actual: Any?, message: String ) { contract { returns() implies (actual != null) } Assertions.assertNotNull(actual, message) } /** * Example usage: * ```kotlin * val nullableString: String? = ... * * assertNotNull(nullableString) { "Should be non-nullable" } * * // The compiler smart casts nullableString to a non-nullable object. * assertTrue(nullableString.isNotEmpty()) * ``` * @see Assertions.assertNotNull */ @OptIn(ExperimentalContracts::class) @API(status = MAINTAINED, since = "5.13.3") fun assertNotNull( actual: Any?, messageSupplier: () -> String ) { contract { returns() implies (actual != null) callsInPlace(messageSupplier, AT_MOST_ONCE) } Assertions.assertNotNull(actual, messageSupplier) } /** * Example usage: * ```kotlin * val exception = assertThrows { * throw IllegalArgumentException("Talk to a duck") * } * assertEquals("Talk to a duck", exception.message) * ``` * @see Assertions.assertThrows */ inline fun assertThrows(executable: () -> Unit): T { // no contract for `executable` because it is expected to throw an exception instead // of being executed completely (see https://youtrack.jetbrains.com/issue/KT-27748) val throwable: Throwable? = try { executable() } catch (caught: Throwable) { caught } as? Throwable return Assertions.assertThrows(T::class.java) { if (throwable != null) { throw throwable } } } /** * Example usage: * ```kotlin * val exception = assertThrows("Should throw an Exception") { * throw IllegalArgumentException("Talk to a duck") * } * assertEquals("Talk to a duck", exception.message) * ``` * @see Assertions.assertThrows */ inline fun assertThrows( message: String, executable: () -> Unit ): T = assertThrows({ message }, executable) /** * Example usage: * ```kotlin * val exception = assertThrows({ "Should throw an Exception" }) { * throw IllegalArgumentException("Talk to a duck") * } * assertEquals("Talk to a duck", exception.message) * ``` * @see Assertions.assertThrows */ @OptIn(ExperimentalContracts::class) inline fun assertThrows( noinline message: () -> String, executable: () -> Unit ): T { contract { callsInPlace(message, AT_MOST_ONCE) // no contract for `executable` because it is expected to throw an exception instead // of being executed completely (see https://youtrack.jetbrains.com/issue/KT-27748) } val throwable: Throwable? = try { executable() } catch (caught: Throwable) { caught } as? Throwable return Assertions.assertThrows( T::class.java, { if (throwable != null) { throw throwable } }, message ) } /** * Example usage: * ```kotlin * val exception = assertThrows { * throw IllegalArgumentException("Talk to a duck") * } * assertEquals("Talk to a duck", exception.message) * ``` * @see Assertions.assertThrowsExactly */ inline fun assertThrowsExactly(executable: () -> Unit): T { // no contract for `executable` because it is expected to throw an exception instead // of being executed completely (see https://youtrack.jetbrains.com/issue/KT-27748) val throwable: Throwable? = try { executable() } catch (caught: Throwable) { caught } as? Throwable return Assertions.assertThrowsExactly(T::class.java) { if (throwable != null) { throw throwable } } } /** * Example usage: * ```kotlin * val exception = assertThrowsExactly("Should throw an Exception") { * throw IllegalArgumentException("Talk to a duck") * } * assertEquals("Talk to a duck", exception.message) * ``` * @see Assertions.assertThrowsExactly */ inline fun assertThrowsExactly( message: String, executable: () -> Unit ): T = assertThrowsExactly({ message }, executable) /** * Example usage: * ```kotlin * val exception = assertThrowsExactly({ "Should throw an Exception" }) { * throw IllegalArgumentException("Talk to a duck") * } * assertEquals("Talk to a duck", exception.message) * ``` * @see Assertions.assertThrowsExactly */ @OptIn(ExperimentalContracts::class) inline fun assertThrowsExactly( noinline message: () -> String, executable: () -> Unit ): T { contract { callsInPlace(message, AT_MOST_ONCE) // no contract for `executable` because it is expected to throw an exception instead // of being executed completely (see https://youtrack.jetbrains.com/issue/KT-27748) } val throwable: Throwable? = try { executable() } catch (caught: Throwable) { caught } as? Throwable return Assertions.assertThrowsExactly( T::class.java, { if (throwable != null) { throw throwable } }, message ) } /** * Example usage: * ```kotlin * val result = assertDoesNotThrow { * // Code block that is expected to not throw an exception * } * ``` * @see Assertions.assertDoesNotThrow * @param R the result type of the [executable] */ @OptIn(ExperimentalContracts::class) @API(status = STABLE, since = "5.11") inline fun assertDoesNotThrow(executable: () -> R): R { contract { callsInPlace(executable, EXACTLY_ONCE) } try { return executable() } catch (t: Throwable) { rethrowIfUnrecoverable(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) .trimStacktrace(AssertionFailureBuilder::class.java) // we don't want to retain any stacktrace elements from the AssertionFailureBuilder .retainStackTraceElements(0) .build() } } /** * Example usage: * ```kotlin * val result = assertDoesNotThrow("Should not throw an exception") { * // Code block that is expected to not throw an exception * } * ``` * @see Assertions.assertDoesNotThrow * @param R the result type of the [executable] */ @OptIn(ExperimentalContracts::class) @API(status = STABLE, since = "5.11") inline fun assertDoesNotThrow( message: String, executable: () -> R ): R { contract { callsInPlace(executable, EXACTLY_ONCE) } return assertDoesNotThrow({ message }, executable) } /** * Example usage: * ```kotlin * val result = assertDoesNotThrow({ "Should not throw an exception" }) { * // Code block that is expected to not throw an exception * } * ``` * @see Assertions.assertDoesNotThrow * @param R the result type of the [executable] */ @OptIn(ExperimentalContracts::class) @API(status = STABLE, since = "5.11") inline fun assertDoesNotThrow( noinline message: () -> String, executable: () -> R ): R { contract { callsInPlace(executable, EXACTLY_ONCE) callsInPlace(message, AT_MOST_ONCE) } try { return executable() } catch (t: Throwable) { rethrowIfUnrecoverable(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) .trimStacktrace(AssertionFailureBuilder::class.java) // we don't want to retain any stacktrace elements from the AssertionFailureBuilder .retainStackTraceElements(0) .build() } } /** * Example usage: * ```kotlin * val result = assertTimeout(Duration.seconds(1)) { * // Code block that is being timed. * } * ``` * @see Assertions.assertTimeout * @param R the result of the [executable]. */ @OptIn(ExperimentalContracts::class) @API(status = STABLE, since = "5.11") fun assertTimeout( timeout: Duration, executable: () -> R ): R { contract { callsInPlace(executable, AT_MOST_ONCE) } return Assertions.assertTimeout(timeout, executable) } /** * Example usage: * ```kotlin * val result = assertTimeout(Duration.seconds(1), "Should only take one second") { * // Code block that is being timed. * } * ``` * @see Assertions.assertTimeout * @param R the result of the [executable]. */ @OptIn(ExperimentalContracts::class) @API(status = STABLE, since = "5.11") fun assertTimeout( timeout: Duration, message: String, executable: () -> R ): R { contract { callsInPlace(executable, AT_MOST_ONCE) } return Assertions.assertTimeout(timeout, executable, message) } /** * Example usage: * ```kotlin * val result = assertTimeout(Duration.seconds(1), { "Should only take one second" }) { * // Code block that is being timed. * } * ``` * @see Assertions.assertTimeout * @param R the result of the [executable]. */ @OptIn(ExperimentalContracts::class) @API(status = STABLE, since = "5.11") fun assertTimeout( timeout: Duration, message: () -> String, executable: () -> R ): R { contract { callsInPlace(executable, AT_MOST_ONCE) callsInPlace(message, AT_MOST_ONCE) } return Assertions.assertTimeout(timeout, executable, message) } /** * Example usage: * ```kotlin * val result = assertTimeoutPreemptively(Duration.seconds(1)) { * // Code block that is being timed. * } * ``` * @see Assertions.assertTimeoutPreemptively * @param R the result of the [executable]. */ @API(status = STABLE, since = "5.11") fun assertTimeoutPreemptively( timeout: Duration, executable: () -> R ): R = // no contract for `executable` because it might be interrupted and throw an exception // (see https://youtrack.jetbrains.com/issue/KT-27748) Assertions.assertTimeoutPreemptively(timeout, executable) /** * Example usage: * ```kotlin * val result = assertTimeoutPreemptively(Duration.seconds(1), "Should only take one second") { * // Code block that is being timed. * } * ``` * @see Assertions.assertTimeoutPreemptively * @param R the result of the [executable]. */ @API(status = STABLE, since = "5.11") fun assertTimeoutPreemptively( timeout: Duration, message: String, executable: () -> R ): R = // no contract for `executable` because it might be interrupted and throw an exception // (see https://youtrack.jetbrains.com/issue/KT-27748) Assertions.assertTimeoutPreemptively(timeout, executable, message) /** * Example usage: * ```kotlin * val result = assertTimeoutPreemptively(Duration.seconds(1), { "Should only take one second" }) { * // Code block that is being timed. * } * ``` * @see Assertions.assertTimeoutPreemptively * @param R the result of the [executable]. */ @OptIn(ExperimentalContracts::class) @API(status = STABLE, since = "5.11") fun assertTimeoutPreemptively( timeout: Duration, message: () -> String, executable: () -> R ): R { contract { callsInPlace(message, AT_MOST_ONCE) // no contract for `executable` because it might be interrupted and throw an exception // (see https://youtrack.jetbrains.com/issue/KT-27748) } return Assertions.assertTimeoutPreemptively(timeout, executable, message) } /** * Example usage: * ```kotlin * val maybeString: Any = ... * * assertInstanceOf(maybeString) * * // The compiler smart casts maybeString to a String object. * assertTrue(maybeString.isNotEmpty()) * ``` * @see Assertions.assertInstanceOf * @since 5.11 */ @OptIn(ExperimentalContracts::class) @API(status = MAINTAINED, since = "5.13.3") inline fun assertInstanceOf( actualValue: Any?, message: String? = null ): T { contract { returns() implies (actualValue is T) } return Assertions.assertInstanceOf(T::class.java, actualValue, message) } /** * Example usage: * ```kotlin * val maybeString: Any = ... * * assertInstanceOf(maybeString) { "Should be a string" } * * // The compiler smart casts maybeString to a String object. * assertTrue(maybeString.isNotEmpty()) * ``` * @see Assertions.assertInstanceOf * @since 5.11 */ @OptIn(ExperimentalContracts::class) @API(status = MAINTAINED, since = "5.13.3") inline fun assertInstanceOf( actualValue: Any?, noinline message: () -> String ): T { contract { returns() implies (actualValue is T) callsInPlace(message, AT_MOST_ONCE) } return Assertions.assertInstanceOf(T::class.java, actualValue, message) } ================================================ FILE: junit-jupiter-api/src/templates/resources/main/org/junit/jupiter/api/condition/JRE.java.jte ================================================ @import java.util.List @import gg.jte.support.ForSupport @import junitbuild.generator.model.JRE @param String classNamePrefix = "" @param int minRuntimeVersion @param List allJres @param String licenseHeader ${licenseHeader} package org.junit.jupiter.api.condition; import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; /** * Enumeration of Java Runtime Environment (JRE) versions. * *

If the current JRE version can be detected but is not one of the predefined * constants in this enum, {@link #OTHER} will be considered to be the * {@linkplain #isCurrentVersion current JRE version}. * * @since 5.1 @for(JRE jre : allJres)<%-- --%> * @see #JAVA_${jre.getVersion()} @endfor<%-- --%> * @see #OTHER * @see EnabledOnJre * @see DisabledOnJre * @see EnabledForJreRange * @see DisabledForJreRange */ @API(status = STABLE, since = "5.1") public enum ${classNamePrefix}JRE { /** * An undefined JRE version. * *

This constant is used by JUnit as a default configuration value but is * not intended to be used by users. * *

This constant returns {@code -1} for its {@linkplain #version() version}. * * @since 5.12 */ @API(status = MAINTAINED, since = "5.13.3") UNDEFINED(-1), @for(var jre : allJres) /** * Java ${jre.getVersion()}. @if(jre.getSince() != null || jre.getVersion() < minRuntimeVersion)<%-- --%> * @endif<%-- --%>@if(jre.getSince() != null)<%-- --%> * @since ${jre.getSince()} @endif<%-- --%>@if(jre.getVersion() < minRuntimeVersion)<%-- --%> * @deprecated No longer supported at runtime; please use {@link #JAVA_17} or later @endif<%-- --%> */ @if(jre.getVersion() < minRuntimeVersion)<%-- --%>@API(status = DEPRECATED, since = "6.0") // @Deprecated(since = "6.0", forRemoval = true) @elseif(jre.getSince() != null)<%-- --%>@API(status = STABLE, since = "${jre.getSince()}") @endif<%-- --%>JAVA_${jre.getVersion()}(${jre.getVersion()}), @endfor /** * A JRE version other than <%-- --%>@for(var jre : ForSupport.of(allJres))<%-- --%>@if(jre.isLast())or @endif<%-- --%>{@link #JAVA_${jre.get().getVersion()}}<%-- --%>@if(jre.isLast()).@else,@endif<%-- --%>@if(jre.getIndex() % 3 == 1 && !jre.isLast()) * @elseif(!jre.isLast()) @endif<%-- --%>@endfor * *

This constant returns {@link Integer#MAX_VALUE} for its * {@linkplain #version() version}. To retrieve the actual version number, * use {@link #currentVersionNumber()}. * * @deprecated You should not reference this constant directly. If you need * to reference a later version than supported by this enum, you should * instead use an {@code int} with one of the following annotation * attributes: * *

    *
  • {@link EnabledOnJre#versions()}
  • *
  • {@link DisabledOnJre#versions()}
  • *
  • {@link EnabledForJreRange#minVersion()}
  • *
  • {@link EnabledForJreRange#maxVersion()}
  • *
  • {@link DisabledForJreRange#minVersion()}
  • *
  • {@link DisabledForJreRange#maxVersion()}
  • *
*/ @API(status = DEPRECATED, since = "6.1") // @Deprecated(since = "6.1") OTHER(Integer.MAX_VALUE); static final int UNDEFINED_VERSION = -1; static final int MINIMUM_VERSION = ${allJres.getFirst().getVersion()}; private static final int CURRENT_VERSION = Runtime.version().feature(); private final int version; ${classNamePrefix}JRE(int version) { this.version = version; } /** * Get the version of this {@code JRE}. * *

If this {@code JRE} is {@link #UNDEFINED}, this method returns * {@code -1}. If this {@code JRE} is {@link #OTHER}, this method returns * {@link Integer#MAX_VALUE}. * * @return the version of this {@code JRE} * @since 5.12 * @see Runtime.Version#feature() * @see #currentVersionNumber() */ @API(status = MAINTAINED, since = "5.13.3") public int version() { return this.version; } /** * {@return {@code true} if this {@code JRE} is known to be the * Java Runtime Environment version for the currently executing JVM or if * the version is {@link #OTHER}} * * @see #currentJre() * @see #currentVersionNumber() */ public boolean isCurrentVersion() { return this == currentJre(); } /** * {@return the {@link ${classNamePrefix}JRE} for the currently executing JVM, potentially * {@link #OTHER}} * * @since 5.7 * @see #currentVersionNumber() * @deprecated in favor of {@link #currentJre()} */ @API(status = DEPRECATED, since = "5.12") @Deprecated(since = "5.12") public static ${classNamePrefix}JRE currentVersion() { return currentJre(); } /** * {@return the {@link ${classNamePrefix}JRE} for the currently executing JVM, potentially * {@link #OTHER}} * * @since 5.12 * @see #currentVersionNumber() */ @API(status = STABLE, since = "5.12") public static ${classNamePrefix}JRE currentJre() { return switch (CURRENT_VERSION) {<%-- --%>@for(var jre : allJres) case ${jre.getVersion()} -> JAVA_${jre.getVersion()};<%-- --%>@endfor default -> OTHER; }; } /** * {@return the version number for the currently executing JVM, or {@code -1} * if the current JVM version could not be determined} * * @since 5.12 * @see Runtime.Version#feature() * @see #currentJre() */ @API(status = MAINTAINED, since = "5.13.3") public static int currentVersionNumber() { return CURRENT_VERSION; } /** * Determine if the supplied version number is considered to be the current * JRE version. * *

Returns {@code true} if either of the following is {@code true}. * *

    *
  • The supplied version number is known to be the Java Runtime Environment * version for the currently executing JVM.
  • *
  • The supplied version number is {@link Integer#MAX_VALUE} and the current * {@code JRE} is {@link JRE#OTHER OTHER}.
  • *
* * @return {@code true} if the supplied version number is considered to be * the current JRE version * @since 5.12 * @see Runtime.Version#feature() * @see #isCurrentVersion() * @see #currentJre() */ @API(status = MAINTAINED, since = "5.13.3") public static boolean isCurrentVersion(int version) { return (version == CURRENT_VERSION) || (version == ${classNamePrefix}JRE.currentJre().version); } static boolean isCurrentVersionWithinRange(int min, int max) { return CURRENT_VERSION >= min && CURRENT_VERSION <= max; } } ================================================ FILE: junit-jupiter-api/src/templates/resources/testFixtures/org/junit/jupiter/api/condition/JavaVersionPredicates.java.jte ================================================ @import java.util.List @import gg.jte.support.ForSupport @import junitbuild.generator.model.JRE @param List supportedJres @param String licenseHeader ${licenseHeader} package org.junit.jupiter.api.condition; public class JavaVersionPredicates { private static final int JAVA_VERSION = Runtime.version().feature(); @for(JRE jre : supportedJres) static boolean onJava${jre.getVersion()}() { return JAVA_VERSION == ${jre.getVersion()}; } @endfor static boolean onKnownVersion() { return @for(var jre : ForSupport.of(supportedJres))onJava${jre.get().getVersion()}()@if(!jre.isLast()) // || @endif@endfor; } @SuppressWarnings("deprecation") static boolean onOtherVersion() { return JRE.OTHER.isCurrentVersion(); } } ================================================ FILE: junit-jupiter-api/src/test/README.md ================================================ For compatibility with the Eclipse IDE, the test for this module are in the `jupiter-tests` project. ================================================ FILE: junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/AssertionTestUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ 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.jspecify.annotations.Nullable; import org.opentest4j.AssertionFailedError; import org.opentest4j.MultipleFailuresError; import org.opentest4j.ValueWrapper; public class AssertionTestUtils { private AssertionTestUtils() { /* no-op */ } public static void expectAssertionFailedError() { throw new AssertionError("Should have thrown an " + AssertionFailedError.class.getName()); } 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() + "]."); } } 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() + "]."); } } 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() + "]."); } } public static void assertMessageStartsWith(@Nullable Throwable ex, String msg) throws AssertionError { if (ex == null) { throw new AssertionError("Cause should not have been null"); } if (ex.getMessage() == null || !ex.getMessage().startsWith(msg)) { throw new AssertionError( "Exception message should start with [" + msg + "], but was [" + ex.getMessage() + "]."); } } 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() + "]."); } } public static void assertMessageContains(@Nullable Throwable ex, String msg) throws AssertionError { if (ex == null) { throw new AssertionError("Cause should not have been null"); } if (ex.getMessage() == null || !ex.getMessage().contains(msg)) { throw new AssertionError( "Exception message should contain [" + msg + "], but was [" + ex.getMessage() + "]."); } } 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 [" + ValueWrapper.create(expected) + "], but was [" + ex.getExpected() + "]."); } if (!wrapsEqualValue(ex.getActual(), actual)) { throw new AssertionError("Actual value in AssertionFailedError should equal [" + ValueWrapper.create(actual) + "], but was [" + ex.getActual() + "]."); } } public static boolean wrapsEqualValue(ValueWrapper wrapper, @Nullable Object value) { if (value == null || value instanceof Serializable) { return Objects.equals(value, wrapper.getValue()); } return wrapper.getIdentityHashCode() == System.identityHashCode(value) && Objects.equals(wrapper.getStringRepresentation(), String.valueOf(value)) && Objects.equals(wrapper.getType(), value.getClass()); } public static void recurseIndefinitely() { // simulate infinite recursion throw new StackOverflowError(); } 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 + "]"); } } } ================================================ FILE: junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/EqualsAndHashCodeAssertions.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.assertj.core.api.Assertions.assertThat; /** * Assertions for unit tests that wish to test * {@link Object#equals(Object)} and {@link Object#hashCode()}. * * @since 5.3 */ public class EqualsAndHashCodeAssertions { private EqualsAndHashCodeAssertions() { } @SuppressWarnings("EqualsWithItself") public static void assertEqualsAndHashCode(T equal1, T equal2, T different) { // Prerequisites assertThat(equal1).isNotNull(); assertThat(equal2).isNotNull(); assertThat(different).isNotNull(); assertThat(equal1).isNotSameAs(equal2); // Are equal assertThat(equal1).isEqualTo(equal1); assertThat(equal1).isEqualTo(equal2); assertThat(equal2).isEqualTo(equal1); // Are not equal assertThat(equal1).isNotEqualTo(null); assertThat(equal1).isNotEqualTo(new Object()); assertThat(equal1).isNotEqualTo(different); assertThat(different).isNotEqualTo(equal1); assertThat(different).isNotEqualTo(equal2); // Hash codes assertThat(equal1).hasSameHashCodeAs(equal2); assertThat(equal1).doesNotHaveSameHashCodeAs(different); } } ================================================ FILE: junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/TemporaryClasspathExecutor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import java.io.IOException; import java.io.UncheckedIOException; import java.net.URL; import java.net.URLClassLoader; /** * Utility class for executing code with a temporary classpath. * * @since 5.13 */ public class TemporaryClasspathExecutor { private TemporaryClasspathExecutor() { } /** * Execute the {@link Runnable} within a custom classpath, temporarily modifying the * thread's {@link Thread#getContextClassLoader() context class loader} to include * the provided {@code classpathRoot}. * *

After the given {@code Runnable} completes, the original context class loader is * restored. * * @param classpathRoot the root path to be added to the classpath, resolved relative * to the current thread's context class loader. * @param runnable the {@code Runnable} to execute with the temporary classpath. */ public static void withAdditionalClasspathRoot(String classpathRoot, Runnable runnable) { var current = Thread.currentThread().getContextClassLoader(); try (var classLoader = new URLClassLoader(new URL[] { current.getResource(classpathRoot) }, current)) { Thread.currentThread().setContextClassLoader(classLoader); runnable.run(); } catch (IOException e) { throw new UncheckedIOException(e); } finally { Thread.currentThread().setContextClassLoader(current); } } } ================================================ FILE: junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/extension/DisabledInEclipse.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.junit.jupiter.api.condition.DisabledIf; /** * @see org.junit.platform.commons.test.IdeUtils#runningInEclipse() */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) @DisabledIf("org.junit.platform.commons.test.IdeUtils#runningInEclipse()") public @interface DisabledInEclipse { String value() default ""; } ================================================ FILE: junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/extension/DisabledOnOpenJ9.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.junit.jupiter.api.condition.DisabledIfSystemProperty; @Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @DisabledIfSystemProperty(named = "java.vm.vendor", matches = ".*OpenJ9.*") public @interface DisabledOnOpenJ9 { } ================================================ FILE: junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/extension/ExtensionContextParameterResolver.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; public class ExtensionContextParameterResolver implements ParameterResolver { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return ExtensionContext.class.equals(parameterContext.getParameter().getType()); } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return extensionContext; } } ================================================ FILE: junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/fixtures/TrackLogRecords.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.fixtures; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Namespace; import org.junit.jupiter.api.extension.ExtensionContext.Store; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.commons.logging.LoggerFactory; /** * {@code @TrackLogRecords} registers an extension that tracks log records * logged via JUnit's logging facade for JUL. * *

Log records are tracked on a per-method basis (e.g., for a single * test method). * *

Test methods can gain access to the {@link LogRecordListener} managed by * the extension by having an instance of {@code LogRecordListener} injected as * a method parameter. * * @since 5.1 * @see LoggerFactory * @see LogRecordListener */ @Target({ ElementType.TYPE, ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) @ExtendWith(TrackLogRecords.Extension.class) public @interface TrackLogRecords { class Extension implements BeforeEachCallback, AfterEachCallback, ParameterResolver { @Override public void beforeEach(ExtensionContext context) { LoggerFactory.addListener(getListener(context)); } @Override public void afterEach(ExtensionContext context) { LoggerFactory.removeListener(getListener(context)); } @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { boolean isTestMethodLevel = extensionContext.getTestMethod().isPresent(); boolean isListener = parameterContext.getParameter().getType() == LogRecordListener.class; return isTestMethodLevel && isListener; } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return getListener(extensionContext); } private LogRecordListener getListener(ExtensionContext context) { return getStore(context).computeIfAbsent(LogRecordListener.class); } private Store getStore(ExtensionContext context) { return context.getStore(Namespace.create(getClass(), context.getRequiredTestMethod())); } } } ================================================ FILE: junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/io/FailingTempDirDeletionStrategy.java ================================================ /* * Copyright 2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.io; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import org.jspecify.annotations.NullMarked; import org.junit.jupiter.api.extension.AnnotatedElementContext; import org.junit.jupiter.api.extension.ExtensionContext; /** * A {@link TempDirDeletionStrategy} for testing that simulates a deletion * failure for any path ending in {@link #UNDELETABLE_PATH}. */ @NullMarked public class FailingTempDirDeletionStrategy implements TempDirDeletionStrategy { /** * A path segment that, when present at the end of a path, causes deletion * to fail with a simulated {@link java.io.IOException}. */ public static final Path UNDELETABLE_PATH = Path.of("undeletable"); @Override public DeletionResult delete(Path tempDir, AnnotatedElementContext elementContext, ExtensionContext extensionContext) throws IOException { return Standard.INSTANCE.delete(tempDir, path -> { if (path.endsWith(UNDELETABLE_PATH)) { throw new IOException("Simulated failure"); } else { Files.delete(path); } }); } } ================================================ FILE: junit-jupiter-engine/junit-jupiter-engine.gradle.kts ================================================ plugins { id("junitbuild.java-library-conventions") `java-test-fixtures` } description = "JUnit Jupiter Engine" dependencies { api(platform(projects.junitBom)) api(projects.junitPlatformEngine) api(projects.junitJupiterApi) compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) osgiVerification(projects.junitPlatformLauncher) } tasks { jar { bundle { bnd(""" Provide-Capability:\ org.junit.platform.engine;\ org.junit.platform.engine='junit-jupiter';\ version:Version="${'$'}{version_cleanup;${project.version}}" Require-Capability:\ org.junit.platform.launcher;\ filter:='(&(org.junit.platform.launcher=junit-platform-launcher)(version>=${'$'}{version_cleanup;${project.version}})(!(version>=${'$'}{versionmask;+;${'$'}{version_cleanup;${project.version}}})))';\ effective:=active """) } } } ================================================ FILE: junit-jupiter-engine/src/main/java/module-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Provides the JUnit Jupiter {@link org.junit.platform.engine.TestEngine} * implementation. * * @since 5.0 * @uses org.junit.jupiter.api.extension.Extension * @provides org.junit.platform.engine.TestEngine The {@code JupiterTestEngine} * runs Jupiter based tests on the platform. */ module org.junit.jupiter.engine { requires static org.apiguardian.api; requires static transitive org.jspecify; requires org.junit.jupiter.api; requires org.junit.platform.commons; requires org.junit.platform.engine; requires org.opentest4j; uses org.junit.jupiter.api.extension.Extension; provides org.junit.platform.engine.TestEngine with org.junit.jupiter.engine.JupiterTestEngine; opens org.junit.jupiter.engine.extension to org.junit.platform.commons; } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/Constants.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.MAINTAINED; import org.apiguardian.api.API; 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.ClassOrderer; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.parallel.Execution; /** * Collection of constants related to the Jupiter test engine. * * @since 5.0 * @see org.junit.platform.engine.ConfigurationParameters * @deprecated Please use {@link org.junit.jupiter.api.Constants} instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public final class Constants { /** * Property name used to include patterns for auto-detecting extensions: {@value} * *

Pattern Matching Syntax

* *

If the property value consists solely of an asterisk ({@code *}), all * extensions will be included. Otherwise, the property value will be treated * as a comma-separated list of patterns where each individual pattern will be * matched against the fully qualified class name (FQCN) of each extension. * Any dot ({@code .}) in a pattern will match against a dot ({@code .}) * or a dollar sign ({@code $}) in a FQCN. Any asterisk ({@code *}) will match * against one or more characters in a FQCN. All other characters in a pattern * will be matched one-to-one against a FQCN. * *

Examples

* *
    *
  • {@code *}: includes all extensions. *
  • {@code org.junit.*}: includes every extension under the {@code org.junit} * base package and any of its subpackages. *
  • {@code *.MyExtension}: includes every extension whose simple class name is * exactly {@code MyExtension}. *
  • {@code *System*}: includes every extension whose FQCN contains * {@code System}. *
  • {@code *System*, *Dev*}: includes every extension whose FQCN contains * {@code System} or {@code Dev}. *
  • {@code org.example.MyExtension, org.example.TheirExtension}: includes * extensions whose FQCN is exactly {@code org.example.MyExtension} or * {@code org.example.TheirExtension}. *
* *

Note: A class that matches both an inclusion and exclusion pattern will be excluded. * @deprecated Please use * {@link org.junit.jupiter.api.Constants#EXTENSIONS_AUTODETECTION_INCLUDE_PROPERTY_NAME} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public static final String EXTENSIONS_AUTODETECTION_INCLUDE_PROPERTY_NAME = org.junit.jupiter.api.Constants.EXTENSIONS_AUTODETECTION_INCLUDE_PROPERTY_NAME; /** * Property name used to exclude patterns for auto-detecting extensions: {@value} * *

Pattern Matching Syntax

* *

If the property value consists solely of an asterisk ({@code *}), all * extensions will be excluded. Otherwise, the property value will be treated * as a comma-separated list of patterns where each individual pattern will be * matched against the fully qualified class name (FQCN) of each extension. * Any dot ({@code .}) in a pattern will match against a dot ({@code .}) * or a dollar sign ({@code $}) in a FQCN. Any asterisk ({@code *}) will match * against one or more characters in a FQCN. All other characters in a pattern * will be matched one-to-one against a FQCN. * *

Examples

* *
    *
  • {@code *}: excludes all extensions. *
  • {@code org.junit.*}: excludes every extension under the {@code org.junit} * base package and any of its subpackages. *
  • {@code *.MyExtension}: excludes every extension whose simple class name is * exactly {@code MyExtension}. *
  • {@code *System*}: excludes every extension whose FQCN contains * {@code System}. *
  • {@code *System*, *Dev*}: excludes every extension whose FQCN contains * {@code System} or {@code Dev}. *
  • {@code org.example.MyExtension, org.example.TheirExtension}: excludes * extensions whose FQCN is exactly {@code org.example.MyExtension} or * {@code org.example.TheirExtension}. *
* *

Note: A class that matches both an inclusion and exclusion pattern will be excluded. * @deprecated Please use * {@link org.junit.jupiter.api.Constants#EXTENSIONS_AUTODETECTION_EXCLUDE_PROPERTY_NAME} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public static final String EXTENSIONS_AUTODETECTION_EXCLUDE_PROPERTY_NAME = org.junit.jupiter.api.Constants.EXTENSIONS_AUTODETECTION_EXCLUDE_PROPERTY_NAME; /** * Property name used to provide patterns for deactivating conditions: {@value} * *

Pattern Matching Syntax

* *

If the property value consists solely of an asterisk ({@code *}), all * conditions will be deactivated. Otherwise, the property value will be treated * as a comma-separated list of patterns where each individual pattern will be * matched against the fully qualified class name (FQCN) of each registered * condition. Any dot ({@code .}) in a pattern will match against a dot ({@code .}) * or a dollar sign ({@code $}) in a FQCN. Any asterisk ({@code *}) will match * against one or more characters in a FQCN. All other characters in a pattern * will be matched one-to-one against a FQCN. * *

Examples

* *
    *
  • {@code *}: deactivates all conditions. *
  • {@code org.junit.*}: deactivates every condition under the {@code org.junit} * base package and any of its subpackages. *
  • {@code *.MyCondition}: deactivates every condition whose simple class name is * exactly {@code MyCondition}. *
  • {@code *System*}: deactivates every condition whose FQCN contains * {@code System}. *
  • {@code *System*, *Dev*}: deactivates every condition whose FQCN contains * {@code System} or {@code Dev}. *
  • {@code org.example.MyCondition, org.example.TheirCondition}: deactivates * conditions whose FQCN is exactly {@code org.example.MyCondition} or * {@code org.example.TheirCondition}. *
* * @see #DEACTIVATE_ALL_CONDITIONS_PATTERN * @see org.junit.jupiter.api.extension.ExecutionCondition * @deprecated Please use * {@link org.junit.jupiter.api.Constants#DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public static final String DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME = org.junit.jupiter.api.Constants.DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME; /** * Wildcard pattern which signals that all conditions should be deactivated: {@value} * * @see #DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME * @see org.junit.jupiter.api.extension.ExecutionCondition * @deprecated Please use * {@link org.junit.jupiter.api.Constants#DEACTIVATE_ALL_CONDITIONS_PATTERN} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public static final String DEACTIVATE_ALL_CONDITIONS_PATTERN = org.junit.jupiter.api.Constants.DEACTIVATE_ALL_CONDITIONS_PATTERN; /** * Property name used to set the default display name generator class name: {@value} * * @see DisplayNameGenerator#DEFAULT_GENERATOR_PROPERTY_NAME * @deprecated Please use * {@link org.junit.jupiter.api.Constants#DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public static final String DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME = org.junit.jupiter.api.Constants.DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME; /** * Property name used to enable auto-detection and registration of extensions via * Java's {@link java.util.ServiceLoader} mechanism: {@value} * *

The default behavior is not to perform auto-detection. * @deprecated Please use * {@link org.junit.jupiter.api.Constants#EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public static final String EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME = org.junit.jupiter.api.Constants.EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME; /** * Property name used to enable dumping the stack of all * {@linkplain Thread threads} to {@code System.out} when a timeout has occurred: {@value} * *

This behavior is disabled by default. * * @since 5.12 */ @API(status = MAINTAINED, since = "5.13.3") public static final String EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_PROPERTY_NAME = org.junit.jupiter.api.Constants.EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_PROPERTY_NAME; /** * Property name used to set the default test instance lifecycle mode: {@value} * * @see TestInstance.Lifecycle#DEFAULT_LIFECYCLE_PROPERTY_NAME * @deprecated Please use * {@link org.junit.jupiter.api.Constants#DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public static final String DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME = org.junit.jupiter.api.Constants.DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME; /** * Property name used to enable parallel test execution: {@value} * *

By default, tests are executed sequentially in a single thread. * * @since 5.3 * @deprecated Please use * {@link org.junit.jupiter.api.Constants#PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public static final String PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME = org.junit.jupiter.api.Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME; /** * Property name used to enable auto-closing of {@link AutoCloseable} instances: {@value} * *

By default, auto-closing is enabled. * * @since 5.13 * @deprecated Please use * {@link org.junit.jupiter.api.Constants#CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_PROPERTY_NAME} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public static final String CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_PROPERTY_NAME = org.junit.jupiter.api.Constants.CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_PROPERTY_NAME; /** * Property name used to set the default test execution mode: {@value} * * @see Execution#DEFAULT_EXECUTION_MODE_PROPERTY_NAME * @deprecated Please use * {@link org.junit.jupiter.api.Constants#DEFAULT_EXECUTION_MODE_PROPERTY_NAME} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public static final String DEFAULT_PARALLEL_EXECUTION_MODE = org.junit.jupiter.api.Constants.DEFAULT_EXECUTION_MODE_PROPERTY_NAME; /** * Property name used to set the default test execution mode for top-level * classes: {@value} * * @see Execution#DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME * @deprecated Please use * {@link org.junit.jupiter.api.Constants#DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public static final String DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME = org.junit.jupiter.api.Constants.DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME; /** * Property name used to determine the desired parallel executor service * type: {@value} * *

Value must be {@code FORK_JOIN_POOL} or {@code WORKER_THREAD_POOL}, * ignoring case. * * @since 6.1 * @deprecated Please use * {@link org.junit.jupiter.api.Constants#PARALLEL_CONFIG_EXECUTOR_SERVICE_PROPERTY_NAME} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public static final String PARALLEL_CONFIG_EXECUTOR_SERVICE_PROPERTY_NAME = org.junit.jupiter.api.Constants.PARALLEL_CONFIG_EXECUTOR_SERVICE_PROPERTY_NAME; /** * Property name used to select the parallel execution configuration * strategy: {@value} * *

Potential values: {@code dynamic} (default), {@code fixed}, or * {@code custom}. * * @since 5.3 * @deprecated Please use * {@link org.junit.jupiter.api.Constants#PARALLEL_CONFIG_STRATEGY_PROPERTY_NAME} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public static final String PARALLEL_CONFIG_STRATEGY_PROPERTY_NAME = org.junit.jupiter.api.Constants.PARALLEL_CONFIG_STRATEGY_PROPERTY_NAME; /** * Property name used to set the desired parallelism for the {@code fixed} * configuration strategy: {@value} * *

No default value; must be a positive integer. * * @since 5.3 * @deprecated Please use * {@link org.junit.jupiter.api.Constants#PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public static final String PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME = org.junit.jupiter.api.Constants.PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME; /** * Property name used to configure the maximum pool size of the underlying * fork-join pool for the {@code fixed} configuration strategy: {@value} * *

Value must be an integer and greater than or equal to * {@value #PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME}; defaults to * {@code 256 + fixed.parallelism}. * * @since 5.10 * @deprecated Please use * {@link org.junit.jupiter.api.Constants#PARALLEL_CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public static final String PARALLEL_CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME = org.junit.jupiter.api.Constants.PARALLEL_CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME; /** * Property name used to disable saturation of the underlying fork-join pool * for the {@code fixed} configuration strategy: {@value} * *

When set to {@code false} the underlying fork-join pool will reject * additional tasks if all available workers are busy and the maximum * pool-size would be exceeded. * *

Value must either {@code true} or {@code false}; defaults to {@code true}. * * @since 5.10 * @deprecated Please use * {@link org.junit.jupiter.api.Constants#PARALLEL_CONFIG_FIXED_SATURATE_PROPERTY_NAME} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public static final String PARALLEL_CONFIG_FIXED_SATURATE_PROPERTY_NAME = org.junit.jupiter.api.Constants.PARALLEL_CONFIG_FIXED_SATURATE_PROPERTY_NAME; /** * Property name used to set the factor to be multiplied with the number of * available processors/cores to determine the desired parallelism for the * {@code dynamic} configuration strategy: {@value} * *

Value must be a positive decimal number; defaults to {@code 1}. * * @since 5.3 * @deprecated Please use * {@link org.junit.jupiter.api.Constants#PARALLEL_CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public static final String PARALLEL_CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME = org.junit.jupiter.api.Constants.PARALLEL_CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME; /** * Property name used to specify the fully qualified class name of the * {@code custom} parallel execution configuration strategy to be used: * {@value} * * @since 5.3 * @deprecated Please use * {@link org.junit.jupiter.api.Constants#PARALLEL_CONFIG_CUSTOM_CLASS_PROPERTY_NAME} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public static final String PARALLEL_CONFIG_CUSTOM_CLASS_PROPERTY_NAME = org.junit.jupiter.api.Constants.PARALLEL_CONFIG_CUSTOM_CLASS_PROPERTY_NAME; /** * Property name used to set the default timeout for all testable and * lifecycle methods: {@value} * * @see Timeout#DEFAULT_TIMEOUT_PROPERTY_NAME * @deprecated Please use * {@link org.junit.jupiter.api.Constants#DEFAULT_TIMEOUT_PROPERTY_NAME} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public static final String DEFAULT_TIMEOUT_PROPERTY_NAME = org.junit.jupiter.api.Constants.DEFAULT_TIMEOUT_PROPERTY_NAME; /** * Property name used to set the default timeout for all testable methods: {@value} * * @see Timeout#DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME * @deprecated Please use * {@link org.junit.jupiter.api.Constants#DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public static final String DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME = org.junit.jupiter.api.Constants.DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME; /** * Property name used to set the default timeout for all * {@link Test @Test} methods: {@value} * * @see Timeout#DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME * @deprecated Please use * {@link org.junit.jupiter.api.Constants#DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public static final String DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME = org.junit.jupiter.api.Constants.DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME; /** * Property name used to set the default timeout for all * {@link TestTemplate @TestTemplate} methods: {@value} * * @see Timeout#DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME * @deprecated Please use * {@link org.junit.jupiter.api.Constants#DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public static final String DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME = org.junit.jupiter.api.Constants.DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME; /** * Property name used to set the default timeout for all * {@link TestFactory @TestFactory} methods: {@value} * * @see Timeout#DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME * @deprecated Please use * {@link org.junit.jupiter.api.Constants#DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public static final String DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME = org.junit.jupiter.api.Constants.DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME; /** * Property name used to set the default timeout for all lifecycle methods: {@value} * * @see Timeout#DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME * @deprecated Please use * {@link org.junit.jupiter.api.Constants#DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public static final String DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME = org.junit.jupiter.api.Constants.DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME; /** * Property name used to set the default timeout for all * {@link BeforeAll @BeforeAll} methods: {@value} * * @see Timeout#DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME * @deprecated Please use * {@link org.junit.jupiter.api.Constants#DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public static final String DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME = org.junit.jupiter.api.Constants.DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME; /** * Property name used to set the default timeout for all * {@link BeforeEach @BeforeEach} methods: {@value} * * @see Timeout#DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME * @deprecated Please use * {@link org.junit.jupiter.api.Constants#DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public static final String DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME = org.junit.jupiter.api.Constants.DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME; /** * Property name used to set the default timeout for all * {@link AfterEach @AfterEach} methods: {@value} * * @see Timeout#DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME * @deprecated Please use * {@link org.junit.jupiter.api.Constants#DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public static final String DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME = org.junit.jupiter.api.Constants.DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME; /** * Property name used to set the default timeout for all * {@link AfterAll @AfterAll} methods: {@value} * * @see Timeout#DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME * @deprecated Please use * {@link org.junit.jupiter.api.Constants#DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public static final String DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME = org.junit.jupiter.api.Constants.DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME; /** * Property name used to configure whether timeouts are applied to tests: {@value} * * @see Timeout#TIMEOUT_MODE_PROPERTY_NAME * @deprecated Please use * {@link org.junit.jupiter.api.Constants#TIMEOUT_MODE_PROPERTY_NAME} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public static final String TIMEOUT_MODE_PROPERTY_NAME = org.junit.jupiter.api.Constants.TIMEOUT_MODE_PROPERTY_NAME; /** * Property name used to set the default method orderer class name: {@value} * * @see MethodOrderer#DEFAULT_ORDER_PROPERTY_NAME * @deprecated Please use * {@link org.junit.jupiter.api.Constants#DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public static final String DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME = org.junit.jupiter.api.Constants.DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME; /** * Property name used to set the default class orderer class name: {@value} * * @see ClassOrderer#DEFAULT_ORDER_PROPERTY_NAME * @deprecated Please use * {@link org.junit.jupiter.api.Constants#DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public static final String DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME = org.junit.jupiter.api.Constants.DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME; /** * Property name used to set the default timeout thread mode: {@value} * * @since 5.9 * @see Timeout * @see Timeout.ThreadMode * @deprecated Please use * {@link org.junit.jupiter.api.Constants#DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public static final String DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME = org.junit.jupiter.api.Constants.DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME; /** * Property name used to set the default factory for temporary directories created via * the {@link TempDir @TempDir} annotation: {@value} * * @since 5.10 * @see TempDir#DEFAULT_FACTORY_PROPERTY_NAME * @deprecated Please use * {@link org.junit.jupiter.api.Constants#DEFAULT_TEMP_DIR_FACTORY_PROPERTY_NAME} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public static final String DEFAULT_TEMP_DIR_FACTORY_PROPERTY_NAME = org.junit.jupiter.api.Constants.DEFAULT_TEMP_DIR_FACTORY_PROPERTY_NAME; /** * Property name used to set the default extension context scope for * extensions that participate in test instantiation: {@value} * * @since 5.12 * @see org.junit.jupiter.api.extension.TestInstantiationAwareExtension * @deprecated Please use * {@link org.junit.jupiter.api.Constants#DEFAULT_TEST_CLASS_INSTANCE_CONSTRUCTION_EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(forRemoval = true, since = "6.1") public static final String DEFAULT_TEST_CLASS_INSTANCE_CONSTRUCTION_EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME = org.junit.jupiter.api.Constants.DEFAULT_TEST_CLASS_INSTANCE_CONSTRUCTION_EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME; private Constants() { /* no-op */ } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/JupiterTestEngine.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.Optional; import org.apiguardian.api.API; import org.junit.jupiter.api.Constants; import org.junit.jupiter.engine.config.CachingJupiterConfiguration; import org.junit.jupiter.engine.config.DefaultJupiterConfiguration; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; import org.junit.jupiter.engine.discovery.DiscoverySelectorResolver; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; import org.junit.jupiter.engine.execution.LauncherStoreFacade; import org.junit.jupiter.engine.support.JupiterThrowableCollectorFactory; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.config.PrefixedConfigurationParameters; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter; import org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine; import org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutorService; import org.junit.platform.engine.support.hierarchical.ParallelHierarchicalTestExecutorServiceFactory; import org.junit.platform.engine.support.hierarchical.ThrowableCollector; /** * The JUnit Jupiter {@link org.junit.platform.engine.TestEngine TestEngine}. * * @since 5.0 */ @API(status = INTERNAL, since = "5.0") public final class JupiterTestEngine extends HierarchicalTestEngine { @Override public String getId() { return JupiterEngineDescriptor.ENGINE_ID; } /** * Returns {@code org.junit.jupiter} as the group ID. */ @Override public Optional getGroupId() { return Optional.of("org.junit.jupiter"); } /** * Returns {@code junit-jupiter-engine} as the artifact ID. */ @Override public Optional getArtifactId() { return Optional.of("junit-jupiter-engine"); } @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { DiscoveryIssueReporter issueReporter = DiscoveryIssueReporter.deduplicating( DiscoveryIssueReporter.forwarding(discoveryRequest.getDiscoveryListener(), uniqueId)); JupiterConfiguration configuration = new CachingJupiterConfiguration( new DefaultJupiterConfiguration(discoveryRequest.getConfigurationParameters(), discoveryRequest.getOutputDirectoryCreator(), issueReporter)); JupiterEngineDescriptor engineDescriptor = new JupiterEngineDescriptor(uniqueId, configuration); DiscoverySelectorResolver.resolveSelectors(discoveryRequest, engineDescriptor, issueReporter); return engineDescriptor; } @Override protected HierarchicalTestExecutorService createExecutorService(ExecutionRequest request) { JupiterConfiguration configuration = getJupiterConfiguration(request); if (configuration.isParallelExecutionEnabled()) { return ParallelHierarchicalTestExecutorServiceFactory.create(new PrefixedConfigurationParameters( request.getConfigurationParameters(), Constants.PARALLEL_CONFIG_PREFIX)); } return super.createExecutorService(request); } @Override protected JupiterEngineExecutionContext createExecutionContext(ExecutionRequest request) { return new JupiterEngineExecutionContext(request.getEngineExecutionListener(), getJupiterConfiguration(request), new LauncherStoreFacade(request.getStore())); } /** * @since 5.4 */ @Override protected ThrowableCollector.Factory createThrowableCollectorFactory(ExecutionRequest request) { return JupiterThrowableCollectorFactory::createThrowableCollector; } private JupiterConfiguration getJupiterConfiguration(ExecutionRequest request) { JupiterEngineDescriptor engineDescriptor = (JupiterEngineDescriptor) request.getRootTestDescriptor(); return engineDescriptor.getConfiguration(); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/CachingJupiterConfiguration.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.config; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.jupiter.api.Constants.CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_EXECUTION_MODE_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_TEMP_DIR_CLEANUP_MODE_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_TEMP_DIR_DELETION_STRATEGY_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_TEMP_DIR_FACTORY_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_TEST_CLASS_INSTANCE_CONSTRUCTION_EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.ClassOrderer; import org.junit.jupiter.api.Constants; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope; import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDirDeletionStrategy; import org.junit.jupiter.api.io.TempDirFactory; import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.platform.engine.OutputDirectoryCreator; /** * Caching implementation of the {@link JupiterConfiguration} API. * * @since 5.4 */ @API(status = INTERNAL, since = "5.4") public class CachingJupiterConfiguration implements JupiterConfiguration { private final ConcurrentHashMap cache = new ConcurrentHashMap<>(); private final JupiterConfiguration delegate; public CachingJupiterConfiguration(JupiterConfiguration delegate) { this.delegate = delegate; } @Override public Predicate> getFilterForAutoDetectedExtensions() { return delegate.getFilterForAutoDetectedExtensions(); } @Override public Optional getRawConfigurationParameter(String key) { return delegate.getRawConfigurationParameter(key); } @Override public Optional getRawConfigurationParameter(String key, Function transformer) { return delegate.getRawConfigurationParameter(key, transformer); } @Override public boolean isParallelExecutionEnabled() { return (boolean) cache.computeIfAbsent(PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, __ -> delegate.isParallelExecutionEnabled()); } @Override public boolean isClosingStoredAutoCloseablesEnabled() { return (boolean) cache.computeIfAbsent(CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_PROPERTY_NAME, __ -> delegate.isClosingStoredAutoCloseablesEnabled()); } @Override public boolean isExtensionAutoDetectionEnabled() { return (boolean) cache.computeIfAbsent(EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME, __ -> delegate.isExtensionAutoDetectionEnabled()); } @Override public boolean isThreadDumpOnTimeoutEnabled() { return (boolean) cache.computeIfAbsent(EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_PROPERTY_NAME, __ -> delegate.isThreadDumpOnTimeoutEnabled()); } @Override public ExecutionMode getDefaultExecutionMode() { return (ExecutionMode) cache.computeIfAbsent(DEFAULT_EXECUTION_MODE_PROPERTY_NAME, __ -> delegate.getDefaultExecutionMode()); } @Override public ExecutionMode getDefaultClassesExecutionMode() { return (ExecutionMode) cache.computeIfAbsent(DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME, __ -> delegate.getDefaultClassesExecutionMode()); } @Override public TestInstance.Lifecycle getDefaultTestInstanceLifecycle() { return (TestInstance.Lifecycle) cache.computeIfAbsent(DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME, __ -> delegate.getDefaultTestInstanceLifecycle()); } @SuppressWarnings("unchecked") @Override public Predicate getExecutionConditionFilter() { return (Predicate) cache.computeIfAbsent( Constants.DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME, __ -> delegate.getExecutionConditionFilter()); } @Override public DisplayNameGenerator getDefaultDisplayNameGenerator() { return (DisplayNameGenerator) cache.computeIfAbsent(DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME, __ -> delegate.getDefaultDisplayNameGenerator()); } @SuppressWarnings("unchecked") @Override public Optional getDefaultTestMethodOrderer() { return (Optional) cache.computeIfAbsent(DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME, __ -> delegate.getDefaultTestMethodOrderer()); } @SuppressWarnings("unchecked") @Override public Optional getDefaultTestClassOrderer() { return (Optional) cache.computeIfAbsent(DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME, __ -> delegate.getDefaultTestClassOrderer()); } @Override public CleanupMode getDefaultTempDirCleanupMode() { return (CleanupMode) cache.computeIfAbsent(DEFAULT_TEMP_DIR_CLEANUP_MODE_PROPERTY_NAME, __ -> delegate.getDefaultTempDirCleanupMode()); } @SuppressWarnings("unchecked") @Override public Supplier getDefaultTempDirFactorySupplier() { return (Supplier) cache.computeIfAbsent(DEFAULT_TEMP_DIR_FACTORY_PROPERTY_NAME, __ -> delegate.getDefaultTempDirFactorySupplier()); } @SuppressWarnings("unchecked") @Override public Supplier getDefaultTempDirDeletionStrategySupplier() { return (Supplier) cache.computeIfAbsent( DEFAULT_TEMP_DIR_DELETION_STRATEGY_PROPERTY_NAME, __ -> delegate.getDefaultTempDirDeletionStrategySupplier()); } @Override public ExtensionContextScope getDefaultTestInstantiationExtensionContextScope() { return (ExtensionContextScope) cache.computeIfAbsent( DEFAULT_TEST_CLASS_INSTANCE_CONSTRUCTION_EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME, __ -> delegate.getDefaultTestInstantiationExtensionContextScope()); } @Override public OutputDirectoryCreator getOutputDirectoryCreator() { return delegate.getOutputDirectoryCreator(); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/ConfigurationParameterConverter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.config; import java.util.Optional; import org.junit.platform.engine.ConfigurationParameters; /** * @since 6.0 */ interface ConfigurationParameterConverter { default T getOrDefault(ConfigurationParameters configParams, String key, T defaultValue) { return get(configParams, key).orElse(defaultValue); } Optional get(ConfigurationParameters configurationParameters, String key); } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.config; import static java.util.function.Predicate.isEqual; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.jupiter.api.Constants.CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_EXECUTION_MODE_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_TEMP_DIR_CLEANUP_MODE_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_TEMP_DIR_DELETION_STRATEGY_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_TEMP_DIR_FACTORY_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_TEST_CLASS_INSTANCE_CONSTRUCTION_EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.EXTENSIONS_AUTODETECTION_EXCLUDE_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.EXTENSIONS_AUTODETECTION_INCLUDE_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.PARALLEL_CONFIG_EXECUTOR_SERVICE_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME; import static org.junit.jupiter.api.io.CleanupMode.ALWAYS; import static org.junit.jupiter.engine.config.FilteringConfigurationParameterConverter.exclude; import static org.junit.platform.engine.support.hierarchical.ParallelHierarchicalTestExecutorServiceFactory.ParallelExecutorServiceType.FORK_JOIN_POOL; import static org.junit.platform.engine.support.hierarchical.ParallelHierarchicalTestExecutorServiceFactory.ParallelExecutorServiceType.WORKER_THREAD_POOL; import java.util.List; import java.util.Optional; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.ClassOrderer; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope; import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDirDeletionStrategy; import org.junit.jupiter.api.io.TempDirFactory; import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.platform.commons.util.ClassNamePatternFilterUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.OutputDirectoryCreator; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter; /** * Default implementation of the {@link JupiterConfiguration} API. * * @since 5.4 */ @API(status = INTERNAL, since = "5.4") public class DefaultJupiterConfiguration implements JupiterConfiguration { private static final List UNSUPPORTED_CONFIGURATION_PARAMETERS = List.of( // "junit.jupiter.tempdir.scope", // "junit.jupiter.params.arguments.conversion.locale.format" // ); private static final ConfigurationParameterConverter executionModeConverter = // new EnumConfigurationParameterConverter<>(ExecutionMode.class, "parallel execution mode"); private static final ConfigurationParameterConverter lifecycleConverter = // new EnumConfigurationParameterConverter<>(Lifecycle.class, "test instance lifecycle mode"); private static final ConfigurationParameterConverter displayNameGeneratorConverter = // new InstantiatingConfigurationParameterConverter<>(DisplayNameGenerator.class, "display name generator"); private static final ConfigurationParameterConverter methodOrdererConverter = // exclude(isEqual(MethodOrderer.Default.class.getName()), new InstantiatingConfigurationParameterConverter<>(MethodOrderer.class, "method orderer")); private static final ConfigurationParameterConverter classOrdererConverter = // exclude(isEqual(ClassOrderer.Default.class.getName()), new InstantiatingConfigurationParameterConverter<>(ClassOrderer.class, "class orderer")); private static final ConfigurationParameterConverter cleanupModeConverter = // new EnumConfigurationParameterConverter<>(CleanupMode.class, "cleanup mode"); private static final InstantiatingConfigurationParameterConverter tempDirFactoryConverter = // new InstantiatingConfigurationParameterConverter<>(TempDirFactory.class, "temp dir factory"); private static final InstantiatingConfigurationParameterConverter tempDirDeletionStrategyConverter = // new InstantiatingConfigurationParameterConverter<>(TempDirDeletionStrategy.class, "temp dir deletion strategy"); private static final ConfigurationParameterConverter extensionContextScopeConverter = // new EnumConfigurationParameterConverter<>(ExtensionContextScope.class, "extension context scope"); private final ConfigurationParameters configurationParameters; private final OutputDirectoryCreator outputDirectoryCreator; public DefaultJupiterConfiguration(ConfigurationParameters configurationParameters, OutputDirectoryCreator outputDirectoryCreator, DiscoveryIssueReporter issueReporter) { this.configurationParameters = Preconditions.notNull(configurationParameters, "ConfigurationParameters must not be null"); this.outputDirectoryCreator = outputDirectoryCreator; validateConfigurationParameters(issueReporter); } private void validateConfigurationParameters(DiscoveryIssueReporter issueReporter) { UNSUPPORTED_CONFIGURATION_PARAMETERS.forEach(key -> configurationParameters.get(key) // .ifPresent(value -> { var warning = DiscoveryIssue.create(Severity.WARNING, """ The '%s' configuration parameter is no longer supported. \ Please remove it from your configuration.""".formatted(key)); issueReporter.reportIssue(warning); })); if (isParallelExecutionEnabled() && configurationParameters.get(PARALLEL_CONFIG_EXECUTOR_SERVICE_PROPERTY_NAME).isEmpty()) { var info = DiscoveryIssue.create(Severity.INFO, "Parallel test execution is enabled but the default ForkJoinPool-based executor service will be used. " + "Please give the new implementation based on a regular thread pool a try by setting the '" + PARALLEL_CONFIG_EXECUTOR_SERVICE_PROPERTY_NAME + "' configuration parameter to '" + WORKER_THREAD_POOL + "' and report any issues to the JUnit team. " + "Alternatively, set the configuration parameter to '" + FORK_JOIN_POOL + "' to hide this message and keep using the original implementation."); issueReporter.reportIssue(info); } } @Override public Predicate> getFilterForAutoDetectedExtensions() { String includePattern = getExtensionAutoDetectionIncludePattern(); String excludePattern = getExtensionAutoDetectionExcludePattern(); Predicate predicate = ClassNamePatternFilterUtils.includeMatchingClassNames(includePattern) // .and(ClassNamePatternFilterUtils.excludeMatchingClassNames(excludePattern)); return clazz -> predicate.test(clazz.getName()); } private String getExtensionAutoDetectionIncludePattern() { return configurationParameters.get(EXTENSIONS_AUTODETECTION_INCLUDE_PROPERTY_NAME) // .orElse(ClassNamePatternFilterUtils.ALL_PATTERN); } private String getExtensionAutoDetectionExcludePattern() { return configurationParameters.get(EXTENSIONS_AUTODETECTION_EXCLUDE_PROPERTY_NAME) // .orElse(ClassNamePatternFilterUtils.BLANK); } @Override public Optional getRawConfigurationParameter(String key) { return configurationParameters.get(key); } @Override public Optional getRawConfigurationParameter(String key, Function transformer) { return configurationParameters.get(key, transformer); } @Override public boolean isParallelExecutionEnabled() { return configurationParameters.getBoolean(PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME).orElse(false); } @Override public boolean isClosingStoredAutoCloseablesEnabled() { return configurationParameters.getBoolean(CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_PROPERTY_NAME).orElse(true); } @Override public boolean isExtensionAutoDetectionEnabled() { return configurationParameters.getBoolean(EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME).orElse(false); } @Override public boolean isThreadDumpOnTimeoutEnabled() { return configurationParameters.getBoolean(EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_PROPERTY_NAME).orElse(false); } @Override public ExecutionMode getDefaultExecutionMode() { return executionModeConverter.getOrDefault(configurationParameters, DEFAULT_EXECUTION_MODE_PROPERTY_NAME, ExecutionMode.SAME_THREAD); } @Override public ExecutionMode getDefaultClassesExecutionMode() { return executionModeConverter.getOrDefault(configurationParameters, DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME, getDefaultExecutionMode()); } @Override public Lifecycle getDefaultTestInstanceLifecycle() { return lifecycleConverter.getOrDefault(configurationParameters, DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME, Lifecycle.PER_METHOD); } @Override public Predicate getExecutionConditionFilter() { return ClassNamePatternFilterUtils.excludeMatchingClasses( configurationParameters.get(DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME).orElse(null)); } @Override public DisplayNameGenerator getDefaultDisplayNameGenerator() { return displayNameGeneratorConverter.get(configurationParameters, DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME) // .orElseGet(() -> DisplayNameGenerator.getDisplayNameGenerator(DisplayNameGenerator.Standard.class)); } @Override public Optional getDefaultTestMethodOrderer() { return methodOrdererConverter.get(configurationParameters, DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME); } @Override public Optional getDefaultTestClassOrderer() { return classOrdererConverter.get(configurationParameters, DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME); } @Override public CleanupMode getDefaultTempDirCleanupMode() { return cleanupModeConverter.getOrDefault(configurationParameters, DEFAULT_TEMP_DIR_CLEANUP_MODE_PROPERTY_NAME, ALWAYS); } @Override public Supplier getDefaultTempDirFactorySupplier() { Supplier> supplier = tempDirFactoryConverter.supply(configurationParameters, DEFAULT_TEMP_DIR_FACTORY_PROPERTY_NAME); return () -> supplier.get().orElse(TempDirFactory.Standard.INSTANCE); } @Override public Supplier getDefaultTempDirDeletionStrategySupplier() { Supplier> supplier = tempDirDeletionStrategyConverter.supply( configurationParameters, DEFAULT_TEMP_DIR_DELETION_STRATEGY_PROPERTY_NAME); return () -> supplier.get().orElse(TempDirDeletionStrategy.Standard.INSTANCE); } @SuppressWarnings("deprecation") @Override public ExtensionContextScope getDefaultTestInstantiationExtensionContextScope() { return extensionContextScopeConverter.getOrDefault(configurationParameters, DEFAULT_TEST_CLASS_INSTANCE_CONSTRUCTION_EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME, ExtensionContextScope.DEFAULT); } @Override public OutputDirectoryCreator getOutputDirectoryCreator() { return outputDirectoryCreator; } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/EnumConfigurationParameterConverter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.config; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.Locale; import java.util.Optional; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.engine.ConfigurationParameters; /** * @since 5.4 */ @API(status = INTERNAL, since = "5.8") public class EnumConfigurationParameterConverter> implements ConfigurationParameterConverter { private static final Logger logger = LoggerFactory.getLogger(EnumConfigurationParameterConverter.class); private final Class enumType; private final String enumDisplayName; public EnumConfigurationParameterConverter(Class enumType, String enumDisplayName) { this.enumType = enumType; this.enumDisplayName = enumDisplayName; } @Override public Optional get(ConfigurationParameters configParams, String key) { return configParams.get(key) // .map(value -> convert(key, value)); } public Optional get(ExtensionContext extensionContext, String key) { return extensionContext.getConfigurationParameter(key, value -> convert(key, value)); } private E convert(String key, String value) { String constantName = null; try { constantName = value.strip().toUpperCase(Locale.ROOT); E result = Enum.valueOf(enumType, constantName); logger.config(() -> "Using %s '%s' set via the '%s' configuration parameter.".formatted(enumDisplayName, result, key)); return result; } catch (Exception ex) { throw new JUnitException("Invalid %s '%s' set via the '%s' configuration parameter.".formatted( enumDisplayName, constantName, key)); } } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/FilteringConfigurationParameterConverter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.config; import static java.util.function.Predicate.not; import java.util.Optional; import java.util.function.Predicate; import org.junit.platform.engine.ConfigurationParameters; /** * @since 6.0 */ class FilteringConfigurationParameterConverter implements ConfigurationParameterConverter { private final Predicate predicate; private final ConfigurationParameterConverter delegate; static FilteringConfigurationParameterConverter exclude(Predicate exclusion, ConfigurationParameterConverter delegate) { return new FilteringConfigurationParameterConverter<>(not(exclusion), delegate); } private FilteringConfigurationParameterConverter(Predicate predicate, ConfigurationParameterConverter delegate) { this.predicate = predicate; this.delegate = delegate; } @Override public Optional get(ConfigurationParameters configurationParameters, String key) { return configurationParameters.get(key) // .map(String::strip) // .filter(predicate) // .flatMap(__ -> delegate.get(configurationParameters, key)); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.config; import java.util.Optional; import java.util.function.Supplier; import org.junit.platform.commons.function.Try; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.engine.ConfigurationParameters; /** * @since 5.5 */ class InstantiatingConfigurationParameterConverter implements ConfigurationParameterConverter { private static final Logger logger = LoggerFactory.getLogger(InstantiatingConfigurationParameterConverter.class); private final Class clazz; private final String name; InstantiatingConfigurationParameterConverter(Class clazz, String name) { this.clazz = clazz; this.name = name; } @Override public Optional get(ConfigurationParameters configurationParameters, String key) { return supply(configurationParameters, key).get(); } Supplier> supply(ConfigurationParameters configurationParameters, String key) { // @formatter:off return configurationParameters.get(key) .map(String::strip) .filter(className -> !className.isEmpty()) .map(className -> newInstanceSupplier(className, key)) .orElse(Optional::empty); // @formatter:on } private Supplier> newInstanceSupplier(String className, String key) { Try> clazz = ReflectionSupport.tryToLoadClass(className); // @formatter:off return () -> clazz.andThenTry(ReflectionSupport::newInstance) .andThenTry(this.clazz::cast) .ifSuccess(generator -> logSuccessMessage(className, key)) .ifFailure(cause -> logFailureMessage(className, key, cause)) .toOptional(); // @formatter:on } private void logFailureMessage(String className, String key, Exception cause) { logger.warn(cause, () -> """ Failed to load default %s class '%s' set via the '%s' configuration parameter. \ Falling back to default behavior.""".formatted(this.name, className, key)); } private void logSuccessMessage(String className, String key) { logger.config(() -> "Using default %s '%s' set via the '%s' configuration parameter.".formatted(this.name, className, key)); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/JupiterConfiguration.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.config; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.Optional; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.ClassOrderer; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope; import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDirDeletionStrategy; import org.junit.jupiter.api.io.TempDirFactory; import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.platform.engine.OutputDirectoryCreator; /** * @since 5.4 */ @API(status = INTERNAL, since = "5.4") public interface JupiterConfiguration { Predicate> getFilterForAutoDetectedExtensions(); Optional getRawConfigurationParameter(String key); Optional getRawConfigurationParameter(String key, Function transformer); boolean isParallelExecutionEnabled(); boolean isClosingStoredAutoCloseablesEnabled(); boolean isExtensionAutoDetectionEnabled(); boolean isThreadDumpOnTimeoutEnabled(); ExecutionMode getDefaultExecutionMode(); ExecutionMode getDefaultClassesExecutionMode(); TestInstance.Lifecycle getDefaultTestInstanceLifecycle(); Predicate getExecutionConditionFilter(); DisplayNameGenerator getDefaultDisplayNameGenerator(); Optional getDefaultTestMethodOrderer(); Optional getDefaultTestClassOrderer(); CleanupMode getDefaultTempDirCleanupMode(); Supplier getDefaultTempDirFactorySupplier(); Supplier getDefaultTempDirDeletionStrategySupplier(); ExtensionContextScope getDefaultTestInstantiationExtensionContextScope(); OutputDirectoryCreator getOutputDirectoryCreator(); } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Configuration specific to the JUnit Jupiter test engine. */ @NullMarked package org.junit.jupiter.engine.config; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.toCollection; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Function; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.MediaType; import org.junit.jupiter.api.extension.ExecutableInvoker; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.function.ThrowingConsumer; import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.DefaultExecutableInvoker; import org.junit.jupiter.engine.execution.LauncherStoreFacade; import org.junit.jupiter.engine.extension.ExtensionContextInternal; import org.junit.jupiter.engine.extension.ExtensionRegistry; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.UnrecoverableExceptions; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestTag; import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.engine.support.hierarchical.Node; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; /** * @since 5.0 */ 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 @Nullable ExtensionContext parent; private final EngineExecutionListener engineExecutionListener; private final T testDescriptor; private final Set tags; private final JupiterConfiguration configuration; private final ExecutableInvoker executableInvoker; private final ExtensionRegistry extensionRegistry; private final LauncherStoreFacade launcherStoreFacade; private final NamespacedHierarchicalStore valuesStore; AbstractExtensionContext(@Nullable ExtensionContext parent, EngineExecutionListener engineExecutionListener, T testDescriptor, JupiterConfiguration configuration, ExtensionRegistry extensionRegistry, LauncherStoreFacade launcherStoreFacade) { Preconditions.notNull(testDescriptor, "TestDescriptor must not be null"); Preconditions.notNull(configuration, "JupiterConfiguration must not be null"); Preconditions.notNull(extensionRegistry, "ExtensionRegistry must not be null"); this.executableInvoker = new DefaultExecutableInvoker(this, extensionRegistry); this.parent = parent; this.engineExecutionListener = engineExecutionListener; this.testDescriptor = testDescriptor; this.configuration = configuration; this.extensionRegistry = extensionRegistry; this.launcherStoreFacade = launcherStoreFacade; // @formatter:off this.tags = testDescriptor.getTags().stream() .map(TestTag::getName) .collect(collectingAndThen(toCollection(LinkedHashSet::new), Collections::unmodifiableSet)); // @formatter:on this.valuesStore = new NamespacedHierarchicalStore<>(getParentStore(parent), createCloseAction()); } private NamespacedHierarchicalStore getParentStore( @Nullable ExtensionContext parent) { return parent == null // ? this.launcherStoreFacade.getRequestLevelStore() // : ((AbstractExtensionContext) parent).valuesStore; } @SuppressWarnings("deprecation") private NamespacedHierarchicalStore.CloseAction createCloseAction() { var store = this.launcherStoreFacade.getSessionLevelStore(CLOSEABLE_RESOURCE_LOGGING_NAMESPACE); return (__, ___, value) -> { boolean isAutoCloseEnabled = this.configuration.isClosingStoredAutoCloseablesEnabled(); if (isAutoCloseEnabled && value instanceof @SuppressWarnings("resource") AutoCloseable closeable) { closeable.close(); return; } if (value instanceof Store.CloseableResource resource) { if (isAutoCloseEnabled) { store.computeIfAbsent(value.getClass(), type -> { logger.warn(() -> "Type implements CloseableResource but not AutoCloseable: " + type.getName()); return true; }); } resource.close(); } }; } @Override public void close() { this.valuesStore.close(); } @Override public String getUniqueId() { return getTestDescriptor().getUniqueId().toString(); } @Override public String getDisplayName() { return getTestDescriptor().getDisplayName(); } @Override public void publishReportEntry(Map values) { this.engineExecutionListener.reportingEntryPublished(this.testDescriptor, ReportEntry.from(values)); } @Override public void publishFile(String name, MediaType mediaType, ThrowingConsumer action) { Preconditions.notBlank(name, "name must not be null or blank"); Preconditions.notNull(mediaType, "mediaType must not be null"); Preconditions.notNull(action, "action must not be null"); publishFileEntry(name, action, path -> { Preconditions.condition(Files.isRegularFile(path), () -> "Published path must be a regular file: " + path); return FileEntry.from(path, mediaType.toString()); }); } @Override public void publishDirectory(String name, ThrowingConsumer action) { Preconditions.notBlank(name, "name must not be null or blank"); Preconditions.notNull(action, "action must not be null"); ThrowingConsumer enhancedAction = path -> { if (!Files.isDirectory(path)) { Files.createDirectory(path); } action.accept(path); }; publishFileEntry(name, enhancedAction, path -> { Preconditions.condition(Files.isDirectory(path), () -> "Published path must be a directory: " + path); return FileEntry.from(path, null); }); } private void publishFileEntry(String name, ThrowingConsumer action, Function fileEntryCreator) { Path dir = createOutputDirectory(); Path path = dir.resolve(name); Preconditions.condition(path.getParent() != null && path.getParent().equals(dir), () -> "name must not contain path separators: " + name); try { action.accept(path); } catch (Throwable t) { UnrecoverableExceptions.rethrowIfUnrecoverable(t); throw new JUnitException("Failed to publish path", t); } Preconditions.condition(Files.exists(path), () -> "Published path must exist: " + path); FileEntry fileEntry = fileEntryCreator.apply(path); this.engineExecutionListener.fileEntryPublished(this.testDescriptor, fileEntry); } private Path createOutputDirectory() { try { return configuration.getOutputDirectoryCreator().createOutputDirectory(this.testDescriptor); } catch (IOException e) { throw new JUnitException("Failed to create output directory", e); } } @Override public Optional getParent() { return Optional.ofNullable(this.parent); } @Override public ExtensionContext getRoot() { if (this.parent != null) { return this.parent.getRoot(); } return this; } protected T getTestDescriptor() { return this.testDescriptor; } @Override public Store getStore(Namespace namespace) { return launcherStoreFacade.getStoreAdapter(this.valuesStore, namespace); } @Override public Store getStore(StoreScope scope, Namespace namespace) { return switch (scope) { case LAUNCHER_SESSION -> launcherStoreFacade.getSessionLevelStore(namespace); case EXECUTION_REQUEST -> launcherStoreFacade.getRequestLevelStore(namespace); case EXTENSION_CONTEXT -> getStore(namespace); }; } @Override public Set getTags() { // return modifiable copy return new LinkedHashSet<>(this.tags); } @Override public Optional getConfigurationParameter(String key) { return this.configuration.getRawConfigurationParameter(key); } @Override public Optional getConfigurationParameter(String key, Function transformer) { return this.configuration.getRawConfigurationParameter(key, transformer); } @Override public ExecutionMode getExecutionMode() { return toJupiterExecutionMode(getPlatformExecutionMode()); } @Override public ExecutableInvoker getExecutableInvoker() { return executableInvoker; } @Override public List getExtensions(Class extensionType) { return extensionRegistry.getExtensions(extensionType); } protected abstract Node.ExecutionMode getPlatformExecutionMode(); private ExecutionMode toJupiterExecutionMode(Node.ExecutionMode mode) { return switch (mode) { case CONCURRENT -> ExecutionMode.CONCURRENT; case SAME_THREAD -> ExecutionMode.SAME_THREAD; }; } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/CallbackSupport.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static org.junit.platform.commons.util.CollectionUtils.forEachInReverseOrder; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; import org.junit.jupiter.engine.extension.ExtensionRegistry; import org.junit.platform.engine.support.hierarchical.ThrowableCollector; /** * @since 5.13 */ class CallbackSupport { static void invokeBeforeCallbacks(Class type, JupiterEngineExecutionContext context, CallbackInvoker callbackInvoker) { ExtensionRegistry registry = context.getExtensionRegistry(); ExtensionContext extensionContext = context.getExtensionContext(); ThrowableCollector throwableCollector = context.getThrowableCollector(); for (T callback : registry.getExtensions(type)) { throwableCollector.execute(() -> callbackInvoker.invoke(callback, extensionContext)); if (throwableCollector.isNotEmpty()) { break; } } } static void invokeAfterCallbacks(Class type, JupiterEngineExecutionContext context, CallbackInvoker callbackInvoker) { ExtensionRegistry registry = context.getExtensionRegistry(); ExtensionContext extensionContext = context.getExtensionContext(); ThrowableCollector throwableCollector = context.getThrowableCollector(); forEachInReverseOrder(registry.getExtensions(type), // callback -> throwableCollector.execute(() -> callbackInvoker.invoke(callback, extensionContext))); } @FunctionalInterface protected interface CallbackInvoker { void invoke(T t, ExtensionContext context) throws Throwable; } private CallbackSupport() { } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.joining; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.jupiter.engine.descriptor.CallbackSupport.invokeAfterCallbacks; import static org.junit.jupiter.engine.descriptor.CallbackSupport.invokeBeforeCallbacks; import static org.junit.jupiter.engine.descriptor.ExtensionUtils.populateNewExtensionRegistryFromExtendWithAnnotation; import static org.junit.jupiter.engine.descriptor.ExtensionUtils.registerExtensionsFromConstructorParameters; import static org.junit.jupiter.engine.descriptor.ExtensionUtils.registerExtensionsFromExecutableParameters; import static org.junit.jupiter.engine.descriptor.ExtensionUtils.registerExtensionsFromInstanceFields; import static org.junit.jupiter.engine.descriptor.ExtensionUtils.registerExtensionsFromStaticFields; import static org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findAfterAllMethods; import static org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findAfterEachMethods; import static org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findBeforeAllMethods; import static org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findBeforeEachMethods; import static org.junit.jupiter.engine.descriptor.TestInstanceLifecycleUtils.getTestInstanceLifecycle; import static org.junit.jupiter.engine.support.JupiterThrowableCollectorFactory.createThrowableCollector; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.function.Supplier; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionConfigurationException; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.InvocationInterceptor; import org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler; import org.junit.jupiter.api.extension.TestInstanceFactory; import org.junit.jupiter.api.extension.TestInstanceFactoryContext; import org.junit.jupiter.api.extension.TestInstancePostProcessor; import org.junit.jupiter.api.extension.TestInstancePreConstructCallback; import org.junit.jupiter.api.extension.TestInstancePreDestroyCallback; import org.junit.jupiter.api.extension.TestInstances; import org.junit.jupiter.api.extension.TestInstantiationException; import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.AfterEachMethodAdapter; import org.junit.jupiter.engine.execution.BeforeEachMethodAdapter; import org.junit.jupiter.engine.execution.DefaultTestInstances; import org.junit.jupiter.engine.execution.ExtensionContextSupplier; import org.junit.jupiter.engine.execution.InterceptingExecutableInvoker; import org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.ReflectiveInterceptorCall.VoidMethodInterceptorCall; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; import org.junit.jupiter.engine.execution.TestInstancesProvider; import org.junit.jupiter.engine.extension.ExtensionRegistrar; import org.junit.jupiter.engine.extension.ExtensionRegistry; import org.junit.jupiter.engine.extension.MutableExtensionRegistry; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.commons.util.StringUtils; import org.junit.platform.commons.util.UnrecoverableExceptions; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestTag; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter; import org.junit.platform.engine.support.hierarchical.ThrowableCollector; /** * {@link TestDescriptor} for tests based on Java classes. * * @since 5.5 */ @API(status = INTERNAL, since = "5.5") public abstract class ClassBasedTestDescriptor extends JupiterTestDescriptor implements ResourceLockAware, TestClassAware, Validatable { private static final InterceptingExecutableInvoker executableInvoker = new InterceptingExecutableInvoker(); protected final ClassInfo classInfo; private @Nullable LifecycleMethods lifecycleMethods; private @Nullable TestInstanceFactory testInstanceFactory; ClassBasedTestDescriptor(UniqueId uniqueId, Class testClass, Supplier displayNameSupplier, JupiterConfiguration configuration) { super(uniqueId, testClass, displayNameSupplier, ClassSource.from(testClass), configuration); this.classInfo = new ClassInfo(testClass, configuration); this.lifecycleMethods = new LifecycleMethods(this.classInfo); } ClassBasedTestDescriptor(UniqueId uniqueId, Class testClass, String displayName, JupiterConfiguration configuration) { super(uniqueId, displayName, ClassSource.from(testClass), configuration); this.classInfo = new ClassInfo(testClass, configuration); this.lifecycleMethods = new LifecycleMethods(this.classInfo); } // --- TestClassAware ------------------------------------------------------ @Override public final Class getTestClass() { return this.classInfo.testClass; } // --- TestDescriptor ------------------------------------------------------ @Override public final Type getType() { return Type.CONTAINER; } @Override protected final String getLegacyReportingBaseName() { return getTestClass().getName(); } // --- Validatable --------------------------------------------------------- @Override public final void validate(DiscoveryIssueReporter reporter) { validateCoreLifecycleMethods(reporter); validateClassTemplateInvocationLifecycleMethods(reporter); validateTags(reporter); validateDisplayNameAnnotation(reporter); } private void validateDisplayNameAnnotation(DiscoveryIssueReporter reporter) { DisplayNameUtils.validateAnnotation(getTestClass(), // () -> "class '%s'".formatted(getTestClass().getName()), // () -> getSource().orElse(null), // reporter); } protected void validateCoreLifecycleMethods(DiscoveryIssueReporter reporter) { Validatable.reportAndClear(requireLifecycleMethods().discoveryIssues, reporter); } protected void validateClassTemplateInvocationLifecycleMethods(DiscoveryIssueReporter reporter) { LifecycleMethodUtils.validateNoClassTemplateInvocationLifecycleMethodsAreDeclared(getTestClass(), reporter); } private void validateTags(DiscoveryIssueReporter reporter) { Validatable.reportAndClear(this.classInfo.discoveryIssues, reporter); } // --- Node ---------------------------------------------------------------- @Override protected final Optional getExplicitExecutionMode() { return getExecutionModeFromAnnotation(getTestClass()); } @Override protected final Optional getDefaultChildExecutionMode() { return Optional.ofNullable(this.classInfo.defaultChildExecutionMode); } public final void setDefaultChildExecutionMode(ExecutionMode defaultChildExecutionMode) { this.classInfo.defaultChildExecutionMode = defaultChildExecutionMode; } @Override public final ExclusiveResourceCollector getExclusiveResourceCollector() { return this.classInfo.exclusiveResourceCollector; } @Override public final JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) { MutableExtensionRegistry registry = populateNewExtensionRegistryFromExtendWithAnnotation( context.getExtensionRegistry(), getTestClass()); // Register extensions from static fields here, at the class level but // after extensions registered via @ExtendWith. registerExtensionsFromStaticFields(registry, getTestClass()); // Resolve the TestInstanceFactory at the class level in order to fail // the entire class in case of configuration errors (e.g., more than // one factory registered per class). this.testInstanceFactory = resolveTestInstanceFactory(registry); if (this.testInstanceFactory == null) { registerExtensionsFromConstructorParameters(registry, getTestClass()); } requireLifecycleMethods().beforeAll.forEach( method -> registerExtensionsFromExecutableParameters(registry, method)); // Since registerBeforeEachMethodAdapters() and registerAfterEachMethodAdapters() also // invoke registerExtensionsFromExecutableParameters(), we invoke those methods before // invoking registerExtensionsFromExecutableParameters() for @AfterAll methods, // thereby ensuring proper registration order for extensions registered via @ExtendWith // on parameters in lifecycle methods. registerBeforeEachMethodAdapters(registry); registerAfterEachMethodAdapters(registry); requireLifecycleMethods().afterAll.forEach( method -> registerExtensionsFromExecutableParameters(registry, method)); registerExtensionsFromInstanceFields(registry, getTestClass()); ThrowableCollector throwableCollector = createThrowableCollector(); ClassExtensionContext extensionContext = new ClassExtensionContext(context.getExtensionContext(), context.getExecutionListener(), this, this.classInfo.lifecycle, context.getConfiguration(), registry, context.getLauncherStoreFacade(), throwableCollector); // @formatter:off return context.extend() .withTestInstancesProvider(testInstancesProvider(context, extensionContext)) .withExtensionRegistry(registry) .withExtensionContext(extensionContext) .withThrowableCollector(throwableCollector) .build(); // @formatter:on } @Override public final JupiterEngineExecutionContext before(JupiterEngineExecutionContext context) { ThrowableCollector throwableCollector = context.getThrowableCollector(); if (isPerClassLifecycle(context)) { // Eagerly load test instance for BeforeAllCallbacks, if necessary, // and store the instance in the ExtensionContext. ClassExtensionContext extensionContext = (ClassExtensionContext) context.getExtensionContext(); throwableCollector.execute(() -> { TestInstances testInstances = context.getTestInstancesProvider().getTestInstances(context); extensionContext.setTestInstances(testInstances); }); } if (throwableCollector.isEmpty()) { context.beforeAllCallbacksExecuted(true); invokeBeforeAllCallbacks(context); if (throwableCollector.isEmpty()) { context.beforeAllMethodsExecuted(true); invokeBeforeAllMethods(context); } } throwableCollector.assertEmpty(); return context; } @Override public final void after(JupiterEngineExecutionContext context) { ThrowableCollector throwableCollector = context.getThrowableCollector(); Throwable previousThrowable = throwableCollector.getThrowable(); if (context.beforeAllMethodsExecuted()) { invokeAfterAllMethods(context); } if (context.beforeAllCallbacksExecuted()) { invokeAfterAllCallbacks(context); } if (isPerClassLifecycle(context) && context.getExtensionContext().getTestInstance().isPresent()) { invokeTestInstancePreDestroyCallbacks(context); } // If the previous Throwable was not null when this method was called, // that means an exception was already thrown either before or during // the execution of this Node. If an exception was already thrown, any // later exceptions were added as suppressed exceptions to that original // exception unless a more severe exception occurred in the meantime. if (previousThrowable != throwableCollector.getThrowable()) { throwableCollector.assertEmpty(); } } @Override public void cleanUp(JupiterEngineExecutionContext context) throws Exception { super.cleanUp(context); this.lifecycleMethods = null; this.testInstanceFactory = null; } private @Nullable TestInstanceFactory resolveTestInstanceFactory(ExtensionRegistry registry) { List factories = registry.getExtensions(TestInstanceFactory.class); if (factories.size() == 1) { return factories.get(0); } if (factories.size() > 1) { String factoryNames = factories.stream()// .map(factory -> factory.getClass().getName())// .collect(joining(", ")); String errorMessage = "The following TestInstanceFactory extensions were registered for test class [%s], but only one is permitted: %s".formatted( getTestClass().getName(), factoryNames); throw new ExtensionConfigurationException(errorMessage); } return null; } private TestInstancesProvider testInstancesProvider(JupiterEngineExecutionContext parentExecutionContext, ClassExtensionContext ourExtensionContext) { // For Lifecycle.PER_CLASS, ourExtensionContext.getTestInstances() is used to store the instance. // Otherwise, extensionContext.getTestInstances() is always empty and we always create a new instance. return (registry, context) -> ourExtensionContext.getTestInstances().orElseGet( () -> instantiateAndPostProcessTestInstance(parentExecutionContext, ourExtensionContext, registry, context)); } private TestInstances instantiateAndPostProcessTestInstance(JupiterEngineExecutionContext parentExecutionContext, ClassExtensionContext ourExtensionContext, ExtensionRegistry registry, JupiterEngineExecutionContext context) { ExtensionContextSupplier extensionContext = ExtensionContextSupplier.create(context.getExtensionContext(), ourExtensionContext, configuration); TestInstances instances = instantiateTestClass(parentExecutionContext, extensionContext, registry, context); context.getThrowableCollector().execute(() -> { invokeTestInstancePostProcessors(instances.getInnermostInstance(), registry, extensionContext); // In addition, we initialize extension registered programmatically from instance fields here // since the best time to do that is immediately following test class instantiation // and post-processing. context.getExtensionRegistry().initializeExtensions(getTestClass(), instances.getInnermostInstance()); }); return instances; } protected abstract TestInstances instantiateTestClass(JupiterEngineExecutionContext parentExecutionContext, ExtensionContextSupplier extensionContext, ExtensionRegistry registry, JupiterEngineExecutionContext context); protected final TestInstances instantiateTestClass(Optional outerInstances, ExtensionRegistry registry, ExtensionContextSupplier extensionContext) { Optional outerInstance = outerInstances.map(TestInstances::getInnermostInstance); invokeTestInstancePreConstructCallbacks(new DefaultTestInstanceFactoryContext(getTestClass(), outerInstance), registry, extensionContext); Object instance = this.testInstanceFactory != null // ? invokeTestInstanceFactory(this.testInstanceFactory, outerInstance, extensionContext) // : invokeTestClassConstructor(outerInstance, registry, extensionContext); return outerInstances.map(instances -> DefaultTestInstances.of(instances, instance)) // .orElse(DefaultTestInstances.of(instance)); } private Object invokeTestInstanceFactory(TestInstanceFactory testInstanceFactory, Optional outerInstance, ExtensionContextSupplier extensionContext) { Object instance; try { ExtensionContext actualExtensionContext = extensionContext.get(testInstanceFactory); instance = testInstanceFactory.createTestInstance( new DefaultTestInstanceFactoryContext(getTestClass(), outerInstance), actualExtensionContext); } catch (Throwable throwable) { UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); if (throwable instanceof TestInstantiationException exception) { throw exception; } String message = "TestInstanceFactory [%s] failed to instantiate test class [%s]".formatted( testInstanceFactory.getClass().getName(), getTestClass().getName()); if (StringUtils.isNotBlank(throwable.getMessage())) { message += ": " + throwable.getMessage(); } throw new TestInstantiationException(message, throwable); } if (!getTestClass().isInstance(instance)) { String testClassName = getTestClass().getName(); Class instanceClass = (instance == null ? null : instance.getClass()); String instanceClassName = (instanceClass == null ? "null" : instanceClass.getName()); // If the test instance was loaded via a different ClassLoader, append // the identity hash codes to the type names to help users disambiguate // between otherwise identical "fully qualified class names". if (testClassName.equals(instanceClassName)) { testClassName += "@" + Integer.toHexString(System.identityHashCode(getTestClass())); instanceClassName += "@" + Integer.toHexString(System.identityHashCode(instanceClass)); } String message = "TestInstanceFactory [%s] failed to return an instance of [%s] and instead returned an instance of [%s].".formatted( testInstanceFactory.getClass().getName(), testClassName, instanceClassName); throw new TestInstantiationException(message); } return instance; } private Object invokeTestClassConstructor(Optional outerInstance, ExtensionRegistry registry, ExtensionContextSupplier extensionContext) { Constructor constructor = ReflectionUtils.getDeclaredConstructor(getTestClass()); return executableInvoker.invoke(constructor, outerInstance, extensionContext, registry, InvocationInterceptor::interceptTestClassConstructor); } private void invokeTestInstancePreConstructCallbacks(TestInstanceFactoryContext factoryContext, ExtensionRegistry registry, ExtensionContextSupplier context) { registry.stream(TestInstancePreConstructCallback.class).forEach(extension -> executeAndMaskThrowable( () -> extension.preConstructTestInstance(factoryContext, context.get(extension)))); } private void invokeTestInstancePostProcessors(Object instance, ExtensionRegistry registry, ExtensionContextSupplier context) { registry.stream(TestInstancePostProcessor.class).forEach(extension -> executeAndMaskThrowable( () -> extension.postProcessTestInstance(instance, context.get(extension)))); } private void executeAndMaskThrowable(Executable executable) { try { executable.execute(); } catch (Throwable throwable) { throw ExceptionUtils.throwAsUncheckedException(throwable); } } private void invokeBeforeAllCallbacks(JupiterEngineExecutionContext context) { invokeBeforeCallbacks(BeforeAllCallback.class, context, BeforeAllCallback::beforeAll); } private void invokeBeforeAllMethods(JupiterEngineExecutionContext context) { ExtensionRegistry registry = context.getExtensionRegistry(); ExtensionContext extensionContext = context.getExtensionContext(); ThrowableCollector throwableCollector = context.getThrowableCollector(); Object testInstance = extensionContext.getTestInstance().orElse(null); for (Method method : requireLifecycleMethods().beforeAll) { throwableCollector.execute(() -> { try { executableInvoker.invokeVoid(method, testInstance, extensionContext, registry, InvocationInterceptor::interceptBeforeAllMethod); } catch (Throwable throwable) { invokeBeforeAllMethodExecutionExceptionHandlers(registry, extensionContext, throwable); } }); if (throwableCollector.isNotEmpty()) { break; } } } private void invokeBeforeAllMethodExecutionExceptionHandlers(ExtensionRegistry registry, ExtensionContext context, Throwable throwable) { invokeExecutionExceptionHandlers(LifecycleMethodExecutionExceptionHandler.class, registry, throwable, (handler, handledThrowable) -> handler.handleBeforeAllMethodExecutionException(context, handledThrowable)); } private void invokeAfterAllMethods(JupiterEngineExecutionContext context) { ExtensionRegistry registry = context.getExtensionRegistry(); ExtensionContext extensionContext = context.getExtensionContext(); ThrowableCollector throwableCollector = context.getThrowableCollector(); Object testInstance = extensionContext.getTestInstance().orElse(null); requireLifecycleMethods().afterAll.forEach(method -> throwableCollector.execute(() -> { try { executableInvoker.invokeVoid(method, testInstance, extensionContext, registry, InvocationInterceptor::interceptAfterAllMethod); } catch (Throwable throwable) { invokeAfterAllMethodExecutionExceptionHandlers(registry, extensionContext, throwable); } })); } private void invokeAfterAllMethodExecutionExceptionHandlers(ExtensionRegistry registry, ExtensionContext context, Throwable throwable) { invokeExecutionExceptionHandlers(LifecycleMethodExecutionExceptionHandler.class, registry, throwable, (handler, handledThrowable) -> handler.handleAfterAllMethodExecutionException(context, handledThrowable)); } private void invokeAfterAllCallbacks(JupiterEngineExecutionContext context) { invokeAfterCallbacks(AfterAllCallback.class, context, AfterAllCallback::afterAll); } private void invokeTestInstancePreDestroyCallbacks(JupiterEngineExecutionContext context) { invokeAfterCallbacks(TestInstancePreDestroyCallback.class, context, TestInstancePreDestroyCallback::preDestroyTestInstance); } private boolean isPerClassLifecycle(JupiterEngineExecutionContext context) { return context.getExtensionContext().getTestInstanceLifecycle().orElse( Lifecycle.PER_METHOD) == Lifecycle.PER_CLASS; } private void registerBeforeEachMethodAdapters(ExtensionRegistrar registrar) { registerMethodsAsExtensions(requireLifecycleMethods().beforeEach, registrar, this::synthesizeBeforeEachMethodAdapter); } private void registerAfterEachMethodAdapters(ExtensionRegistrar registrar) { // Make a local copy since findAfterEachMethods() returns an immutable list. List afterEachMethods = new ArrayList<>(requireLifecycleMethods().afterEach); // Since the bottom-up ordering of afterEachMethods will later be reversed when the // synthesized AfterEachMethodAdapters are executed within TestMethodTestDescriptor, // we have to reverse the afterEachMethods list to put them in top-down order before // we register them as synthesized extensions. Collections.reverse(afterEachMethods); registerMethodsAsExtensions(afterEachMethods, registrar, this::synthesizeAfterEachMethodAdapter); } private void registerMethodsAsExtensions(List methods, ExtensionRegistrar registrar, Function extensionSynthesizer) { methods.forEach(method -> { registerExtensionsFromExecutableParameters(registrar, method); registrar.registerSyntheticExtension(extensionSynthesizer.apply(method), method); }); } private BeforeEachMethodAdapter synthesizeBeforeEachMethodAdapter(Method method) { return (extensionContext, registry) -> invokeMethodInExtensionContext(method, extensionContext, registry, InvocationInterceptor::interceptBeforeEachMethod); } private AfterEachMethodAdapter synthesizeAfterEachMethodAdapter(Method method) { return (extensionContext, registry) -> invokeMethodInExtensionContext(method, extensionContext, registry, InvocationInterceptor::interceptAfterEachMethod); } private void invokeMethodInExtensionContext(Method method, ExtensionContext context, ExtensionRegistry registry, VoidMethodInterceptorCall interceptorCall) { TestInstances testInstances = context.getRequiredTestInstances(); Object target = testInstances.findInstance(getTestClass()).orElseThrow( () -> new JUnitException("Failed to find instance for method: " + method.toGenericString())); executableInvoker.invokeVoid(method, target, context, registry, interceptorCall); } private LifecycleMethods requireLifecycleMethods() { return requireNonNull(this.lifecycleMethods); } protected static class ClassInfo { private final List discoveryIssues = new ArrayList<>(); final Class testClass; final Set tags; final Lifecycle lifecycle; @Nullable ExecutionMode defaultChildExecutionMode; final ExclusiveResourceCollector exclusiveResourceCollector; ClassInfo(Class testClass, JupiterConfiguration configuration) { this.testClass = testClass; this.tags = getTags(testClass, // () -> "class '%s'".formatted(testClass.getName()), // () -> ClassSource.from(testClass), // discoveryIssues::add); this.lifecycle = getTestInstanceLifecycle(testClass, configuration); this.defaultChildExecutionMode = (this.lifecycle == Lifecycle.PER_CLASS ? ExecutionMode.SAME_THREAD : null); this.exclusiveResourceCollector = ExclusiveResourceCollector.from(testClass); } } private static class LifecycleMethods { private final List discoveryIssues = new ArrayList<>(); private final List beforeAll; private final List afterAll; private final List beforeEach; private final List afterEach; LifecycleMethods(ClassInfo classInfo) { Class testClass = classInfo.testClass; boolean requireStatic = classInfo.lifecycle == Lifecycle.PER_METHOD; DiscoveryIssueReporter issueReporter = DiscoveryIssueReporter.collecting(discoveryIssues); this.beforeAll = findBeforeAllMethods(testClass, requireStatic, issueReporter); this.afterAll = findAfterAllMethods(testClass, requireStatic, issueReporter); this.beforeEach = findBeforeEachMethods(testClass, issueReporter); this.afterEach = findAfterEachMethods(testClass, issueReporter); } } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassExtensionContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.util.List; import java.util.Optional; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestInstances; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.LauncherStoreFacade; import org.junit.jupiter.engine.extension.ExtensionRegistry; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.support.hierarchical.Node; import org.junit.platform.engine.support.hierarchical.ThrowableCollector; /** * @since 5.0 */ final class ClassExtensionContext extends AbstractExtensionContext { private final Lifecycle lifecycle; private final ThrowableCollector throwableCollector; private @Nullable TestInstances testInstances; ClassExtensionContext(ExtensionContext parent, EngineExecutionListener engineExecutionListener, ClassBasedTestDescriptor testDescriptor, Lifecycle lifecycle, JupiterConfiguration configuration, ExtensionRegistry extensionRegistry, LauncherStoreFacade launcherStoreFacade, ThrowableCollector throwableCollector) { super(parent, engineExecutionListener, testDescriptor, configuration, extensionRegistry, launcherStoreFacade); this.lifecycle = lifecycle; this.throwableCollector = throwableCollector; } @Override public Optional getElement() { return Optional.of(getTestDescriptor().getTestClass()); } @Override public Optional> getTestClass() { return Optional.of(getTestDescriptor().getTestClass()); } @Override public List> getEnclosingTestClasses() { return getTestDescriptor().getEnclosingTestClasses(); } @Override public Optional getTestInstanceLifecycle() { return Optional.of(this.lifecycle); } @Override public Optional getTestInstance() { return getTestInstances().map(TestInstances::getInnermostInstance); } @Override public Optional getTestInstances() { return Optional.ofNullable(testInstances); } void setTestInstances(TestInstances testInstances) { this.testInstances = testInstances; } @Override public Optional getTestMethod() { return Optional.empty(); } @Override public Optional getExecutionException() { return Optional.ofNullable(this.throwableCollector.getThrowable()); } @Override protected Node.ExecutionMode getPlatformExecutionMode() { return getTestDescriptor().getExecutionMode(); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTemplateInvocationExtensionContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.util.List; import java.util.Optional; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestInstances; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.LauncherStoreFacade; import org.junit.jupiter.engine.extension.ExtensionRegistry; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.support.hierarchical.Node; /** * @since 5.13 */ final class ClassTemplateInvocationExtensionContext extends AbstractExtensionContext { ClassTemplateInvocationExtensionContext(ExtensionContext parent, EngineExecutionListener engineExecutionListener, ClassTemplateInvocationTestDescriptor testDescriptor, JupiterConfiguration configuration, ExtensionRegistry extensionRegistry, LauncherStoreFacade launcherStoreFacade) { super(parent, engineExecutionListener, testDescriptor, configuration, extensionRegistry, launcherStoreFacade); } @Override public Optional getElement() { return Optional.of(getTestDescriptor().getTestClass()); } @Override public Optional> getTestClass() { return Optional.of(getTestDescriptor().getTestClass()); } @Override public List> getEnclosingTestClasses() { return getTestDescriptor().getEnclosingTestClasses(); } @Override public Optional getTestInstanceLifecycle() { return getParent().flatMap(ExtensionContext::getTestInstanceLifecycle); } @Override public Optional getTestInstance() { return getParent().flatMap(ExtensionContext::getTestInstance); } @Override public Optional getTestInstances() { return getParent().flatMap(ExtensionContext::getTestInstances); } @Override public Optional getTestMethod() { return Optional.empty(); } @Override public Optional getExecutionException() { return Optional.empty(); } @Override protected Node.ExecutionMode getPlatformExecutionMode() { return getTestDescriptor().getExecutionMode(); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTemplateInvocationTestDescriptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static java.util.Collections.emptySet; import static java.util.Objects.requireNonNull; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.jupiter.engine.descriptor.CallbackSupport.invokeAfterCallbacks; import static org.junit.jupiter.engine.descriptor.CallbackSupport.invokeBeforeCallbacks; import static org.junit.jupiter.engine.extension.MutableExtensionRegistry.createRegistryFrom; import static org.junit.jupiter.engine.support.JupiterThrowableCollectorFactory.createThrowableCollector; import java.util.List; import java.util.OptionalInt; import java.util.Set; import java.util.function.Function; import java.util.function.UnaryOperator; import java.util.stream.Stream; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.AfterClassTemplateInvocationCallback; import org.junit.jupiter.api.extension.BeforeClassTemplateInvocationCallback; import org.junit.jupiter.api.extension.ClassTemplateInvocationContext; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.parallel.ResourceLocksProvider; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; import org.junit.jupiter.engine.extension.MutableExtensionRegistry; import org.junit.platform.engine.TestSource; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.hierarchical.ExclusiveResource; import org.junit.platform.engine.support.hierarchical.ThrowableCollector; /** * @since 5.13 */ @API(status = INTERNAL, since = "5.13") public class ClassTemplateInvocationTestDescriptor extends JupiterTestDescriptor implements TestClassAware, ResourceLockAware { public static final String SEGMENT_TYPE = "class-template-invocation"; private final ClassTemplateTestDescriptor parent; private @Nullable ClassTemplateInvocationContext invocationContext; private final int index; public ClassTemplateInvocationTestDescriptor(UniqueId uniqueId, ClassTemplateTestDescriptor parent, ClassTemplateInvocationContext invocationContext, int index, @Nullable TestSource source, JupiterConfiguration configuration) { super(uniqueId, invocationContext.getDisplayName(index), source, configuration); this.parent = parent; this.invocationContext = invocationContext; this.index = index; } public int getIndex() { return index; } // --- JupiterTestDescriptor ----------------------------------------------- @Override protected ClassTemplateInvocationTestDescriptor withUniqueId(UnaryOperator uniqueIdTransformer) { return new ClassTemplateInvocationTestDescriptor(uniqueIdTransformer.apply(getUniqueId()), parent, requiredInvocationContext(), this.index, getSource().orElse(null), this.configuration); } // --- TestDescriptor ------------------------------------------------------ @Override public Type getType() { return Type.CONTAINER; } @Override protected String getLegacyReportingBaseName() { return getTestClass().getName(); } @Override protected OptionalInt getLegacyReportingIndex() { return OptionalInt.of(index); } // --- TestClassAware ------------------------------------------------------ @Override public Class getTestClass() { return parent.getTestClass(); } @Override public List> getEnclosingTestClasses() { return parent.getEnclosingTestClasses(); } // --- ResourceLockAware --------------------------------------------------- @Override public ExclusiveResourceCollector getExclusiveResourceCollector() { return parent.getExclusiveResourceCollector(); } @Override public Function> getResourceLocksProviderEvaluator() { return parent.getResourceLocksProviderEvaluator(); } // --- Node ---------------------------------------------------------------- @Override public Set getExclusiveResources() { // Resources are already collected and returned by the enclosing ClassTemplateTestDescriptor return emptySet(); } @Override public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) { MutableExtensionRegistry registry = context.getExtensionRegistry(); List additionalExtensions = requiredInvocationContext().getAdditionalExtensions(); if (!additionalExtensions.isEmpty()) { MutableExtensionRegistry childRegistry = createRegistryFrom(registry, Stream.empty()); additionalExtensions.forEach( extension -> childRegistry.registerExtension(extension, requiredInvocationContext())); registry = childRegistry; } ExtensionContext extensionContext = new ClassTemplateInvocationExtensionContext(context.getExtensionContext(), context.getExecutionListener(), this, context.getConfiguration(), registry, context.getLauncherStoreFacade()); ThrowableCollector throwableCollector = createThrowableCollector(); throwableCollector.execute(() -> requiredInvocationContext().prepareInvocation(extensionContext)); return context.extend() // .withExtensionRegistry(registry) // .withExtensionContext(extensionContext) // .withThrowableCollector(throwableCollector) // .build(); } @Override public SkipResult shouldBeSkipped(JupiterEngineExecutionContext context) { context.getThrowableCollector().assertEmpty(); return SkipResult.doNotSkip(); } @Override public JupiterEngineExecutionContext before(JupiterEngineExecutionContext context) throws Exception { invokeBeforeCallbacks(BeforeClassTemplateInvocationCallback.class, context, BeforeClassTemplateInvocationCallback::beforeClassTemplateInvocation); context.getThrowableCollector().assertEmpty(); return context; } @Override public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext context, DynamicTestExecutor dynamicTestExecutor) throws Exception { Visitor visitor = context.getExecutionListener()::dynamicTestRegistered; getChildren().forEach(child -> child.accept(visitor)); return context; } @Override public void after(JupiterEngineExecutionContext context) throws Exception { ThrowableCollector throwableCollector = context.getThrowableCollector(); Throwable previousThrowable = throwableCollector.getThrowable(); invokeAfterCallbacks(AfterClassTemplateInvocationCallback.class, context, AfterClassTemplateInvocationCallback::afterClassTemplateInvocation); // If the previous Throwable was not null when this method was called, // that means an exception was already thrown either before or during // the execution of this Node. If an exception was already thrown, any // later exceptions were added as suppressed exceptions to that original // exception unless a more severe exception occurred in the meantime. if (previousThrowable != throwableCollector.getThrowable()) { throwableCollector.assertEmpty(); } } @Override public void cleanUp(JupiterEngineExecutionContext context) throws Exception { // forget invocationContext so it can be garbage collected this.invocationContext = null; super.cleanUp(context); } private ClassTemplateInvocationContext requiredInvocationContext() { return requireNonNull(this.invocationContext); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTemplateTestDescriptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static java.util.stream.Collectors.toCollection; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.validateClassTemplateInvocationLifecycleMethodsAreDeclaredCorrectly; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Function; import java.util.function.UnaryOperator; import java.util.stream.Stream; import org.apiguardian.api.API; import org.junit.jupiter.api.ClassTemplate; import org.junit.jupiter.api.extension.ClassTemplateInvocationContext; import org.junit.jupiter.api.extension.ClassTemplateInvocationContextProvider; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestInstances; import org.junit.jupiter.api.parallel.ResourceLocksProvider; import org.junit.jupiter.engine.execution.ExtensionContextSupplier; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; import org.junit.jupiter.engine.extension.ExtensionRegistry; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestTag; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter; import org.junit.platform.engine.support.hierarchical.ExclusiveResource; import org.junit.platform.engine.support.hierarchical.Node; /** * @since 5.13 */ @API(status = INTERNAL, since = "5.13") public class ClassTemplateTestDescriptor extends ClassBasedTestDescriptor implements Filterable { public static final String STANDALONE_CLASS_SEGMENT_TYPE = "class-template"; public static final String NESTED_CLASS_SEGMENT_TYPE = "nested-class-template"; private final Map> childrenPrototypesByIndex = new HashMap<>(); private final List childrenPrototypes = new ArrayList<>(); private final ClassBasedTestDescriptor delegate; private final DynamicDescendantFilter dynamicDescendantFilter; public ClassTemplateTestDescriptor(UniqueId uniqueId, ClassBasedTestDescriptor delegate) { this(uniqueId, delegate, new DynamicDescendantFilter()); } private ClassTemplateTestDescriptor(UniqueId uniqueId, ClassBasedTestDescriptor delegate, DynamicDescendantFilter dynamicDescendantFilter) { super(uniqueId, delegate.getTestClass(), delegate.getDisplayName(), delegate.configuration); this.delegate = delegate; this.dynamicDescendantFilter = dynamicDescendantFilter; } // --- TestDescriptor ------------------------------------------------------ @Override public Set getTags() { return this.delegate.getTags(); } // --- Validatable --------------------------------------------------------- @Override protected void validateCoreLifecycleMethods(DiscoveryIssueReporter reporter) { this.delegate.validateCoreLifecycleMethods(reporter); } @Override protected void validateClassTemplateInvocationLifecycleMethods(DiscoveryIssueReporter reporter) { boolean requireStatic = this.classInfo.lifecycle == PER_METHOD; validateClassTemplateInvocationLifecycleMethodsAreDeclaredCorrectly(getTestClass(), requireStatic, reporter); } // --- Filterable ---------------------------------------------------------- @Override public DynamicDescendantFilter getDynamicDescendantFilter() { return this.dynamicDescendantFilter; } // --- JupiterTestDescriptor ----------------------------------------------- @Override protected JupiterTestDescriptor copyIncludingDescendants(UnaryOperator uniqueIdTransformer) { ClassTemplateTestDescriptor copy = (ClassTemplateTestDescriptor) super.copyIncludingDescendants( uniqueIdTransformer); this.childrenPrototypes.forEach(oldChild -> { TestDescriptor newChild = ((JupiterTestDescriptor) oldChild).copyIncludingDescendants(uniqueIdTransformer); copy.childrenPrototypes.add(newChild); }); this.childrenPrototypesByIndex.forEach((index, oldChildren) -> { List newChildren = oldChildren.stream() // .map(oldChild -> ((JupiterTestDescriptor) oldChild).copyIncludingDescendants(uniqueIdTransformer)) // .toList(); copy.childrenPrototypesByIndex.put(index, newChildren); }); return copy; } @Override protected ClassTemplateTestDescriptor withUniqueId(UnaryOperator uniqueIdTransformer) { return new ClassTemplateTestDescriptor(uniqueIdTransformer.apply(getUniqueId()), this.delegate, this.dynamicDescendantFilter.copy(uniqueIdTransformer)); } // --- TestDescriptor ------------------------------------------------------ @Override public void prune() { super.prune(); if (this.children.isEmpty()) { return; } // Create copy to avoid ConcurrentModificationException new LinkedHashSet<>(this.children).forEach(child -> child.accept(TestDescriptor::prune)); // Second iteration to avoid processing children that were pruned in the first iteration this.children.forEach(child -> { if (child instanceof ClassTemplateInvocationTestDescriptor descriptor) { int index = descriptor.getIndex(); this.dynamicDescendantFilter.allowIndex(index - 1); this.childrenPrototypesByIndex.put(index, child.getChildren()); } else { this.childrenPrototypes.add(child); } }); this.children.clear(); } @Override public boolean mayRegisterTests() { return !childrenPrototypes.isEmpty() || !childrenPrototypesByIndex.isEmpty(); } // --- TestClassAware ------------------------------------------------------ @Override public List> getEnclosingTestClasses() { return delegate.getEnclosingTestClasses(); } // --- ClassBasedTestDescriptor -------------------------------------------- @Override public TestInstances instantiateTestClass(JupiterEngineExecutionContext parentExecutionContext, ExtensionContextSupplier extensionContext, ExtensionRegistry registry, JupiterEngineExecutionContext context) { return delegate.instantiateTestClass(parentExecutionContext, extensionContext, registry, context); } // --- ResourceLockAware --------------------------------------------------- @Override public Function> getResourceLocksProviderEvaluator() { return delegate.getResourceLocksProviderEvaluator(); } // --- Node ---------------------------------------------------------------- @Override public Set getExclusiveResources() { Set result = determineExclusiveResources().collect(toCollection(HashSet::new)); Visitor visitor = testDescriptor -> { if (testDescriptor instanceof Node node) { result.addAll(node.getExclusiveResources()); } }; this.childrenPrototypes.forEach(child -> child.accept(visitor)); this.childrenPrototypesByIndex.values() // .forEach(prototypes -> prototypes // .forEach(child -> child.accept(visitor))); return result; } @Override public void cleanUp(JupiterEngineExecutionContext context) throws Exception { this.childrenPrototypes.clear(); this.childrenPrototypesByIndex.clear(); this.dynamicDescendantFilter.allowAll(); super.cleanUp(context); } @Override public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext context, DynamicTestExecutor dynamicTestExecutor) throws Exception { new ClassTemplateExecutor().execute(context, dynamicTestExecutor); return context; } class ClassTemplateExecutor extends TemplateExecutor { ClassTemplateExecutor() { super(ClassTemplateTestDescriptor.this, ClassTemplateInvocationContextProvider.class); } @Override boolean supports(ClassTemplateInvocationContextProvider provider, ExtensionContext extensionContext) { return provider.supportsClassTemplate(extensionContext); } @Override protected String getNoRegisteredProviderErrorMessage() { return "You must register at least one %s that supports @%s class [%s]".formatted( ClassTemplateInvocationContextProvider.class.getSimpleName(), ClassTemplate.class.getSimpleName(), getTestClass().getName()); } @Override Stream provideContexts( ClassTemplateInvocationContextProvider provider, ExtensionContext extensionContext) { return provider.provideClassTemplateInvocationContexts(extensionContext); } @Override boolean mayReturnZeroContexts(ClassTemplateInvocationContextProvider provider, ExtensionContext extensionContext) { return provider.mayReturnZeroClassTemplateInvocationContexts(extensionContext); } @Override protected String getZeroContextsProvidedErrorMessage(ClassTemplateInvocationContextProvider provider) { return """ Provider [%s] did not provide any invocation contexts, but was expected to do so. \ You may override mayReturnZeroClassTemplateInvocationContexts() to allow this.""".formatted( provider.getClass().getSimpleName()); } @Override UniqueId createInvocationUniqueId(UniqueId parentUniqueId, int index) { return parentUniqueId.append(ClassTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#" + index); } @Override TestDescriptor createInvocationTestDescriptor(UniqueId uniqueId, ClassTemplateInvocationContext invocationContext, int index) { ClassTemplateInvocationTestDescriptor containerInvocationDescriptor = new ClassTemplateInvocationTestDescriptor( uniqueId, ClassTemplateTestDescriptor.this, invocationContext, index, getSource().orElse(null), ClassTemplateTestDescriptor.this.configuration); collectChildren(index, uniqueId) // .forEach(containerInvocationDescriptor::addChild); return containerInvocationDescriptor; } private Stream collectChildren(int index, UniqueId invocationUniqueId) { if (ClassTemplateTestDescriptor.this.childrenPrototypesByIndex.containsKey(index)) { return ClassTemplateTestDescriptor.this.childrenPrototypesByIndex.remove(index).stream(); } UnaryOperator transformer = new UniqueIdPrefixTransformer(getUniqueId(), invocationUniqueId); return ClassTemplateTestDescriptor.this.childrenPrototypes.stream() // .map(JupiterTestDescriptor.class::cast) // .map(it -> it.copyIncludingDescendants(transformer)); } } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static java.util.Collections.emptyList; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.jupiter.engine.descriptor.DisplayNameUtils.createDisplayNameSupplierForClass; import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.function.UnaryOperator; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.TestInstances; import org.junit.jupiter.api.parallel.ResourceLocksProvider; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.ExtensionContextSupplier; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; import org.junit.jupiter.engine.extension.ExtensionRegistry; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestTag; import org.junit.platform.engine.UniqueId; /** * {@link TestDescriptor} for tests based on Java classes. * *

Default Display Names

* *

The default display name for a top-level or nested static test class is * the fully qualified name of the class with the package name and leading dot * (".") removed. * * @since 5.0 */ @API(status = INTERNAL, since = "5.0") public class ClassTestDescriptor extends ClassBasedTestDescriptor { public static final String SEGMENT_TYPE = "class"; public ClassTestDescriptor(UniqueId uniqueId, Class testClass, JupiterConfiguration configuration) { super(uniqueId, testClass, createDisplayNameSupplierForClass(testClass, configuration), configuration); } private ClassTestDescriptor(UniqueId uniqueId, Class testClass, String displayName, JupiterConfiguration configuration) { super(uniqueId, testClass, displayName, configuration); } // --- JupiterTestDescriptor ----------------------------------------------- @Override protected ClassTestDescriptor withUniqueId(UnaryOperator uniqueIdTransformer) { return new ClassTestDescriptor(uniqueIdTransformer.apply(getUniqueId()), getTestClass(), getDisplayName(), configuration); } // --- TestDescriptor ------------------------------------------------------ @Override public Set getTags() { // return modifiable copy return new LinkedHashSet<>(this.classInfo.tags); } // --- TestClassAware ------------------------------------------------------ @Override public List> getEnclosingTestClasses() { return emptyList(); } // --- Node ---------------------------------------------------------------- @Override ExecutionMode getDefaultExecutionMode() { return toExecutionMode(configuration.getDefaultClassesExecutionMode()); } // --- ClassBasedTestDescriptor -------------------------------------------- @Override protected TestInstances instantiateTestClass(JupiterEngineExecutionContext parentExecutionContext, ExtensionContextSupplier extensionContext, ExtensionRegistry registry, JupiterEngineExecutionContext context) { return instantiateTestClass(Optional.empty(), registry, extensionContext); } // --- ResourceLockAware --------------------------------------------------- @Override public Function> getResourceLocksProviderEvaluator() { return provider -> provider.provideForClass(getTestClass()); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DefaultDynamicTestInvocationContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import org.junit.jupiter.api.extension.DynamicTestInvocationContext; import org.junit.jupiter.api.function.Executable; /** * Default implementation of the {@link DynamicTestInvocationContext} API. * * @since 5.8 */ record DefaultDynamicTestInvocationContext(Executable executable) implements DynamicTestInvocationContext { @Override public Executable getExecutable() { return this.executable; } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DefaultTestInstanceFactoryContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import java.util.Optional; import org.junit.jupiter.api.extension.TestInstanceFactoryContext; import org.junit.platform.commons.util.ToStringBuilder; /** * Default implementation of the {@link TestInstanceFactoryContext} API. * * @since 5.3 */ record DefaultTestInstanceFactoryContext(Class testClass, Optional outerInstance) implements TestInstanceFactoryContext { @Override public Class getTestClass() { return this.testClass; } @Override public Optional getOuterInstance() { return this.outerInstance; } @Override public String toString() { // @formatter:off return new ToStringBuilder(this) .append("testClass", this.testClass) .append("outerInstance", this.outerInstance) .toString(); // @formatter:on } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DisplayNameUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.function.BiFunction; import java.util.function.Supplier; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.DisplayNameGenerator.IndicativeSentences; import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; import org.junit.jupiter.api.DisplayNameGenerator.Simple; import org.junit.jupiter.api.DisplayNameGenerator.Standard; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.StringUtils; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.TestSource; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter; /** * Collection of utilities for working with display names. * * @since 5.4 * @see DisplayName * @see DisplayNameGenerator * @see DisplayNameGeneration */ final class DisplayNameUtils { /** * Pre-defined standard display name generator instance. */ private static final DisplayNameGenerator standardGenerator = DisplayNameGenerator.getDisplayNameGenerator( Standard.class); /** * Pre-defined simple display name generator instance. */ private static final DisplayNameGenerator simpleGenerator = DisplayNameGenerator.getDisplayNameGenerator( Simple.class); /** * Pre-defined display name generator instance replacing underscores. */ private static final DisplayNameGenerator replaceUnderscoresGenerator = DisplayNameGenerator.getDisplayNameGenerator( ReplaceUnderscores.class); /** * Pre-defined display name generator instance producing indicative sentences. */ private static final DisplayNameGenerator indicativeSentencesGenerator = DisplayNameGenerator.getDisplayNameGenerator( IndicativeSentences.class); static String determineDisplayName(AnnotatedElement element, Supplier displayNameSupplier) { Preconditions.notNull(element, "Annotated element must not be null"); return findAnnotation(element, DisplayName.class) // .map(DisplayName::value) // .filter(StringUtils::isNotBlank) // .map(String::strip) // .orElseGet(displayNameSupplier); } static void validateAnnotation(AnnotatedElement element, Supplier elementDescription, Supplier<@Nullable TestSource> sourceProvider, DiscoveryIssueReporter reporter) { findAnnotation(element, DisplayName.class) // .map(DisplayName::value) // .filter(StringUtils::isBlank) // .ifPresent(__ -> { String message = "@DisplayName on %s must be declared with a non-blank value.".formatted( elementDescription.get()); reporter.reportIssue( DiscoveryIssue.builder(Severity.WARNING, message).source(sourceProvider.get()).build()); }); } static String determineDisplayNameForMethod(Supplier>> enclosingInstanceTypes, Class testClass, Method testMethod, JupiterConfiguration configuration) { return determineDisplayName(testMethod, createDisplayNameSupplierForMethod(enclosingInstanceTypes, testClass, testMethod, configuration)); } static Supplier createDisplayNameSupplierForClass(Class testClass, JupiterConfiguration configuration) { return createDisplayNameSupplier(Collections::emptyList, testClass, configuration, (generator, __) -> generator.generateDisplayNameForClass(testClass)); } static Supplier createDisplayNameSupplierForNestedClass( Supplier>> enclosingInstanceTypesSupplier, Class testClass, JupiterConfiguration configuration) { return createDisplayNameSupplier(enclosingInstanceTypesSupplier, testClass, configuration, (generator, enclosingInstanceTypes) -> generator.generateDisplayNameForNestedClass(enclosingInstanceTypes, testClass)); } private static Supplier createDisplayNameSupplierForMethod( Supplier>> enclosingInstanceTypesSupplier, Class testClass, Method testMethod, JupiterConfiguration configuration) { return createDisplayNameSupplier(enclosingInstanceTypesSupplier, testClass, configuration, (generator, enclosingInstanceTypes) -> generator.generateDisplayNameForMethod(enclosingInstanceTypes, testClass, testMethod)); } private static Supplier createDisplayNameSupplier(Supplier>> enclosingInstanceTypesSupplier, Class testClass, JupiterConfiguration configuration, BiFunction>, String> generatorFunction) { return () -> { List> enclosingInstanceTypes = List.copyOf(enclosingInstanceTypesSupplier.get()); return findDisplayNameGenerator(enclosingInstanceTypes, testClass) // .map(it -> generatorFunction.apply(it, enclosingInstanceTypes)) // .orElseGet(() -> generatorFunction.apply(configuration.getDefaultDisplayNameGenerator(), enclosingInstanceTypes)); }; } private static Optional findDisplayNameGenerator(List> enclosingInstanceTypes, Class testClass) { Preconditions.notNull(testClass, "Test class must not be null"); return findAnnotation(testClass, DisplayNameGeneration.class, enclosingInstanceTypes) // .map(DisplayNameGeneration::value) // .map(displayNameGeneratorClass -> { if (displayNameGeneratorClass == Standard.class) { return standardGenerator; } if (displayNameGeneratorClass == Simple.class) { return simpleGenerator; } if (displayNameGeneratorClass == ReplaceUnderscores.class) { return replaceUnderscoresGenerator; } if (displayNameGeneratorClass == IndicativeSentences.class) { return indicativeSentencesGenerator; } return ReflectionSupport.newInstance(displayNameGeneratorClass); }); } private DisplayNameUtils() { } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicContainerTestDescriptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor.createDynamicDescriptor; import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.UnaryOperator; import java.util.stream.Stream; import org.junit.jupiter.api.DynamicContainer; import org.junit.jupiter.api.DynamicNode; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestSource; import org.junit.platform.engine.UniqueId; /** * {@link TestDescriptor} for a {@link DynamicContainer}. * * @since 5.0 */ class DynamicContainerTestDescriptor extends DynamicNodeTestDescriptor { private final DynamicContainer dynamicContainer; private final TestSource testSource; private final DynamicDescendantFilter dynamicDescendantFilter; DynamicContainerTestDescriptor(UniqueId uniqueId, int index, DynamicContainer dynamicContainer, TestSource testSource, DynamicDescendantFilter dynamicDescendantFilter, JupiterConfiguration configuration) { super(uniqueId, index, dynamicContainer, testSource, configuration); this.dynamicContainer = dynamicContainer; this.testSource = testSource; this.dynamicDescendantFilter = dynamicDescendantFilter; } @Override protected DynamicContainerTestDescriptor withUniqueId(UnaryOperator uniqueIdTransformer) { return new DynamicContainerTestDescriptor(uniqueIdTransformer.apply(getUniqueId()), this.index, this.dynamicContainer, this.testSource, this.dynamicDescendantFilter, this.configuration); } @Override public Type getType() { return Type.CONTAINER; } @Override Optional getExplicitChildExecutionMode() { return this.dynamicContainer.getChildExecutionMode().map(JupiterTestDescriptor::toExecutionMode); } @Override public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext context, DynamicTestExecutor dynamicTestExecutor) throws Exception { AtomicInteger index = new AtomicInteger(1); try (Stream children = dynamicContainer.getChildren()) { // @formatter:off children.map(child -> { Preconditions.notNull(child, "individual dynamic node must not be null"); return toDynamicDescriptor(index.getAndIncrement(), child); }) .flatMap(Optional::stream) .forEachOrdered(dynamicTestExecutor::execute); // @formatter:on } return context; } private Optional toDynamicDescriptor(int index, DynamicNode childNode) { return createDynamicDescriptor(this, childNode, index, testSource, dynamicDescendantFilter, configuration); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicDescendantFilter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.HashSet; import java.util.Set; import java.util.function.BiPredicate; import java.util.function.UnaryOperator; import org.apiguardian.api.API; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; /** * Filter for dynamic descendants of {@link TestDescriptor TestDescriptors} that * implement {@link Filterable}. * * @since 5.1 * @see Filterable */ @API(status = INTERNAL, since = "5.1") public class DynamicDescendantFilter implements BiPredicate { private final Set allowedUniqueIds = new HashSet<>(); private final Set allowedIndices = new HashSet<>(); private Mode mode = Mode.EXPLICIT; public void allowUniqueIdPrefix(UniqueId uniqueId) { if (this.mode == Mode.EXPLICIT) { this.allowedUniqueIds.add(uniqueId); } } public void allowIndex(int index) { if (this.mode == Mode.EXPLICIT) { this.allowedIndices.add(index); } } public void allowIndex(Set indices) { if (this.mode == Mode.EXPLICIT) { this.allowedIndices.addAll(indices); } } public void allowAll() { this.mode = Mode.ALLOW_ALL; this.allowedUniqueIds.clear(); this.allowedIndices.clear(); } @Override public boolean test(UniqueId uniqueId, Integer index) { return isEverythingAllowed() // || isUniqueIdAllowed(uniqueId) // || allowedIndices.contains(index); } private boolean isEverythingAllowed() { return allowedUniqueIds.isEmpty() && allowedIndices.isEmpty(); } private boolean isUniqueIdAllowed(UniqueId uniqueId) { return allowedUniqueIds.stream().anyMatch(allowedUniqueId -> isPrefixOrViceVersa(uniqueId, allowedUniqueId)); } private boolean isPrefixOrViceVersa(UniqueId currentUniqueId, UniqueId allowedUniqueId) { return allowedUniqueId.hasPrefix(currentUniqueId) || currentUniqueId.hasPrefix(allowedUniqueId); } public DynamicDescendantFilter withoutIndexFiltering() { return new WithoutIndexFiltering(); } private enum Mode { EXPLICIT, ALLOW_ALL } public DynamicDescendantFilter copy(UnaryOperator uniqueIdTransformer) { return configure(uniqueIdTransformer, new DynamicDescendantFilter()); } protected DynamicDescendantFilter configure(UnaryOperator uniqueIdTransformer, DynamicDescendantFilter copy) { this.allowedUniqueIds.stream().map(uniqueIdTransformer).forEach(copy.allowedUniqueIds::add); copy.allowedIndices.addAll(this.allowedIndices); copy.mode = this.mode; return copy; } private class WithoutIndexFiltering extends DynamicDescendantFilter { @Override public boolean test(UniqueId uniqueId, Integer index) { return isEverythingAllowed() || isUniqueIdAllowed(uniqueId); } @Override public DynamicDescendantFilter withoutIndexFiltering() { return this; } @Override public DynamicDescendantFilter copy(UnaryOperator uniqueIdTransformer) { return configure(uniqueIdTransformer, new WithoutIndexFiltering()); } } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicExtensionContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static java.util.Collections.emptyList; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.util.List; import java.util.Optional; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestInstances; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.LauncherStoreFacade; import org.junit.jupiter.engine.extension.ExtensionRegistry; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.support.hierarchical.Node; class DynamicExtensionContext extends AbstractExtensionContext { DynamicExtensionContext(ExtensionContext parent, EngineExecutionListener engineExecutionListener, DynamicNodeTestDescriptor testDescriptor, JupiterConfiguration configuration, ExtensionRegistry extensionRegistry, LauncherStoreFacade launcherStoreFacade) { super(parent, engineExecutionListener, testDescriptor, configuration, extensionRegistry, launcherStoreFacade); } @Override public Optional getElement() { return Optional.empty(); } @Override public Optional> getTestClass() { return Optional.empty(); } @Override public List> getEnclosingTestClasses() { return emptyList(); } @Override public Optional getTestInstanceLifecycle() { return Optional.empty(); } @Override public Optional getTestInstance() { return Optional.empty(); } @Override public Optional getTestInstances() { return Optional.empty(); } @Override public Optional getTestMethod() { return Optional.empty(); } @Override public Optional getExecutionException() { return Optional.empty(); } @Override protected Node.ExecutionMode getPlatformExecutionMode() { return getTestDescriptor().getExecutionMode(); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import java.util.Optional; import java.util.OptionalInt; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.DynamicNode; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestSource; import org.junit.platform.engine.UniqueId; /** * Base {@link TestDescriptor} for a {@link DynamicNode}. * * @since 5.0.3 */ abstract class DynamicNodeTestDescriptor extends JupiterTestDescriptor { protected final int index; private final Optional executionMode; DynamicNodeTestDescriptor(UniqueId uniqueId, int index, DynamicNode dynamicNode, @Nullable TestSource testSource, JupiterConfiguration configuration) { super(uniqueId, dynamicNode.getDisplayName(), testSource, configuration); this.index = index; this.executionMode = dynamicNode.getExecutionMode().map(JupiterTestDescriptor::toExecutionMode); } @Override Optional getExplicitExecutionMode() { return executionMode; } @Override protected final String getLegacyReportingBaseName() { // @formatter:off return getParent() .map(it -> { if (it instanceof JupiterTestDescriptor parent) { return parent.getLegacyReportingBaseName(); } return it.getLegacyReportingName(); }) .orElseGet(this::getDisplayName); // @formatter:on } @Override protected OptionalInt getLegacyReportingIndex() { return OptionalInt.of(index); } @Override public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) { ExtensionContext extensionContext = new DynamicExtensionContext(context.getExtensionContext(), context.getExecutionListener(), this, context.getConfiguration(), context.getExtensionRegistry(), context.getLauncherStoreFacade()); // @formatter:off return context.extend() .withExtensionContext(extensionContext) .build(); // @formatter:on } @Override public SkipResult shouldBeSkipped(JupiterEngineExecutionContext context) { return SkipResult.doNotSkip(); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicTestTestDescriptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static java.util.Objects.requireNonNull; import java.util.function.UnaryOperator; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.extension.DynamicTestInvocationContext; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.InvocationInterceptor; import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.InvocationInterceptorChain; import org.junit.jupiter.engine.execution.InvocationInterceptorChain.InterceptorCall; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; import org.junit.jupiter.engine.extension.ExtensionRegistry; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestSource; import org.junit.platform.engine.UniqueId; /** * {@link TestDescriptor} for a {@link DynamicTest}. * * @since 5.0 */ class DynamicTestTestDescriptor extends DynamicNodeTestDescriptor { private static final InvocationInterceptorChain interceptorChain = new InvocationInterceptorChain(); private @Nullable DynamicTest dynamicTest; DynamicTestTestDescriptor(UniqueId uniqueId, int index, DynamicTest dynamicTest, @Nullable TestSource source, JupiterConfiguration configuration) { super(uniqueId, index, dynamicTest, source, configuration); this.dynamicTest = dynamicTest; } @Override protected DynamicTestTestDescriptor withUniqueId(UnaryOperator uniqueIdTransformer) { return new DynamicTestTestDescriptor(uniqueIdTransformer.apply(getUniqueId()), this.index, requireNonNull(this.dynamicTest), this.getSource().orElse(null), this.configuration); } @Override public Type getType() { return Type.TEST; } @Override public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext context, DynamicTestExecutor dynamicTestExecutor) { DynamicTestInvocationContext dynamicTestInvocationContext = new DefaultDynamicTestInvocationContext( requiredDynamicTest().getExecutable()); ExtensionContext extensionContext = context.getExtensionContext(); ExtensionRegistry extensionRegistry = context.getExtensionRegistry(); interceptorChain.<@Nullable Void> invoke(toInvocation(), extensionRegistry, InterceptorCall.ofVoid(( InvocationInterceptor interceptor, InvocationInterceptor.Invocation<@Nullable Void> wrappedInvocation) -> interceptor.interceptDynamicTest( wrappedInvocation, dynamicTestInvocationContext, extensionContext))); return context; } private InvocationInterceptor.Invocation<@Nullable Void> toInvocation() { return () -> { requiredDynamicTest().getExecutable().execute(); return null; }; } /** * Avoid an {@link OutOfMemoryError} by releasing the reference to this * descriptor's {@link DynamicTest} which holds a reference to the user-supplied * {@link Executable} which may potentially consume large amounts of memory * on the heap. * * @since 5.5 * @see Issue 1865 */ @Override public void after(JupiterEngineExecutionContext context) throws Exception { super.after(context); this.dynamicTest = null; } private DynamicTest requiredDynamicTest() { return requireNonNull(dynamicTest); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExclusiveResourceCollector.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static org.junit.jupiter.api.parallel.ResourceLockTarget.SELF; import static org.junit.platform.commons.support.AnnotationSupport.findRepeatableAnnotations; import java.lang.reflect.AnnotatedElement; import java.util.Collection; import java.util.List; import java.util.Set; import java.util.function.Function; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.parallel.ResourceAccessMode; import org.junit.jupiter.api.parallel.ResourceLock; import org.junit.jupiter.api.parallel.ResourceLockTarget; import org.junit.jupiter.api.parallel.ResourceLocksProvider; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.commons.util.StringUtils; import org.junit.platform.engine.support.hierarchical.ExclusiveResource; /** * @since 5.12 */ abstract class ExclusiveResourceCollector { private static final ExclusiveResourceCollector NO_EXCLUSIVE_RESOURCES = new ExclusiveResourceCollector() { @Override Stream getAllExclusiveResources( Function> providerToLocks) { return Stream.empty(); } @Override Stream getStaticResourcesFor(ResourceLockTarget target) { return Stream.empty(); } @Override Stream getDynamicResources( Function> providerToLocks) { return Stream.empty(); } }; Stream getAllExclusiveResources( Function> providerToLocks) { return Stream.concat(getStaticResourcesFor(SELF), getDynamicResources(providerToLocks)); } abstract Stream getStaticResourcesFor(ResourceLockTarget target); abstract Stream getDynamicResources( Function> providerToLocks); static ExclusiveResourceCollector from(AnnotatedElement element) { List annotations = findRepeatableAnnotations(element, ResourceLock.class); return annotations.isEmpty() ? NO_EXCLUSIVE_RESOURCES : new DefaultExclusiveResourceCollector(annotations); } private static class DefaultExclusiveResourceCollector extends ExclusiveResourceCollector { private final List annotations; @Nullable private List providers; DefaultExclusiveResourceCollector(List annotations) { this.annotations = annotations; } @Override Stream getStaticResourcesFor(ResourceLockTarget target) { return annotations.stream() // .filter(annotation -> StringUtils.isNotBlank(annotation.value())) // .filter(annotation -> annotation.target() == target) // .map(annotation -> new ExclusiveResource(annotation.value(), toLockMode(annotation.mode()))); } @Override Stream getDynamicResources( Function> providerToLocks) { List providers = getProviders(); if (providers.isEmpty()) { return Stream.empty(); } return providers.stream() // .map(providerToLocks) // .flatMap(Collection::stream) // .map(lock -> new ExclusiveResource(lock.getKey(), toLockMode(lock.getAccessMode()))); } private List getProviders() { if (this.providers == null) { this.providers = annotations.stream() // .flatMap(annotation -> instantiate(annotation.providers())) // .toList(); } return providers; } private static Stream instantiate(Class[] providers) { return Stream.of(providers).map(ReflectionUtils::newInstance); } private static ExclusiveResource.LockMode toLockMode(ResourceAccessMode mode) { return switch (mode) { case READ -> ExclusiveResource.LockMode.READ; case READ_WRITE -> ExclusiveResource.LockMode.READ_WRITE; }; } } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExtensionUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static java.util.stream.Collectors.toList; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated; import static org.junit.platform.commons.util.AnnotationUtils.findRepeatableAnnotations; import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.TOP_DOWN; import static org.junit.platform.commons.util.ReflectionUtils.getDeclaredConstructor; import static org.junit.platform.commons.util.ReflectionUtils.streamFields; import static org.junit.platform.commons.util.ReflectionUtils.tryToReadFieldValue; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Executable; import java.lang.reflect.Field; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Predicate; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.engine.extension.ExtensionRegistrar; import org.junit.jupiter.engine.extension.MutableExtensionRegistry; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.support.ModifierSupport; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ReflectionUtils; /** * Collection of utilities for working with extensions and the extension registry. * * @since 5.1 * @see ExtensionRegistrar * @see MutableExtensionRegistry * @see ExtendWith * @see RegisterExtension */ final class ExtensionUtils { private ExtensionUtils() { /* no-op */ } /** * Populate a new {@link MutableExtensionRegistry} from extension types declared via * {@link ExtendWith @ExtendWith} on the supplied {@link AnnotatedElement}. * * @param parentRegistry the parent extension registry to set in the newly * created registry; never {@code null} * @param annotatedElement the annotated element on which to search for * declarations of {@code @ExtendWith}; never {@code null} * * @return the new extension registry; never {@code null} * @since 5.0 */ static MutableExtensionRegistry populateNewExtensionRegistryFromExtendWithAnnotation( MutableExtensionRegistry parentRegistry, AnnotatedElement annotatedElement) { Preconditions.notNull(parentRegistry, "Parent ExtensionRegistry must not be null"); Preconditions.notNull(annotatedElement, "AnnotatedElement must not be null"); return MutableExtensionRegistry.createRegistryFrom(parentRegistry, streamDeclarativeExtensionTypes(annotatedElement)); } /** * Register extensions using the supplied registrar from static fields in * the supplied class that are annotated with {@link ExtendWith @ExtendWith} * or {@link RegisterExtension @RegisterExtension}. * *

The extensions will be sorted according to {@link Order @Order} semantics * prior to registration. * * @param registrar the registrar with which to register the extensions; never {@code null} * @param clazz the class or interface in which to find the fields; never {@code null} * @since 5.11 */ static void registerExtensionsFromStaticFields(ExtensionRegistrar registrar, Class clazz) { streamExtensionRegisteringFields(clazz, ModifierSupport::isStatic) // .forEach(field -> { List> extensionTypes = streamDeclarativeExtensionTypes(field).collect( toList()); boolean isExtendWithPresent = !extensionTypes.isEmpty(); if (isExtendWithPresent) { extensionTypes.forEach(registrar::registerExtension); } if (isAnnotated(field, RegisterExtension.class)) { Extension extension = readAndValidateExtensionFromField(field, null, extensionTypes); registrar.registerExtension(extension, field); } }); } /** * Register extensions using the supplied registrar from instance fields in * the supplied class that are annotated with {@link ExtendWith @ExtendWith} * or {@link RegisterExtension @RegisterExtension}. * *

The extensions will be sorted according to {@link Order @Order} semantics * prior to registration. * * @param registrar the registrar with which to register the extensions; never {@code null} * @param clazz the class or interface in which to find the fields; never {@code null} * @since 5.11 */ static void registerExtensionsFromInstanceFields(ExtensionRegistrar registrar, Class clazz) { streamExtensionRegisteringFields(clazz, ReflectionUtils::isNotStatic) // .forEach(field -> { List> extensionTypes = streamDeclarativeExtensionTypes(field).collect( toList()); boolean isExtendWithPresent = !extensionTypes.isEmpty(); if (isExtendWithPresent) { extensionTypes.forEach(registrar::registerExtension); } if (isAnnotated(field, RegisterExtension.class)) { registrar.registerUninitializedExtension(clazz, field, instance -> readAndValidateExtensionFromField(field, instance, extensionTypes)); } }); } /** * @since 5.11 */ private static Extension readAndValidateExtensionFromField(Field field, @Nullable Object instance, List> declarativeExtensionTypes) { Object value = tryToReadFieldValue(field, instance) // .getOrThrow(e -> new PreconditionViolationException( "Failed to read @RegisterExtension field [%s]".formatted(field), e)); Preconditions.condition(value instanceof Extension, () -> "Failed to register extension via @RegisterExtension field [%s]: field value's type [%s] must implement an [%s] API.".formatted( field, (value != null ? value.getClass().getName() : null), Extension.class.getName())); declarativeExtensionTypes.forEach(extensionType -> { Class valueType = value.getClass(); Preconditions.condition(!extensionType.equals(valueType), () -> """ Failed to register extension via field [%s]. \ The field registers an extension of type [%s] via @RegisterExtension and @ExtendWith, \ but only one registration of a given extension type is permitted.""".formatted(field, valueType.getName())); }); return (Extension) value; } /** * Register extensions using the supplied registrar from parameters in the * declared constructor of the supplied class that are annotated with * {@link ExtendWith @ExtendWith}. * * @param registrar the registrar with which to register the extensions; never {@code null} * @param clazz the class in which to find the declared constructor; never {@code null} * @since 5.8 */ static void registerExtensionsFromConstructorParameters(ExtensionRegistrar registrar, Class clazz) { registerExtensionsFromExecutableParameters(registrar, getDeclaredConstructor(clazz)); } /** * Register extensions using the supplied registrar from parameters in the * supplied {@link Executable} (i.e., a {@link java.lang.reflect.Constructor} * or {@link java.lang.reflect.Method}) that are annotated with * {@link ExtendWith @ExtendWith}. * * @param registrar the registrar with which to register the extensions; never {@code null} * @param executable the constructor or method whose parameters should be searched; never {@code null} * @since 5.8 */ static void registerExtensionsFromExecutableParameters(ExtensionRegistrar registrar, Executable executable) { Preconditions.notNull(registrar, "ExtensionRegistrar must not be null"); Preconditions.notNull(executable, "Executable must not be null"); AtomicInteger index = new AtomicInteger(); // @formatter:off Arrays.stream(executable.getParameters()) .map(parameter -> findRepeatableAnnotations(parameter, index.getAndIncrement(), ExtendWith.class)) .flatMap(ExtensionUtils::streamDeclarativeExtensionTypes) .forEach(registrar::registerExtension); // @formatter:on } /** * @since 5.11 */ private static Stream streamExtensionRegisteringFields(Class clazz, Predicate predicate) { return streamFields(clazz, predicate.and(registersExtension), TOP_DOWN)// .sorted(orderComparator); } /** * @since 5.11 */ private static Stream> streamDeclarativeExtensionTypes( AnnotatedElement annotatedElement) { return streamDeclarativeExtensionTypes(findRepeatableAnnotations(annotatedElement, ExtendWith.class)); } /** * @since 5.11 */ private static Stream> streamDeclarativeExtensionTypes( List extendWithAnnotations) { return extendWithAnnotations.stream().map(ExtendWith::value).flatMap(Arrays::stream); } /** * @since 5.4 */ private static final Comparator orderComparator = // Comparator.comparingInt(ExtensionUtils::getOrder); /** * @since 5.4 */ private static int getOrder(Field field) { return findAnnotation(field, Order.class).map(Order::value).orElse(Order.DEFAULT); } /** * {@link Predicate} which determines if a {@link Field} registers an extension via * {@link RegisterExtension @RegisterExtension} or {@link ExtendWith @ExtendWith}. * * @since 5.11.3 */ private static final Predicate registersExtension = // field -> isAnnotated(field, RegisterExtension.class) || !findRepeatableAnnotations(field, ExtendWith.class).isEmpty(); } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/Filterable.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static org.apiguardian.api.API.Status.INTERNAL; import org.apiguardian.api.API; /** * {@code Filterable} is implemented by * {@link org.junit.platform.engine.TestDescriptor TestDescriptors} that may * register dynamic tests during execution and support selective test execution. * * @since 5.1 * @see DynamicDescendantFilter */ @API(status = INTERNAL, since = "5.1") public interface Filterable { DynamicDescendantFilter getDynamicDescendantFilter(); } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineDescriptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.jupiter.engine.descriptor.JupiterTestDescriptor.toExecutionMode; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; import org.junit.jupiter.engine.extension.MutableExtensionRegistry; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.EngineDescriptor; import org.junit.platform.engine.support.hierarchical.Node; /** * @since 5.0 */ @API(status = INTERNAL, since = "5.0") public class JupiterEngineDescriptor extends EngineDescriptor implements Node { public static final String ENGINE_ID = "junit-jupiter"; private final JupiterConfiguration configuration; public JupiterEngineDescriptor(UniqueId uniqueId, JupiterConfiguration configuration) { super(uniqueId, "JUnit Jupiter"); this.configuration = configuration; } public JupiterConfiguration getConfiguration() { return configuration; } @Override public ExecutionMode getExecutionMode() { return toExecutionMode(configuration.getDefaultExecutionMode()); } @Override public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) { MutableExtensionRegistry extensionRegistry = MutableExtensionRegistry.createRegistryWithDefaultExtensions( context.getConfiguration()); EngineExecutionListener executionListener = context.getExecutionListener(); ExtensionContext extensionContext = new JupiterEngineExtensionContext(executionListener, this, context.getConfiguration(), extensionRegistry, context.getLauncherStoreFacade()); // @formatter:off return context.extend() .withExtensionRegistry(extensionRegistry) .withExtensionContext(extensionContext) .build(); // @formatter:on } @Override public void cleanUp(JupiterEngineExecutionContext context) throws Exception { context.close(); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineExtensionContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static java.util.Collections.emptyList; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.util.List; import java.util.Optional; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.extension.TestInstances; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.LauncherStoreFacade; import org.junit.jupiter.engine.extension.ExtensionRegistry; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.support.hierarchical.Node; /** * @since 5.0 */ final class JupiterEngineExtensionContext extends AbstractExtensionContext { JupiterEngineExtensionContext(EngineExecutionListener engineExecutionListener, JupiterEngineDescriptor testDescriptor, JupiterConfiguration configuration, ExtensionRegistry extensionRegistry, LauncherStoreFacade launcherStoreFacade) { super(null, engineExecutionListener, testDescriptor, configuration, extensionRegistry, launcherStoreFacade); } @Override public Optional getElement() { return Optional.empty(); } @Override public Optional> getTestClass() { return Optional.empty(); } @Override public List> getEnclosingTestClasses() { return emptyList(); } @Override public Optional getTestInstanceLifecycle() { return Optional.empty(); } @Override public Optional getTestInstance() { return Optional.empty(); } @Override public Optional getTestInstances() { return Optional.empty(); } @Override public Optional getTestMethod() { return Optional.empty(); } @Override public Optional getExecutionException() { return Optional.empty(); } @Override protected Node.ExecutionMode getPlatformExecutionMode() { return getTestDescriptor().getExecutionMode(); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static java.util.Collections.emptySet; import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toCollection; import static java.util.stream.Collectors.toSet; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.jupiter.engine.descriptor.DisplayNameUtils.determineDisplayName; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import static org.junit.platform.commons.support.AnnotationSupport.findRepeatableAnnotations; import java.lang.reflect.AnnotatedElement; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.OptionalInt; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Supplier; import java.util.function.UnaryOperator; import java.util.stream.IntStream; import java.util.stream.Stream; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.ConditionEvaluator; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; import org.junit.jupiter.engine.extension.ExtensionRegistry; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.commons.util.UnrecoverableExceptions; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestSource; import org.junit.platform.engine.TestTag; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; import org.junit.platform.engine.support.hierarchical.ExclusiveResource; import org.junit.platform.engine.support.hierarchical.Node; /** * @since 5.0 */ @API(status = INTERNAL, since = "5.0") public abstract class JupiterTestDescriptor extends AbstractTestDescriptor implements Node { private static final ConditionEvaluator conditionEvaluator = new ConditionEvaluator(); final JupiterConfiguration configuration; JupiterTestDescriptor(UniqueId uniqueId, AnnotatedElement element, Supplier displayNameSupplier, @Nullable TestSource source, JupiterConfiguration configuration) { this(uniqueId, determineDisplayName(element, displayNameSupplier), source, configuration); } JupiterTestDescriptor(UniqueId uniqueId, String displayName, @Nullable TestSource source, JupiterConfiguration configuration) { super(uniqueId, displayName, source); this.configuration = configuration; } // --- TestDescriptor ------------------------------------------------------ @Override public final String getLegacyReportingName() { return getLegacyReportingBaseName() + getLegacyReportingIndexes().mapToObj(i -> "[" + i + "]").collect(joining()); } protected String getLegacyReportingBaseName() { return getDisplayName(); } private IntStream getLegacyReportingIndexes() { var ownIndex = getLegacyReportingIndex(); return getParent() // .map(it -> it instanceof JupiterTestDescriptor parent // ? parent.getLegacyReportingIndexes() // : null) // .map(parentIndexes -> IntStream.concat(parentIndexes, ownIndex.stream())) // .orElseGet(ownIndex::stream); } protected OptionalInt getLegacyReportingIndex() { return OptionalInt.empty(); } static Set getTags(AnnotatedElement element, Supplier elementDescription, Supplier sourceProvider, Consumer issueCollector) { AtomicReference<@Nullable TestSource> source = new AtomicReference<>(); return findRepeatableAnnotations(element, Tag.class).stream() // .map(Tag::value) // .filter(tag -> { boolean isValid = TestTag.isValid(tag); if (!isValid) { String message = "Invalid tag syntax in @Tag(\"%s\") declaration on %s. Tag will be ignored.".formatted( tag, elementDescription.get()); if (source.get() == null) { source.set(sourceProvider.get()); } issueCollector.accept( DiscoveryIssue.builder(Severity.WARNING, message).source(source.get()).build()); } return isValid; }) // .map(TestTag::create) // .collect(collectingAndThen(toCollection(LinkedHashSet::new), Collections::unmodifiableSet)); } /** * Invoke exception handlers for the supplied {@code Throwable} one-by-one * until none are left or the throwable to handle has been swallowed. */ void invokeExecutionExceptionHandlers(Class handlerType, ExtensionRegistry registry, Throwable throwable, ExceptionHandlerInvoker handlerInvoker) { List extensions = registry.getExtensions(handlerType); Collections.reverse(extensions); invokeExecutionExceptionHandlers(extensions, throwable, handlerInvoker); } private void invokeExecutionExceptionHandlers(List exceptionHandlers, Throwable throwable, ExceptionHandlerInvoker handlerInvoker) { // No handlers left? if (exceptionHandlers.isEmpty()) { throw ExceptionUtils.throwAsUncheckedException(throwable); } try { // Invoke next available handler handlerInvoker.invoke(exceptionHandlers.remove(0), throwable); } catch (Throwable handledThrowable) { UnrecoverableExceptions.rethrowIfUnrecoverable(handledThrowable); invokeExecutionExceptionHandlers(exceptionHandlers, handledThrowable, handlerInvoker); } } // --- Node ---------------------------------------------------------------- @Override public final ExecutionMode getExecutionMode() { return getExplicitExecutionMode() // .or(this::determineExecutionModeFromAncestors) // .orElseGet(this::getDefaultExecutionMode); } private Optional determineExecutionModeFromAncestors() { return ancestors() // .takeWhile(JupiterTestDescriptor.class::isInstance) // .flatMap(ancestor -> determineExecutionModeFromAncestor((JupiterTestDescriptor) ancestor).stream()) // .findFirst(); } private static Optional determineExecutionModeFromAncestor(JupiterTestDescriptor ancestor) { return ancestor.getExplicitChildExecutionMode() // .or(ancestor::getExplicitExecutionMode) // .or(ancestor::getDefaultChildExecutionMode); } Stream ancestors() { return Stream.iterate(getParent(), Optional::isPresent, it -> it.flatMap(TestDescriptor::getParent)) // .map(Optional::orElseThrow); } Optional getExplicitChildExecutionMode() { return Optional.empty(); } Optional getExplicitExecutionMode() { return Optional.empty(); } Optional getDefaultChildExecutionMode() { return getExplicitChildExecutionMode(); } ExecutionMode getDefaultExecutionMode() { return toExecutionMode(configuration.getDefaultExecutionMode()); } Optional getExecutionModeFromAnnotation(AnnotatedElement element) { // @formatter:off return findAnnotation(element, Execution.class) .map(Execution::value) .map(JupiterTestDescriptor::toExecutionMode); // @formatter:on } public static ExecutionMode toExecutionMode(org.junit.jupiter.api.parallel.ExecutionMode mode) { return switch (mode) { case CONCURRENT -> ExecutionMode.CONCURRENT; case SAME_THREAD -> ExecutionMode.SAME_THREAD; }; } @Override public Set getExclusiveResources() { if (this instanceof ResourceLockAware resourceLockAware) { return resourceLockAware.determineExclusiveResources().collect(toSet()); } return emptySet(); } @Override public SkipResult shouldBeSkipped(JupiterEngineExecutionContext context) { context.getThrowableCollector().assertEmpty(); ConditionEvaluationResult evaluationResult = conditionEvaluator.evaluate(context.getExtensionRegistry(), context.getConfiguration(), context.getExtensionContext()); return toSkipResult(evaluationResult); } private SkipResult toSkipResult(ConditionEvaluationResult evaluationResult) { if (evaluationResult.isDisabled()) { return SkipResult.skip(evaluationResult.getReason().orElse("")); } return SkipResult.doNotSkip(); } /** * Must be overridden and return a new context with a new {@link ExtensionContext} * so cleanUp() does not accidentally close the parent context. */ @Override public abstract JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) throws Exception; @Override public void cleanUp(JupiterEngineExecutionContext context) throws Exception { context.close(); } /** * {@return a deep copy (with copies of children) of this descriptor with the supplied unique ID} */ protected JupiterTestDescriptor copyIncludingDescendants(UnaryOperator uniqueIdTransformer) { JupiterTestDescriptor result = withUniqueId(uniqueIdTransformer); getChildren().forEach(oldChild -> { TestDescriptor newChild = ((JupiterTestDescriptor) oldChild).copyIncludingDescendants(uniqueIdTransformer); result.addChild(newChild); }); return result; } /** * {@return shallow copy (without children) of this descriptor with the supplied unique ID} */ protected abstract JupiterTestDescriptor withUniqueId(UnaryOperator uniqueIdTransformer); /** * @since 5.5 */ @FunctionalInterface interface ExceptionHandlerInvoker { /** * Invoke the supplied {@code exceptionHandler} with the supplied {@code throwable}. */ void invoke(E exceptionHandler, Throwable throwable) throws Throwable; } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static org.junit.jupiter.engine.support.MethodReflectionUtils.getReturnType; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotatedMethods; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import static org.junit.platform.engine.support.discovery.DiscoveryIssueReporter.Condition.alwaysSatisfied; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.List; import java.util.Optional; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Stream; 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.extension.ClassTemplateInvocationLifecycleMethod; import org.junit.platform.commons.support.HierarchyTraversalMode; import org.junit.platform.commons.support.ModifierSupport; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.support.descriptor.MethodSource; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter.Condition; /** * Collection of utilities for working with test lifecycle methods. * * @since 5.0 */ final class LifecycleMethodUtils { private LifecycleMethodUtils() { /* no-op */ } static List findBeforeAllMethods(Class testClass, boolean requireStatic, DiscoveryIssueReporter issueReporter) { return findMethodsAndCheckStatic(testClass, requireStatic, BeforeAll.class, HierarchyTraversalMode.TOP_DOWN, issueReporter); } static List findAfterAllMethods(Class testClass, boolean requireStatic, DiscoveryIssueReporter issueReporter) { return findMethodsAndCheckStatic(testClass, requireStatic, AfterAll.class, HierarchyTraversalMode.BOTTOM_UP, issueReporter); } static List findBeforeEachMethods(Class testClass, DiscoveryIssueReporter issueReporter) { return findMethodsAndCheckNonStatic(testClass, BeforeEach.class, HierarchyTraversalMode.TOP_DOWN, issueReporter); } static List findAfterEachMethods(Class testClass, DiscoveryIssueReporter issueReporter) { return findMethodsAndCheckNonStatic(testClass, AfterEach.class, HierarchyTraversalMode.BOTTOM_UP, issueReporter); } static void validateNoClassTemplateInvocationLifecycleMethodsAreDeclared(Class testClass, DiscoveryIssueReporter issueReporter) { findAllClassTemplateInvocationLifecycleMethods(testClass) // .forEach(method -> findClassTemplateInvocationLifecycleMethodAnnotation(method) // .ifPresent(annotation -> { String message = "@%s method '%s' must not be declared in test class '%s' because it is not annotated with @%s.".formatted( annotation.lifecycleMethodAnnotation().getSimpleName(), method.toGenericString(), testClass.getName(), annotation.classTemplateAnnotation().getSimpleName()); issueReporter.reportIssue(createIssue(Severity.ERROR, message, method)); })); } static void validateClassTemplateInvocationLifecycleMethodsAreDeclaredCorrectly(Class testClass, boolean requireStatic, DiscoveryIssueReporter issueReporter) { findAllClassTemplateInvocationLifecycleMethods(testClass) // .forEach(isNotPrivateError(issueReporter) // .and(returnsPrimitiveVoid(issueReporter, LifecycleMethodUtils::classTemplateInvocationLifecycleMethodAnnotationName)) // .and(requireStatic ? isStatic(issueReporter, LifecycleMethodUtils::classTemplateInvocationLifecycleMethodAnnotationName) : alwaysSatisfied()) // .toConsumer()); } private static Stream findAllClassTemplateInvocationLifecycleMethods(Class testClass) { Stream allMethods = Stream.concat( // findAnnotatedMethods(testClass, ClassTemplateInvocationLifecycleMethod.class, HierarchyTraversalMode.TOP_DOWN).stream(), // findAnnotatedMethods(testClass, ClassTemplateInvocationLifecycleMethod.class, HierarchyTraversalMode.BOTTOM_UP).stream() // ); return allMethods.distinct(); } private static List findMethodsAndCheckStatic(Class testClass, boolean requireStatic, Class annotationType, HierarchyTraversalMode traversalMode, DiscoveryIssueReporter issueReporter) { Condition additionalCondition = requireStatic ? isStatic(issueReporter, __ -> annotationType.getSimpleName()) : alwaysSatisfied(); return findMethodsAndCheckVoidReturnType(testClass, annotationType, traversalMode, issueReporter, additionalCondition); } private static List findMethodsAndCheckNonStatic(Class testClass, Class annotationType, HierarchyTraversalMode traversalMode, DiscoveryIssueReporter issueReporter) { return findMethodsAndCheckVoidReturnType(testClass, annotationType, traversalMode, issueReporter, isNotStatic(issueReporter, __ -> annotationType.getSimpleName())); } private static List findMethodsAndCheckVoidReturnType(Class testClass, Class annotationType, HierarchyTraversalMode traversalMode, DiscoveryIssueReporter issueReporter, Condition additionalCondition) { return findAnnotatedMethods(testClass, annotationType, traversalMode).stream() // .peek(isNotPrivateWarning(issueReporter, annotationType::getSimpleName).toConsumer()) // .filter(returnsPrimitiveVoid(issueReporter, __ -> annotationType.getSimpleName()).and( additionalCondition).toPredicate()) // .toList(); } private static Condition isStatic(DiscoveryIssueReporter issueReporter, Function annotationNameProvider) { return issueReporter.createReportingCondition(ModifierSupport::isStatic, method -> { String message = "@%s method '%s' must be static unless the test class is annotated with @TestInstance(Lifecycle.PER_CLASS).".formatted( annotationNameProvider.apply(method), method.toGenericString()); return createIssue(Severity.ERROR, message, method); }); } private static Condition isNotStatic(DiscoveryIssueReporter issueReporter, Function annotationNameProvider) { return issueReporter.createReportingCondition(ModifierSupport::isNotStatic, method -> { String message = "@%s method '%s' must not be static.".formatted(annotationNameProvider.apply(method), method.toGenericString()); return createIssue(Severity.ERROR, message, method); }); } private static Condition isNotPrivateError(DiscoveryIssueReporter issueReporter) { return issueReporter.createReportingCondition(ModifierSupport::isNotPrivate, method -> { String message = "@%s method '%s' must not be private.".formatted( classTemplateInvocationLifecycleMethodAnnotationName(method), method.toGenericString()); return createIssue(Severity.ERROR, message, method); }); } private static Condition isNotPrivateWarning(DiscoveryIssueReporter issueReporter, Supplier annotationNameProvider) { return issueReporter.createReportingCondition(ModifierSupport::isNotPrivate, method -> { String message = "@%s method '%s' should not be private. This will be disallowed in a future release.".formatted( annotationNameProvider.get(), method.toGenericString()); return createIssue(Severity.WARNING, message, method); }); } private static Condition returnsPrimitiveVoid(DiscoveryIssueReporter issueReporter, Function annotationNameProvider) { return issueReporter.createReportingCondition(method -> getReturnType(method) == void.class, method -> { String message = "@%s method '%s' must not return a value.".formatted(annotationNameProvider.apply(method), method.toGenericString()); return createIssue(Severity.ERROR, message, method); }); } private static String classTemplateInvocationLifecycleMethodAnnotationName(Method method) { return findClassTemplateInvocationLifecycleMethodAnnotation(method) // .map(ClassTemplateInvocationLifecycleMethod::lifecycleMethodAnnotation) // .map(Class::getSimpleName) // .orElseGet(ClassTemplateInvocationLifecycleMethod.class::getSimpleName); } private static Optional findClassTemplateInvocationLifecycleMethodAnnotation( Method method) { return findAnnotation(method, ClassTemplateInvocationLifecycleMethod.class); } private static DiscoveryIssue createIssue(Severity severity, String message, Method method) { return DiscoveryIssue.builder(severity, message).source(MethodSource.from(method)).build(); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.jupiter.api.parallel.ResourceLockTarget.CHILDREN; import static org.junit.jupiter.engine.descriptor.DisplayNameUtils.determineDisplayNameForMethod; import static org.junit.jupiter.engine.descriptor.ResourceLockAware.enclosingInstanceTypesDependentResourceLocksProviderEvaluator; import static org.junit.platform.commons.util.CollectionUtils.forEachInReverseOrder; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestWatcher; import org.junit.jupiter.api.parallel.ResourceLocksProvider; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.ClassUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.commons.util.UnrecoverableExceptions; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestTag; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.MethodSource; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter; /** * Base class for {@link TestDescriptor TestDescriptors} based on Java methods. * * @since 5.0 */ @API(status = INTERNAL, since = "5.0") public abstract class MethodBasedTestDescriptor extends JupiterTestDescriptor implements ResourceLockAware, TestClassAware, Validatable { private static final Logger logger = LoggerFactory.getLogger(MethodBasedTestDescriptor.class); private final MethodInfo methodInfo; MethodBasedTestDescriptor(UniqueId uniqueId, Class testClass, Method testMethod, Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration) { this(uniqueId, determineDisplayNameForMethod(enclosingInstanceTypes, testClass, testMethod, configuration), testClass, testMethod, configuration); } MethodBasedTestDescriptor(UniqueId uniqueId, String displayName, Class testClass, Method testMethod, JupiterConfiguration configuration) { super(uniqueId, displayName, MethodSource.from(testClass, testMethod), configuration); this.methodInfo = new MethodInfo(testClass, testMethod); } public final Method getTestMethod() { return this.methodInfo.testMethod; } // --- TestDescriptor ------------------------------------------------------ @Override public final Set getTags() { // return modifiable copy Set allTags = new LinkedHashSet<>(this.methodInfo.tags); getParent().ifPresent(parentDescriptor -> allTags.addAll(parentDescriptor.getTags())); return allTags; } @Override protected final String getLegacyReportingBaseName() { return "%s(%s)".formatted(getTestMethod().getName(), ClassUtils.nullSafeToString(Class::getSimpleName, getTestMethod().getParameterTypes())); } // --- TestClassAware ------------------------------------------------------ @Override public final Class getTestClass() { return this.methodInfo.testClass; } @Override public List> getEnclosingTestClasses() { return getParent() // .filter(TestClassAware.class::isInstance) // .map(TestClassAware.class::cast) // .map(TestClassAware::getEnclosingTestClasses) // .orElseGet(Collections::emptyList); } // --- Validatable --------------------------------------------------------- @Override public void validate(DiscoveryIssueReporter reporter) { Validatable.reportAndClear(this.methodInfo.discoveryIssues, reporter); DisplayNameUtils.validateAnnotation(getTestMethod(), // () -> "method '%s'".formatted(getTestMethod().toGenericString()), // // Use _declaring_ class here because that's where the `@DisplayName` annotation is declared () -> MethodSource.from(getTestMethod()), // reporter); } // --- Node ---------------------------------------------------------------- @Override public ExclusiveResourceCollector getExclusiveResourceCollector() { // There's no need to cache this as this method should only be called once ExclusiveResourceCollector collector = ExclusiveResourceCollector.from(getTestMethod()); if (collector.getStaticResourcesFor(CHILDREN).findAny().isPresent()) { String message = "'ResourceLockTarget.CHILDREN' is not supported for methods." + // " Invalid method: " + getTestMethod(); throw new JUnitException(message); } return collector; } @Override public Function> getResourceLocksProviderEvaluator() { return enclosingInstanceTypesDependentResourceLocksProviderEvaluator(this::getEnclosingTestClasses, (provider, enclosingInstanceTypes) -> provider.provideForMethod(enclosingInstanceTypes, getTestClass(), getTestMethod())); } @Override protected Optional getExplicitExecutionMode() { return getExecutionModeFromAnnotation(getTestMethod()); } /** * Invoke {@link TestWatcher#testDisabled(ExtensionContext, Optional)} on each * registered {@link TestWatcher}, in registration order. * * @since 5.4 */ @Override public void nodeSkipped(JupiterEngineExecutionContext context, TestDescriptor descriptor, SkipResult result) { invokeTestWatchers(context, false, watcher -> watcher.testDisabled(context.getExtensionContext(), result.getReason())); } /** * @since 5.4 */ protected void invokeTestWatchers(JupiterEngineExecutionContext context, boolean reverseOrder, Consumer callback) { List watchers = context.getExtensionRegistry().getExtensions(TestWatcher.class); Consumer action = watcher -> { try { callback.accept(watcher); } catch (Throwable throwable) { UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); ExtensionContext extensionContext = context.getExtensionContext(); logger.warn(throwable, () -> "Failed to invoke TestWatcher [%s] for method [%s] with display name [%s]".formatted( watcher.getClass().getName(), ReflectionUtils.getFullyQualifiedMethodName(extensionContext.getRequiredTestClass(), extensionContext.getRequiredTestMethod()), getDisplayName())); } }; if (reverseOrder) { forEachInReverseOrder(watchers, action); } else { watchers.forEach(action); } } private static class MethodInfo { private final List discoveryIssues = new ArrayList<>(); private final Class testClass; private final Method testMethod; /** * Set of method-level tags; does not contain tags from parent. */ private final Set tags; MethodInfo(Class testClass, Method testMethod) { this.testClass = Preconditions.notNull(testClass, "Class must not be null"); this.testMethod = testMethod; this.tags = getTags(testMethod, // () -> "method '%s'".formatted(testMethod.toGenericString()), // // Use _declaring_ class here because that's where the `@Tag` annotation is declared () -> MethodSource.from(testMethod.getDeclaringClass(), testMethod), // discoveryIssues::add); } } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodExtensionContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.util.List; import java.util.Optional; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestInstances; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.LauncherStoreFacade; import org.junit.jupiter.engine.extension.ExtensionRegistry; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.support.hierarchical.Node; import org.junit.platform.engine.support.hierarchical.ThrowableCollector; /** * @since 5.0 */ final class MethodExtensionContext extends AbstractExtensionContext { private final ThrowableCollector throwableCollector; private @Nullable TestInstances testInstances; MethodExtensionContext(ExtensionContext parent, EngineExecutionListener engineExecutionListener, TestMethodTestDescriptor testDescriptor, JupiterConfiguration configuration, ExtensionRegistry extensionRegistry, LauncherStoreFacade launcherStoreFacade, ThrowableCollector throwableCollector) { super(parent, engineExecutionListener, testDescriptor, configuration, extensionRegistry, launcherStoreFacade); this.throwableCollector = throwableCollector; } @Override public Optional getElement() { return Optional.of(getTestDescriptor().getTestMethod()); } @Override public Optional> getTestClass() { return Optional.of(getTestDescriptor().getTestClass()); } @Override public List> getEnclosingTestClasses() { return getTestDescriptor().getEnclosingTestClasses(); } @Override public Optional getTestInstanceLifecycle() { return getParent().flatMap(ExtensionContext::getTestInstanceLifecycle); } @Override public Optional getTestInstance() { return getTestInstances().map(TestInstances::getInnermostInstance); } @Override public Optional getTestInstances() { return Optional.ofNullable(this.testInstances); } void setTestInstances(TestInstances testInstances) { this.testInstances = testInstances; } @Override public Optional getTestMethod() { return Optional.of(getTestDescriptor().getTestMethod()); } @Override public Optional getExecutionException() { return Optional.ofNullable(this.throwableCollector.getThrowable()); } @Override protected Node.ExecutionMode getPlatformExecutionMode() { return getTestDescriptor().getExecutionMode(); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodSourceSupport.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import java.net.URI; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.engine.discovery.DiscoverySelectors; import org.junit.platform.engine.discovery.MethodSelector; import org.junit.platform.engine.support.descriptor.MethodSource; /** * Jupiter internal support for creating {@link MethodSource} from {@link URI}. * * @since 5.5 * @see MethodSource * @see MethodSelector */ class MethodSourceSupport { static final String METHOD_SCHEME = "method"; /** * Create a new {@code MethodSource} from the supplied {@link URI}. * *

The supplied {@link URI} should be of the form {@code method:} * where FQMN is the fully qualified method name. See * {@link DiscoverySelectors#selectMethod(String)} for the supported formats. * *

The {@link URI#getSchemeSpecificPart() scheme-specific part} * component of the {@code URI} will be used as fully qualified class name. * The {@linkplain URI#getFragment() fragment} component of the {@code URI} * will be used to retrieve the method name and method parameter types. * * @param uri the {@code URI} for the method; never {@code null} * @return a new {@code MethodSource}; never {@code null} * @since 5.5 * @see #METHOD_SCHEME * @see DiscoverySelectors#selectMethod(String) */ static MethodSource from(URI uri) { Preconditions.notNull(uri, "URI must not be null"); Preconditions.condition(METHOD_SCHEME.equals(uri.getScheme()), () -> "URI [" + uri + "] must have [" + METHOD_SCHEME + "] scheme"); String schemeSpecificPart = Preconditions.notNull(uri.getSchemeSpecificPart(), () -> "Invalid method URI (scheme-specific part must not be null). Please consult the Javadoc of " + DiscoverySelectors.class.getName() + "#selectMethod(String) for details on the supported formats."); String fragment = Preconditions.notNull(uri.getFragment(), () -> "Invalid method URI (fragment must not be null). Please consult the Javadoc of " + DiscoverySelectors.class.getName() + "#selectMethod(String) for details on the supported formats."); String fullyQualifiedMethodName = schemeSpecificPart + "#" + fragment; String[] methodSpec = ReflectionUtils.parseFullyQualifiedMethodName(fullyQualifiedMethodName); return MethodSource.from(methodSpec[0], methodSpec[1], methodSpec[2]); } private MethodSourceSupport() { } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.jupiter.engine.descriptor.DisplayNameUtils.createDisplayNameSupplierForNestedClass; import static org.junit.jupiter.engine.descriptor.ResourceLockAware.enclosingInstanceTypesDependentResourceLocksProviderEvaluator; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.function.Supplier; import java.util.function.UnaryOperator; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.TestInstances; import org.junit.jupiter.api.parallel.ResourceLocksProvider; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.ExtensionContextSupplier; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; import org.junit.jupiter.engine.extension.ExtensionRegistry; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestTag; import org.junit.platform.engine.UniqueId; /** * {@link TestDescriptor} for tests based on nested (but not static) Java classes. * *

Default Display Names

* *

The default display name for a non-static nested test class is the simple * name of the class. * * @since 5.0 */ @API(status = INTERNAL, since = "5.0") public class NestedClassTestDescriptor extends ClassBasedTestDescriptor { public static final String SEGMENT_TYPE = "nested-class"; public NestedClassTestDescriptor(UniqueId uniqueId, Class testClass, Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration) { super(uniqueId, testClass, createDisplayNameSupplierForNestedClass(enclosingInstanceTypes, testClass, configuration), configuration); } private NestedClassTestDescriptor(UniqueId uniqueId, Class testClass, String displayName, JupiterConfiguration configuration) { super(uniqueId, testClass, displayName, configuration); } // --- JupiterTestDescriptor ----------------------------------------------- @Override protected NestedClassTestDescriptor withUniqueId(UnaryOperator uniqueIdTransformer) { return new NestedClassTestDescriptor(uniqueIdTransformer.apply(getUniqueId()), getTestClass(), getDisplayName(), configuration); } // --- TestDescriptor ------------------------------------------------------ @Override public final Set getTags() { // return modifiable copy Set allTags = new LinkedHashSet<>(this.classInfo.tags); getParent().ifPresent(parentDescriptor -> allTags.addAll(parentDescriptor.getTags())); return allTags; } // --- TestClassAware ------------------------------------------------------ @Override public List> getEnclosingTestClasses() { return getEnclosingTestClasses(getParent().orElse(null)); } @API(status = INTERNAL, since = "5.12") public static List> getEnclosingTestClasses(@Nullable TestDescriptor parent) { if (parent instanceof TestClassAware testClassAwareParent) { List> result = new ArrayList<>(testClassAwareParent.getEnclosingTestClasses()); result.add(testClassAwareParent.getTestClass()); return List.copyOf(result); } return List.of(); } // --- ClassBasedTestDescriptor -------------------------------------------- @Override protected TestInstances instantiateTestClass(JupiterEngineExecutionContext parentExecutionContext, ExtensionContextSupplier extensionContext, ExtensionRegistry registry, JupiterEngineExecutionContext context) { // Extensions registered for nested classes and below are not to be used for instantiating and initializing outer classes ExtensionRegistry extensionRegistryForOuterInstanceCreation = parentExecutionContext.getExtensionRegistry(); TestInstances outerInstances = parentExecutionContext.getTestInstancesProvider().getTestInstances( extensionRegistryForOuterInstanceCreation, context); return instantiateTestClass(Optional.of(outerInstances), registry, extensionContext); } // --- ResourceLockAware --------------------------------------------------- @Override public Function> getResourceLocksProviderEvaluator() { return enclosingInstanceTypesDependentResourceLocksProviderEvaluator(this::getEnclosingTestClasses, (provider, enclosingInstanceTypes) -> provider.provideForNestedClass(enclosingInstanceTypes, getTestClass())); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ResourceLockAware.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static org.junit.jupiter.api.parallel.ResourceLockTarget.CHILDREN; import java.util.ArrayDeque; import java.util.Deque; import java.util.List; import java.util.Set; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.parallel.ResourceLocksProvider; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.support.hierarchical.ExclusiveResource; /** * @since 5.12 */ interface ResourceLockAware extends TestDescriptor { default Stream determineExclusiveResources() { Deque ancestors = new ArrayDeque<>(); TestDescriptor parent = this.getParent().orElse(null); while (parent instanceof ResourceLockAware resourceLockAwareParent) { ancestors.addFirst(resourceLockAwareParent); parent = parent.getParent().orElse(null); } Function> evaluator = getResourceLocksProviderEvaluator(); if (ancestors.isEmpty()) { return determineOwnExclusiveResources(evaluator); } Stream parentStaticResourcesForChildren = ancestors.getLast() // .getExclusiveResourceCollector().getStaticResourcesFor(CHILDREN); Stream ancestorDynamicResources = ancestors.stream() // .map(ResourceLockAware::getExclusiveResourceCollector) // .flatMap(collector -> collector.getDynamicResources(evaluator)); return Stream.of(ancestorDynamicResources, parentStaticResourcesForChildren, determineOwnExclusiveResources(evaluator))// .flatMap(s -> s); } default Stream determineOwnExclusiveResources( Function> providerToLocks) { return this.getExclusiveResourceCollector().getAllExclusiveResources(providerToLocks); } ExclusiveResourceCollector getExclusiveResourceCollector(); Function> getResourceLocksProviderEvaluator(); static Function> enclosingInstanceTypesDependentResourceLocksProviderEvaluator( Supplier>> enclosingInstanceTypesSupplier, BiFunction>, Set> evaluator) { return new Function<>() { @Nullable private List> enclosingInstanceTypes; @Override public Set apply(ResourceLocksProvider provider) { if (this.enclosingInstanceTypes == null) { this.enclosingInstanceTypes = List.copyOf(enclosingInstanceTypesSupplier.get()); } return evaluator.apply(provider, this.enclosingInstanceTypes); } }; } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TemplateExecutor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import java.util.List; import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Stream; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TemplateInvocationValidationException; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; import org.junit.jupiter.engine.extension.ExtensionRegistry; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.hierarchical.Node; abstract class TemplateExecutor

{ private final TestDescriptor parent; private final Class

providerType; private final DynamicDescendantFilter dynamicDescendantFilter; TemplateExecutor(T parent, Class

providerType) { this.parent = parent; this.providerType = providerType; this.dynamicDescendantFilter = parent.getDynamicDescendantFilter(); } void execute(JupiterEngineExecutionContext context, Node.DynamicTestExecutor dynamicTestExecutor) { ExtensionContext extensionContext = context.getExtensionContext(); List

providers = validateProviders(extensionContext, context.getExtensionRegistry()); AtomicInteger invocationIndex = new AtomicInteger(); for (P provider : providers) { executeForProvider(provider, invocationIndex, dynamicTestExecutor, extensionContext); } } private void executeForProvider(P provider, AtomicInteger invocationIndex, Node.DynamicTestExecutor dynamicTestExecutor, ExtensionContext extensionContext) { int initialValue = invocationIndex.get(); Stream stream = provideContexts(provider, extensionContext); try { stream.forEach(invocationContext -> createInvocationTestDescriptor(invocationContext, invocationIndex.incrementAndGet()) // .ifPresent(testDescriptor -> execute(dynamicTestExecutor, testDescriptor))); } catch (Throwable t) { try { stream.close(); } catch (TemplateInvocationValidationException ignore) { // ignore exceptions from close() to avoid masking the original failure } throw ExceptionUtils.throwAsUncheckedException(t); } finally { stream.close(); } Preconditions.condition( invocationIndex.get() != initialValue || mayReturnZeroContexts(provider, extensionContext), getZeroContextsProvidedErrorMessage(provider)); } private List

validateProviders(ExtensionContext extensionContext, ExtensionRegistry extensionRegistry) { List

providers = extensionRegistry.stream(providerType) // .filter(provider -> supports(provider, extensionContext)) // .toList(); return Preconditions.notEmpty(providers, this::getNoRegisteredProviderErrorMessage); } private Optional createInvocationTestDescriptor(C invocationContext, int index) { UniqueId invocationUniqueId = createInvocationUniqueId(parent.getUniqueId(), index); if (this.dynamicDescendantFilter.test(invocationUniqueId, index - 1)) { return Optional.of(createInvocationTestDescriptor(invocationUniqueId, invocationContext, index)); } return Optional.empty(); } private void execute(Node.DynamicTestExecutor dynamicTestExecutor, TestDescriptor testDescriptor) { testDescriptor.setParent(parent); dynamicTestExecutor.execute(testDescriptor); } abstract boolean supports(P provider, ExtensionContext extensionContext); protected abstract String getNoRegisteredProviderErrorMessage(); abstract Stream provideContexts(P provider, ExtensionContext extensionContext); abstract boolean mayReturnZeroContexts(P provider, ExtensionContext extensionContext); protected abstract String getZeroContextsProvidedErrorMessage(P provider); abstract UniqueId createInvocationUniqueId(UniqueId parentUniqueId, int index); abstract TestDescriptor createInvocationTestDescriptor(UniqueId uniqueId, C invocationContext, int index); } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestClassAware.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.List; import org.apiguardian.api.API; /** * @since 5.13 */ @API(status = INTERNAL, since = "5.13") public interface TestClassAware { Class getTestClass(); List> getEnclosingTestClasses(); } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.jupiter.engine.descriptor.MethodSourceSupport.METHOD_SCHEME; import static org.junit.platform.engine.support.descriptor.ClassSource.CLASS_SCHEME; import static org.junit.platform.engine.support.descriptor.ClasspathResourceSource.CLASSPATH_SCHEME; import java.lang.reflect.Method; import java.net.URI; import java.util.Iterator; import java.util.List; import java.util.Optional; import java.util.function.Supplier; import java.util.function.UnaryOperator; import java.util.stream.Stream; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.DynamicContainer; import org.junit.jupiter.api.DynamicNode; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.InvocationInterceptor; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.InterceptingExecutableInvoker; import org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.ReflectiveInterceptorCall; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.CollectionUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.TestSource; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.platform.engine.support.descriptor.ClasspathResourceSource; import org.junit.platform.engine.support.descriptor.UriSource; /** * {@link org.junit.platform.engine.TestDescriptor TestDescriptor} for * {@link org.junit.jupiter.api.TestFactory @TestFactory} methods. * * @since 5.0 */ @API(status = INTERNAL, since = "5.0") public class TestFactoryTestDescriptor extends TestMethodTestDescriptor implements Filterable { public static final String SEGMENT_TYPE = "test-factory"; public static final String DYNAMIC_CONTAINER_SEGMENT_TYPE = "dynamic-container"; public static final String DYNAMIC_TEST_SEGMENT_TYPE = "dynamic-test"; private static final ReflectiveInterceptorCall interceptorCall = InvocationInterceptor::interceptTestFactoryMethod; private static final InterceptingExecutableInvoker executableInvoker = new InterceptingExecutableInvoker(); private final DynamicDescendantFilter dynamicDescendantFilter; public TestFactoryTestDescriptor(UniqueId uniqueId, Class testClass, Method testMethod, Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration) { super(uniqueId, testClass, testMethod, enclosingInstanceTypes, configuration); this.dynamicDescendantFilter = new DynamicDescendantFilter(); } private TestFactoryTestDescriptor(UniqueId uniqueId, String displayName, Class testClass, Method testMethod, JupiterConfiguration configuration, DynamicDescendantFilter dynamicDescendantFilter) { super(uniqueId, displayName, testClass, testMethod, configuration); this.dynamicDescendantFilter = dynamicDescendantFilter; } // --- JupiterTestDescriptor ----------------------------------------------- @Override protected TestFactoryTestDescriptor withUniqueId(UnaryOperator uniqueIdTransformer) { return new TestFactoryTestDescriptor(uniqueIdTransformer.apply(getUniqueId()), getDisplayName(), getTestClass(), getTestMethod(), this.configuration, this.dynamicDescendantFilter.copy(uniqueIdTransformer)); } // --- Filterable ---------------------------------------------------------- @Override public DynamicDescendantFilter getDynamicDescendantFilter() { return dynamicDescendantFilter; } // --- TestDescriptor ------------------------------------------------------ @Override public Type getType() { return Type.CONTAINER; } @Override public boolean mayRegisterTests() { return true; } // --- Node ---------------------------------------------------------------- @Override protected void invokeTestMethod(JupiterEngineExecutionContext context, DynamicTestExecutor dynamicTestExecutor) { ExtensionContext extensionContext = context.getExtensionContext(); context.getThrowableCollector().execute(() -> { Object instance = extensionContext.getRequiredTestInstance(); Object testFactoryMethodResult = executableInvoker.<@Nullable Object> invoke(getTestMethod(), instance, extensionContext, context.getExtensionRegistry(), interceptorCall); TestSource defaultTestSource = getSource().orElseThrow( () -> new JUnitException("Illegal state: TestSource must be present")); try (Stream dynamicNodeStream = toDynamicNodeStream(testFactoryMethodResult)) { int index = 1; Iterator iterator = dynamicNodeStream.iterator(); while (iterator.hasNext()) { DynamicNode dynamicNode = iterator.next(); Optional descriptor = createDynamicDescriptor(this, dynamicNode, index, defaultTestSource, getDynamicDescendantFilter(), configuration); descriptor.ifPresent(dynamicTestExecutor::execute); index++; } } catch (ClassCastException ex) { throw invalidReturnTypeException(ex); } dynamicTestExecutor.awaitFinished(); }); } @SuppressWarnings("unchecked") private Stream toDynamicNodeStream(@Nullable Object testFactoryMethodResult) { if (testFactoryMethodResult == null) { throw new JUnitException("@TestFactory method must not return null"); } if (testFactoryMethodResult instanceof DynamicNode node) { return Stream.of(node); } return (Stream) CollectionUtils.toStream(testFactoryMethodResult); } private JUnitException invalidReturnTypeException(Throwable cause) { String message = "Objects produced by @TestFactory method '%s' must be of type %s.".formatted( getTestMethod().toGenericString(), DynamicNode.class.getName()); return new JUnitException(message, cause); } static Optional createDynamicDescriptor(JupiterTestDescriptor parent, DynamicNode node, int index, TestSource defaultTestSource, DynamicDescendantFilter dynamicDescendantFilter, JupiterConfiguration configuration) { UniqueId uniqueId; Supplier descriptorCreator; Optional customTestSource = node.getTestSourceUri().map(TestFactoryTestDescriptor::fromUri); TestSource source = customTestSource.orElse(defaultTestSource); if (node instanceof DynamicTest test) { uniqueId = parent.getUniqueId().append(DYNAMIC_TEST_SEGMENT_TYPE, "#" + index); descriptorCreator = () -> new DynamicTestTestDescriptor(uniqueId, index, test, source, configuration); } else { DynamicContainer container = (DynamicContainer) node; uniqueId = parent.getUniqueId().append(DYNAMIC_CONTAINER_SEGMENT_TYPE, "#" + index); descriptorCreator = () -> new DynamicContainerTestDescriptor(uniqueId, index, container, source, dynamicDescendantFilter.withoutIndexFiltering(), configuration); } if (dynamicDescendantFilter.test(uniqueId, index - 1)) { JupiterTestDescriptor descriptor = descriptorCreator.get(); descriptor.setParent(parent); return Optional.of(descriptor); } return Optional.empty(); } /** * @since 5.3 */ static TestSource fromUri(URI uri) { Preconditions.notNull(uri, "URI must not be null"); if (CLASSPATH_SCHEME.equals(uri.getScheme())) { return ClasspathResourceSource.from(uri); } if (CLASS_SCHEME.equals(uri.getScheme())) { return ClassSource.from(uri); } if (METHOD_SCHEME.equals(uri.getScheme())) { return MethodSourceSupport.from(uri); } return UriSource.from(uri); } /** * Override {@link TestMethodTestDescriptor#nodeSkipped} as a no-op, since * the {@code TestWatcher} API is not supported for {@code @TestFactory} * containers. * * @since 5.4 */ @Override public void nodeSkipped(JupiterEngineExecutionContext context, TestDescriptor descriptor, SkipResult result) { /* no-op */ } /** * Override {@link TestMethodTestDescriptor#nodeFinished} as a no-op, since * the {@code TestWatcher} API is not supported for {@code @TestFactory} * containers. * * @since 5.4 */ @Override public void nodeFinished(JupiterEngineExecutionContext context, TestDescriptor descriptor, TestExecutionResult result) { /* no-op */ } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static org.apiguardian.api.API.Status.INTERNAL; import org.apiguardian.api.API; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.platform.commons.support.AnnotationSupport; import org.junit.platform.commons.util.Preconditions; /** * Collection of utilities for retrieving the test instance lifecycle mode. * * @since 5.0 * @see TestInstance * @see TestInstance.Lifecycle */ @API(status = INTERNAL, since = "5.0") public final class TestInstanceLifecycleUtils { private TestInstanceLifecycleUtils() { /* no-op */ } static TestInstance.Lifecycle getTestInstanceLifecycle(Class testClass, JupiterConfiguration configuration) { Preconditions.notNull(testClass, "testClass must not be null"); Preconditions.notNull(configuration, "configuration must not be null"); // @formatter:off return AnnotationSupport.findAnnotation(testClass, TestInstance.class) .map(TestInstance::value) .orElseGet(configuration::getDefaultTestInstanceLifecycle); // @formatter:on } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.jupiter.engine.descriptor.CallbackSupport.invokeAfterCallbacks; import static org.junit.jupiter.engine.descriptor.CallbackSupport.invokeBeforeCallbacks; import static org.junit.jupiter.engine.descriptor.ExtensionUtils.populateNewExtensionRegistryFromExtendWithAnnotation; import static org.junit.jupiter.engine.descriptor.ExtensionUtils.registerExtensionsFromExecutableParameters; import static org.junit.jupiter.engine.support.JupiterThrowableCollectorFactory.createThrowableCollector; import java.lang.reflect.Method; import java.util.List; import java.util.function.Supplier; import java.util.function.UnaryOperator; import org.apiguardian.api.API; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.AfterTestExecutionCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.InvocationInterceptor; import org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler; import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; import org.junit.jupiter.api.extension.TestInstancePreDestroyCallback; import org.junit.jupiter.api.extension.TestInstances; import org.junit.jupiter.api.extension.TestWatcher; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.AfterEachMethodAdapter; import org.junit.jupiter.engine.execution.BeforeEachMethodAdapter; import org.junit.jupiter.engine.execution.InterceptingExecutableInvoker; import org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.ReflectiveInterceptorCall.VoidMethodInterceptorCall; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; import org.junit.jupiter.engine.extension.ExtensionRegistry; import org.junit.jupiter.engine.extension.MutableExtensionRegistry; import org.junit.platform.commons.util.UnrecoverableExceptions; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.hierarchical.ThrowableCollector; /** * {@link TestDescriptor} for {@link org.junit.jupiter.api.Test @Test} methods. * *

Default Display Names

* *

The default display name for a test method is the name of the method * concatenated with a comma-separated list of parameter types in parentheses. * The names of parameter types are retrieved using {@link Class#getSimpleName()}. * For example, the default display name for the following test method is * {@code testUser(TestInfo, User)}. * *

 *   {@literal @}Test
 *   void testUser(TestInfo testInfo, {@literal @}Mock User user) { ... }
 * 
* * @since 5.0 */ @API(status = INTERNAL, since = "5.0") public class TestMethodTestDescriptor extends MethodBasedTestDescriptor { public static final String SEGMENT_TYPE = "method"; private static final InterceptingExecutableInvoker executableInvoker = new InterceptingExecutableInvoker(); private static final VoidMethodInterceptorCall defaultInterceptorCall = InvocationInterceptor::interceptTestMethod; private final VoidMethodInterceptorCall interceptorCall; public TestMethodTestDescriptor(UniqueId uniqueId, Class testClass, Method testMethod, Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration) { super(uniqueId, testClass, testMethod, enclosingInstanceTypes, configuration); this.interceptorCall = defaultInterceptorCall; } TestMethodTestDescriptor(UniqueId uniqueId, String displayName, Class testClass, Method testMethod, JupiterConfiguration configuration) { this(uniqueId, displayName, testClass, testMethod, configuration, defaultInterceptorCall); } TestMethodTestDescriptor(UniqueId uniqueId, String displayName, Class testClass, Method testMethod, JupiterConfiguration configuration, VoidMethodInterceptorCall interceptorCall) { super(uniqueId, displayName, testClass, testMethod, configuration); this.interceptorCall = interceptorCall; } // --- JupiterTestDescriptor ----------------------------------------------- @Override protected TestMethodTestDescriptor withUniqueId(UnaryOperator uniqueIdTransformer) { return new TestMethodTestDescriptor(uniqueIdTransformer.apply(getUniqueId()), getDisplayName(), getTestClass(), getTestMethod(), this.configuration, interceptorCall); } // --- TestDescriptor ------------------------------------------------------ @Override public Type getType() { return Type.TEST; } // --- Node ---------------------------------------------------------------- @Override public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) { MutableExtensionRegistry registry = populateNewExtensionRegistry(context); ThrowableCollector throwableCollector = createThrowableCollector(); MethodExtensionContext extensionContext = new MethodExtensionContext(context.getExtensionContext(), context.getExecutionListener(), this, context.getConfiguration(), registry, context.getLauncherStoreFacade(), throwableCollector); // @formatter:off JupiterEngineExecutionContext newContext = context.extend() .withExtensionRegistry(registry) .withExtensionContext(extensionContext) .withThrowableCollector(throwableCollector) .build(); // @formatter:on throwableCollector.execute(() -> { TestInstances testInstances = newContext.getTestInstancesProvider().getTestInstances(newContext); extensionContext.setTestInstances(testInstances); prepareExtensionContext(extensionContext); }); return newContext; } protected void prepareExtensionContext(ExtensionContext extensionContext) { // nothing to do by default } protected MutableExtensionRegistry populateNewExtensionRegistry(JupiterEngineExecutionContext context) { MutableExtensionRegistry registry = populateNewExtensionRegistryFromExtendWithAnnotation( context.getExtensionRegistry(), getTestMethod()); registerExtensionsFromExecutableParameters(registry, getTestMethod()); return registry; } @Override public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext context, DynamicTestExecutor dynamicTestExecutor) { ThrowableCollector throwableCollector = context.getThrowableCollector(); // @formatter:off invokeBeforeEachCallbacks(context); if (throwableCollector.isEmpty()) { invokeBeforeEachMethods(context); if (throwableCollector.isEmpty()) { invokeBeforeTestExecutionCallbacks(context); if (throwableCollector.isEmpty()) { invokeTestMethod(context, dynamicTestExecutor); } invokeAfterTestExecutionCallbacks(context); } invokeAfterEachMethods(context); } invokeAfterEachCallbacks(context); // @formatter:on return context; } @Override public void cleanUp(JupiterEngineExecutionContext context) throws Exception { if (isPerMethodLifecycle(context) && context.getExtensionContext().getTestInstance().isPresent()) { invokeTestInstancePreDestroyCallbacks(context); } context.getThrowableCollector().execute(() -> super.cleanUp(context)); context.getThrowableCollector().assertEmpty(); } private boolean isPerMethodLifecycle(JupiterEngineExecutionContext context) { return context.getExtensionContext().getTestInstanceLifecycle().orElse( Lifecycle.PER_CLASS) == Lifecycle.PER_METHOD; } private void invokeBeforeEachCallbacks(JupiterEngineExecutionContext context) { invokeBeforeCallbacks(BeforeEachCallback.class, context, BeforeEachCallback::beforeEach); } private void invokeBeforeEachMethods(JupiterEngineExecutionContext context) { ExtensionRegistry registry = context.getExtensionRegistry(); invokeBeforeCallbacks(BeforeEachMethodAdapter.class, context, (adapter, extensionContext) -> { try { adapter.invokeBeforeEachMethod(extensionContext, registry); } catch (Throwable throwable) { invokeBeforeEachExecutionExceptionHandlers(extensionContext, registry, throwable); } }); } private void invokeBeforeEachExecutionExceptionHandlers(ExtensionContext context, ExtensionRegistry registry, Throwable throwable) { invokeExecutionExceptionHandlers(LifecycleMethodExecutionExceptionHandler.class, registry, throwable, (handler, handledThrowable) -> handler.handleBeforeEachMethodExecutionException(context, handledThrowable)); } private void invokeBeforeTestExecutionCallbacks(JupiterEngineExecutionContext context) { invokeBeforeCallbacks(BeforeTestExecutionCallback.class, context, BeforeTestExecutionCallback::beforeTestExecution); } protected void invokeTestMethod(JupiterEngineExecutionContext context, DynamicTestExecutor dynamicTestExecutor) { ExtensionContext extensionContext = context.getExtensionContext(); ThrowableCollector throwableCollector = context.getThrowableCollector(); throwableCollector.execute(() -> { try { Method testMethod = getTestMethod(); Object instance = extensionContext.getRequiredTestInstance(); executableInvoker.invokeVoid(testMethod, instance, extensionContext, context.getExtensionRegistry(), interceptorCall); } catch (Throwable throwable) { UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); invokeTestExecutionExceptionHandlers(context.getExtensionRegistry(), extensionContext, throwable); } }); } private void invokeTestExecutionExceptionHandlers(ExtensionRegistry registry, ExtensionContext context, Throwable throwable) { invokeExecutionExceptionHandlers(TestExecutionExceptionHandler.class, registry, throwable, (handler, handledThrowable) -> handler.handleTestExecutionException(context, handledThrowable)); } private void invokeAfterTestExecutionCallbacks(JupiterEngineExecutionContext context) { invokeAfterCallbacks(AfterTestExecutionCallback.class, context, AfterTestExecutionCallback::afterTestExecution); } private void invokeAfterEachMethods(JupiterEngineExecutionContext context) { ExtensionRegistry registry = context.getExtensionRegistry(); invokeAfterCallbacks(AfterEachMethodAdapter.class, context, (adapter, extensionContext) -> { try { adapter.invokeAfterEachMethod(extensionContext, registry); } catch (Throwable throwable) { invokeAfterEachExecutionExceptionHandlers(extensionContext, registry, throwable); } }); } private void invokeAfterEachExecutionExceptionHandlers(ExtensionContext context, ExtensionRegistry registry, Throwable throwable) { invokeExecutionExceptionHandlers(LifecycleMethodExecutionExceptionHandler.class, registry, throwable, (handler, handledThrowable) -> handler.handleAfterEachMethodExecutionException(context, handledThrowable)); } private void invokeAfterEachCallbacks(JupiterEngineExecutionContext context) { invokeAfterCallbacks(AfterEachCallback.class, context, AfterEachCallback::afterEach); } private void invokeTestInstancePreDestroyCallbacks(JupiterEngineExecutionContext context) { invokeAfterCallbacks(TestInstancePreDestroyCallback.class, context, TestInstancePreDestroyCallback::preDestroyTestInstance); } /** * Invoke {@link TestWatcher#testSuccessful testSuccessful()}, * {@link TestWatcher#testAborted testAborted()}, or * {@link TestWatcher#testFailed testFailed()} on each * registered {@link TestWatcher} according to the status of the supplied * {@link TestExecutionResult}, in reverse registration order. * * @since 5.4 */ @Override public void nodeFinished(JupiterEngineExecutionContext context, TestDescriptor descriptor, TestExecutionResult result) { ExtensionContext extensionContext = context.getExtensionContext(); TestExecutionResult.Status status = result.getStatus(); invokeTestWatchers(context, true, watcher -> { switch (status) { case SUCCESSFUL -> watcher.testSuccessful(extensionContext); case ABORTED -> watcher.testAborted(extensionContext, result.getThrowable().orElse(null)); case FAILED -> watcher.testFailed(extensionContext, result.getThrowable().orElse(null)); } }); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateExtensionContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.util.List; import java.util.Optional; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestInstances; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.LauncherStoreFacade; import org.junit.jupiter.engine.extension.ExtensionRegistry; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.support.hierarchical.Node; /** * @since 5.0 */ final class TestTemplateExtensionContext extends AbstractExtensionContext { private final @Nullable TestInstances testInstances; TestTemplateExtensionContext(ExtensionContext parent, EngineExecutionListener engineExecutionListener, TestTemplateTestDescriptor testDescriptor, JupiterConfiguration configuration, ExtensionRegistry extensionRegistry, LauncherStoreFacade launcherStoreFacade, @Nullable TestInstances testInstances) { super(parent, engineExecutionListener, testDescriptor, configuration, extensionRegistry, launcherStoreFacade); this.testInstances = testInstances; } @Override public Optional getElement() { return Optional.of(getTestDescriptor().getTestMethod()); } @Override public Optional> getTestClass() { return Optional.of(getTestDescriptor().getTestClass()); } @Override public List> getEnclosingTestClasses() { return getTestDescriptor().getEnclosingTestClasses(); } @Override public Optional getTestInstanceLifecycle() { return getParent().flatMap(ExtensionContext::getTestInstanceLifecycle); } @Override public Optional getTestInstance() { return getTestInstances().map(TestInstances::getInnermostInstance); } @Override public Optional getTestInstances() { return Optional.ofNullable(this.testInstances); } @Override public Optional getTestMethod() { return Optional.of(getTestDescriptor().getTestMethod()); } @Override public Optional getExecutionException() { return Optional.empty(); } @Override protected Node.ExecutionMode getPlatformExecutionMode() { return getTestDescriptor().getExecutionMode(); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static java.util.Collections.emptySet; import static java.util.Objects.requireNonNull; import static org.apiguardian.api.API.Status.INTERNAL; import java.lang.reflect.Method; import java.util.OptionalInt; import java.util.Set; import java.util.function.UnaryOperator; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.InvocationInterceptor; import org.junit.jupiter.api.extension.TestTemplateInvocationContext; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.ReflectiveInterceptorCall.VoidMethodInterceptorCall; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; import org.junit.jupiter.engine.extension.MutableExtensionRegistry; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.hierarchical.ExclusiveResource; /** * {@link TestDescriptor} for a {@link org.junit.jupiter.api.TestTemplate @TestTemplate} * invocation. * * @since 5.0 */ @API(status = INTERNAL, since = "5.0") public class TestTemplateInvocationTestDescriptor extends TestMethodTestDescriptor { public static final String SEGMENT_TYPE = "test-template-invocation"; private static final VoidMethodInterceptorCall interceptorCall = InvocationInterceptor::interceptTestTemplateMethod; private @Nullable TestTemplateInvocationContext invocationContext; private final int index; TestTemplateInvocationTestDescriptor(UniqueId uniqueId, Class testClass, Method templateMethod, TestTemplateInvocationContext invocationContext, int index, JupiterConfiguration configuration) { super(uniqueId, invocationContext.getDisplayName(index), testClass, templateMethod, configuration, interceptorCall); this.invocationContext = invocationContext; this.index = index; } // --- JupiterTestDescriptor ----------------------------------------------- @Override protected TestTemplateInvocationTestDescriptor withUniqueId(UnaryOperator uniqueIdTransformer) { return new TestTemplateInvocationTestDescriptor(uniqueIdTransformer.apply(getUniqueId()), getTestClass(), getTestMethod(), requiredInvocationContext(), this.index, this.configuration); } // --- TestDescriptor ------------------------------------------------------ @Override public Set getExclusiveResources() { // Resources are already collected and returned by the enclosing container return emptySet(); } @Override protected OptionalInt getLegacyReportingIndex() { return OptionalInt.of(index); } @Override protected MutableExtensionRegistry populateNewExtensionRegistry(JupiterEngineExecutionContext context) { MutableExtensionRegistry registry = super.populateNewExtensionRegistry(context); var invocationContext = requiredInvocationContext(); invocationContext.getAdditionalExtensions().forEach( extension -> registry.registerExtension(extension, invocationContext)); return registry; } @Override protected void prepareExtensionContext(ExtensionContext extensionContext) { requiredInvocationContext().prepareInvocation(extensionContext); } @Override public void after(JupiterEngineExecutionContext context) { // forget invocationContext so it can be garbage collected this.invocationContext = null; } private TestTemplateInvocationContext requiredInvocationContext() { return requireNonNull(this.invocationContext); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.jupiter.engine.descriptor.ExtensionUtils.populateNewExtensionRegistryFromExtendWithAnnotation; import java.lang.reflect.Method; import java.util.List; import java.util.function.Supplier; import java.util.function.UnaryOperator; import java.util.stream.Stream; import org.apiguardian.api.API; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestInstances; import org.junit.jupiter.api.extension.TestTemplateInvocationContext; import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; import org.junit.jupiter.engine.extension.MutableExtensionRegistry; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; /** * {@link TestDescriptor} for {@link org.junit.jupiter.api.TestTemplate @TestTemplate} * methods. * * @since 5.0 */ @API(status = INTERNAL, since = "5.0") public class TestTemplateTestDescriptor extends MethodBasedTestDescriptor implements Filterable { public static final String SEGMENT_TYPE = "test-template"; private final DynamicDescendantFilter dynamicDescendantFilter; public TestTemplateTestDescriptor(UniqueId uniqueId, Class testClass, Method templateMethod, Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration) { super(uniqueId, testClass, templateMethod, enclosingInstanceTypes, configuration); this.dynamicDescendantFilter = new DynamicDescendantFilter(); } private TestTemplateTestDescriptor(UniqueId uniqueId, String displayName, Class testClass, Method templateMethod, JupiterConfiguration configuration, DynamicDescendantFilter dynamicDescendantFilter) { super(uniqueId, displayName, testClass, templateMethod, configuration); this.dynamicDescendantFilter = dynamicDescendantFilter; } // --- JupiterTestDescriptor ----------------------------------------------- @Override protected TestTemplateTestDescriptor withUniqueId(UnaryOperator uniqueIdTransformer) { return new TestTemplateTestDescriptor(uniqueIdTransformer.apply(getUniqueId()), getDisplayName(), getTestClass(), getTestMethod(), this.configuration, this.dynamicDescendantFilter.copy(uniqueIdTransformer)); } // --- Filterable ---------------------------------------------------------- @Override public DynamicDescendantFilter getDynamicDescendantFilter() { return dynamicDescendantFilter; } // --- TestDescriptor ------------------------------------------------------ @Override public Type getType() { return Type.CONTAINER; } @Override public boolean mayRegisterTests() { return true; } // --- Node ---------------------------------------------------------------- @Override public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) { MutableExtensionRegistry registry = populateNewExtensionRegistryFromExtendWithAnnotation( context.getExtensionRegistry(), getTestMethod()); // The test instance should be properly maintained by the enclosing class's ExtensionContext. TestInstances testInstances = context.getExtensionContext().getTestInstances().orElse(null); ExtensionContext extensionContext = new TestTemplateExtensionContext(context.getExtensionContext(), context.getExecutionListener(), this, context.getConfiguration(), registry, context.getLauncherStoreFacade(), testInstances); // @formatter:off return context.extend() .withExtensionRegistry(registry) .withExtensionContext(extensionContext) .build(); // @formatter:on } @Override public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext context, DynamicTestExecutor dynamicTestExecutor) throws Exception { new TestTemplateExecutor().execute(context, dynamicTestExecutor); return context; } private class TestTemplateExecutor extends TemplateExecutor { TestTemplateExecutor() { super(TestTemplateTestDescriptor.this, TestTemplateInvocationContextProvider.class); } @Override boolean supports(TestTemplateInvocationContextProvider provider, ExtensionContext extensionContext) { return provider.supportsTestTemplate(extensionContext); } @Override protected String getNoRegisteredProviderErrorMessage() { return "You must register at least one %s that supports @%s method [%s]".formatted( TestTemplateInvocationContextProvider.class.getSimpleName(), TestTemplate.class.getSimpleName(), getTestMethod()); } @Override Stream provideContexts(TestTemplateInvocationContextProvider provider, ExtensionContext extensionContext) { return provider.provideTestTemplateInvocationContexts(extensionContext); } @Override boolean mayReturnZeroContexts(TestTemplateInvocationContextProvider provider, ExtensionContext extensionContext) { return provider.mayReturnZeroTestTemplateInvocationContexts(extensionContext); } @Override protected String getZeroContextsProvidedErrorMessage(TestTemplateInvocationContextProvider provider) { return """ Provider [%s] did not provide any invocation contexts, but was expected to do so. \ You may override mayReturnZeroTestTemplateInvocationContexts() to allow this.""".formatted( provider.getClass().getSimpleName()); } @Override UniqueId createInvocationUniqueId(UniqueId parentUniqueId, int index) { return parentUniqueId.append(TestTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#" + index); } @Override TestDescriptor createInvocationTestDescriptor(UniqueId uniqueId, TestTemplateInvocationContext invocationContext, int index) { return new TestTemplateInvocationTestDescriptor(uniqueId, getTestClass(), getTestMethod(), invocationContext, index, TestTemplateTestDescriptor.this.configuration); } } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/UniqueIdPrefixTransformer.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import java.util.List; import java.util.function.UnaryOperator; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.UniqueId; /** * @since 5.13 */ class UniqueIdPrefixTransformer implements UnaryOperator { private final UniqueId oldPrefix; private final UniqueId newPrefix; private final int oldPrefixLength; UniqueIdPrefixTransformer(UniqueId oldPrefix, UniqueId newPrefix) { this.oldPrefix = oldPrefix; this.newPrefix = newPrefix; this.oldPrefixLength = oldPrefix.getSegments().size(); } @Override public UniqueId apply(UniqueId uniqueId) { Preconditions.condition(uniqueId.hasPrefix(oldPrefix), () -> "Unique ID %s does not have the expected prefix %s".formatted(uniqueId, oldPrefix)); List oldSegments = uniqueId.getSegments(); List suffix = oldSegments.subList(oldPrefixLength, oldSegments.size()); UniqueId newValue = newPrefix; for (UniqueId.Segment segment : suffix) { newValue = newValue.append(segment); } return newValue; } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/Validatable.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.List; import org.apiguardian.api.API; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter; /** * Interface for descriptors that can be validated during discovery. * * @since 5.13 */ @API(status = INTERNAL, since = "5.13") public interface Validatable { /** * Validate the state of this descriptor and report any issues found to the * supplied {@link DiscoveryIssueReporter}. */ void validate(DiscoveryIssueReporter reporter); /** * Report and clear the given list of {@link DiscoveryIssue}s using the * supplied {@link DiscoveryIssueReporter}. */ static void reportAndClear(List issues, DiscoveryIssueReporter reporter) { issues.forEach(reporter::reportIssue); issues.clear(); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Test descriptors used within the JUnit Jupiter test engine. */ @NullMarked package org.junit.jupiter.engine.descriptor; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/AbstractAnnotatedDescriptorWrapper.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.discovery; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.util.List; import java.util.Optional; import org.junit.platform.commons.support.AnnotationSupport; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.TestDescriptor; /** * Abstract base class for wrappers for test descriptors based on annotated * elements. * * @since 5.8 */ abstract class AbstractAnnotatedDescriptorWrapper { private final TestDescriptor testDescriptor; private final E annotatedElement; AbstractAnnotatedDescriptorWrapper(TestDescriptor testDescriptor, E annotatedElement) { this.testDescriptor = testDescriptor; this.annotatedElement = annotatedElement; } E getAnnotatedElement() { return this.annotatedElement; } TestDescriptor getTestDescriptor() { return this.testDescriptor; } public final String getDisplayName() { return this.testDescriptor.getDisplayName(); } public final boolean isAnnotated(Class annotationType) { Preconditions.notNull(annotationType, "annotationType must not be null"); return AnnotationSupport.isAnnotated(getAnnotatedElement(), annotationType); } public final Optional findAnnotation(Class annotationType) { Preconditions.notNull(annotationType, "annotationType must not be null"); return AnnotationSupport.findAnnotation(getAnnotatedElement(), annotationType); } public final List findRepeatableAnnotations(Class annotationType) { Preconditions.notNull(annotationType, "annotationType must not be null"); return AnnotationSupport.findRepeatableAnnotations(getAnnotatedElement(), annotationType); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/AbstractOrderingVisitor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.discovery; import static java.util.Comparator.comparing; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toCollection; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.UnrecoverableExceptions; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter; /** * Abstract base class for {@linkplain TestDescriptor.Visitor visitors} that * order children nodes. * * @since 5.8 */ abstract class AbstractOrderingVisitor implements TestDescriptor.Visitor { private final DiscoveryIssueReporter issueReporter; AbstractOrderingVisitor(DiscoveryIssueReporter issueReporter) { this.issueReporter = issueReporter; } /** * @param the parent container type to search in for matching children */ @SuppressWarnings("unchecked") protected void doWithMatchingDescriptor(Class parentTestDescriptorType, TestDescriptor testDescriptor, Consumer action, Function errorMessageBuilder) { if (parentTestDescriptorType.isInstance(testDescriptor)) { PARENT parentTestDescriptor = (PARENT) testDescriptor; try { action.accept(parentTestDescriptor); } catch (Throwable t) { UnrecoverableExceptions.rethrowIfUnrecoverable(t); String message = errorMessageBuilder.apply(parentTestDescriptor); this.issueReporter.reportIssue(DiscoveryIssue.builder(Severity.ERROR, message) // .source(parentTestDescriptor.getSource()) // .cause(t)); } } } /** * @param the type of children (containers or tests) to order */ protected > void orderChildrenTestDescriptors( PARENT parentTestDescriptor, Class matchingChildrenType, Optional> validationAction, Function descriptorWrapperFactory, DescriptorWrapperOrderer descriptorWrapperOrderer) { Stream matchingChildren = parentTestDescriptor.getChildren()// .stream()// .filter(matchingChildrenType::isInstance)// .map(matchingChildrenType::cast); if (!descriptorWrapperOrderer.canOrderWrappers()) { validationAction.ifPresent(matchingChildren::forEach); return; } if (validationAction.isPresent()) { matchingChildren = matchingChildren.peek(validationAction.get()); } List matchingDescriptorWrappers = matchingChildren// .map(descriptorWrapperFactory)// .collect(toCollection(ArrayList::new)); // If there are no children to order, abort early. if (matchingDescriptorWrappers.isEmpty()) { return; } parentTestDescriptor.orderChildren(children -> { Stream nonMatchingTestDescriptors = children.stream()// .filter(childTestDescriptor -> !matchingChildrenType.isInstance(childTestDescriptor)); descriptorWrapperOrderer.orderWrappers(parentTestDescriptor, matchingDescriptorWrappers, message -> reportWarning(parentTestDescriptor, message)); Stream orderedTestDescriptors = matchingDescriptorWrappers.stream()// .map(AbstractAnnotatedDescriptorWrapper::getTestDescriptor); if (shouldNonMatchingDescriptorsComeBeforeOrderedOnes()) { return Stream.concat(nonMatchingTestDescriptors, orderedTestDescriptors)// .toList(); } else { return Stream.concat(orderedTestDescriptors, nonMatchingTestDescriptors)// .toList(); } }); } private void reportWarning(TestDescriptor parentTestDescriptor, String message) { issueReporter.reportIssue(DiscoveryIssue.builder(Severity.WARNING, message) // .source(parentTestDescriptor.getSource())); } protected abstract boolean shouldNonMatchingDescriptorsComeBeforeOrderedOnes(); /** * @param the wrapper type for the children to order */ protected static class DescriptorWrapperOrderer { private static final DescriptorWrapperOrderer NOOP = new DescriptorWrapperOrderer<>(null, null, (__, ___) -> "", (__, ___) -> ""); @SuppressWarnings("unchecked") static > DescriptorWrapperOrderer noop() { return (DescriptorWrapperOrderer) NOOP; } @Nullable private final ORDERER orderer; @Nullable private final OrderingAction orderingAction; private final MessageGenerator descriptorsAddedMessageGenerator; private final MessageGenerator descriptorsRemovedMessageGenerator; DescriptorWrapperOrderer(@Nullable ORDERER orderer, @Nullable OrderingAction orderingAction, MessageGenerator descriptorsAddedMessageGenerator, MessageGenerator descriptorsRemovedMessageGenerator) { this.orderer = orderer; this.orderingAction = orderingAction; this.descriptorsAddedMessageGenerator = descriptorsAddedMessageGenerator; this.descriptorsRemovedMessageGenerator = descriptorsRemovedMessageGenerator; } @Nullable ORDERER getOrderer() { return orderer; } private boolean canOrderWrappers() { return this.orderingAction != null; } private void orderWrappers(PARENT parentTestDescriptor, List wrappers, Consumer errorHandler) { List orderedWrappers = new ArrayList<>(wrappers); requireNonNull(this.orderingAction).order(parentTestDescriptor, orderedWrappers); Map distinctWrappersToIndex = distinctWrappersToIndex(orderedWrappers); int difference = orderedWrappers.size() - wrappers.size(); int distinctDifference = distinctWrappersToIndex.size() - wrappers.size(); if (difference > 0) { // difference >= distinctDifference reportDescriptorsAddedWarning(difference, errorHandler, parentTestDescriptor); } if (distinctDifference < 0) { // distinctDifference <= difference reportDescriptorsRemovedWarning(distinctDifference, errorHandler, parentTestDescriptor); } wrappers.sort(comparing(wrapper -> distinctWrappersToIndex.getOrDefault(wrapper, -1))); } private Map distinctWrappersToIndex(List wrappers) { Map toIndex = new HashMap<>(); for (int i = 0; i < wrappers.size(); i++) { // Avoid ClassCastException if a misbehaving ordering action added a non-WRAPPER Object wrapper = wrappers.get(i); if (!toIndex.containsKey(wrapper)) { toIndex.put(wrapper, i); } } return toIndex; } private void reportDescriptorsAddedWarning(int number, Consumer errorHandler, PARENT parentTestDescriptor) { errorHandler.accept(this.descriptorsAddedMessageGenerator.generateMessage(parentTestDescriptor, number)); } private void reportDescriptorsRemovedWarning(int number, Consumer errorHandler, PARENT parentTestDescriptor) { errorHandler.accept( this.descriptorsRemovedMessageGenerator.generateMessage(parentTestDescriptor, Math.abs(number))); } } @FunctionalInterface protected interface OrderingAction { void order(PARENT testDescriptor, List wrappers); } @FunctionalInterface protected interface MessageGenerator { String generateMessage(PARENT testDescriptor, int number); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassOrderingVisitor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.discovery; import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated; import java.util.Optional; import java.util.function.Consumer; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.ClassOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.TestClassOrder; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor; import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; import org.junit.jupiter.engine.descriptor.TestClassAware; import org.junit.platform.commons.support.AnnotationSupport; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.LruCache; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter.Condition; /** * @since 5.8 */ class ClassOrderingVisitor extends AbstractOrderingVisitor { private final LruCache> ordererCache = new LruCache<>( 10); private final JupiterConfiguration configuration; private final DescriptorWrapperOrderer globalOrderer; private final Condition noOrderAnnotation; ClassOrderingVisitor(JupiterConfiguration configuration, DiscoveryIssueReporter issueReporter) { super(issueReporter); this.configuration = configuration; this.globalOrderer = createGlobalOrderer(configuration); this.noOrderAnnotation = issueReporter.createReportingCondition( testDescriptor -> !isAnnotated(testDescriptor.getTestClass(), Order.class), testDescriptor -> { String message = """ Ineffective @Order annotation on class '%s'. \ It will not be applied because ClassOrderer.OrderAnnotation is not in use. \ Note that the annotation may be either directly present or meta-present on the class."""// .formatted(testDescriptor.getTestClass().getName()); return DiscoveryIssue.builder(Severity.INFO, message) // .source(testDescriptor.getSource()) // .build(); }); } @Override public void visit(TestDescriptor testDescriptor) { doWithMatchingDescriptor(JupiterEngineDescriptor.class, testDescriptor, this::orderTopLevelClasses, descriptor -> "Failed to order top-level classes"); doWithMatchingDescriptor(ClassBasedTestDescriptor.class, testDescriptor, this::orderNestedClasses, descriptor -> "Failed to order nested classes for " + descriptor.getTestClass()); } @Override protected boolean shouldNonMatchingDescriptorsComeBeforeOrderedOnes() { // Non-matching descriptors can only occur when ordering nested classes in which // case they contain only local test methods (for @Nested classes) which must be // executed before tests in @Nested test classes. So we add the test methods before // adding the @Nested test classes. return true; } private void orderTopLevelClasses(JupiterEngineDescriptor engineDescriptor) { orderChildrenTestDescriptors(// engineDescriptor, // ClassBasedTestDescriptor.class, // toValidationAction(globalOrderer.getOrderer()), // DefaultClassDescriptor::new, // globalOrderer); } private void orderNestedClasses(ClassBasedTestDescriptor descriptor) { var wrapperOrderer = createAndCacheClassLevelOrderer(descriptor); orderChildrenTestDescriptors(// descriptor, // ClassBasedTestDescriptor.class, // toValidationAction(wrapperOrderer.getOrderer()), // DefaultClassDescriptor::new, // wrapperOrderer); } private DescriptorWrapperOrderer createGlobalOrderer( JupiterConfiguration configuration) { ClassOrderer classOrderer = configuration.getDefaultTestClassOrderer().orElse(null); return classOrderer == null ? DescriptorWrapperOrderer.noop() : createDescriptorWrapperOrderer(classOrderer); } private DescriptorWrapperOrderer createAndCacheClassLevelOrderer( ClassBasedTestDescriptor classBasedTestDescriptor) { var orderer = createClassLevelOrderer(classBasedTestDescriptor); ordererCache.put(classBasedTestDescriptor, orderer); return orderer; } private DescriptorWrapperOrderer createClassLevelOrderer( ClassBasedTestDescriptor classBasedTestDescriptor) { return AnnotationSupport.findAnnotation(classBasedTestDescriptor.getTestClass(), TestClassOrder.class)// .map(TestClassOrder::value)// .map(this::createDescriptorWrapperOrderer)// .orElseGet(() -> { Object parent = classBasedTestDescriptor.getParent().orElse(null); if (parent instanceof ClassBasedTestDescriptor parentClassTestDescriptor) { var cacheEntry = ordererCache.get(parentClassTestDescriptor); return cacheEntry != null ? cacheEntry : createClassLevelOrderer(parentClassTestDescriptor); } return globalOrderer; }); } private DescriptorWrapperOrderer createDescriptorWrapperOrderer( Class ordererClass) { if (ordererClass == ClassOrderer.Default.class) { return globalOrderer; } return createDescriptorWrapperOrderer(ReflectionSupport.newInstance(ordererClass)); } private DescriptorWrapperOrderer createDescriptorWrapperOrderer( ClassOrderer classOrderer) { OrderingAction orderingAction = (__, classDescriptors) -> classOrderer.orderClasses( new DefaultClassOrdererContext(classDescriptors, this.configuration)); MessageGenerator descriptorsAddedMessageGenerator = (parent, number) -> "ClassOrderer [%s] added %d %s which will be ignored.".formatted( classOrderer.getClass().getName(), number, describeClassDescriptors(parent)); MessageGenerator descriptorsRemovedMessageGenerator = (parent, number) -> "ClassOrderer [%s] removed %d %s which will be retained with arbitrary ordering.".formatted( classOrderer.getClass().getName(), number, describeClassDescriptors(parent)); return new DescriptorWrapperOrderer<>(classOrderer, orderingAction, descriptorsAddedMessageGenerator, descriptorsRemovedMessageGenerator); } private static String describeClassDescriptors(TestDescriptor parent) { return parent instanceof TestClassAware testClassAware // ? "nested ClassDescriptor(s) for test class [%s]".formatted(testClassAware.getTestClass().getName()) // : "top-level ClassDescriptor(s)"; } private Optional> toValidationAction(@Nullable ClassOrderer orderer) { if (orderer instanceof ClassOrderer.OrderAnnotation) { return Optional.empty(); } return Optional.of(noOrderAnnotation::check); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.discovery; import static java.util.Collections.emptyList; import static java.util.function.Predicate.isEqual; import static java.util.stream.Collectors.toCollection; import static java.util.stream.Collectors.toSet; import static org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor.getEnclosingTestClasses; import static org.junit.jupiter.engine.discovery.predicates.TestClassPredicates.NestedClassInvalidityReason.NOT_INNER; import static org.junit.platform.commons.support.HierarchyTraversalMode.TOP_DOWN; import static org.junit.platform.commons.support.ReflectionSupport.findMethods; import static org.junit.platform.commons.util.FunctionUtils.where; import static org.junit.platform.commons.util.ReflectionUtils.isInnerClass; import static org.junit.platform.commons.util.ReflectionUtils.isNotAbstract; import static org.junit.platform.commons.util.ReflectionUtils.streamNestedClasses; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.unresolved; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.function.BiFunction; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Stream; import org.junit.jupiter.api.extension.ClassTemplateInvocationContext; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor; import org.junit.jupiter.engine.descriptor.ClassTemplateInvocationTestDescriptor; import org.junit.jupiter.engine.descriptor.ClassTemplateTestDescriptor; import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; import org.junit.jupiter.engine.descriptor.Filterable; import org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor; import org.junit.jupiter.engine.descriptor.TestClassAware; import org.junit.jupiter.engine.discovery.predicates.TestClassPredicates; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.commons.util.ReflectionUtils.CycleErrorHandling; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.discovery.ClassSelector; import org.junit.platform.engine.discovery.DiscoverySelectors; import org.junit.platform.engine.discovery.IterationSelector; import org.junit.platform.engine.discovery.NestedClassSelector; import org.junit.platform.engine.discovery.UniqueIdSelector; import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter; import org.junit.platform.engine.support.discovery.SelectorResolver; /** * @since 5.5 */ class ClassSelectorResolver implements SelectorResolver { private final Predicate classNameFilter; private final JupiterConfiguration configuration; private final TestClassPredicates predicates; private final DiscoveryIssueReporter issueReporter; ClassSelectorResolver(Predicate classNameFilter, JupiterConfiguration configuration, DiscoveryIssueReporter issueReporter) { this.classNameFilter = classNameFilter; this.configuration = configuration; this.predicates = new TestClassPredicates(issueReporter); this.issueReporter = issueReporter; } @Override public Resolution resolve(ClassSelector selector, Context context) { Class testClass = selector.getJavaClass(); if (this.predicates.isAnnotatedWithNested.test(testClass)) { // Class name filter is not applied to nested test classes var invalidityReason = this.predicates.validateNestedTestClass(testClass); if (invalidityReason == null) { return toResolution( context.addToParent(() -> DiscoverySelectors.selectClass(testClass.getEnclosingClass()), parent -> Optional.of(newMemberClassTestDescriptor(parent, testClass)))); } if (invalidityReason == NOT_INNER) { return resolveStandaloneTestClass(context, testClass); } return unresolved(); } return resolveStandaloneTestClass(context, testClass); } private Resolution resolveStandaloneTestClass(Context context, Class testClass) { if (isAcceptedStandaloneTestClass(testClass)) { return toResolution( context.addToParent(parent -> Optional.of(newStandaloneClassTestDescriptor(parent, testClass)))); } return unresolved(); } private boolean isAcceptedStandaloneTestClass(Class testClass) { return this.classNameFilter.test(testClass.getName()) // && this.predicates.looksLikeIntendedTestClass(testClass) // && this.predicates.isValidStandaloneTestClass(testClass); } @Override public Resolution resolve(NestedClassSelector selector, Context context) { Class nestedClass = selector.getNestedClass(); if (this.predicates.isAnnotatedWithNested.test(nestedClass)) { if (this.predicates.isValidNestedTestClass(nestedClass)) { return toResolution(context.addToParent(() -> selectClass(selector.getEnclosingClasses()), parent -> Optional.of(newMemberClassTestDescriptor(parent, nestedClass)))); } } else if (isInnerClass(nestedClass) && isNotAbstract(nestedClass) && predicates.looksLikeIntendedTestClass(nestedClass)) { String message = "Inner class '%s' looks like it was intended to be a test class but will not be executed. It must be static or annotated with @Nested.".formatted( nestedClass.getName()); issueReporter.reportIssue(DiscoveryIssue.builder(Severity.WARNING, message) // .source(ClassSource.from(nestedClass))); } return unresolved(); } @Override public Resolution resolve(UniqueIdSelector selector, Context context) { UniqueId uniqueId = selector.getUniqueId(); UniqueId.Segment lastSegment = uniqueId.getLastSegment(); return switch (lastSegment.getType()) { case ClassTestDescriptor.SEGMENT_TYPE -> // resolveStandaloneClassUniqueId(context, lastSegment, __ -> true, this::newClassTestDescriptor); case ClassTemplateTestDescriptor.STANDALONE_CLASS_SEGMENT_TYPE -> // resolveStandaloneClassUniqueId(context, lastSegment, this.predicates.isAnnotatedWithClassTemplate, this::newClassTemplateTestDescriptor); case NestedClassTestDescriptor.SEGMENT_TYPE -> // resolveNestedClassUniqueId(context, uniqueId, __ -> true, this::newNestedClassTestDescriptor); case ClassTemplateTestDescriptor.NESTED_CLASS_SEGMENT_TYPE -> // resolveNestedClassUniqueId(context, uniqueId, this.predicates.isAnnotatedWithClassTemplate, this::newNestedClassTemplateTestDescriptor); case ClassTemplateInvocationTestDescriptor.SEGMENT_TYPE -> { Optional testDescriptor = context.addToParent( () -> selectUniqueId(uniqueId.removeLastSegment()), parent -> { int index = Integer.parseInt(lastSegment.getValue().substring(1)); return Optional.of(newDummyClassTemplateInvocationTestDescriptor(parent, index)); }); yield toInvocationMatch(testDescriptor) // .map(Resolution::match) // .orElse(unresolved()); } default -> unresolved(); }; } @Override public Resolution resolve(IterationSelector selector, Context context) { DiscoverySelector parentSelector = selector.getParentSelector(); if (parentSelector instanceof ClassSelector classSelector && this.predicates.isAnnotatedWithClassTemplate.test(classSelector.getJavaClass())) { return resolveIterations(selector, context); } if (parentSelector instanceof NestedClassSelector nestedClassSelector && this.predicates.isAnnotatedWithClassTemplate.test(nestedClassSelector.getNestedClass())) { return resolveIterations(selector, context); } return unresolved(); } private Resolution resolveIterations(IterationSelector selector, Context context) { DiscoverySelector parentSelector = selector.getParentSelector(); Set matches = selector.getIterationIndices().stream() // .map(index -> context.addToParent(() -> parentSelector, parent -> Optional.of(newDummyClassTemplateInvocationTestDescriptor(parent, index + 1)))) // .map(this::toInvocationMatch) // .flatMap(Optional::stream) // .collect(toSet()); return matches.isEmpty() ? unresolved() : Resolution.matches(matches); } private Resolution resolveStandaloneClassUniqueId(Context context, UniqueId.Segment lastSegment, Predicate> condition, BiFunction, ClassBasedTestDescriptor> factory) { String className = lastSegment.getValue(); return ReflectionSupport.tryToLoadClass(className).toOptional() // .filter(this.predicates::isValidStandaloneTestClass) // .filter(condition) // .map(testClass -> toResolution( context.addToParent(parent -> Optional.of(factory.apply(parent, testClass))))) // .orElse(unresolved()); } private Resolution resolveNestedClassUniqueId(Context context, UniqueId uniqueId, Predicate> condition, BiFunction, ClassBasedTestDescriptor> factory) { String simpleClassName = uniqueId.getLastSegment().getValue(); return toResolution(context.addToParent(() -> selectUniqueId(uniqueId.removeLastSegment()), parent -> { Class parentTestClass = ((TestClassAware) parent).getTestClass(); return ReflectionSupport.findNestedClasses(parentTestClass, this.predicates.isAnnotatedWithNestedAndValid.and( where(Class::getSimpleName, isEqual(simpleClassName)))).stream() // .findFirst() // .filter(condition) // .map(testClass -> factory.apply(parent, testClass)); })); } private ClassTemplateInvocationTestDescriptor newDummyClassTemplateInvocationTestDescriptor(TestDescriptor parent, int index) { UniqueId uniqueId = parent.getUniqueId().append(ClassTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#" + index); return new ClassTemplateInvocationTestDescriptor(uniqueId, (ClassTemplateTestDescriptor) parent, DummyClassTemplateInvocationContext.INSTANCE, index, parent.getSource().orElse(null), configuration); } private ClassBasedTestDescriptor newStandaloneClassTestDescriptor(TestDescriptor parent, Class testClass) { return this.predicates.isAnnotatedWithClassTemplate.test(testClass) // ? newClassTemplateTestDescriptor(parent, testClass) // : newClassTestDescriptor(parent, testClass); } private ClassTemplateTestDescriptor newClassTemplateTestDescriptor(TestDescriptor parent, Class testClass) { return newClassTemplateTestDescriptor(parent, ClassTemplateTestDescriptor.STANDALONE_CLASS_SEGMENT_TYPE, newClassTestDescriptor(parent, testClass)); } private ClassTestDescriptor newClassTestDescriptor(TestDescriptor parent, Class testClass) { return new ClassTestDescriptor( parent.getUniqueId().append(ClassTestDescriptor.SEGMENT_TYPE, testClass.getName()), testClass, configuration); } private ClassBasedTestDescriptor newMemberClassTestDescriptor(TestDescriptor parent, Class testClass) { return this.predicates.isAnnotatedWithClassTemplate.test(testClass) // ? newNestedClassTemplateTestDescriptor(parent, testClass) // : newNestedClassTestDescriptor(parent, testClass); } private ClassTemplateTestDescriptor newNestedClassTemplateTestDescriptor(TestDescriptor parent, Class testClass) { return newClassTemplateTestDescriptor(parent, ClassTemplateTestDescriptor.NESTED_CLASS_SEGMENT_TYPE, newNestedClassTestDescriptor(parent, testClass)); } private NestedClassTestDescriptor newNestedClassTestDescriptor(TestDescriptor parent, Class testClass) { UniqueId uniqueId = parent.getUniqueId().append(NestedClassTestDescriptor.SEGMENT_TYPE, testClass.getSimpleName()); return new NestedClassTestDescriptor(uniqueId, testClass, () -> getEnclosingTestClasses(parent), configuration); } private ClassTemplateTestDescriptor newClassTemplateTestDescriptor(TestDescriptor parent, String segmentType, ClassBasedTestDescriptor delegate) { delegate.setParent(parent); String segmentValue = delegate.getUniqueId().getLastSegment().getValue(); UniqueId uniqueId = parent.getUniqueId().append(segmentType, segmentValue); return new ClassTemplateTestDescriptor(uniqueId, delegate); } private Optional toInvocationMatch(Optional testDescriptor) { return testDescriptor // .map(it -> Match.exact(it, expansionCallback(it, () -> it.getParent().map(parent -> getTestClasses((TestClassAware) parent)).orElse(emptyList())))); } private Resolution toResolution(Optional testDescriptor) { return testDescriptor // .map(it -> Resolution.match(Match.exact(it, expansionCallback(it)))) // .orElse(unresolved()); } private Supplier> expansionCallback(ClassBasedTestDescriptor testDescriptor) { return expansionCallback(testDescriptor, () -> getTestClasses(testDescriptor)); } private static List> getTestClasses(TestClassAware testDescriptor) { List> testClasses = new ArrayList<>(testDescriptor.getEnclosingTestClasses()); testClasses.add(testDescriptor.getTestClass()); return testClasses; } private Supplier> expansionCallback(TestDescriptor testDescriptor, Supplier>> testClassesSupplier) { return () -> { if (testDescriptor instanceof Filterable filterable) { filterable.getDynamicDescendantFilter().allowAll(); } List> testClasses = testClassesSupplier.get(); Class testClass = testClasses.get(testClasses.size() - 1); Stream methods = findMethods(testClass, this.predicates.isTestOrTestFactoryOrTestTemplateMethod, TOP_DOWN).stream() // .map(method -> selectMethod(testClasses, method)); Stream> annotatedNestedClasses = streamNestedClasses(testClass, this.predicates.isAnnotatedWithNested); Stream> notAnnotatedInnerClasses = streamNestedClasses(testClass, this.predicates.isAnnotatedWithNested.negate().and(ReflectionUtils::isInnerClass), CycleErrorHandling.ABORT_VISIT); var nestedClasses = Stream.concat(annotatedNestedClasses, notAnnotatedInnerClasses) // .map(nestedClass -> DiscoverySelectors.selectNestedClass(testClasses, nestedClass)); return Stream.concat(methods, nestedClasses).collect( toCollection((Supplier>) LinkedHashSet::new)); }; } private DiscoverySelector selectClass(List> classes) { if (classes.size() == 1) { return DiscoverySelectors.selectClass(classes.get(0)); } int lastIndex = classes.size() - 1; return DiscoverySelectors.selectNestedClass(classes.subList(0, lastIndex), classes.get(lastIndex)); } private DiscoverySelector selectMethod(List> classes, Method method) { return new DeclaredMethodSelector(classes, method); } static class DummyClassTemplateInvocationContext implements ClassTemplateInvocationContext { private static final DummyClassTemplateInvocationContext INSTANCE = new DummyClassTemplateInvocationContext(); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DeclaredMethodSelector.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.discovery; import java.lang.reflect.Method; import java.util.List; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.discovery.MethodSelector; /** * Jupiter-specific selector for methods, potentially in nested classes. * *

The important difference to {@link MethodSelector} is that this selector's * {@link #equals(Object)} method takes into account the selected method's * {@linkplain Method#getDeclaringClass() declaring class} to support cases * where a package-private method is declared in a super class in a different * package and a method with the same signature is declared in a subclass. In * that case both methods should be discovered because the one declared in the * subclass does not override the one in the super class. * * @since 5.14.1 */ record DeclaredMethodSelector(List> testClasses, Method method) implements DiscoverySelector { DeclaredMethodSelector { Preconditions.notEmpty(testClasses, "testClasses must not be empty"); Preconditions.containsNoNullElements(testClasses, "testClasses must not contain null elements"); Preconditions.notNull(method, "method must not be null"); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultClassDescriptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.discovery; import org.junit.jupiter.api.ClassDescriptor; import org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor; import org.junit.platform.commons.util.ToStringBuilder; /** * Default implementation of {@link ClassDescriptor}, backed by * a {@link ClassBasedTestDescriptor}. * * @since 5.8 */ class DefaultClassDescriptor extends AbstractAnnotatedDescriptorWrapper> implements ClassDescriptor { DefaultClassDescriptor(ClassBasedTestDescriptor testDescriptor) { super(testDescriptor, testDescriptor.getTestClass()); } @Override public final Class getTestClass() { return getAnnotatedElement(); } @Override public String toString() { return new ToStringBuilder(this).append("class", getTestClass().toGenericString()).toString(); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultClassOrdererContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.discovery; import java.util.List; import java.util.Optional; import org.junit.jupiter.api.ClassDescriptor; import org.junit.jupiter.api.ClassOrdererContext; import org.junit.jupiter.engine.config.JupiterConfiguration; /** * Default implementation of {@link ClassOrdererContext}. * * @since 5.8 */ class DefaultClassOrdererContext implements ClassOrdererContext { private final List classDescriptors; private final JupiterConfiguration configuration; DefaultClassOrdererContext(List classDescriptors, JupiterConfiguration configuration) { this.classDescriptors = classDescriptors; this.configuration = configuration; } @Override public List getClassDescriptors() { return this.classDescriptors; } @Override public Optional getConfigurationParameter(String key) { return this.configuration.getRawConfigurationParameter(key); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultMethodDescriptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.discovery; import java.lang.reflect.Method; import org.junit.jupiter.api.MethodDescriptor; import org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor; import org.junit.platform.commons.util.ToStringBuilder; /** * Default implementation of {@link MethodDescriptor}, backed by * a {@link MethodBasedTestDescriptor}. * * @since 5.4 */ class DefaultMethodDescriptor extends AbstractAnnotatedDescriptorWrapper implements MethodDescriptor { DefaultMethodDescriptor(MethodBasedTestDescriptor testDescriptor) { super(testDescriptor, testDescriptor.getTestMethod()); } @Override public final Method getMethod() { return getAnnotatedElement(); } @Override public String toString() { return new ToStringBuilder(this).append("method", getMethod().toGenericString()).toString(); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultMethodOrdererContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.discovery; import java.util.List; import java.util.Optional; import org.junit.jupiter.api.MethodDescriptor; import org.junit.jupiter.api.MethodOrdererContext; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.platform.commons.util.ToStringBuilder; /** * Default implementation of {@link MethodOrdererContext}. * * @since 5.4 */ class DefaultMethodOrdererContext implements MethodOrdererContext { private final Class testClass; private final List methodDescriptors; private final JupiterConfiguration configuration; DefaultMethodOrdererContext(Class testClass, List methodDescriptors, JupiterConfiguration configuration) { this.testClass = testClass; this.methodDescriptors = methodDescriptors; this.configuration = configuration; } @Override public final Class getTestClass() { return this.testClass; } @Override public List getMethodDescriptors() { return this.methodDescriptors; } @Override public Optional getConfigurationParameter(String key) { return this.configuration.getRawConfigurationParameter(key); } @Override public String toString() { return new ToStringBuilder(this).append("methodDescriptors", methodDescriptors).toString(); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolver.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.discovery; import static org.apiguardian.api.API.Status.INTERNAL; import org.apiguardian.api.API; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; import org.junit.jupiter.engine.descriptor.Validatable; import org.junit.jupiter.engine.discovery.predicates.TestClassPredicates; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter; import org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver; import org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver.InitializationContext; /** * {@code DiscoverySelectorResolver} resolves {@link TestDescriptor TestDescriptors} * for containers and tests selected by {@link org.junit.platform.engine.DiscoverySelector * DiscoverySelectors}, with the help of an {@link EngineDiscoveryRequestResolver}. * *

This is an internal utility which is only {@code public} in order to provide * the {@link org.junit.jupiter.engine.JupiterTestEngine JupiterTestEngine} access * to the functionality of the {@code discovery} package. * * @since 5.0 */ @API(status = INTERNAL, since = "5.0") public class DiscoverySelectorResolver { private static final EngineDiscoveryRequestResolver resolver = EngineDiscoveryRequestResolver. builder() // .addClassContainerSelectorResolverWithContext( ctx -> new TestClassPredicates(ctx.getIssueReporter()).looksLikeNestedOrStandaloneTestClass) // .addSelectorResolver(ctx -> new ClassSelectorResolver(ctx.getClassNameFilter(), getConfiguration(ctx), ctx.getIssueReporter())) // .addSelectorResolver(ctx -> new MethodSelectorResolver(getConfiguration(ctx), ctx.getIssueReporter())) // .addTestDescriptorVisitor(ctx -> TestDescriptor.Visitor.composite( // new ClassOrderingVisitor(getConfiguration(ctx), ctx.getIssueReporter()), // new MethodOrderingVisitor(getConfiguration(ctx), ctx.getIssueReporter()), // descriptor -> { if (descriptor instanceof Validatable validatable) { validatable.validate(ctx.getIssueReporter()); } })) // .build(); private static JupiterConfiguration getConfiguration(InitializationContext context) { return context.getEngineDescriptor().getConfiguration(); } public static void resolveSelectors(EngineDiscoveryRequest request, JupiterEngineDescriptor engineDescriptor, DiscoveryIssueReporter issueReporter) { resolver.resolve(request, engineDescriptor, issueReporter); } private DiscoverySelectorResolver() { } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodOrderingVisitor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.discovery; import static java.util.Comparator.comparing; import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated; import java.util.List; import java.util.Optional; import java.util.function.Consumer; import java.util.function.UnaryOperator; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor; import org.junit.jupiter.engine.descriptor.JupiterTestDescriptor; import org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor; import org.junit.platform.commons.support.AnnotationSupport; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.LruCache; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter.Condition; /** * @since 5.5 */ class MethodOrderingVisitor extends AbstractOrderingVisitor { private final LruCache> ordererCache = new LruCache<>( 10); private final JupiterConfiguration configuration; private final DescriptorWrapperOrderer globalOrderer; private final Condition noOrderAnnotation; // Not a static field to avoid initialization at build time for GraalVM private final UnaryOperator> methodsBeforeNestedClassesOrderer; MethodOrderingVisitor(JupiterConfiguration configuration, DiscoveryIssueReporter issueReporter) { super(issueReporter); this.configuration = configuration; this.globalOrderer = createGlobalOrderer(configuration); this.noOrderAnnotation = issueReporter.createReportingCondition( testDescriptor -> !isAnnotated(testDescriptor.getTestMethod(), Order.class), testDescriptor -> { String message = """ Ineffective @Order annotation on method '%s'. \ It will not be applied because MethodOrderer.OrderAnnotation is not in use. \ Note that the annotation may be either directly present or meta-present on the method."""// .formatted(testDescriptor.getTestMethod().toGenericString()); return DiscoveryIssue.builder(Severity.INFO, message) // .source(testDescriptor.getSource()) // .build(); }); this.methodsBeforeNestedClassesOrderer = createMethodsBeforeNestedClassesOrderer(); } @Override public void visit(TestDescriptor testDescriptor) { doWithMatchingDescriptor(ClassBasedTestDescriptor.class, testDescriptor, this::orderContainedMethods, descriptor -> "Failed to order methods for " + descriptor.getTestClass()); } @Override protected boolean shouldNonMatchingDescriptorsComeBeforeOrderedOnes() { // Non-matching descriptors can only contain @Nested test classes which should be // added after local test methods. return false; } private void orderContainedMethods(ClassBasedTestDescriptor descriptor) { var wrapperOrderer = createAndCacheClassLevelOrderer(descriptor); var methodOrderer = wrapperOrderer.getOrderer(); orderChildrenTestDescriptors(descriptor, // MethodBasedTestDescriptor.class, // toValidationAction(methodOrderer), // DefaultMethodDescriptor::new, // wrapperOrderer); if (methodOrderer == null) { // If there is an orderer, this is ensured by the call above descriptor.orderChildren(methodsBeforeNestedClassesOrderer); } else { // Note: MethodOrderer#getDefaultExecutionMode() is guaranteed // to be invoked after MethodOrderer#orderMethods(). methodOrderer.getDefaultExecutionMode() // .map(JupiterTestDescriptor::toExecutionMode) // .ifPresent(descriptor::setDefaultChildExecutionMode); } } private DescriptorWrapperOrderer createGlobalOrderer( JupiterConfiguration configuration) { MethodOrderer methodOrderer = configuration.getDefaultTestMethodOrderer().orElse(null); return methodOrderer == null ? DescriptorWrapperOrderer.noop() : createDescriptorWrapperOrderer(methodOrderer); } private DescriptorWrapperOrderer createAndCacheClassLevelOrderer( ClassBasedTestDescriptor classBasedTestDescriptor) { var orderer = createClassLevelOrderer(classBasedTestDescriptor); ordererCache.put(classBasedTestDescriptor, orderer); return orderer; } private DescriptorWrapperOrderer createClassLevelOrderer( ClassBasedTestDescriptor classBasedTestDescriptor) { return AnnotationSupport.findAnnotation(classBasedTestDescriptor.getTestClass(), TestMethodOrder.class)// .map(TestMethodOrder::value)// .map(this::createDescriptorWrapperOrderer)// .orElseGet(() -> { Object parent = classBasedTestDescriptor.getParent().orElse(null); if (parent instanceof ClassBasedTestDescriptor parentClassTestDescriptor) { var cacheEntry = ordererCache.get(parentClassTestDescriptor); return cacheEntry != null ? cacheEntry : createClassLevelOrderer(parentClassTestDescriptor); } return globalOrderer; }); } private DescriptorWrapperOrderer createDescriptorWrapperOrderer( Class ordererClass) { if (ordererClass == MethodOrderer.Default.class) { return globalOrderer; } return createDescriptorWrapperOrderer(ReflectionSupport.newInstance(ordererClass)); } private DescriptorWrapperOrderer createDescriptorWrapperOrderer( MethodOrderer methodOrderer) { OrderingAction orderingAction = (parent, methodDescriptors) -> methodOrderer.orderMethods( new DefaultMethodOrdererContext(parent.getTestClass(), methodDescriptors, this.configuration)); MessageGenerator descriptorsAddedMessageGenerator = (parent, number) -> "MethodOrderer [%s] added %d MethodDescriptor(s) for test class [%s] which will be ignored.".formatted( methodOrderer.getClass().getName(), number, parent.getTestClass().getName()); MessageGenerator descriptorsRemovedMessageGenerator = (parent, number) -> "MethodOrderer [%s] removed %d MethodDescriptor(s) for test class [%s] which will be retained with arbitrary ordering.".formatted( methodOrderer.getClass().getName(), number, parent.getTestClass().getName()); return new DescriptorWrapperOrderer<>(methodOrderer, orderingAction, descriptorsAddedMessageGenerator, descriptorsRemovedMessageGenerator); } private Optional> toValidationAction(@Nullable MethodOrderer methodOrderer) { if (methodOrderer instanceof MethodOrderer.OrderAnnotation) { return Optional.empty(); } return Optional.of(noOrderAnnotation::check); } private static UnaryOperator> createMethodsBeforeNestedClassesOrderer() { var methodsFirst = comparing(MethodBasedTestDescriptor.class::isInstance).reversed(); return children -> { children.sort(methodsFirst); return children; }; } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSegmentResolver.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.discovery; import static org.junit.platform.commons.util.ReflectionUtils.isDeclaredInSamePackage; import static org.junit.platform.commons.util.ReflectionUtils.isPackagePrivate; import java.lang.reflect.Method; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.ClassUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ReflectionUtils; /** * @since 5.0 */ class MethodSegmentResolver { // Pattern: [declaringClassName#]methodName(comma-separated list of parameter type names) private static final Pattern METHOD_PATTERN = Pattern.compile( "(?:(?.+)#)?(?.+)\\((?.*)\\)"); /** * If the {@code method} is package-private and declared a class in a * different package than {@code testClass}, the declaring class name is * included in the method's unique ID segment. Otherwise, it only * consists of the method name and its parameter types. */ String formatMethodSpecPart(Method method, Class testClass) { var parameterTypes = ClassUtils.nullSafeToString(method.getParameterTypes()); if (isPackagePrivate(method) && !isDeclaredInSamePackage(method.getDeclaringClass(), testClass)) { return "%s#%s(%s)".formatted(method.getDeclaringClass().getName(), method.getName(), parameterTypes); } return "%s(%s)".formatted(method.getName(), parameterTypes); } Optional findMethod(String methodSpecPart, Class testClass) { Matcher matcher = METHOD_PATTERN.matcher(methodSpecPart); Preconditions.condition(matcher.matches(), () -> "Method [%s] does not match pattern [%s]".formatted(methodSpecPart, METHOD_PATTERN)); Class targetClass = testClass; String declaringClass = matcher.group("declaringClass"); if (declaringClass != null) { targetClass = ReflectionUtils.tryToLoadClass(declaringClass).getNonNullOrThrow( cause -> new PreconditionViolationException( "Could not load declaring class with name: " + declaringClass, cause)); } String methodName = matcher.group("method"); String parameterTypeNames = matcher.group("parameters"); return ReflectionSupport.findMethod(targetClass, methodName, parameterTypeNames); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.discovery; import static java.util.Collections.emptyList; import static java.util.Collections.emptySet; import static java.util.stream.Collectors.toSet; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.matches; import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.unresolved; import java.lang.reflect.Method; import java.util.Arrays; import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.function.BiFunction; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Stream; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.descriptor.Filterable; import org.junit.jupiter.engine.descriptor.TestClassAware; import org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor; import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor; import org.junit.jupiter.engine.descriptor.TestTemplateInvocationTestDescriptor; import org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor; import org.junit.jupiter.engine.discovery.predicates.IsTestFactoryMethod; import org.junit.jupiter.engine.discovery.predicates.IsTestMethod; import org.junit.jupiter.engine.discovery.predicates.IsTestTemplateMethod; import org.junit.jupiter.engine.discovery.predicates.TestClassPredicates; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.discovery.DiscoverySelectors; import org.junit.platform.engine.discovery.IterationSelector; import org.junit.platform.engine.discovery.MethodSelector; import org.junit.platform.engine.discovery.NestedMethodSelector; import org.junit.platform.engine.discovery.UniqueIdSelector; import org.junit.platform.engine.support.descriptor.MethodSource; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter; import org.junit.platform.engine.support.discovery.SelectorResolver; /** * @since 5.5 */ class MethodSelectorResolver implements SelectorResolver { private static final MethodSegmentResolver methodSegmentResolver = new MethodSegmentResolver(); private final Predicate> testClassPredicate; private final JupiterConfiguration configuration; private final DiscoveryIssueReporter issueReporter; private final List methodTypes; MethodSelectorResolver(JupiterConfiguration configuration, DiscoveryIssueReporter issueReporter) { this.configuration = configuration; this.issueReporter = issueReporter; this.methodTypes = MethodType.allPossibilities(issueReporter); this.testClassPredicate = new TestClassPredicates(issueReporter).looksLikeNestedOrStandaloneTestClass; } @Override public Resolution resolve(MethodSelector selector, Context context) { return resolve(context, emptyList(), selector.getJavaClass(), selector::getJavaMethod, Match::exact); } @Override public Resolution resolve(NestedMethodSelector selector, Context context) { return resolve(context, selector.getEnclosingClasses(), selector.getNestedClass(), selector::getMethod, Match::exact); } @Override public Resolution resolve(DiscoverySelector selector, Context context) { if (selector instanceof DeclaredMethodSelector methodSelector) { var testClasses = methodSelector.testClasses(); if (testClasses.size() == 1) { return resolve(context, emptyList(), testClasses.get(0), methodSelector::method, Match::exact); } int lastIndex = testClasses.size() - 1; return resolve(context, testClasses.subList(0, lastIndex), testClasses.get(lastIndex), methodSelector::method, Match::exact); } return unresolved(); } private Resolution resolve(Context context, List> enclosingClasses, Class testClass, Supplier methodSupplier, BiFunction>, Match> matchFactory) { if (!testClassPredicate.test(testClass)) { return unresolved(); } Method method = methodSupplier.get(); // @formatter:off Set matches = methodTypes.stream() .map(methodType -> methodType.resolve(enclosingClasses, testClass, method, context, configuration)) .flatMap(Optional::stream) .map(testDescriptor -> matchFactory.apply(testDescriptor, expansionCallback(testDescriptor))) .collect(toSet()); // @formatter:on if (matches.size() > 1) { Stream testDescriptors = matches.stream().map(Match::getTestDescriptor); String message = """ Possible configuration error: method [%s] resulted in multiple TestDescriptors %s. \ This is typically the result of annotating a method with multiple competing annotations \ such as @Test, @RepeatedTest, @ParameterizedTest, @TestFactory, etc.""".formatted( method.toGenericString(), testDescriptors.map(d -> d.getClass().getName()).toList()); issueReporter.reportIssue( DiscoveryIssue.builder(Severity.WARNING, message).source(MethodSource.from(method))); } return matches.isEmpty() ? unresolved() : matches(matches); } @Override public Resolution resolve(UniqueIdSelector selector, Context context) { UniqueId uniqueId = selector.getUniqueId(); // @formatter:off return methodTypes.stream() .map(methodType -> methodType.resolveUniqueIdIntoTestDescriptor(uniqueId, context, configuration)) .flatMap(Optional::stream) .map(testDescriptor -> { boolean exactMatch = uniqueId.equals(testDescriptor.getUniqueId()); if (testDescriptor instanceof Filterable filterable) { if (exactMatch) { filterable.getDynamicDescendantFilter().allowAll(); } else { filterable.getDynamicDescendantFilter().allowUniqueIdPrefix(uniqueId); } } return Resolution.match(exactMatch ? Match.exact(testDescriptor) : Match.partial(testDescriptor, expansionCallback(testDescriptor))); }) .findFirst() .orElse(unresolved()); // @formatter:on } @Override public Resolution resolve(IterationSelector selector, Context context) { if (selector.getParentSelector() instanceof MethodSelector methodSelector) { return resolve(context, emptyList(), methodSelector.getJavaClass(), methodSelector::getJavaMethod, (testDescriptor, childSelectorsSupplier) -> { if (testDescriptor instanceof Filterable filterable) { filterable.getDynamicDescendantFilter().allowIndex(selector.getIterationIndices()); } return Match.partial(testDescriptor, childSelectorsSupplier); }); } return unresolved(); } private Supplier> expansionCallback(TestDescriptor testDescriptor) { return () -> { if (testDescriptor instanceof Filterable filterable) { filterable.getDynamicDescendantFilter().allowAll(); } return emptySet(); }; } private static class MethodType { static List allPossibilities(DiscoveryIssueReporter issueReporter) { return Arrays.asList( // new MethodType(new IsTestMethod(issueReporter), TestMethodTestDescriptor::new, TestMethodTestDescriptor.SEGMENT_TYPE), // new MethodType(new IsTestFactoryMethod(issueReporter), TestFactoryTestDescriptor::new, TestFactoryTestDescriptor.SEGMENT_TYPE, TestFactoryTestDescriptor.DYNAMIC_CONTAINER_SEGMENT_TYPE, TestFactoryTestDescriptor.DYNAMIC_TEST_SEGMENT_TYPE), // new MethodType(new IsTestTemplateMethod(issueReporter), TestTemplateTestDescriptor::new, TestTemplateTestDescriptor.SEGMENT_TYPE, TestTemplateInvocationTestDescriptor.SEGMENT_TYPE) // ); } private final Predicate methodPredicate; private final TestDescriptorFactory testDescriptorFactory; private final String segmentType; private final Set dynamicDescendantSegmentTypes; private MethodType(Predicate methodPredicate, TestDescriptorFactory testDescriptorFactory, String segmentType, String... dynamicDescendantSegmentTypes) { this.methodPredicate = methodPredicate; this.testDescriptorFactory = testDescriptorFactory; this.segmentType = segmentType; this.dynamicDescendantSegmentTypes = new LinkedHashSet<>(Arrays.asList(dynamicDescendantSegmentTypes)); } Optional resolve(List> enclosingClasses, Class testClass, Method method, Context context, JupiterConfiguration configuration) { if (!methodPredicate.test(method)) { return Optional.empty(); } return context.addToParent(() -> selectClass(enclosingClasses, testClass), // parent -> Optional.of(createTestDescriptor(parent, testClass, method, configuration))); } private DiscoverySelector selectClass(List> enclosingClasses, Class testClass) { if (enclosingClasses.isEmpty()) { return DiscoverySelectors.selectClass(testClass); } return DiscoverySelectors.selectNestedClass(enclosingClasses, testClass); } Optional resolveUniqueIdIntoTestDescriptor(UniqueId uniqueId, Context context, JupiterConfiguration configuration) { UniqueId.Segment lastSegment = uniqueId.getLastSegment(); if (segmentType.equals(lastSegment.getType())) { return context.addToParent(() -> selectUniqueId(uniqueId.removeLastSegment()), parent -> { String methodSpecPart = lastSegment.getValue(); Class testClass = ((TestClassAware) parent).getTestClass(); // @formatter:off return methodSegmentResolver.findMethod(methodSpecPart, testClass) .filter(methodPredicate) .map(method -> createTestDescriptor(parent, testClass, method, configuration)); // @formatter:on }); } if (dynamicDescendantSegmentTypes.contains(lastSegment.getType())) { return resolveUniqueIdIntoTestDescriptor(uniqueId.removeLastSegment(), context, configuration); } return Optional.empty(); } private TestDescriptor createTestDescriptor(TestDescriptor parent, Class testClass, Method method, JupiterConfiguration configuration) { UniqueId uniqueId = createUniqueId(method, parent, testClass); return testDescriptorFactory.create(uniqueId, testClass, method, ((TestClassAware) parent)::getEnclosingTestClasses, configuration); } private UniqueId createUniqueId(Method method, TestDescriptor parent, Class testClass) { return parent.getUniqueId().append(segmentType, methodSegmentResolver.formatMethodSpecPart(method, testClass)); } interface TestDescriptorFactory { TestDescriptor create(UniqueId uniqueId, Class testClass, Method method, Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration); } } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Internal classes for test discovery within the JUnit Jupiter test engine. * Contains resolvers for Java elements. */ @NullMarked package org.junit.jupiter.engine.discovery; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethod.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.discovery.predicates; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.jupiter.engine.support.MethodReflectionUtils.getGenericReturnType; import static org.junit.jupiter.engine.support.MethodReflectionUtils.getReturnType; import static org.junit.platform.commons.util.CollectionUtils.isConvertibleToStream; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.WildcardType; import org.apiguardian.api.API; import org.junit.jupiter.api.DynamicNode; import org.junit.jupiter.api.TestFactory; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.support.descriptor.MethodSource; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter; /** * Test if a method is a JUnit Jupiter {@link TestFactory @TestFactory} method. * *

NOTE: this predicate does not check if a candidate method * has an appropriate return type for a {@code @TestFactory} method. * * @since 5.0 */ @API(status = INTERNAL, since = "5.0") public class IsTestFactoryMethod extends IsTestableMethod { private static final String EXPECTED_RETURN_TYPE_MESSAGE = "must return a single %1$s or a Stream, Collection, Iterable, Iterator, Iterator provider, or array of %1$s".formatted( DynamicNode.class.getName()); public IsTestFactoryMethod(DiscoveryIssueReporter issueReporter) { super(TestFactory.class, IsTestFactoryMethod::hasCompatibleReturnType, issueReporter); } private static DiscoveryIssueReporter.Condition hasCompatibleReturnType( Class annotationType, DiscoveryIssueReporter issueReporter) { return issueReporter.createReportingCondition(method -> isCompatible(method, issueReporter), method -> createIssue(annotationType, method, EXPECTED_RETURN_TYPE_MESSAGE)); } private static boolean isCompatible(Method method, DiscoveryIssueReporter issueReporter) { Class returnType = getReturnType(method); if (DynamicNode.class.isAssignableFrom(returnType) || DynamicNode[].class.isAssignableFrom(returnType)) { return true; } if (returnType == Object.class || returnType == Object[].class) { issueReporter.reportIssue(createTooGenericReturnTypeIssue(method)); return true; } boolean validContainerType = !returnType.isArray() && isConvertibleToStream(returnType); return validContainerType && isCompatibleContainerType(method, issueReporter); } private static boolean isCompatibleContainerType(Method method, DiscoveryIssueReporter issueReporter) { Type genericReturnType = getGenericReturnType(method); if (genericReturnType instanceof ParameterizedType type) { Type[] typeArguments = type.getActualTypeArguments(); if (typeArguments.length == 1) { Type typeArgument = typeArguments[0]; if (typeArgument instanceof Class clazz) { // Stream etc. return DynamicNode.class.isAssignableFrom(clazz); } if (typeArgument instanceof WildcardType wildcardType) { Type[] upperBounds = wildcardType.getUpperBounds(); Type[] lowerBounds = wildcardType.getLowerBounds(); if (upperBounds.length == 1 && lowerBounds.length == 0 && upperBounds[0] instanceof Class upperBound) { if (Object.class.equals(upperBound)) { // Stream etc. issueReporter.reportIssue(createTooGenericReturnTypeIssue(method)); return true; } // Stream etc. return DynamicNode.class.isAssignableFrom(upperBound); } } } return false; } // Raw Stream etc. without type argument issueReporter.reportIssue(createTooGenericReturnTypeIssue(method)); return true; } private static DiscoveryIssue.Builder createTooGenericReturnTypeIssue(Method method) { String message = ("The declared return type of @TestFactory method '%s' does not support static validation. It " + EXPECTED_RETURN_TYPE_MESSAGE + ".").formatted(method.toGenericString()); return DiscoveryIssue.builder(Severity.INFO, message) // .source(MethodSource.from(method)); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestMethod.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.discovery.predicates; import static org.apiguardian.api.API.Status.INTERNAL; import org.apiguardian.api.API; import org.junit.jupiter.api.Test; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter; /** * Test if a method is a JUnit Jupiter {@link Test @Test} method. * * @since 5.0 */ @API(status = INTERNAL, since = "5.0") public class IsTestMethod extends IsTestableMethod { public IsTestMethod(DiscoveryIssueReporter issueReporter) { super(Test.class, IsTestableMethod::hasVoidReturnType, issueReporter); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestTemplateMethod.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.discovery.predicates; import static org.apiguardian.api.API.Status.INTERNAL; import org.apiguardian.api.API; import org.junit.jupiter.api.TestTemplate; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter; /** * Test if a method is a JUnit Jupiter {@link TestTemplate @TestTemplate} method. * * @since 5.0 */ @API(status = INTERNAL, since = "5.0") public class IsTestTemplateMethod extends IsTestableMethod { public IsTestTemplateMethod(DiscoveryIssueReporter issueReporter) { super(TestTemplate.class, IsTestableMethod::hasVoidReturnType, issueReporter); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestableMethod.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.discovery.predicates; import static org.junit.jupiter.engine.support.MethodReflectionUtils.getReturnType; import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated; import static org.junit.platform.commons.support.ModifierSupport.isNotAbstract; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.function.BiFunction; import java.util.function.Predicate; import org.junit.platform.commons.support.ModifierSupport; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.support.descriptor.MethodSource; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter.Condition; /** * @since 5.0 */ abstract class IsTestableMethod implements Predicate { private final Class annotationType; private final Condition condition; IsTestableMethod(Class annotationType, BiFunction, DiscoveryIssueReporter, Condition> returnTypeConditionFactory, DiscoveryIssueReporter issueReporter) { this.annotationType = annotationType; this.condition = isNotStatic(annotationType, issueReporter) // .and(isNotPrivate(annotationType, issueReporter)) // .and(returnTypeConditionFactory.apply(annotationType, issueReporter)); } @Override public boolean test(Method candidate) { if (!candidate.isSynthetic() && isAnnotated(candidate, this.annotationType)) { return condition.check(candidate) && isNotAbstract(candidate); } return false; } private static Condition isNotStatic(Class annotationType, DiscoveryIssueReporter issueReporter) { return issueReporter.createReportingCondition(ModifierSupport::isNotStatic, method -> createIssue(annotationType, method, "must not be static")); } private static Condition isNotPrivate(Class annotationType, DiscoveryIssueReporter issueReporter) { return issueReporter.createReportingCondition(ModifierSupport::isNotPrivate, method -> createIssue(annotationType, method, "must not be private")); } protected static Condition hasVoidReturnType(Class annotationType, DiscoveryIssueReporter issueReporter) { return issueReporter.createReportingCondition(method -> getReturnType(method) == void.class, method -> createIssue(annotationType, method, "must not return a value")); } protected static DiscoveryIssue createIssue(Class annotationType, Method method, String condition) { String message = "@%s method '%s' %s. It will not be executed.".formatted(annotationType.getSimpleName(), method.toGenericString(), condition); return DiscoveryIssue.builder(Severity.WARNING, message) // .source(MethodSource.from(method)) // .build(); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/TestClassPredicates.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.discovery.predicates; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated; import static org.junit.platform.commons.support.ModifierSupport.isAbstract; import static org.junit.platform.commons.support.ModifierSupport.isNotAbstract; import static org.junit.platform.commons.support.ModifierSupport.isNotPrivate; import static org.junit.platform.commons.util.KotlinReflectionUtils.isKotlinInterfaceDefaultImplsClass; import static org.junit.platform.commons.util.ReflectionUtils.isInnerClass; import static org.junit.platform.commons.util.ReflectionUtils.isMethodPresent; import static org.junit.platform.commons.util.ReflectionUtils.isNestedClassPresent; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.HashSet; import java.util.Set; import java.util.function.Predicate; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.ClassTemplate; import org.junit.jupiter.api.Nested; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.commons.util.ReflectionUtils.CycleErrorHandling; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter.Condition; /** * Predicates for determining whether a class is a JUnit Jupiter test class. * * @since 5.13 */ @API(status = INTERNAL, since = "5.13") public class TestClassPredicates { public final Predicate> isAnnotatedWithNested = candidate -> isAnnotatedButNotComposed(candidate, Nested.class); public final Predicate> isAnnotatedWithClassTemplate = candidate -> isAnnotatedButNotComposed(candidate, ClassTemplate.class); public final Predicate> isAnnotatedWithNestedAndValid = candidate -> this.isAnnotatedWithNested.test( candidate) && isValidNestedTestClass(candidate); public final Predicate> looksLikeNestedOrStandaloneTestClass = candidate -> this.isAnnotatedWithNested.test( candidate) || looksLikeIntendedTestClass(candidate); public final Predicate isTestOrTestFactoryOrTestTemplateMethod; private final Condition> isNotPrivateUnlessAbstractNestedClass; private final Condition> isInnerNestedClass; private final Condition> isValidStandaloneTestClass; public TestClassPredicates(DiscoveryIssueReporter issueReporter) { this.isTestOrTestFactoryOrTestTemplateMethod = new IsTestMethod(issueReporter) // .or(new IsTestFactoryMethod(issueReporter)) // .or(new IsTestTemplateMethod(issueReporter)); this.isNotPrivateUnlessAbstractNestedClass = isNotPrivateUnlessAbstract("@Nested", issueReporter); this.isInnerNestedClass = isInner(issueReporter); this.isValidStandaloneTestClass = isNotPrivateUnlessAbstract("Test", issueReporter) // .and(isNotLocal(issueReporter)) // .and(isNotInnerUnlessAbstract(issueReporter)) // .and(isNotAnonymous(issueReporter)); } public boolean looksLikeIntendedTestClass(Class candidate) { return looksLikeIntendedTestClass(candidate, new HashSet<>()); } private boolean looksLikeIntendedTestClass(Class candidate, Set> seen) { if (seen.add(candidate) && !isKotlinInterfaceDefaultImplsClass(candidate)) { return this.isAnnotatedWithClassTemplate.test(candidate) // || hasTestOrTestFactoryOrTestTemplateMethods(candidate) // || hasNestedTests(candidate, seen); } return false; } public boolean isValidNestedTestClass(Class candidate) { return validateNestedTestClass(candidate) == null; } public @Nullable NestedClassInvalidityReason validateNestedTestClass(Class candidate) { boolean isInner = this.isInnerNestedClass.check(candidate); boolean isNotPrivateUnlessAbstract = this.isNotPrivateUnlessAbstractNestedClass.check(candidate); if (isNotPrivateUnlessAbstract && isNotAbstract(candidate)) { return isInner ? null : NestedClassInvalidityReason.NOT_INNER; } return NestedClassInvalidityReason.OTHER; } public boolean isValidStandaloneTestClass(Class candidate) { return this.isValidStandaloneTestClass.check(candidate) // && isNotAbstract(candidate); } private boolean hasTestOrTestFactoryOrTestTemplateMethods(Class candidate) { return isMethodPresent(candidate, this.isTestOrTestFactoryOrTestTemplateMethod); } private boolean hasNestedTests(Class candidate, Set> seen) { var hasAnnotatedClass = isNestedClassPresent(candidate, this.isAnnotatedWithNested, CycleErrorHandling.THROW_EXCEPTION); if (hasAnnotatedClass) { return true; } return isNestedClassPresent( // candidate, // it -> isInnerClass(it) && looksLikeIntendedTestClass(it, seen), // CycleErrorHandling.ABORT_VISIT // ); } private static Condition> isNotPrivateUnlessAbstract(String prefix, DiscoveryIssueReporter issueReporter) { // Allow abstract test classes to be private because subclasses may widen access. return issueReporter.createReportingCondition(testClass -> isNotPrivate(testClass) || isAbstract(testClass), testClass -> createIssue(prefix, testClass, "must not be private")); } private static Condition> isNotLocal(DiscoveryIssueReporter issueReporter) { return issueReporter.createReportingCondition(testClass -> !testClass.isLocalClass(), testClass -> createIssue("Test", testClass, "must not be a local class")); } private static Condition> isInner(DiscoveryIssueReporter issueReporter) { return issueReporter.createReportingCondition(ReflectionUtils::isInnerClass, testClass -> { if (testClass.getEnclosingClass() == null) { return createIssue("Top-level", testClass, "must not be annotated with @Nested", "It will be executed anyway for backward compatibility. " + "You should remove the @Nested annotation to resolve this warning."); } return createIssue("@Nested", testClass, "must not be static", "It will only be executed if discovered as a standalone test class. " + "You should remove the annotation or make it non-static to resolve this warning."); }); } private static Condition> isNotInnerUnlessAbstract(DiscoveryIssueReporter issueReporter) { return issueReporter.createReportingCondition(testClass -> !isInnerClass(testClass) || isAbstract(testClass), testClass -> createIssue("Test", testClass, "must not be an inner class unless annotated with @Nested")); } private static Condition> isNotAnonymous(DiscoveryIssueReporter issueReporter) { return issueReporter.createReportingCondition(testClass -> !testClass.isAnonymousClass(), testClass -> createIssue("Test", testClass, "must not be anonymous")); } private static DiscoveryIssue createIssue(String prefix, Class testClass, String detailMessage) { return createIssue(prefix, testClass, detailMessage, "It will not be executed."); } private static DiscoveryIssue createIssue(String prefix, Class testClass, String detailMessage, String effect) { String message = "%s class '%s' %s. %s".formatted(prefix, testClass.getName(), detailMessage, effect); return DiscoveryIssue.builder(DiscoveryIssue.Severity.WARNING, message) // .source(ClassSource.from(testClass)) // .build(); } private static boolean isAnnotatedButNotComposed(Class candidate, Class annotationType) { return !candidate.isAnnotation() && isAnnotated(candidate, annotationType); } @API(status = INTERNAL, since = "5.13.3") public enum NestedClassInvalidityReason { NOT_INNER, OTHER } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Internal predicate classes used by test discovery within the JUnit Jupiter test engine. */ @NullMarked package org.junit.jupiter.engine.discovery.predicates; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/AfterEachMethodAdapter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution; import static org.apiguardian.api.API.Status.INTERNAL; import org.apiguardian.api.API; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.engine.extension.ExtensionRegistry; /** * Functional interface for registering an {@link AfterEach @AfterEach} method * as a pseudo-extension. * * @since 5.0 */ @FunctionalInterface @API(status = INTERNAL, since = "5.0") public interface AfterEachMethodAdapter extends Extension { void invokeAfterEachMethod(ExtensionContext context, ExtensionRegistry registry) throws Throwable; } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/BeforeEachMethodAdapter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution; import static org.apiguardian.api.API.Status.INTERNAL; import org.apiguardian.api.API; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.engine.extension.ExtensionRegistry; /** * Functional interface for registering a {@link BeforeEach @BeforeEach} method * as a pseudo-extension. * * @since 5.0 */ @FunctionalInterface @API(status = INTERNAL, since = "5.0") public interface BeforeEachMethodAdapter extends Extension { void invokeBeforeEachMethod(ExtensionContext context, ExtensionRegistry registry) throws Throwable; } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConditionEvaluationException.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution; import java.io.Serial; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.platform.commons.JUnitException; /** * Thrown if an error is encountered while evaluating an * {@link ExecutionCondition}. * * @since 5.0 * @see ConditionEvaluator */ class ConditionEvaluationException extends JUnitException { @Serial private static final long serialVersionUID = 1L; ConditionEvaluationException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConditionEvaluator.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution; import static org.apiguardian.api.API.Status.INTERNAL; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.extension.ExtensionRegistry; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.StringUtils; /** * {@code ConditionEvaluator} evaluates {@link ExecutionCondition} extensions. * * @since 5.0 * @see ExecutionCondition */ @API(status = INTERNAL, since = "5.0") public class ConditionEvaluator { private static final Logger logger = LoggerFactory.getLogger(ConditionEvaluator.class); private static final ConditionEvaluationResult ENABLED = ConditionEvaluationResult.enabled( "No 'disabled' conditions encountered"); /** * Evaluate all {@link ExecutionCondition} extensions registered for the * supplied {@link ExtensionContext}. * * @param context the current {@code ExtensionContext} * @return the first disabled {@code ConditionEvaluationResult}, * or a default enabled {@code ConditionEvaluationResult} if no * disabled conditions are encountered */ public ConditionEvaluationResult evaluate(ExtensionRegistry extensionRegistry, JupiterConfiguration configuration, ExtensionContext context) { // @formatter:off return extensionRegistry.stream(ExecutionCondition.class) .filter(configuration.getExecutionConditionFilter()) .map(condition -> evaluate(condition, context)) .filter(ConditionEvaluationResult::isDisabled) .findFirst() .orElse(ENABLED); // @formatter:on } private ConditionEvaluationResult evaluate(ExecutionCondition condition, ExtensionContext context) { try { ConditionEvaluationResult result = condition.evaluateExecutionCondition(context); logResult(condition.getClass(), result, context); return result; } catch (Exception ex) { throw evaluationException(condition.getClass(), ex); } } private void logResult(Class conditionType, ConditionEvaluationResult result, ExtensionContext context) { logger.trace(() -> "Evaluation of condition [%s] on [%s] resulted in: %s".formatted(conditionType.getName(), context.getElement().orElse(null), result)); } private ConditionEvaluationException evaluationException(Class conditionType, Exception ex) { String cause = StringUtils.isNotBlank(ex.getMessage()) ? ": " + ex.getMessage() : ""; return new ConditionEvaluationException( "Failed to evaluate condition [%s]%s".formatted(conditionType.getName(), cause), ex); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConstructorInvocation.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution; import static java.util.Collections.unmodifiableList; import java.lang.reflect.Constructor; import java.util.Arrays; import java.util.List; import java.util.Optional; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; import org.junit.jupiter.api.extension.ReflectiveInvocationContext; import org.junit.platform.commons.util.ReflectionUtils; class ConstructorInvocation implements Invocation, ReflectiveInvocationContext> { private final Constructor constructor; private final @Nullable Object[] arguments; ConstructorInvocation(Constructor constructor, @Nullable Object[] arguments) { this.constructor = constructor; this.arguments = arguments; } @Override public Class getTargetClass() { return this.constructor.getDeclaringClass(); } @Override public Constructor getExecutable() { return this.constructor; } @Override public List getArguments() { return unmodifiableList(Arrays.asList(this.arguments)); } @Override public Optional getTarget() { return Optional.empty(); } @Override public T proceed() { return ReflectionUtils.newInstance(this.constructor, this.arguments); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultExecutableInvoker.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.jupiter.engine.execution.ParameterResolutionUtils.resolveParameters; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.Optional; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExecutableInvoker; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.engine.extension.ExtensionRegistry; import org.junit.jupiter.engine.support.MethodReflectionUtils; import org.junit.platform.commons.util.ReflectionUtils; /** * @since 5.9 */ @API(status = INTERNAL, since = "5.9") public class DefaultExecutableInvoker implements ExecutableInvoker { private final ExtensionContext extensionContext; private final ExtensionRegistry extensionRegistry; public DefaultExecutableInvoker(ExtensionContext extensionContext, ExtensionRegistry extensionRegistry) { this.extensionContext = extensionContext; this.extensionRegistry = extensionRegistry; } @Override public T invoke(Constructor constructor, @Nullable Object outerInstance) { @Nullable Object[] arguments = resolveParameters(constructor, Optional.empty(), Optional.ofNullable(outerInstance), extensionContext, extensionRegistry); return ReflectionUtils.newInstance(constructor, arguments); } @Override public @Nullable Object invoke(Method method, @Nullable Object target) { @Nullable Object[] arguments = resolveParameters(method, Optional.ofNullable(target), extensionContext, extensionRegistry); return MethodReflectionUtils.invoke(method, target, arguments); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultParameterContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution; import java.lang.reflect.Parameter; import java.util.Optional; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; /** * @since 5.0 */ record DefaultParameterContext(Parameter parameter, int index, Optional target) implements ParameterContext { DefaultParameterContext { Preconditions.condition(index >= 0, "index must be greater than or equal to zero"); Preconditions.notNull(parameter, "parameter must not be null"); Preconditions.notNull(target, "target must not be null"); } @Override public Parameter getParameter() { return this.parameter; } @Override public int getIndex() { return this.index; } @Override public Optional getTarget() { return this.target; } @Override public String toString() { // @formatter:off return new ToStringBuilder(this) .append("parameter", this.parameter) .append("index", this.index) .append("target", this.target) .toString(); // @formatter:on } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultTestInstances.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.ListIterator; import java.util.Optional; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.TestInstances; import org.junit.platform.commons.util.Preconditions; @API(status = INTERNAL, since = "5.4") public class DefaultTestInstances implements TestInstances { public static DefaultTestInstances of(Object instance) { return new DefaultTestInstances(List.of(instance)); } public static DefaultTestInstances of(TestInstances testInstances, Object instance) { List allInstances = new ArrayList<>(testInstances.getAllInstances()); allInstances.add(instance); return new DefaultTestInstances(Collections.unmodifiableList(allInstances)); } private final List instances; private DefaultTestInstances(List instances) { this.instances = Preconditions.notEmpty(instances, "instances must not be empty"); } @Override public Object getInnermostInstance() { return instances.get(instances.size() - 1); } @Override public List getEnclosingInstances() { return instances.subList(0, instances.size() - 1); } @Override public List getAllInstances() { return instances; } @Override public Optional findInstance(Class requiredType) { Preconditions.notNull(requiredType, "requiredType must not be null"); ListIterator iterator = instances.listIterator(instances.size()); while (iterator.hasPrevious()) { Object instance = iterator.previous(); if (requiredType.isInstance(instance)) { return Optional.of(requiredType.cast(instance)); } } return Optional.empty(); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ExtensionContextSupplier.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.TEST_METHOD; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestInstantiationAwareExtension; import org.junit.jupiter.engine.config.JupiterConfiguration; /** * Container of two instances of {@link ExtensionContext} to simplify the legacy for * #3445. * * @since 5.12 * @see TestInstantiationAwareExtension */ @FunctionalInterface @API(status = INTERNAL, since = "5.12") public interface ExtensionContextSupplier { static ExtensionContextSupplier create(ExtensionContext currentExtensionContext, ExtensionContext legacyExtensionContext, JupiterConfiguration configuration) { if (currentExtensionContext == legacyExtensionContext || configuration.getDefaultTestInstantiationExtensionContextScope() == TEST_METHOD) { return __ -> currentExtensionContext; } return new ScopeBasedExtensionContextSupplier(currentExtensionContext, legacyExtensionContext); } ExtensionContext get(TestInstantiationAwareExtension extension); class ScopeBasedExtensionContextSupplier implements ExtensionContextSupplier { private final ExtensionContext currentExtensionContext; private final ExtensionContext legacyExtensionContext; private ScopeBasedExtensionContextSupplier(ExtensionContext currentExtensionContext, ExtensionContext legacyExtensionContext) { this.currentExtensionContext = currentExtensionContext; this.legacyExtensionContext = legacyExtensionContext; } @Override public ExtensionContext get(TestInstantiationAwareExtension extension) { return isTestScoped(extension) ? currentExtensionContext : legacyExtensionContext; } private boolean isTestScoped(TestInstantiationAwareExtension extension) { ExtensionContext rootContext = legacyExtensionContext.getRoot(); return extension.getTestInstantiationExtensionContextScope(rootContext) == TEST_METHOD; } } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvoker.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.jupiter.engine.execution.ParameterResolutionUtils.resolveParameters; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Method; import java.util.Optional; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.InvocationInterceptor; import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.jupiter.api.extension.ReflectiveInvocationContext; import org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.ReflectiveInterceptorCall.VoidMethodInterceptorCall; import org.junit.jupiter.engine.extension.ExtensionRegistry; /** * {@code InterceptingExecutableInvoker} encapsulates the invocation of a * {@link java.lang.reflect.Executable} (i.e., method or constructor), * including support for dynamic resolution of method parameters via * {@link ParameterResolver ParameterResolvers}. * * @since 5.0 */ @API(status = INTERNAL, since = "5.0") public class InterceptingExecutableInvoker { private static final InvocationInterceptorChain interceptorChain = new InvocationInterceptorChain(); /** * Invoke the supplied constructor with the supplied outer instance and * dynamic parameter resolution. * * @param constructor the constructor to invoke and resolve parameters for * @param outerInstance the outer instance to supply as the first argument * to the constructor; empty, for top-level classes * @param extensionContext the current {@code ExtensionContext} * @param extensionRegistry the {@code ExtensionRegistry} to retrieve * {@code ParameterResolvers} from * @param interceptorCall the call for intercepting this constructor * invocation via all registered {@linkplain InvocationInterceptor * interceptors} */ public T invoke(Constructor constructor, Optional outerInstance, ExtensionContextSupplier extensionContext, ExtensionRegistry extensionRegistry, ReflectiveInterceptorCall, T> interceptorCall) { @Nullable Object[] arguments = resolveParameters(constructor, Optional.empty(), outerInstance, extensionContext, extensionRegistry); ConstructorInvocation invocation = new ConstructorInvocation<>(constructor, arguments); return invoke(invocation, invocation, extensionContext, extensionRegistry, interceptorCall); } public void invokeVoid(Method method, @Nullable Object target, ExtensionContext extensionContext, ExtensionRegistry extensionRegistry, VoidMethodInterceptorCall interceptorCall) { this.<@Nullable Void> invoke(method, target, extensionContext, extensionRegistry, ReflectiveInterceptorCall.ofVoidMethod(interceptorCall)); } /** * Invoke the supplied method with dynamic parameter resolution. * * @param method the method to invoke and resolve parameters for * @param target the target on which the executable will be invoked, * potentially wrapped in an {@link Optional}; can be {@code null} or an * empty {@code Optional} for a {@code static} method * @param extensionContext the current {@code ExtensionContext} * @param extensionRegistry the {@code ExtensionRegistry} to retrieve * {@code ParameterResolvers} from * @param interceptorCall the call for intercepting this method invocation * via all registered {@linkplain InvocationInterceptor interceptors} */ public T invoke(Method method, @Nullable Object target, ExtensionContext extensionContext, ExtensionRegistry extensionRegistry, ReflectiveInterceptorCall interceptorCall) { @SuppressWarnings({ "unchecked", "rawtypes" }) Optional optionalTarget = (target instanceof Optional optional ? optional : Optional.ofNullable(target)); @Nullable Object[] arguments = resolveParameters(method, optionalTarget, extensionContext, extensionRegistry); MethodInvocation invocation = new MethodInvocation<>(method, optionalTarget, arguments); return invoke(invocation, invocation, extensionContext, extensionRegistry, interceptorCall); } private T invoke(Invocation originalInvocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext, ExtensionRegistry extensionRegistry, ReflectiveInterceptorCall call) { return interceptorChain.invoke(originalInvocation, extensionRegistry, (interceptor, wrappedInvocation) -> call.apply(interceptor, wrappedInvocation, invocationContext, extensionContext)); } private T invoke(Invocation originalInvocation, ReflectiveInvocationContext invocationContext, ExtensionContextSupplier extensionContext, ExtensionRegistry extensionRegistry, ReflectiveInterceptorCall call) { return interceptorChain.invoke(originalInvocation, extensionRegistry, (interceptor, wrappedInvocation) -> call.apply(interceptor, wrappedInvocation, invocationContext, extensionContext.get(interceptor))); } public interface ReflectiveInterceptorCall { T apply(InvocationInterceptor interceptor, Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable; @SuppressWarnings("NullAway") // for JDK 26 and earlier static ReflectiveInterceptorCall ofVoidMethod(VoidMethodInterceptorCall call) { return (interceptorChain, invocation, invocationContext, extensionContext) -> { call.apply(interceptorChain, invocation, invocationContext, extensionContext); return null; }; } interface VoidMethodInterceptorCall { void apply(InvocationInterceptor interceptor, Invocation<@Nullable Void> invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable; } } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InvocationInterceptorChain.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution; import static java.util.stream.Collectors.joining; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.List; import java.util.ListIterator; import java.util.concurrent.atomic.AtomicBoolean; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.InvocationInterceptor; import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; import org.junit.jupiter.engine.extension.ExtensionRegistry; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.ExceptionUtils; @API(status = INTERNAL, since = "5.5") public class InvocationInterceptorChain { public T invoke(Invocation invocation, ExtensionRegistry extensionRegistry, InterceptorCall call) { List interceptors = extensionRegistry.getExtensions(InvocationInterceptor.class); if (interceptors.isEmpty()) { return proceed(invocation); } return chainAndInvoke(invocation, call, interceptors); } private T chainAndInvoke(Invocation invocation, InterceptorCall call, List interceptors) { ValidatingInvocation validatingInvocation = new ValidatingInvocation<>(invocation, interceptors); Invocation chainedInvocation = chainInterceptors(validatingInvocation, call, interceptors); T result = proceed(chainedInvocation); validatingInvocation.verifyInvokedAtLeastOnce(); return result; } private Invocation chainInterceptors(Invocation invocation, InterceptorCall call, List interceptors) { Invocation result = invocation; ListIterator iterator = interceptors.listIterator(interceptors.size()); while (iterator.hasPrevious()) { InvocationInterceptor interceptor = iterator.previous(); result = new InterceptedInvocation<>(result, call, interceptor); } return result; } private T proceed(Invocation invocation) { try { return invocation.proceed(); } catch (Throwable t) { throw ExceptionUtils.throwAsUncheckedException(t); } } @FunctionalInterface public interface InterceptorCall { T apply(InvocationInterceptor interceptor, Invocation invocation) throws Throwable; static InterceptorCall<@Nullable Void> ofVoid(VoidInterceptorCall call) { return (InvocationInterceptor interceptorChain, Invocation<@Nullable Void> invocation) -> { call.apply(interceptorChain, invocation); return null; }; } } @FunctionalInterface public interface VoidInterceptorCall { void apply(InvocationInterceptor interceptor, Invocation<@Nullable Void> invocation) throws Throwable; } private record InterceptedInvocation(Invocation invocation, InterceptorCall call, InvocationInterceptor interceptor) implements Invocation { @Override public T proceed() throws Throwable { return call.apply(interceptor, invocation); } @Override public void skip() { invocation.skip(); } } private static class ValidatingInvocation implements Invocation { private static final Logger logger = LoggerFactory.getLogger(ValidatingInvocation.class); private final AtomicBoolean invokedOrSkipped = new AtomicBoolean(); private final Invocation delegate; private final List interceptors; ValidatingInvocation(Invocation delegate, List interceptors) { this.delegate = delegate; this.interceptors = interceptors; } @Override public T proceed() throws Throwable { markInvokedOrSkipped(); return delegate.proceed(); } @Override public void skip() { logger.debug(() -> "The invocation is skipped"); markInvokedOrSkipped(); delegate.skip(); } private void markInvokedOrSkipped() { if (!invokedOrSkipped.compareAndSet(false, true)) { fail("Chain of InvocationInterceptors called invocation multiple times instead of just once"); } } void verifyInvokedAtLeastOnce() { if (!invokedOrSkipped.get()) { fail("Chain of InvocationInterceptors never called invocation"); } } private void fail(String prefix) { String commaSeparatedInterceptorClasses = interceptors.stream().map(Object::getClass).map( Class::getName).collect(joining(", ")); throw new JUnitException(prefix + ": " + commaSeparatedInterceptorClasses); } } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution; import static java.util.Objects.requireNonNull; import static org.apiguardian.api.API.Status.INTERNAL; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.extension.MutableExtensionRegistry; import org.junit.platform.commons.JUnitException; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.support.hierarchical.EngineExecutionContext; import org.junit.platform.engine.support.hierarchical.ThrowableCollector; /** * @since 5.0 */ @API(status = INTERNAL, since = "5.0") public class JupiterEngineExecutionContext implements EngineExecutionContext { private final State state; // The following is not "cloneable" State. private boolean beforeAllCallbacksExecuted = false; private boolean beforeAllMethodsExecuted = false; public JupiterEngineExecutionContext(EngineExecutionListener executionListener, JupiterConfiguration configuration, LauncherStoreFacade launcherStoreFacade) { this(new State(executionListener, configuration, launcherStoreFacade)); } private JupiterEngineExecutionContext(State state) { this.state = state; } public void close() throws Exception { ExtensionContext extensionContext = getExtensionContext(); if (extensionContext instanceof @SuppressWarnings("resource") AutoCloseable closeable) { try { closeable.close(); } catch (Exception e) { throw new JUnitException("Failed to close extension context", e); } } } public EngineExecutionListener getExecutionListener() { return this.state.executionListener; } public JupiterConfiguration getConfiguration() { return this.state.configuration; } public LauncherStoreFacade getLauncherStoreFacade() { return this.state.launcherStoreFacade; } public TestInstancesProvider getTestInstancesProvider() { return requireNonNull(this.state.testInstancesProvider); } public MutableExtensionRegistry getExtensionRegistry() { return requireNonNull(this.state.extensionRegistry); } public ExtensionContext getExtensionContext() { return requireNonNull(this.state.extensionContext); } public ThrowableCollector getThrowableCollector() { return requireNonNull(this.state.throwableCollector); } /** * Track that an attempt was made to execute {@code BeforeAllCallback} extensions. * * @since 5.3 */ public void beforeAllCallbacksExecuted(boolean beforeAllCallbacksExecuted) { this.beforeAllCallbacksExecuted = beforeAllCallbacksExecuted; } /** * @return {@code true} if an attempt was made to execute {@code BeforeAllCallback} * extensions * @since 5.3 */ public boolean beforeAllCallbacksExecuted() { return beforeAllCallbacksExecuted; } /** * Track that an attempt was made to execute {@code @BeforeAll} methods. */ public void beforeAllMethodsExecuted(boolean beforeAllMethodsExecuted) { this.beforeAllMethodsExecuted = beforeAllMethodsExecuted; } /** * @return {@code true} if an attempt was made to execute {@code @BeforeAll} * methods */ public boolean beforeAllMethodsExecuted() { return this.beforeAllMethodsExecuted; } public Builder extend() { return new Builder(this.state); } private static final class State implements Cloneable { final EngineExecutionListener executionListener; final JupiterConfiguration configuration; final LauncherStoreFacade launcherStoreFacade; @Nullable TestInstancesProvider testInstancesProvider; @Nullable MutableExtensionRegistry extensionRegistry; @Nullable ExtensionContext extensionContext; @Nullable ThrowableCollector throwableCollector; State(EngineExecutionListener executionListener, JupiterConfiguration configuration, LauncherStoreFacade launcherStoreFacade) { this.executionListener = executionListener; this.configuration = configuration; this.launcherStoreFacade = launcherStoreFacade; } @Override public State clone() { try { return (State) super.clone(); } catch (CloneNotSupportedException e) { throw new JUnitException("State could not be cloned", e); } } } public static class Builder { private State originalState; @Nullable private State newState = null; private Builder(State originalState) { this.originalState = originalState; } public Builder withTestInstancesProvider(TestInstancesProvider testInstancesProvider) { newState().testInstancesProvider = testInstancesProvider; return this; } public Builder withExtensionRegistry(MutableExtensionRegistry extensionRegistry) { newState().extensionRegistry = extensionRegistry; return this; } public Builder withExtensionContext(ExtensionContext extensionContext) { newState().extensionContext = extensionContext; return this; } public Builder withThrowableCollector(ThrowableCollector throwableCollector) { newState().throwableCollector = throwableCollector; return this; } public JupiterEngineExecutionContext build() { if (newState != null) { originalState = newState; newState = null; } return new JupiterEngineExecutionContext(originalState); } private State newState() { if (newState == null) { this.newState = originalState.clone(); } return newState; } } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/LauncherStoreFacade.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution; import static org.apiguardian.api.API.Status.INTERNAL; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.support.store.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; @API(status = INTERNAL, since = "5.14") public class LauncherStoreFacade { private final NamespacedHierarchicalStore requestLevelStore; private final NamespacedHierarchicalStore sessionLevelStore; public LauncherStoreFacade(NamespacedHierarchicalStore requestLevelStore) { this.requestLevelStore = requestLevelStore; this.sessionLevelStore = requestLevelStore.getParent().orElseThrow( () -> new JUnitException("Request-level store must have a parent")); } public NamespacedHierarchicalStore getRequestLevelStore() { return this.requestLevelStore; } public ExtensionContext.Store getRequestLevelStore(ExtensionContext.Namespace namespace) { return getStoreAdapter(this.requestLevelStore, namespace); } public ExtensionContext.Store getSessionLevelStore(ExtensionContext.Namespace namespace) { return getStoreAdapter(this.sessionLevelStore, namespace); } public NamespaceAwareStore getStoreAdapter(NamespacedHierarchicalStore valuesStore, ExtensionContext.Namespace namespace) { Preconditions.notNull(namespace, "Namespace must not be null"); return new NamespaceAwareStore(valuesStore, convert(namespace)); } private Namespace convert(ExtensionContext.Namespace namespace) { return namespace.equals(ExtensionContext.Namespace.GLOBAL) // ? Namespace.GLOBAL // : Namespace.create(namespace.getParts()); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/MethodInvocation.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution; import static java.util.Collections.unmodifiableList; import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; import java.util.Optional; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; import org.junit.jupiter.api.extension.ReflectiveInvocationContext; import org.junit.jupiter.engine.support.MethodReflectionUtils; class MethodInvocation implements Invocation, ReflectiveInvocationContext { private final Method method; private final Optional target; private final @Nullable Object[] arguments; MethodInvocation(Method method, Optional target, @Nullable Object[] arguments) { this.method = method; this.target = target; this.arguments = arguments; } @Override public Class getTargetClass() { return this.target.> map(Object::getClass).orElseGet(this.method::getDeclaringClass); } @Override public Optional getTarget() { return this.target; } @Override public Method getExecutable() { return this.method; } @Override public List getArguments() { return unmodifiableList(Arrays.asList(this.arguments)); } @Override @SuppressWarnings({ "unchecked", "NullAway" }) public T proceed() { var actualTarget = this.target.orElse(null); return (T) MethodReflectionUtils.invoke(this.method, actualTarget, this.arguments); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/NamespaceAwareStore.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.function.Function; import java.util.function.Supplier; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExtensionContext.Store; import org.junit.jupiter.api.extension.ExtensionContextException; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.support.store.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.engine.support.store.NamespacedHierarchicalStoreException; /** * @since 5.0 */ @API(status = INTERNAL, since = "5.0") public class NamespaceAwareStore implements Store { private final NamespacedHierarchicalStore valuesStore; private final Namespace namespace; public NamespaceAwareStore(NamespacedHierarchicalStore valuesStore, Namespace namespace) { this.valuesStore = valuesStore; this.namespace = namespace; } @Override public @Nullable Object get(Object key) { Preconditions.notNull(key, "key must not be null"); Supplier<@Nullable Object> action = () -> this.valuesStore.get(this.namespace, key); return this.<@Nullable Object> accessStore(action); } @Override public @Nullable T get(Object key, Class requiredType) { Preconditions.notNull(key, "key must not be null"); Preconditions.notNull(requiredType, "requiredType must not be null"); Supplier<@Nullable T> action = () -> this.valuesStore.get(this.namespace, key, requiredType); return this.<@Nullable T> accessStore(action); } @SuppressWarnings("deprecation") @Override public @Nullable Object getOrComputeIfAbsent(K key, Function defaultCreator) { Preconditions.notNull(key, "key must not be null"); Preconditions.notNull(defaultCreator, "defaultCreator function must not be null"); Supplier<@Nullable Object> action = () -> this.valuesStore.getOrComputeIfAbsent(this.namespace, key, defaultCreator); return this.<@Nullable Object> accessStore(action); } @SuppressWarnings("deprecation") @Override public @Nullable V getOrComputeIfAbsent(K key, Function defaultCreator, Class requiredType) { Preconditions.notNull(key, "key must not be null"); Preconditions.notNull(defaultCreator, "defaultCreator function must not be null"); Preconditions.notNull(requiredType, "requiredType must not be null"); Supplier<@Nullable V> action = () -> this.valuesStore.getOrComputeIfAbsent(this.namespace, key, defaultCreator, requiredType); return this.<@Nullable V> accessStore(action); } @Override public Object computeIfAbsent(K key, Function defaultCreator) { Preconditions.notNull(key, "key must not be null"); Preconditions.notNull(defaultCreator, "defaultCreator function must not be null"); Supplier action = () -> this.valuesStore.computeIfAbsent(this.namespace, key, defaultCreator); return accessStore(action); } @Override public V computeIfAbsent(K key, Function defaultCreator, Class requiredType) { Preconditions.notNull(key, "key must not be null"); Preconditions.notNull(defaultCreator, "defaultCreator function must not be null"); Preconditions.notNull(requiredType, "requiredType must not be null"); Supplier action = () -> this.valuesStore.computeIfAbsent(this.namespace, key, defaultCreator, requiredType); return accessStore(action); } @Override public void put(Object key, @Nullable Object value) { Preconditions.notNull(key, "key must not be null"); Supplier<@Nullable Object> action = () -> this.valuesStore.put(this.namespace, key, value); this.<@Nullable Object> accessStore(action); } @Override public @Nullable Object remove(Object key) { Preconditions.notNull(key, "key must not be null"); Supplier<@Nullable Object> action = () -> this.valuesStore.remove(this.namespace, key); return this.<@Nullable Object> accessStore(action); } @Override public @Nullable T remove(Object key, Class requiredType) { Preconditions.notNull(key, "key must not be null"); Preconditions.notNull(requiredType, "requiredType must not be null"); Supplier<@Nullable T> action = () -> this.valuesStore.remove(this.namespace, key, requiredType); return this.<@Nullable T> accessStore(action); } private T accessStore(Supplier action) { try { return action.get(); } catch (NamespacedHierarchicalStoreException e) { throw new ExtensionContextException(e.getMessage(), e); } } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ParameterResolutionUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution; import static java.util.stream.Collectors.joining; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.commons.util.KotlinReflectionUtils.getKotlinSuspendingFunctionParameters; import static org.junit.platform.commons.util.KotlinReflectionUtils.isKotlinSuspendingFunction; import static org.junit.platform.commons.util.ReflectionUtils.isAssignableTo; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.List; import java.util.Optional; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.jupiter.engine.extension.ExtensionRegistry; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.StringUtils; import org.junit.platform.commons.util.UnrecoverableExceptions; /** * {@code ParameterResolutionUtils} provides support for dynamic resolution * of executable parameters via {@link ParameterResolver ParameterResolvers}. * * @since 5.9 */ @API(status = INTERNAL, since = "5.9") public class ParameterResolutionUtils { private static final Logger logger = LoggerFactory.getLogger(ParameterResolutionUtils.class); /** * Resolve the array of parameters for the supplied method and target. * * @param method the method for which to resolve parameters * @param target an {@code Optional} containing the target on which the * executable will be invoked; never {@code null} but should be empty for * static methods and constructors * @param extensionContext the current {@code ExtensionContext} * @param extensionRegistry the {@code ExtensionRegistry} to retrieve * {@code ParameterResolvers} from * @return the array of Objects to be used as parameters in the executable * invocation; never {@code null} though potentially empty */ public static @Nullable Object[] resolveParameters(Method method, Optional target, ExtensionContext extensionContext, ExtensionRegistry extensionRegistry) { return resolveParameters(method, target, Optional.empty(), __ -> extensionContext, extensionRegistry, isKotlinSuspendingFunction(method) // ? getKotlinSuspendingFunctionParameters(method) // : method.getParameters()); } /** * Resolve the array of parameters for the supplied executable, target, and * outer instance. * * @param executable the executable for which to resolve parameters * @param target an {@code Optional} containing the target on which the * executable will be invoked; never {@code null} but should be empty for * static methods and constructors * @param outerInstance the outer instance that will be supplied as the * first argument to a constructor for an inner class; should be {@code null} * for methods and constructors for top-level or static classes * @param extensionContext the current {@code ExtensionContext} * @param extensionRegistry the {@code ExtensionRegistry} to retrieve * {@code ParameterResolvers} from * @return the array of Objects to be used as parameters in the executable * invocation; never {@code null} though potentially empty */ public static @Nullable Object[] resolveParameters(Executable executable, Optional target, Optional outerInstance, ExtensionContext extensionContext, ExtensionRegistry extensionRegistry) { return resolveParameters(executable, target, outerInstance, __ -> extensionContext, extensionRegistry); } public static @Nullable Object[] resolveParameters(Executable executable, Optional target, Optional outerInstance, ExtensionContextSupplier extensionContext, ExtensionRegistry extensionRegistry) { return resolveParameters(executable, target, outerInstance, extensionContext, extensionRegistry, executable.getParameters()); } private static @Nullable Object[] resolveParameters(Executable executable, Optional target, Optional outerInstance, ExtensionContextSupplier extensionContext, ExtensionRegistry extensionRegistry, Parameter[] parameters) { Preconditions.notNull(target, "target must not be null"); @Nullable Object[] values = new Object[parameters.length]; int start = 0; // Ensure that the outer instance is resolved as the first parameter if // the executable is a constructor for an inner class. if (outerInstance.isPresent()) { values[0] = outerInstance.get(); start = 1; } // Resolve remaining parameters dynamically for (int i = start; i < parameters.length; i++) { ParameterContext parameterContext = new DefaultParameterContext(parameters[i], i, target); values[i] = resolveParameter(parameterContext, executable, extensionContext, extensionRegistry); } return values; } private static @Nullable Object resolveParameter(ParameterContext parameterContext, Executable executable, ExtensionContextSupplier extensionContext, ExtensionRegistry extensionRegistry) { try { // @formatter:off List matchingResolvers = extensionRegistry.stream(ParameterResolver.class) .filter(resolver -> resolver.supportsParameter(parameterContext, extensionContext.get(resolver))) .toList(); // @formatter:on if (matchingResolvers.isEmpty()) { throw new ParameterResolutionException( "No ParameterResolver registered for parameter [%s] in %s [%s].".formatted( parameterContext.getParameter(), asLabel(executable), executable.toGenericString())); } if (matchingResolvers.size() > 1) { // @formatter:off String resolvers = matchingResolvers.stream() .map(StringUtils::defaultToString) .collect(joining(", ")); // @formatter:on throw new ParameterResolutionException( "Discovered multiple competing ParameterResolvers for parameter [%s] in %s [%s]: %s".formatted( parameterContext.getParameter(), asLabel(executable), executable.toGenericString(), resolvers)); } ParameterResolver resolver = matchingResolvers.get(0); Object value = resolver.resolveParameter(parameterContext, extensionContext.get(resolver)); validateResolvedType(parameterContext.getParameter(), value, executable, resolver); logger.trace( () -> "ParameterResolver [%s] resolved a value of type [%s] for parameter [%s] in %s [%s].".formatted( resolver.getClass().getName(), (value != null ? value.getClass().getTypeName() : null), parameterContext.getParameter(), asLabel(executable), executable.toGenericString())); return value; } catch (ParameterResolutionException ex) { throw ex; } catch (Throwable throwable) { UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); String message = "Failed to resolve parameter [%s] in %s [%s]".formatted(parameterContext.getParameter(), asLabel(executable), executable.toGenericString()); if (StringUtils.isNotBlank(throwable.getMessage())) { message += ": " + throwable.getMessage(); } throw new ParameterResolutionException(message, throwable); } } private static void validateResolvedType(Parameter parameter, @Nullable Object value, Executable executable, ParameterResolver resolver) { Class type = parameter.getType(); // Note: null is permissible as a resolved value but only for non-primitive types. if (!isAssignableTo(value, type)) { String message; if (value == null && type.isPrimitive()) { message = """ ParameterResolver [%s] resolved a null value for parameter [%s] \ in %s [%s], but a primitive of type [%s] is required.""".formatted( resolver.getClass().getName(), parameter, asLabel(executable), executable.toGenericString(), type.getName()); } else { message = """ ParameterResolver [%s] resolved a value of type [%s] for parameter [%s] \ in %s [%s], but a value assignment compatible with [%s] is required.""".formatted( resolver.getClass().getName(), (value != null ? value.getClass().getTypeName() : null), parameter, asLabel(executable), executable.toGenericString(), type.getTypeName()); } throw new ParameterResolutionException(message); } } private static String asLabel(Executable executable) { return executable instanceof Constructor ? "constructor" : "method"; } private ParameterResolutionUtils() { } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/TestInstancesProvider.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution; import static org.apiguardian.api.API.Status.INTERNAL; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.TestInstances; import org.junit.jupiter.engine.extension.ExtensionRegistry; /** * @since 5.0 */ @FunctionalInterface @API(status = INTERNAL, since = "5.0") public interface TestInstancesProvider { default TestInstances getTestInstances(JupiterEngineExecutionContext context) { return getTestInstances(context.getExtensionRegistry(), context); } TestInstances getTestInstances(ExtensionRegistry extensionRegistry, JupiterEngineExecutionContext executionContext); } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Internal classes for test execution within the JUnit Jupiter test engine. */ @NullMarked package org.junit.jupiter.engine.execution; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/AutoCloseExtension.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static org.junit.platform.commons.support.HierarchyTraversalMode.BOTTOM_UP; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.function.Predicate; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AutoClose; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.ExtensionConfigurationException; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestInstancePreDestroyCallback; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.support.AnnotationSupport; import org.junit.platform.commons.support.ModifierSupport; import org.junit.platform.commons.support.ReflectionSupport; 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.engine.support.hierarchical.ThrowableCollector; /** * {@code AutoCloseExtension} is a JUnit Jupiter extension that closes resources * if a field in a test class is annotated with {@link AutoClose @AutoClose}. * *

Consult the Javadoc for {@code @AutoClose} for details on the contract. * * @since 5.11 * @see AutoClose */ class AutoCloseExtension implements TestInstancePreDestroyCallback, AfterAllCallback { private static final Logger logger = LoggerFactory.getLogger(AutoCloseExtension.class); @Override public void preDestroyTestInstance(ExtensionContext context) { ThrowableCollector throwableCollector = new ThrowableCollector(__ -> false); TestInstancePreDestroyCallback.preDestroyTestInstances(context, testInstance -> closeFields(testInstance.getClass(), testInstance, throwableCollector)); throwableCollector.assertEmpty(); } @Override public void afterAll(ExtensionContext context) { ThrowableCollector throwableCollector = new ThrowableCollector(__ -> false); closeFields(context.getRequiredTestClass(), null, throwableCollector); throwableCollector.assertEmpty(); } private static void closeFields(Class testClass, @Nullable Object testInstance, ThrowableCollector throwableCollector) { Predicate predicate = (testInstance == null ? ModifierSupport::isStatic : ModifierSupport::isNotStatic); AnnotationSupport.findAnnotatedFields(testClass, AutoClose.class, predicate, BOTTOM_UP)// .forEach(field -> throwableCollector.execute(() -> closeField(field, testInstance))); } private static void closeField(Field field, @Nullable Object testInstance) throws Exception { String methodName = AnnotationSupport.findAnnotation(field, AutoClose.class).orElseThrow().value(); Class fieldType = field.getType(); checkCondition(StringUtils.isNotBlank(methodName), "@AutoClose on field %s must specify a method name.", field); checkCondition(!fieldType.isPrimitive(), "@AutoClose is not supported on primitive field %s.", field); checkCondition(!fieldType.isArray(), "@AutoClose is not supported on array field %s.", field); Object fieldValue = ReflectionSupport.tryToReadFieldValue(field, testInstance).get(); if (fieldValue == null) { logger.warn(() -> "Cannot @AutoClose field %s because it is null.".formatted(getQualifiedName(field))); } else { invokeCloseMethod(field, fieldValue, methodName.strip()); } } private static void invokeCloseMethod(Field field, Object target, String methodName) throws Exception { // Avoid reflection if we can directly invoke close() via AutoCloseable. if (target instanceof @SuppressWarnings("resource") AutoCloseable closeable && "close".equals(methodName)) { closeable.close(); return; } Class targetType = target.getClass(); Method closeMethod = ReflectionSupport.findMethod(targetType, methodName).orElseThrow( () -> new ExtensionConfigurationException( "Cannot @AutoClose field %s because %s does not define method %s()."// .formatted(getQualifiedName(field), targetType.getName(), methodName))); closeMethod = ReflectionUtils.getInterfaceMethodIfPossible(closeMethod, targetType); ReflectionSupport.invokeMethod(closeMethod, target); } private static void checkCondition(boolean condition, String messageFormat, Field field) { Preconditions.condition(condition, () -> messageFormat.formatted(getQualifiedName(field))); } private static String getQualifiedName(Field field) { String typeName = field.getDeclaringClass().getCanonicalName(); if (typeName == null) { typeName = field.getDeclaringClass().getTypeName(); } return typeName + "." + field.getName(); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DefaultPreInterruptContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import org.junit.jupiter.api.extension.PreInterruptContext; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; /** * @since 5.12 */ record DefaultPreInterruptContext(Thread threadToInterrupt) implements PreInterruptContext { DefaultPreInterruptContext { Preconditions.notNull(threadToInterrupt, "threadToInterrupt must not be null"); } @Override public Thread getThreadToInterrupt() { return threadToInterrupt; } @Override public String toString() { // @formatter:off return new ToStringBuilder(this) .append("threadToInterrupt", this.threadToInterrupt) .toString(); // @formatter:on } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DefaultRepetitionInfo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.RepetitionInfo; import org.junit.platform.commons.util.ToStringBuilder; /** * Default implementation of {@link RepetitionInfo}. */ record DefaultRepetitionInfo(int currentRepetition, int totalRepetitions, AtomicInteger failureCount, int failureThreshold) implements RepetitionInfo { @Override public int getCurrentRepetition() { return this.currentRepetition; } @Override public int getTotalRepetitions() { return this.totalRepetitions; } @Override public int getFailureCount() { return this.failureCount.get(); } @Override public int getFailureThreshold() { return this.failureThreshold; } @Override public String toString() { // @formatter:off return new ToStringBuilder(this) .append("currentRepetition", this.currentRepetition) .append("totalRepetitions", this.totalRepetitions) .append("failureCount", this.failureCount) .append("failureThreshold", this.failureThreshold) .toString(); // @formatter:on } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DefaultTestReporter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import java.nio.file.Path; import java.util.Map; import org.junit.jupiter.api.MediaType; import org.junit.jupiter.api.TestReporter; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.function.ThrowingConsumer; /** * @since 5.12 */ class DefaultTestReporter implements TestReporter { private final ExtensionContext extensionContext; DefaultTestReporter(ExtensionContext extensionContext) { this.extensionContext = extensionContext; } @Override public void publishEntry(Map map) { this.extensionContext.publishReportEntry(map); } @Override public void publishFile(String name, MediaType mediaType, ThrowingConsumer action) { this.extensionContext.publishFile(name, mediaType, action); } @Override public void publishDirectory(String name, ThrowingConsumer action) { this.extensionContext.publishDirectory(name, action); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DisabledCondition.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import java.lang.reflect.AnnotatedElement; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.platform.commons.util.StringUtils; /** * {@link ExecutionCondition} that supports the {@code @Disabled} annotation. * * @since 5.0 * @see Disabled * @see #evaluateExecutionCondition(ExtensionContext) */ class DisabledCondition implements ExecutionCondition { private static final ConditionEvaluationResult ENABLED = ConditionEvaluationResult.enabled( "@Disabled is not present"); /** * Containers/tests are disabled if {@code @Disabled} is present on the test * class or method. */ @Override public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { AnnotatedElement element = context.getElement().orElse(null); return findAnnotation(element, Disabled.class) // .map(annotation -> toResult(element, annotation)) // .orElse(ENABLED); } private ConditionEvaluationResult toResult(@Nullable AnnotatedElement element, Disabled annotation) { String value = annotation.value(); String reason = StringUtils.isNotBlank(value) ? value : element + " is @Disabled"; return ConditionEvaluationResult.disabled(reason); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/ExtensionContextInternal.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.List; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; /** * {@code ExtensionContextInternal} extends the {@link ExtensionContext} with internal API. * * @since 5.12 * @see ExtensionContext */ @API(status = INTERNAL, since = "5.12") public interface ExtensionContextInternal extends ExtensionContext { /** * Returns a list of registered extension at this context of the passed {@code extensionType}. * * @param the extension type * @param extensionType the extension type * @return the list of extensions */ List getExtensions(Class extensionType); } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/ExtensionRegistrar.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static org.apiguardian.api.API.Status.INTERNAL; import java.lang.reflect.Field; import java.util.function.Function; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.RegisterExtension; /** * An {@code ExtensionRegistrar} is used to register extensions. * * @since 5.5 */ @API(status = INTERNAL, since = "5.5") public interface ExtensionRegistrar { /** * Instantiate an extension of the given type using its default constructor * and register it in the registry. * *

A new {@link Extension} should not be registered if an extension of the * given type already exists in the registry or a parent registry. * * @param extensionType the type of extension to register * @since 5.8 */ void registerExtension(Class extensionType); /** * Register the supplied {@link Extension}, without checking if an extension * of that type has already been registered. * *

Semantics for Source

* *

If an extension is registered declaratively via * {@link org.junit.jupiter.api.extension.ExtendWith @ExtendWith}, the * {@code source} and the {@code extension} should be the same object. * However, if an extension is registered programmatically via * {@link RegisterExtension @RegisterExtension}, the {@code source} object * should be the {@link java.lang.reflect.Field} that is annotated with * {@code @RegisterExtension}. Similarly, if an extension is registered * programmatically as a lambda expression or method reference, the * {@code source} object should be the underlying * {@link java.lang.reflect.Method} that implements the extension API. * * @param extension the extension to register; never {@code null} * @param source the source of the extension; never {@code null} */ void registerExtension(Extension extension, Object source); /** * Register the supplied {@link Extension} as a synthetic extension, * without checking if an extension of that type has already been registered. * * @param extension the extension to register; never {@code null} * @param source the source of the extension; never {@code null} * @since 5.8 * @see #registerExtension(Extension, Object) */ void registerSyntheticExtension(Extension extension, Object source); /** * Register an uninitialized extension for the supplied {@code testClass} to * be initialized using the supplied {@code initializer} when an instance of * the test class is created. * *

Uninitialized extensions are typically registered for fields annotated * with {@link RegisterExtension @RegisterExtension} that cannot be * initialized until an instance of the test class is created. Until they * are initialized, such extensions are not available for use. * * @param testClass the test class for which the extension is registered; * never {@code null} * @param source the source of the extension; never {@code null} * @param initializer the initializer function to be used to create the * extension; never {@code null} */ void registerUninitializedExtension(Class testClass, Field source, Function initializer); /** * Initialize all registered extensions for the supplied {@code testClass} * using the supplied {@code testInstance}. * * @param testClass the test class for which the extensions are initialized; * never {@code null} * @param testInstance the test instance to be used to initialize the * extensions; never {@code null} */ void initializeExtensions(Class testClass, Object testInstance); } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/ExtensionRegistry.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static java.util.stream.Collectors.toCollection; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.Extension; /** * An {@code ExtensionRegistry} holds all registered extensions (i.e. * instances of {@link Extension}) for a given * {@link org.junit.platform.engine.support.hierarchical.Node}. * * @since 5.0 */ @API(status = INTERNAL, since = "5.0") public interface ExtensionRegistry { /** * Stream all {@code Extensions} of the specified type that are present * in this registry or one of its ancestors. * * @param extensionType the type of {@link Extension} to stream * @see #getExtensions(Class) */ Stream stream(Class extensionType); /** * Get all {@code Extensions} of the specified type that are present * in this registry or one of its ancestors. * * @param extensionType the type of {@link Extension} to get * @see #stream(Class) */ default List getExtensions(Class extensionType) { return stream(extensionType).collect(toCollection(ArrayList::new)); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/MutableExtensionRegistry.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static java.util.Collections.emptyList; import static java.util.Collections.emptySet; import static org.apiguardian.api.API.Status.INTERNAL; import java.lang.reflect.Field; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.ServiceLoader; import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Stream; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Constants; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.ClassLoaderUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ServiceLoaderUtils; /** * Default, mutable implementation of {@link ExtensionRegistry}. * * @since 5.5 */ @API(status = INTERNAL, since = "5.5") public class MutableExtensionRegistry implements ExtensionRegistry, ExtensionRegistrar { private static final Logger logger = LoggerFactory.getLogger(MutableExtensionRegistry.class); private static final List DEFAULT_STATELESS_EXTENSIONS = List.of( // new DisabledCondition(), // new AutoCloseExtension(), // new TimeoutExtension(), // new RepeatedTestExtension(), // new TestInfoParameterResolver(), // new TestReporterParameterResolver() // ); /** * Factory for creating and populating a new root registry with the default * extensions. * *

If the {@link Constants#EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME} * configuration parameter has been set to {@code true}, extensions will be * auto-detected using Java's {@link ServiceLoader} mechanism and automatically * registered after the default extensions. * *

If the * {@value Constants#EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_PROPERTY_NAME} * configuration parameter has been set to {@code true}, the * {@link PreInterruptThreadDumpPrinter} will be registered. * * @param configuration configuration parameters used to retrieve the extension * auto-detection flag; never {@code null} * @return a new {@code ExtensionRegistry}; never {@code null} */ public static MutableExtensionRegistry createRegistryWithDefaultExtensions(JupiterConfiguration configuration) { MutableExtensionRegistry extensionRegistry = new MutableExtensionRegistry(); DEFAULT_STATELESS_EXTENSIONS.forEach(extensionRegistry::registerDefaultExtension); extensionRegistry.registerDefaultExtension(new TempDirectory(configuration)); if (configuration.isExtensionAutoDetectionEnabled()) { registerAutoDetectedExtensions(extensionRegistry, configuration); } if (configuration.isThreadDumpOnTimeoutEnabled()) { extensionRegistry.registerDefaultExtension(new PreInterruptThreadDumpPrinter()); } return extensionRegistry; } private static void registerAutoDetectedExtensions(MutableExtensionRegistry extensionRegistry, JupiterConfiguration configuration) { Predicate> filter = configuration.getFilterForAutoDetectedExtensions(); List> excludedExtensions = new ArrayList<>(); ServiceLoader serviceLoader = ServiceLoader.load(Extension.class, ClassLoaderUtils.getDefaultClassLoader()); ServiceLoaderUtils.filter(serviceLoader, clazz -> { boolean included = filter.test(clazz); if (!included) { excludedExtensions.add(clazz); } return included; }) // .forEach(extensionRegistry::registerAutoDetectedExtension); logExcludedExtensions(excludedExtensions); } private static void logExcludedExtensions(List> excludedExtensions) { if (!excludedExtensions.isEmpty()) { // @formatter:off List excludeExtensionNames = excludedExtensions .stream() .map(Class::getName) .toList(); // @formatter:on logger.config(() -> "Excluded auto-detected extensions due to configured includes/excludes: %s".formatted( excludeExtensionNames)); } } /** * Factory for creating and populating a new registry from a list of * extension types and a parent registry. * * @param parentRegistry the parent registry * @param extensionTypes the types of extensions to be registered in * the new registry * @return a new {@code ExtensionRegistry}; never {@code null} */ public static MutableExtensionRegistry createRegistryFrom(MutableExtensionRegistry parentRegistry, Stream> extensionTypes) { Preconditions.notNull(parentRegistry, "parentRegistry must not be null"); MutableExtensionRegistry registry = new MutableExtensionRegistry(parentRegistry); extensionTypes.forEach(registry::registerExtension); return registry; } private final Set> registeredExtensionTypes; private final List registeredExtensions; private final Map, LateInitExtensions> lateInitExtensions; private MutableExtensionRegistry() { this(emptySet(), emptyList()); } private MutableExtensionRegistry(MutableExtensionRegistry parent) { this(parent.registeredExtensionTypes, parent.registeredExtensions); } private MutableExtensionRegistry(Set> registeredExtensionTypes, List registeredExtensions) { this.registeredExtensionTypes = new LinkedHashSet<>(registeredExtensionTypes); this.registeredExtensions = new ArrayList<>(registeredExtensions.size()); this.lateInitExtensions = new LinkedHashMap<>(); registeredExtensions.forEach(entry -> { Entry newEntry = entry; if (entry instanceof LateInitEntry lateInitEntry) { newEntry = lateInitEntry.getExtension() // .map(Entry::of) // .orElseGet(() -> getLateInitExtensions(lateInitEntry.getTestClass()).add(lateInitEntry.copy())); } this.registeredExtensions.add(newEntry); }); } @Override public Stream stream(Class extensionType) { return this.registeredExtensions.stream() // .map(p -> p.getExtension().orElse(null)) // .filter(extensionType::isInstance) // .map(extensionType::cast); } @Override public void registerExtension(Class extensionType) { if (!isAlreadyRegistered(extensionType)) { registerLocalExtension(ReflectionSupport.newInstance(extensionType)); } } /** * Determine if the supplied type is already registered in this registry or in a * parent registry. */ private boolean isAlreadyRegistered(Class extensionType) { return this.registeredExtensionTypes.contains(extensionType); } @Override public void registerExtension(Extension extension, Object source) { Preconditions.notNull(source, "source must not be null"); registerExtension("local", extension, source); } @Override public void registerSyntheticExtension(Extension extension, Object source) { registerExtension("synthetic", extension, source); } @Override public void registerUninitializedExtension(Class testClass, Field source, Function initializer) { Preconditions.notNull(testClass, "testClass must not be null"); Preconditions.notNull(source, "source must not be null"); Preconditions.notNull(initializer, "initializer must not be null"); logger.trace(() -> "Registering local extension (late-init) for [%s]%s".formatted(source.getType().getName(), buildSourceInfo(source))); LateInitEntry entry = getLateInitExtensions(testClass) // .add(new LateInitEntry(testClass, initializer)); this.registeredExtensions.add(entry); } @Override public void initializeExtensions(Class testClass, Object testInstance) { Preconditions.notNull(testClass, "testClass must not be null"); Preconditions.notNull(testInstance, "testInstance must not be null"); LateInitExtensions extensions = lateInitExtensions.remove(testClass); if (extensions != null) { extensions.initialize(testInstance); } } private LateInitExtensions getLateInitExtensions(Class testClass) { return this.lateInitExtensions.computeIfAbsent(testClass, __ -> new LateInitExtensions()); } private void registerDefaultExtension(Extension extension) { registerExtension("default", extension); } private void registerAutoDetectedExtension(Extension extension) { registerExtension("auto-detected", extension); } private void registerLocalExtension(Extension extension) { registerExtension("local", extension); } private void registerExtension(String category, Extension extension) { registerExtension(category, extension, null); } private void registerExtension(String category, Extension extension, @Nullable Object source) { Preconditions.notBlank(category, "category must not be null or blank"); Preconditions.notNull(extension, "extension must not be null"); logger.trace(() -> "Registering %s extension [%s]%s".formatted(category, extension, buildSourceInfo(source))); this.registeredExtensions.add(Entry.of(extension)); this.registeredExtensionTypes.add(extension.getClass()); } private String buildSourceInfo(@Nullable Object source) { if (source == null) { return ""; } if (source instanceof Member member) { Object type = (member instanceof Method ? "method" : "field"); source = "%s %s.%s".formatted(type, member.getDeclaringClass().getName(), member.getName()); } return " from source [" + source + "]"; } private interface Entry { static Entry of(Extension extension) { Optional value = Optional.of(extension); return () -> value; } Optional getExtension(); } private static class LateInitEntry implements Entry { private final Class testClass; private final Function initializer; @SuppressWarnings("OptionalUsedAsFieldOrParameterType") private Optional extension = Optional.empty(); LateInitEntry(Class testClass, Function initializer) { this.testClass = testClass; this.initializer = initializer; } @Override public Optional getExtension() { return extension; } private Class getTestClass() { return testClass; } void initialize(Object testInstance) { Preconditions.condition(extension.isEmpty(), "Extension already initialized"); extension = Optional.of(initializer.apply(testInstance)); } LateInitEntry copy() { Preconditions.condition(extension.isEmpty(), "Extension already initialized"); return new LateInitEntry(testClass, initializer); } } private static class LateInitExtensions { private final List entries = new ArrayList<>(); LateInitEntry add(LateInitEntry entry) { entries.add(entry); return entry; } void initialize(Object testInstance) { entries.forEach(entry -> entry.initialize(testInstance)); } } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/PreInterruptCallbackInvocation.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import java.util.function.Consumer; /** * @since 5.12 */ @FunctionalInterface interface PreInterruptCallbackInvocation { PreInterruptCallbackInvocation NOOP = (t, e) -> { }; void executePreInterruptCallback(Thread threadToInterrupt, Consumer errorHandler); } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/PreInterruptCallbackInvocationFactory.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import java.util.List; import org.junit.jupiter.api.extension.PreInterruptCallback; import org.junit.jupiter.api.extension.PreInterruptContext; import org.junit.platform.commons.util.UnrecoverableExceptions; /** * @since 5.12 * @see PreInterruptCallbackInvocation */ final class PreInterruptCallbackInvocationFactory { private PreInterruptCallbackInvocationFactory() { } static PreInterruptCallbackInvocation create(ExtensionContextInternal extensionContext) { final List callbacks = extensionContext.getExtensions(PreInterruptCallback.class); if (callbacks.isEmpty()) { return PreInterruptCallbackInvocation.NOOP; } return (thread, errorHandler) -> { PreInterruptContext preInterruptContext = new DefaultPreInterruptContext(thread); for (PreInterruptCallback callback : callbacks) { try { callback.beforeThreadInterrupt(preInterruptContext, extensionContext); } catch (Throwable ex) { UnrecoverableExceptions.rethrowIfUnrecoverable(ex); errorHandler.accept(ex); } } }; } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/PreInterruptThreadDumpPrinter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import java.util.Map; import org.junit.jupiter.api.Constants; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.PreInterruptCallback; import org.junit.jupiter.api.extension.PreInterruptContext; /** * Default implementation of {@link PreInterruptCallback}, which prints the stacks * of all {@link Thread}s to {@code System.out}. * *

Note: This is disabled by default and must be enabled via * {@link Constants#EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_PROPERTY_NAME}. * * @since 5.12 */ final class PreInterruptThreadDumpPrinter implements PreInterruptCallback { private static final String NL = "\n"; @Override public void beforeThreadInterrupt(PreInterruptContext preInterruptContext, ExtensionContext extensionContext) { Map stackTraces = Thread.getAllStackTraces(); StringBuilder sb = new StringBuilder("Thread "); appendThreadName(sb, preInterruptContext.getThreadToInterrupt()); sb.append(" will be interrupted."); sb.append(NL); for (Map.Entry entry : stackTraces.entrySet()) { Thread thread = entry.getKey(); StackTraceElement[] stack = entry.getValue(); if (stack.length > 0) { sb.append(NL); appendThreadName(sb, thread); for (StackTraceElement stackTraceElement : stack) { sb.append(NL); // Use the same prefix as java.lang.Throwable.printStackTrace(PrintStreamOrWriter) sb.append("\tat "); sb.append(stackTraceElement); } sb.append(NL); } } System.out.println(sb); } /** * Append the {@link Thread} name and ID in a similar fashion as {@code jstack}. * @param builder the builder to append to * @param thread the thread whose information should be appended */ @SuppressWarnings("deprecation") // Thread.getId() is deprecated on JDK 19+ private static void appendThreadName(StringBuilder builder, Thread thread) { // Use same format as java.lang.management.ThreadInfo.toString() builder.append("\""); builder.append(thread.getName()); builder.append("\""); if (thread.isDaemon()) { builder.append(" daemon"); } builder.append(" prio="); builder.append(thread.getPriority()); builder.append(" Id="); builder.append(thread.getId()); builder.append(" "); builder.append(thread.getState()); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestDisplayNameFormatter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static org.junit.jupiter.api.RepeatedTest.CURRENT_REPETITION_PLACEHOLDER; import static org.junit.jupiter.api.RepeatedTest.DISPLAY_NAME_PLACEHOLDER; import static org.junit.jupiter.api.RepeatedTest.TOTAL_REPETITIONS_PLACEHOLDER; import org.junit.jupiter.api.RepeatedTest; /** * Display name formatter for a {@link RepeatedTest @RepeatedTest}. * * @since 5.0 */ class RepeatedTestDisplayNameFormatter { private final String pattern; private final String displayName; RepeatedTestDisplayNameFormatter(String pattern, String displayName) { this.pattern = pattern; this.displayName = displayName; } String format(int currentRepetition, int totalRepetitions) { return this.pattern// .replace(DISPLAY_NAME_PLACEHOLDER, this.displayName)// .replace(CURRENT_REPETITION_PLACEHOLDER, String.valueOf(currentRepetition))// .replace(TOTAL_REPETITIONS_PLACEHOLDER, String.valueOf(totalRepetitions)); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestExtension.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated; import java.lang.reflect.Method; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.IntStream; import java.util.stream.Stream; import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; import org.junit.platform.commons.util.Preconditions; /** * {@code TestTemplateInvocationContextProvider} that supports the * {@link RepeatedTest @RepeatedTest} annotation. * * @since 5.0 */ class RepeatedTestExtension implements TestTemplateInvocationContextProvider { @Override public boolean supportsTestTemplate(ExtensionContext context) { return isAnnotated(context.getTestMethod(), RepeatedTest.class); } @Override public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { Method testMethod = context.getRequiredTestMethod(); String displayName = context.getDisplayName(); RepeatedTest repeatedTest = findAnnotation(testMethod, RepeatedTest.class).get(); int totalRepetitions = totalRepetitions(repeatedTest, testMethod); AtomicInteger failureCount = new AtomicInteger(); int failureThreshold = failureThreshold(repeatedTest, testMethod); RepeatedTestDisplayNameFormatter formatter = displayNameFormatter(repeatedTest, testMethod, displayName); // @formatter:off return IntStream .rangeClosed(1, totalRepetitions) .mapToObj(repetition -> new DefaultRepetitionInfo(repetition, totalRepetitions, failureCount, failureThreshold)) .map(repetitionInfo -> new RepeatedTestInvocationContext(repetitionInfo, formatter)); // @formatter:on } private int totalRepetitions(RepeatedTest repeatedTest, Method method) { int repetitions = repeatedTest.value(); Preconditions.condition(repetitions > 0, () -> "Configuration error: @RepeatedTest on method [%s] must be declared with a positive 'value'.".formatted( method)); return repetitions; } private int failureThreshold(RepeatedTest repeatedTest, Method method) { int failureThreshold = repeatedTest.failureThreshold(); if (failureThreshold != Integer.MAX_VALUE) { int repetitions = repeatedTest.value(); Preconditions.condition((failureThreshold > 0) && (failureThreshold < repetitions), () -> """ Configuration error: @RepeatedTest on method [%s] must declare a \ 'failureThreshold' greater than zero and less than the total number of repetitions [%d].""".formatted( method, repetitions)); } return failureThreshold; } private RepeatedTestDisplayNameFormatter displayNameFormatter(RepeatedTest repeatedTest, Method method, String displayName) { String pattern = Preconditions.notBlank(repeatedTest.name().strip(), () -> "Configuration error: @RepeatedTest on method [%s] must be declared with a non-empty name.".formatted( method)); return new RepeatedTestDisplayNameFormatter(pattern, displayName); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestInvocationContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import java.util.List; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.TestTemplateInvocationContext; /** * {@link TestTemplateInvocationContext} for a {@link org.junit.jupiter.api.RepeatedTest @RepeatedTest}. * * @since 5.0 */ class RepeatedTestInvocationContext implements TestTemplateInvocationContext { private final DefaultRepetitionInfo repetitionInfo; private final RepeatedTestDisplayNameFormatter formatter; RepeatedTestInvocationContext(DefaultRepetitionInfo repetitionInfo, RepeatedTestDisplayNameFormatter formatter) { this.repetitionInfo = repetitionInfo; this.formatter = formatter; } @Override public String getDisplayName(int invocationIndex) { return this.formatter.format(this.repetitionInfo.currentRepetition(), this.repetitionInfo.totalRepetitions()); } @Override public List getAdditionalExtensions() { return List.of(new RepetitionExtension(this.repetitionInfo)); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepetitionExtension.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled; import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.RepetitionInfo; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.jupiter.api.extension.TestWatcher; /** * {@code RepetitionExtension} implements the following extension APIs to support * repetitions of a {@link RepeatedTest @RepeatedTest} method. * *

    *
  • {@link ParameterResolver} to resolve {@link RepetitionInfo} arguments
  • *
  • {@link TestWatcher} to track the {@linkplain RepetitionInfo#getFailureCount() * failure count}
  • *
  • {@link ExecutionCondition} to disable the repetition if the * {@linkplain RepetitionInfo#getFailureThreshold() failure threshold} has been * exceeded
  • *
* * @since 5.0 */ class RepetitionExtension implements ParameterResolver, TestWatcher, ExecutionCondition { private final DefaultRepetitionInfo repetitionInfo; RepetitionExtension(DefaultRepetitionInfo repetitionInfo) { this.repetitionInfo = repetitionInfo; } @Override public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { return ExtensionContextScope.TEST_METHOD; } @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return (parameterContext.getParameter().getType() == RepetitionInfo.class); } @Override public RepetitionInfo resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return this.repetitionInfo; } @Override public void testFailed(ExtensionContext context, @Nullable Throwable cause) { this.repetitionInfo.failureCount().incrementAndGet(); } @Override public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { int failureThreshold = this.repetitionInfo.getFailureThreshold(); if (this.repetitionInfo.getFailureCount() >= failureThreshold) { return disabled("Failure threshold [" + failureThreshold + "] exceeded"); } return enabled("Failure threshold not exceeded"); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SameThreadTimeoutInvocation.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.function.Supplier; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; import org.junit.platform.commons.util.UnrecoverableExceptions; /** * @since 5.5 */ class SameThreadTimeoutInvocation implements Invocation { private final Invocation delegate; private final TimeoutDuration timeout; private final ScheduledExecutorService executor; private final Supplier descriptionSupplier; private final PreInterruptCallbackInvocation preInterruptCallback; SameThreadTimeoutInvocation(Invocation delegate, TimeoutDuration timeout, ScheduledExecutorService executor, Supplier descriptionSupplier, PreInterruptCallbackInvocation preInterruptCallback) { this.delegate = delegate; this.timeout = timeout; this.executor = executor; this.descriptionSupplier = descriptionSupplier; this.preInterruptCallback = preInterruptCallback; } @SuppressWarnings("NullAway") @Override public T proceed() throws Throwable { InterruptTask interruptTask = new InterruptTask(Thread.currentThread(), preInterruptCallback); ScheduledFuture future = executor.schedule(interruptTask, timeout.value(), timeout.unit()); Throwable failure = null; T result = null; try { result = delegate.proceed(); } catch (Throwable t) { UnrecoverableExceptions.rethrowIfUnrecoverable(t); failure = t; } finally { boolean cancelled = future.cancel(false); if (!cancelled) { future.get(); } if (interruptTask.executed) { Thread.interrupted(); failure = TimeoutExceptionFactory.create(descriptionSupplier.get(), timeout, failure); interruptTask.attachSuppressedExceptions(failure); } } if (failure != null) { throw failure; } return result; } static class InterruptTask implements Runnable { private final PreInterruptCallbackInvocation preInterruptCallback; private final List exceptionsDuringInterruption = new CopyOnWriteArrayList<>(); private final Thread thread; private volatile boolean executed; InterruptTask(Thread thread, PreInterruptCallbackInvocation preInterruptCallback) { this.thread = thread; this.preInterruptCallback = preInterruptCallback; } @Override public void run() { executed = true; preInterruptCallback.executePreInterruptCallback(thread, exceptionsDuringInterruption::add); thread.interrupt(); } void attachSuppressedExceptions(Throwable outerException) { for (Throwable throwable : exceptionsDuringInterruption) { outerException.addSuppressed(throwable); } } } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocation.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static org.junit.jupiter.api.timeout.PreemptiveTimeoutUtils.executeWithPreemptiveTimeout; import java.util.concurrent.TimeoutException; import java.util.function.Supplier; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; /** * @since 5.9 */ class SeparateThreadTimeoutInvocation implements Invocation { private final Invocation delegate; private final TimeoutDuration timeout; private final Supplier descriptionSupplier; private final PreInterruptCallbackInvocation preInterruptCallback; SeparateThreadTimeoutInvocation(Invocation delegate, TimeoutDuration timeout, Supplier descriptionSupplier, PreInterruptCallbackInvocation preInterruptCallback) { this.delegate = delegate; this.timeout = timeout; this.descriptionSupplier = descriptionSupplier; this.preInterruptCallback = preInterruptCallback; } @Override @SuppressWarnings("NullAway") public T proceed() throws Throwable { return executeWithPreemptiveTimeout(timeout.toDuration(), delegate::proceed, descriptionSupplier, (__, ___, cause, testThread) -> { TimeoutException exception = TimeoutExceptionFactory.create(descriptionSupplier.get(), timeout, null); if (testThread != null) { preInterruptCallback.executePreInterruptCallback(testThread, exception::addSuppressed); } exception.initCause(cause); return exception; }); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static java.util.Objects.requireNonNull; import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.TEST_METHOD; import static org.junit.jupiter.api.io.CleanupMode.DEFAULT; import static org.junit.jupiter.api.io.CleanupMode.NEVER; import static org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS; import static org.junit.jupiter.api.io.TempDirDeletionStrategy.IgnoreFailures.descriptionFor; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotatedFields; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import static org.junit.platform.commons.support.ReflectionSupport.makeAccessible; import static org.junit.platform.commons.util.ReflectionUtils.isRecordObject; import java.io.File; import java.io.IOException; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; import java.lang.reflect.Parameter; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.util.function.Predicate; import java.util.function.Supplier; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.AnnotatedElementContext; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionConfigurationException; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Namespace; import org.junit.jupiter.api.extension.ExtensionContext.Store; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDirDeletionStrategy; import org.junit.jupiter.api.io.TempDirFactory; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.support.ModifierSupport; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; /** * {@code TempDirectory} is a JUnit Jupiter extension that creates and cleans * up temporary directories if a field in a test class or a parameter in a * test class constructor, lifecycle method, or test method is annotated with * {@code @TempDir}. * *

Consult the Javadoc for {@link TempDir} for details on the contract. * * @since 5.4 * @see TempDir @TempDir * @see Files#createTempDirectory */ class TempDirectory implements BeforeAllCallback, BeforeEachCallback, ParameterResolver { private static final Namespace NAMESPACE = Namespace.create(TempDirectory.class); private static final String KEY = "temp.dir"; private static final String FAILURE_TRACKER = "failure.tracker"; private static final String CHILD_FAILED = "child.failed"; private final JupiterConfiguration configuration; TempDirectory(JupiterConfiguration configuration) { this.configuration = configuration; } @Override public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { return TEST_METHOD; } /** * Perform field injection for non-private, {@code static} fields (i.e., * class fields) of type {@link Path} or {@link File} that are annotated with * {@link TempDir @TempDir}. */ @Override public void beforeAll(ExtensionContext context) { installFailureTracker(context); injectStaticFields(context, context.getRequiredTestClass()); } /** * Perform field injection for non-private, non-static fields (i.e., * instance fields) of type {@link Path} or {@link File} that are annotated * with {@link TempDir @TempDir}. */ @Override public void beforeEach(ExtensionContext context) { installFailureTracker(context); context.getRequiredTestInstances().getAllInstances() // .forEach(instance -> injectInstanceFields(context, instance)); } private static void installFailureTracker(ExtensionContext context) { context.getParent() // .filter(parentContext -> !context.getRoot().equals(parentContext)) // .ifPresent(parentContext -> installFailureTracker(context, parentContext)); } private static void installFailureTracker(ExtensionContext context, ExtensionContext parentContext) { context.getStore(NAMESPACE).put(FAILURE_TRACKER, new FailureTracker(context, parentContext)); } private void injectStaticFields(ExtensionContext context, Class testClass) { injectFields(context, null, testClass, ModifierSupport::isStatic); } private void injectInstanceFields(ExtensionContext context, Object instance) { if (!isRecordObject(instance)) { injectFields(context, instance, instance.getClass(), ModifierSupport::isNotStatic); } } private void injectFields(ExtensionContext context, @Nullable Object testInstance, Class testClass, Predicate predicate) { findAnnotatedFields(testClass, TempDir.class, predicate).forEach(field -> { assertNonFinalField(field); assertSupportedType("field", field.getType()); try { TempDir tempDir = findAnnotationOnField(field); makeAccessible(field).set(testInstance, getPathOrFile(field.getType(), new FieldContext(field), context, tempDir)); } catch (Throwable t) { throw ExceptionUtils.throwAsUncheckedException(t); } }); } /** * Determine if the {@link Parameter} in the supplied {@link ParameterContext} * is annotated with {@link TempDir @TempDir}. */ @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return parameterContext.isAnnotated(TempDir.class); } /** * Resolve the current temporary directory for the {@link Parameter} in the * supplied {@link ParameterContext}. */ @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { Class parameterType = parameterContext.getParameter().getType(); assertSupportedType("parameter", parameterType); TempDir tempDir = findAnnotationOnParameter(parameterContext); return getPathOrFile(parameterType, parameterContext, extensionContext, tempDir); } private static TempDir findAnnotationOnField(Field field) { return findAnnotation(field, TempDir.class).orElseThrow( () -> new JUnitException("Field " + field + " must be annotated with @TempDir")); } private static TempDir findAnnotationOnParameter(ParameterContext parameterContext) { return parameterContext.findAnnotation(TempDir.class).orElseThrow(() -> new JUnitException( "Parameter " + parameterContext.getParameter() + " must be annotated with @TempDir")); } private CleanupMode determineCleanupMode(TempDir annotation) { var cleanupMode = annotation.cleanup(); return cleanupMode == DEFAULT ? this.configuration.getDefaultTempDirCleanupMode() : cleanupMode; } private Supplier determineDeletionStrategy(TempDir annotation) { var strategyClass = annotation.deletionStrategy(); return strategyClass == TempDirDeletionStrategy.class // ? this.configuration.getDefaultTempDirDeletionStrategySupplier() // : () -> ReflectionSupport.newInstance(strategyClass); } private TempDirFactory determineTempDirFactory(TempDir tempDir) { Class factory = tempDir.factory(); return factory == TempDirFactory.class // ? this.configuration.getDefaultTempDirFactorySupplier().get() : ReflectionSupport.newInstance(factory); } private static void assertNonFinalField(Field field) { if (ModifierSupport.isFinal(field)) { throw new ExtensionConfigurationException("@TempDir field [" + field + "] must not be declared as final."); } } private static void assertSupportedType(String target, Class type) { if (type != Path.class && type != File.class) { throw new ExtensionConfigurationException("Can only resolve @TempDir " + target + " of type " + Path.class.getName() + " or " + File.class.getName() + " but was: " + type.getName()); } } private Object getPathOrFile(Class elementType, AnnotatedElementContext elementContext, ExtensionContext extensionContext, TempDir tempDir) { TempDirFactory factory = determineTempDirFactory(tempDir); Cleanup cleanup = new Cleanup(determineCleanupMode(tempDir), determineDeletionStrategy(tempDir)); return getPathOrFile(elementType, elementContext, factory, cleanup, extensionContext); } private static Object getPathOrFile(Class elementType, AnnotatedElementContext elementContext, TempDirFactory factory, Cleanup cleanup, ExtensionContext extensionContext) { Path path = extensionContext.getStore(NAMESPACE.append(elementContext)) // .computeIfAbsent(KEY, __ -> createTempDir(factory, cleanup, elementType, elementContext, extensionContext), CloseablePath.class) // .get(); return (elementType == Path.class) ? path : path.toFile(); } static CloseablePath createTempDir(TempDirFactory factory, Cleanup cleanup, Class elementType, AnnotatedElementContext elementContext, ExtensionContext extensionContext) { try { return new CloseablePath(factory, cleanup, elementType, elementContext, extensionContext); } catch (Exception ex) { throw new ExtensionConfigurationException("Failed to create default temp directory", ex); } } private static boolean selfOrChildFailed(ExtensionContext context) { return context.getExecutionException().isPresent() // || getContextSpecificStore(context).getOrDefault(CHILD_FAILED, Boolean.class, false); } private static ExtensionContext.Store getContextSpecificStore(ExtensionContext context) { return context.getStore(NAMESPACE.append(context)); } @SuppressWarnings("deprecation") static class CloseablePath implements Store.CloseableResource, AutoCloseable { private final @Nullable Path dir; private final TempDirFactory factory; private final Cleanup cleanup; private final AnnotatedElementContext elementContext; private final ExtensionContext extensionContext; private CloseablePath(TempDirFactory factory, Cleanup cleanup, Class elementType, AnnotatedElementContext elementContext, ExtensionContext extensionContext) throws Exception { this.dir = factory.createTempDirectory(elementContext, extensionContext); this.factory = factory; this.cleanup = cleanup; this.elementContext = elementContext; this.extensionContext = extensionContext; if (this.dir == null || !Files.isDirectory(this.dir)) { close(); throw new PreconditionViolationException("temp directory must be a directory"); } if (elementType == File.class && !this.dir.getFileSystem().equals(FileSystems.getDefault())) { close(); throw new PreconditionViolationException( "temp directory with non-default file system cannot be injected into " + File.class.getName() + " target"); } } Path get() { return requireNonNull(this.dir); } @Override public void close() throws IOException { try { if (this.dir != null) { this.cleanup.run(this.dir, this.elementContext, this.extensionContext); } } finally { this.factory.close(); } } } private record FieldContext(Field field) implements AnnotatedElementContext { private FieldContext(Field field) { this.field = Preconditions.notNull(field, "field must not be null"); } @Override public AnnotatedElement getAnnotatedElement() { return this.field; } @Override public String toString() { // @formatter:off return new ToStringBuilder(this) .append("field", this.field) .toString(); // @formatter:on } } @SuppressWarnings("deprecation") private record FailureTracker(ExtensionContext context, ExtensionContext parentContext) implements Store.CloseableResource, AutoCloseable { @Override public void close() { if (selfOrChildFailed(context)) { getContextSpecificStore(parentContext).put(CHILD_FAILED, true); } } } record Cleanup(CleanupMode cleanupMode, Supplier deletionStrategy) { private static final Logger logger = LoggerFactory.getLogger(Cleanup.class); void run(Path dir, AnnotatedElementContext elementContext, ExtensionContext extensionContext) throws IOException { if (cleanupMode == NEVER || (cleanupMode == ON_SUCCESS && selfOrChildFailed(extensionContext))) { logger.info(() -> "Skipping cleanup of temp dir %s for %s due to CleanupMode.%s.".formatted(dir, descriptionFor(elementContext.getAnnotatedElement()), cleanupMode.name())); return; } logger.trace(() -> "Cleaning up temp dir " + dir); if (Files.exists(dir)) { deletionStrategy.get().delete(dir, elementContext, extensionContext) // .toException() // .ifPresent(exception -> { throw exception; }); } } } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestInfoParameterResolver.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import java.lang.reflect.Method; import java.util.Optional; import java.util.Set; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.platform.commons.util.ToStringBuilder; /** * {@link ParameterResolver} that resolves the {@link TestInfo} for * the currently executing test. * * @since 5.0 */ class TestInfoParameterResolver implements ParameterResolver { @Override public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { return ExtensionContextScope.TEST_METHOD; } @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return (parameterContext.getParameter().getType() == TestInfo.class); } @Override public TestInfo resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return new DefaultTestInfo(extensionContext); } private static class DefaultTestInfo implements TestInfo { private final String displayName; private final Set tags; private final Optional> testClass; private final Optional testMethod; DefaultTestInfo(ExtensionContext extensionContext) { this.displayName = extensionContext.getDisplayName(); this.tags = extensionContext.getTags(); this.testClass = extensionContext.getTestClass(); this.testMethod = extensionContext.getTestMethod(); } @Override public String getDisplayName() { return this.displayName; } @Override public Set getTags() { return this.tags; } @Override public Optional> getTestClass() { return this.testClass; } @Override public Optional getTestMethod() { return this.testMethod; } @Override public String toString() { // @formatter:off return new ToStringBuilder(this) .append("displayName", this.displayName) .append("tags", this.tags) .append("testClass", nullSafeGet(this.testClass)) .append("testMethod", nullSafeGet(this.testMethod)) .toString(); // @formatter:on } @SuppressWarnings({ "OptionalAssignedToNull", "NullableOptional" }) private static @Nullable Object nullSafeGet(@Nullable Optional optional) { return optional != null ? optional.orElse(null) : null; } } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestReporterParameterResolver.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import org.junit.jupiter.api.TestReporter; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; /** * {@link ParameterResolver} that injects a {@link TestReporter}. * * @since 5.0 */ class TestReporterParameterResolver implements ParameterResolver { @Override public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { return ExtensionContextScope.TEST_METHOD; } @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return (parameterContext.getParameter().getType() == TestReporter.class); } @Override public TestReporter resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return new DefaultTestReporter(extensionContext); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutConfiguration.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static org.junit.jupiter.api.Timeout.DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME; import static org.junit.jupiter.api.Timeout.DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME; import static org.junit.jupiter.api.Timeout.DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME; import static org.junit.jupiter.api.Timeout.DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME; import static org.junit.jupiter.api.Timeout.DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME; import static org.junit.jupiter.api.Timeout.DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME; import static org.junit.jupiter.api.Timeout.DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME; import static org.junit.jupiter.api.Timeout.DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME; import static org.junit.jupiter.api.Timeout.DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME; import static org.junit.jupiter.api.Timeout.DEFAULT_TIMEOUT_PROPERTY_NAME; import static org.junit.jupiter.api.Timeout.DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME; import static org.junit.jupiter.api.Timeout.TIMEOUT_MODE_PROPERTY_NAME; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import org.junit.jupiter.api.Timeout.ThreadMode; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.engine.config.EnumConfigurationParameterConverter; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.RuntimeUtils; /** * @since 5.5 */ class TimeoutConfiguration { private static final Logger logger = LoggerFactory.getLogger(TimeoutConfiguration.class); private final TimeoutDurationParser parser = new TimeoutDurationParser(); private final Map> cache = new ConcurrentHashMap<>(); private final AtomicReference> threadMode = new AtomicReference<>(); private final ExtensionContext extensionContext; private final boolean timeoutDisabled; TimeoutConfiguration(ExtensionContext extensionContext) { this.extensionContext = extensionContext; this.timeoutDisabled = new EnumConfigurationParameterConverter<>(TimeoutMode.class, "timeout mode") // .get(extensionContext, TIMEOUT_MODE_PROPERTY_NAME) // .map(TimeoutMode::isTimeoutDisabled) // .orElse(false); } boolean isTimeoutDisabled() { return timeoutDisabled; } Optional getDefaultTestMethodTimeout() { return parseOrDefault(DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME, this::getDefaultTestableMethodTimeout); } Optional getDefaultTestTemplateMethodTimeout() { return parseOrDefault(DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME, this::getDefaultTestableMethodTimeout); } Optional getDefaultTestFactoryMethodTimeout() { return parseOrDefault(DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME, this::getDefaultTestableMethodTimeout); } Optional getDefaultBeforeAllMethodTimeout() { return parseOrDefault(DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME, this::getDefaultLifecycleMethodTimeout); } Optional getDefaultBeforeEachMethodTimeout() { return parseOrDefault(DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME, this::getDefaultLifecycleMethodTimeout); } Optional getDefaultAfterEachMethodTimeout() { return parseOrDefault(DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME, this::getDefaultLifecycleMethodTimeout); } Optional getDefaultAfterAllMethodTimeout() { return parseOrDefault(DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME, this::getDefaultLifecycleMethodTimeout); } private Optional getDefaultTestableMethodTimeout() { return parseOrDefault(DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME, this::getDefaultTimeout); } private Optional getDefaultLifecycleMethodTimeout() { return parseOrDefault(DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME, this::getDefaultTimeout); } private Optional getDefaultTimeout() { return parseTimeoutDuration(DEFAULT_TIMEOUT_PROPERTY_NAME); } private Optional parseOrDefault(String propertyName, Supplier> defaultSupplier) { Optional timeoutConfiguration = parseTimeoutDuration(propertyName); return timeoutConfiguration.isPresent() ? timeoutConfiguration : defaultSupplier.get(); } private Optional parseTimeoutDuration(String propertyName) { return cache.computeIfAbsent(propertyName, key -> extensionContext.getConfigurationParameter(key).map(value -> { try { return parser.parse(value); } catch (Exception e) { logger.warn(e, () -> "Ignored invalid timeout '%s' set via the '%s' configuration parameter.".formatted(value, key)); return null; } })); } Optional getDefaultTimeoutThreadMode() { if (threadMode.get() != null) { return threadMode.get(); } else { Optional configuredThreadMode = parseTimeoutThreadModeConfiguration(); threadMode.set(configuredThreadMode); return configuredThreadMode; } } private Optional parseTimeoutThreadModeConfiguration() { return new EnumConfigurationParameterConverter<>(ThreadMode.class, "timeout thread mode") // .get(extensionContext, DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME); } private enum TimeoutMode { ENABLED { @Override boolean isTimeoutDisabled() { return false; } }, DISABLED { @Override boolean isTimeoutDisabled() { return true; } }, DISABLED_ON_DEBUG { @Override boolean isTimeoutDisabled() { return RuntimeUtils.isDebugMode(); } }; abstract boolean isTimeoutDisabled(); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutDuration.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import java.time.Duration; import java.time.temporal.ChronoUnit; import java.util.Locale; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Timeout; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.Preconditions; /** * @since 5.5 */ record TimeoutDuration(long value, TimeUnit unit) { static TimeoutDuration from(Timeout timeout) { return new TimeoutDuration(timeout.value(), timeout.unit()); } TimeoutDuration(long value, TimeUnit unit) { Preconditions.condition(value > 0, () -> "timeout duration must be a positive number: " + value); this.value = value; this.unit = Preconditions.notNull(unit, "timeout unit must not be null"); } @Override public String toString() { String label = unit.name().toLowerCase(Locale.ROOT); if (value == 1 && label.endsWith("s")) { label = label.substring(0, label.length() - 1); } return value + " " + label; } public Duration toDuration() { return Duration.of(value, toChronoUnit()); } private ChronoUnit toChronoUnit() { return switch (unit) { case NANOSECONDS -> ChronoUnit.NANOS; case MICROSECONDS -> ChronoUnit.MICROS; case MILLISECONDS -> ChronoUnit.MILLIS; case SECONDS -> ChronoUnit.SECONDS; case MINUTES -> ChronoUnit.MINUTES; case HOURS -> ChronoUnit.HOURS; case DAYS -> ChronoUnit.DAYS; default -> throw new JUnitException("Could not map TimeUnit " + unit + " to ChronoUnit"); }; } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutDurationParser.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static java.util.Objects.requireNonNull; import static java.util.concurrent.TimeUnit.DAYS; import static java.util.concurrent.TimeUnit.HOURS; import static java.util.concurrent.TimeUnit.MICROSECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.regex.Pattern.CASE_INSENSITIVE; import static java.util.regex.Pattern.UNICODE_CASE; import java.time.format.DateTimeParseException; import java.util.Locale; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @since 5.5 */ class TimeoutDurationParser { private static final Pattern PATTERN = Pattern.compile("([1-9]\\d*) ?((?:[nμm]?s)|m|h|d)?", CASE_INSENSITIVE | UNICODE_CASE); private static final Map UNITS_BY_ABBREVIATION = Map.of( // "ns", NANOSECONDS, // "μs", MICROSECONDS, // "ms", MILLISECONDS, // "s", SECONDS, // "m", MINUTES, // "h", HOURS, // "d", DAYS // ); TimeoutDuration parse(CharSequence text) throws DateTimeParseException { Matcher matcher = PATTERN.matcher(text); if (matcher.matches()) { long value = Long.parseLong(matcher.group(1)); String unitAbbreviation = matcher.group(2); TimeUnit unit = unitAbbreviation == null ? SECONDS : requireNonNull(UNITS_BY_ABBREVIATION.get(unitAbbreviation.toLowerCase(Locale.ENGLISH))); return new TimeoutDuration(value, unit); } throw new DateTimeParseException("Timeout duration is not in the expected format ( [ns|μs|ms|s|m|h|d])", text, 0); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactory.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import java.util.concurrent.TimeoutException; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; /** * @since 5.9 */ class TimeoutExceptionFactory { private TimeoutExceptionFactory() { } static TimeoutException create(String methodSignature, TimeoutDuration timeoutDuration, @Nullable Throwable failure) { String message = "%s timed out after %s".formatted( Preconditions.notNull(methodSignature, "method signature must not be null"), Preconditions.notNull(timeoutDuration, "timeout duration must not be null")); TimeoutException timeoutException = new TimeoutException(message); if (failure != null) { timeoutException.addSuppressed(failure); } return timeoutException; } static TimeoutException create(String methodSignature, TimeoutDuration timeoutDuration) { return create(methodSignature, timeoutDuration, null); } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExtension.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static org.junit.jupiter.api.Timeout.ThreadMode.SAME_THREAD; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.util.Optional; import java.util.function.Function; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.Timeout.ThreadMode; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.InvocationInterceptor; import org.junit.jupiter.api.extension.ReflectiveInvocationContext; import org.junit.jupiter.engine.extension.TimeoutInvocationFactory.TimeoutInvocationParameters; import org.junit.platform.commons.support.AnnotationSupport; import org.junit.platform.commons.util.ClassUtils; import org.junit.platform.commons.util.ReflectionUtils; /** * @since 5.5 */ class TimeoutExtension implements BeforeAllCallback, BeforeEachCallback, InvocationInterceptor { private static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace.create(Timeout.class); private static final String TESTABLE_METHOD_TIMEOUT_KEY = "testable_method_timeout_from_annotation"; private static final String TESTABLE_METHOD_TIMEOUT_THREAD_MODE_KEY = "testable_method_timeout_thread_mode_from_annotation"; private static final String GLOBAL_TIMEOUT_CONFIG_KEY = "global_timeout_config"; @Override public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { return ExtensionContextScope.TEST_METHOD; } @Override public void beforeAll(ExtensionContext context) { readAndStoreTimeoutSoChildrenInheritIt(context); } @Override public void beforeEach(ExtensionContext context) { readAndStoreTimeoutSoChildrenInheritIt(context); } private void readAndStoreTimeoutSoChildrenInheritIt(ExtensionContext context) { readTimeoutFromAnnotation(context.getElement()).ifPresent( timeout -> context.getStore(NAMESPACE).put(TESTABLE_METHOD_TIMEOUT_KEY, timeout)); readTimeoutThreadModeFromAnnotation(context.getElement()).ifPresent( timeoutThreadMode -> context.getStore(NAMESPACE).put(TESTABLE_METHOD_TIMEOUT_THREAD_MODE_KEY, timeoutThreadMode)); } @Override public void interceptBeforeAllMethod(Invocation<@Nullable Void> invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { interceptLifecycleMethod(invocation, invocationContext, extensionContext, TimeoutConfiguration::getDefaultBeforeAllMethodTimeout); } @Override public void interceptBeforeEachMethod(Invocation<@Nullable Void> invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { interceptLifecycleMethod(invocation, invocationContext, extensionContext, TimeoutConfiguration::getDefaultBeforeEachMethodTimeout); } @Override public void interceptTestMethod(Invocation<@Nullable Void> invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { this.<@Nullable Void> interceptTestableMethod(invocation, invocationContext, extensionContext, TimeoutConfiguration::getDefaultTestMethodTimeout); } @Override public void interceptTestTemplateMethod(Invocation<@Nullable Void> invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { this.<@Nullable Void> interceptTestableMethod(invocation, invocationContext, extensionContext, TimeoutConfiguration::getDefaultTestTemplateMethodTimeout); } @Override public T interceptTestFactoryMethod(Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { return this. interceptTestableMethod(invocation, invocationContext, extensionContext, TimeoutConfiguration::getDefaultTestFactoryMethodTimeout); } @Override public void interceptAfterEachMethod(Invocation<@Nullable Void> invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { interceptLifecycleMethod(invocation, invocationContext, extensionContext, TimeoutConfiguration::getDefaultAfterEachMethodTimeout); } @Override public void interceptAfterAllMethod(Invocation<@Nullable Void> invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { interceptLifecycleMethod(invocation, invocationContext, extensionContext, TimeoutConfiguration::getDefaultAfterAllMethodTimeout); } private void interceptLifecycleMethod(Invocation<@Nullable Void> invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext, TimeoutProvider defaultTimeoutProvider) throws Throwable { TimeoutDuration timeout = readTimeoutFromAnnotation(Optional.of(invocationContext.getExecutable())).orElse( null); this.<@Nullable Void> intercept(invocation, invocationContext, extensionContext, timeout, defaultTimeoutProvider); } @SuppressWarnings("OptionalUsedAsFieldOrParameterType") private Optional readTimeoutFromAnnotation(Optional element) { return AnnotationSupport.findAnnotation(element, Timeout.class).map(TimeoutDuration::from); } @SuppressWarnings("OptionalUsedAsFieldOrParameterType") private Optional readTimeoutThreadModeFromAnnotation(Optional element) { return AnnotationSupport.findAnnotation(element, Timeout.class).map(Timeout::threadMode); } private T interceptTestableMethod(Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext, TimeoutProvider defaultTimeoutProvider) throws Throwable { TimeoutDuration timeout = extensionContext.getStore(NAMESPACE).get(TESTABLE_METHOD_TIMEOUT_KEY, TimeoutDuration.class); return intercept(invocation, invocationContext, extensionContext, timeout, defaultTimeoutProvider); } private T intercept(Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext, @Nullable TimeoutDuration explicitTimeout, TimeoutProvider defaultTimeoutProvider) throws Throwable { TimeoutConfiguration timeoutConfiguration = getGlobalTimeoutConfiguration(extensionContext); if (timeoutConfiguration.isTimeoutDisabled()) { return invocation.proceed(); } TimeoutDuration timeout = explicitTimeout == null ? getDefaultTimeout(defaultTimeoutProvider, timeoutConfiguration) : explicitTimeout; return decorate(invocation, invocationContext, extensionContext, timeout, timeoutConfiguration).proceed(); } private @Nullable TimeoutDuration getDefaultTimeout(TimeoutProvider defaultTimeoutProvider, TimeoutConfiguration timeoutConfiguration) { return defaultTimeoutProvider.apply(timeoutConfiguration).orElse(null); } private TimeoutConfiguration getGlobalTimeoutConfiguration(ExtensionContext extensionContext) { ExtensionContext root = extensionContext.getRoot(); return root.getStore(NAMESPACE).computeIfAbsent(GLOBAL_TIMEOUT_CONFIG_KEY, __ -> new TimeoutConfiguration(root), TimeoutConfiguration.class); } private Invocation decorate(Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext, @Nullable TimeoutDuration timeout, TimeoutConfiguration timeoutConfiguration) { if (timeout == null) { return invocation; } ThreadMode threadMode = resolveTimeoutThreadMode(extensionContext, timeoutConfiguration); return new TimeoutInvocationFactory(extensionContext.getRoot().getStore(NAMESPACE)).create(threadMode, new TimeoutInvocationParameters<>(invocation, timeout, () -> describe(invocationContext, extensionContext), PreInterruptCallbackInvocationFactory.create((ExtensionContextInternal) extensionContext))); } private ThreadMode resolveTimeoutThreadMode(ExtensionContext extensionContext, TimeoutConfiguration timeoutConfiguration) { ThreadMode annotationThreadMode = getAnnotationThreadMode(extensionContext); if (annotationThreadMode == null || annotationThreadMode == ThreadMode.INFERRED) { return timeoutConfiguration.getDefaultTimeoutThreadMode().orElse(SAME_THREAD); } return annotationThreadMode; } private @Nullable ThreadMode getAnnotationThreadMode(ExtensionContext extensionContext) { return extensionContext.getStore(NAMESPACE).get(TESTABLE_METHOD_TIMEOUT_THREAD_MODE_KEY, ThreadMode.class); } private String describe(ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) { Method method = invocationContext.getExecutable(); Optional> testClass = extensionContext.getTestClass(); if (testClass.isPresent() && invocationContext.getTargetClass().equals(testClass.get())) { return "%s(%s)".formatted(method.getName(), ClassUtils.nullSafeToString(method.getParameterTypes())); } return ReflectionUtils.getFullyQualifiedMethodName(invocationContext.getTargetClass(), method); } @FunctionalInterface private interface TimeoutProvider extends Function> { } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactory.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import org.junit.jupiter.api.Timeout.ThreadMode; import org.junit.jupiter.api.extension.ExtensionContext.Store; import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.Preconditions; /** * @since 5.9 */ class TimeoutInvocationFactory { private final Store store; TimeoutInvocationFactory(Store store) { this.store = Preconditions.notNull(store, "store must not be null"); } Invocation create(ThreadMode threadMode, TimeoutInvocationParameters timeoutInvocationParameters) { Preconditions.notNull(threadMode, "thread mode must not be null"); Preconditions.condition(threadMode != ThreadMode.INFERRED, "thread mode must not be INFERRED"); Preconditions.notNull(timeoutInvocationParameters, "timeout invocation parameters must not be null"); if (threadMode == ThreadMode.SEPARATE_THREAD) { return new SeparateThreadTimeoutInvocation<>(timeoutInvocationParameters.getInvocation(), timeoutInvocationParameters.getTimeoutDuration(), timeoutInvocationParameters.getDescriptionSupplier(), timeoutInvocationParameters.getPreInterruptCallback()); } return new SameThreadTimeoutInvocation<>(timeoutInvocationParameters.getInvocation(), timeoutInvocationParameters.getTimeoutDuration(), getThreadExecutorForSameThreadInvocation(), timeoutInvocationParameters.getDescriptionSupplier(), timeoutInvocationParameters.getPreInterruptCallback()); } private ScheduledExecutorService getThreadExecutorForSameThreadInvocation() { return store.computeIfAbsent(SingleThreadExecutorResource.class).get(); } @SuppressWarnings({ "deprecation", "try" }) private abstract static class ExecutorResource implements Store.CloseableResource, AutoCloseable { private final ScheduledExecutorService executor; ExecutorResource(ScheduledExecutorService executor) { this.executor = executor; } ScheduledExecutorService get() { return executor; } @Override public void close() throws Exception { executor.shutdown(); boolean terminated = executor.awaitTermination(5, TimeUnit.SECONDS); if (!terminated) { executor.shutdownNow(); throw new JUnitException("Scheduled executor could not be stopped in an orderly manner"); } } } @SuppressWarnings("try") static class SingleThreadExecutorResource extends ExecutorResource { @SuppressWarnings({ "unused", "ThreadPriorityCheck" }) SingleThreadExecutorResource() { super(Executors.newSingleThreadScheduledExecutor(runnable -> { Thread thread = new Thread(runnable, "junit-jupiter-timeout-watcher"); thread.setPriority(Thread.MAX_PRIORITY); return thread; })); } } static class TimeoutInvocationParameters { private final Invocation invocation; private final TimeoutDuration timeout; private final Supplier descriptionSupplier; private final PreInterruptCallbackInvocation preInterruptCallback; TimeoutInvocationParameters(Invocation invocation, TimeoutDuration timeout, Supplier descriptionSupplier, PreInterruptCallbackInvocation preInterruptCallback) { this.invocation = Preconditions.notNull(invocation, "invocation must not be null"); this.timeout = Preconditions.notNull(timeout, "timeout must not be null"); this.descriptionSupplier = Preconditions.notNull(descriptionSupplier, "description supplier must not be null"); this.preInterruptCallback = Preconditions.notNull(preInterruptCallback, "preInterruptCallback must not be null"); } public Invocation getInvocation() { return invocation; } public TimeoutDuration getTimeoutDuration() { return timeout; } public Supplier getDescriptionSupplier() { return descriptionSupplier; } public PreInterruptCallbackInvocation getPreInterruptCallback() { return preInterruptCallback; } } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Test extensions specific to the JUnit Jupiter test engine. */ @NullMarked package org.junit.jupiter.engine.extension; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Core package for the JUnit Jupiter test engine. */ @NullMarked package org.junit.jupiter.engine; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/JupiterThrowableCollectorFactory.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.support; import static org.apiguardian.api.API.Status.INTERNAL; import org.apiguardian.api.API; import org.junit.platform.engine.support.hierarchical.ThrowableCollector; /** * Factory for creating {@link ThrowableCollector ThrowableCollectors} within * the JUnit Jupiter test engine. * * @since 5.4 * @see ThrowableCollector */ @API(status = INTERNAL, since = "5.4") public class JupiterThrowableCollectorFactory { /** * Create a new {@link ThrowableCollector} that treats instances of the * OTA's {@link org.opentest4j.TestAbortedException} and JUnit 4's * {@code org.junit.AssumptionViolatedException} as aborting. */ public static ThrowableCollector createThrowableCollector() { return new OpenTest4JAndJUnit4AwareThrowableCollector(); } private JupiterThrowableCollectorFactory() { } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/MethodReflectionUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.support; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.commons.util.KotlinReflectionUtils.getKotlinSuspendingFunctionGenericReturnType; import static org.junit.platform.commons.util.KotlinReflectionUtils.getKotlinSuspendingFunctionReturnType; import static org.junit.platform.commons.util.KotlinReflectionUtils.invokeKotlinFunction; import static org.junit.platform.commons.util.KotlinReflectionUtils.invokeKotlinSuspendingFunction; import static org.junit.platform.commons.util.KotlinReflectionUtils.isKotlinSuspendingFunction; import static org.junit.platform.commons.util.KotlinReflectionUtils.isKotlinType; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.Arrays; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.KotlinReflectionUtils; @API(status = INTERNAL, since = "6.0") public class MethodReflectionUtils { public static Class getReturnType(Method method) { return isKotlinSuspendingFunction(method) // ? getKotlinSuspendingFunctionReturnType(method) // : method.getReturnType(); } public static Type getGenericReturnType(Method method) { return isKotlinSuspendingFunction(method) // ? getKotlinSuspendingFunctionGenericReturnType(method) // : method.getGenericReturnType(); } public static @Nullable Object invoke(Method method, @Nullable Object target, @Nullable Object[] arguments) { if (isKotlinSuspendingFunction(method)) { return invokeKotlinSuspendingFunction(method, target, arguments); } if (isKotlinType(method.getDeclaringClass()) && KotlinReflectionUtils.isKotlinReflectPresent() && hasInlineTypeArgument(arguments)) { return invokeKotlinFunction(method, target, arguments); } return ReflectionSupport.invokeMethod(method, target, arguments); } private static boolean hasInlineTypeArgument(@Nullable Object[] arguments) { if (!KotlinReflectionUtils.isKotlinReflectPresent()) { return false; } return arguments.length > 0 && Arrays.stream(arguments).anyMatch(KotlinReflectionUtils::isInstanceOfInlineType); } private MethodReflectionUtils() { } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollector.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.support; import java.util.function.Predicate; import java.util.function.Supplier; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.UnrecoverableExceptions; import org.junit.platform.engine.support.hierarchical.ThrowableCollector; import org.opentest4j.TestAbortedException; /** * Specialization of {@link ThrowableCollector} that treats instances of the * OTA's {@link org.opentest4j.TestAbortedException} and JUnit 4's * {@code org.junit.AssumptionViolatedException} as aborting. * * @since 5.4 * @see ThrowableCollector * @see org.junit.platform.engine.support.hierarchical.OpenTest4JAwareThrowableCollector */ class OpenTest4JAndJUnit4AwareThrowableCollector extends ThrowableCollector { private static final Logger logger = LoggerFactory.getLogger(OpenTest4JAndJUnit4AwareThrowableCollector.class); private static final String ASSUMPTION_VIOLATED_EXCEPTION = "org.junit.internal.AssumptionViolatedException"; private static final String COMMON_FAILURE_MESSAGE = "Failed to load class " + ASSUMPTION_VIOLATED_EXCEPTION + ": only supporting " + TestAbortedException.class.getName() + " for aborted execution."; private static final Predicate abortedExecutionPredicate = createAbortedExecutionPredicate(); OpenTest4JAndJUnit4AwareThrowableCollector() { super(abortedExecutionPredicate); } private static Predicate createAbortedExecutionPredicate() { Predicate otaPredicate = TestAbortedException.class::isInstance; // Additionally support JUnit 4's AssumptionViolatedException? try { Class clazz = ReflectionSupport.tryToLoadClass(ASSUMPTION_VIOLATED_EXCEPTION).get(); if (clazz != null) { return otaPredicate.or(clazz::isInstance); } } catch (Throwable throwable) { UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); Supplier messageSupplier = (throwable instanceof NoClassDefFoundError) ? () -> COMMON_FAILURE_MESSAGE + " Note that " + ASSUMPTION_VIOLATED_EXCEPTION + " requires that Hamcrest is on the classpath." : () -> COMMON_FAILURE_MESSAGE; logger.debug(throwable, messageSupplier); } // Else just OTA's TestAbortedException return otaPredicate; } } ================================================ FILE: junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Internal support classes for the JUnit Jupiter test engine. */ @NullMarked package org.junit.jupiter.engine.support; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-jupiter-engine/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine ================================================ org.junit.jupiter.engine.JupiterTestEngine ================================================ FILE: junit-jupiter-engine/src/test/README.md ================================================ For compatibility with the Eclipse IDE, the test for this module are in the `jupiter-tests` project. ================================================ FILE: junit-jupiter-engine/src/testFixtures/java/org/junit/jupiter/engine/discovery/JupiterUniqueIdBuilder.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.discovery; import static org.junit.platform.commons.util.ReflectionUtils.isInnerClass; import org.junit.jupiter.api.ClassTemplate; import org.junit.jupiter.engine.descriptor.ClassTemplateInvocationTestDescriptor; import org.junit.jupiter.engine.descriptor.ClassTemplateTestDescriptor; import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; import org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor; import org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor; import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor; import org.junit.jupiter.engine.descriptor.TestTemplateInvocationTestDescriptor; import org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor; import org.junit.platform.commons.support.AnnotationSupport; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.engine.UniqueId; /** * Test data builder for unique IDs for JupiterTestEngine. * * Used to decouple tests from concrete unique ID strings. * * @since 5.0 */ public class JupiterUniqueIdBuilder { public static UniqueId uniqueIdForClass(Class clazz) { if (isInnerClass(clazz)) { var segmentType = classSegmentType(clazz, NestedClassTestDescriptor.SEGMENT_TYPE, ClassTemplateTestDescriptor.NESTED_CLASS_SEGMENT_TYPE); return uniqueIdForClass(clazz.getEnclosingClass()).append(segmentType, clazz.getSimpleName()); } return uniqueIdForStaticClass(clazz.getName()); } public static UniqueId uniqueIdForStaticClass(String className) { return engineId().append(staticClassSegmentType(className), className); } private static String staticClassSegmentType(String className) { return ReflectionSupport.tryToLoadClass(className).toOptional() // .map(it -> classSegmentType(it, ClassTestDescriptor.SEGMENT_TYPE, ClassTemplateTestDescriptor.STANDALONE_CLASS_SEGMENT_TYPE)) // .orElse(ClassTestDescriptor.SEGMENT_TYPE); } private static String classSegmentType(Class clazz, String regularSegmentType, String classTemplateSegmentType) { return AnnotationSupport.isAnnotated(clazz, ClassTemplate.class) // ? classTemplateSegmentType // : regularSegmentType; } public static UniqueId uniqueIdForMethod(Class clazz, String methodPart) { return uniqueIdForClass(clazz).append(TestMethodTestDescriptor.SEGMENT_TYPE, methodPart); } public static UniqueId uniqueIdForTestFactoryMethod(Class clazz, String methodPart) { return uniqueIdForClass(clazz).append(TestFactoryTestDescriptor.SEGMENT_TYPE, methodPart); } public static UniqueId uniqueIdForTestTemplateMethod(Class clazz, String methodPart) { return uniqueIdForClass(clazz).append(TestTemplateTestDescriptor.SEGMENT_TYPE, methodPart); } public static UniqueId appendTestTemplateInvocationSegment(UniqueId parentId, int index) { return parentId.append(TestTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#" + index); } public static UniqueId appendClassTemplateInvocationSegment(UniqueId parentId, int index) { return parentId.append(ClassTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#" + index); } public static UniqueId engineId() { return UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); } private JupiterUniqueIdBuilder() { } } ================================================ FILE: junit-jupiter-migrationsupport/README.md ================================================ # Module junit-jupiter-migrationsupport This module provides support for JUnit 4 rules within JUnit Jupiter. Currently, this support is limited to subclasses of the ```org.junit.rules.Verifier``` and ```org.junit.rules.ExternalResource``` rules of JUnit 4, respectively. Please note that a general support for arbitrary ```org.junit.rules.TestRule``` implementations is not possible within the JUnit Jupiter extension model. The main purpose of this module is to facilitate the migration of large JUnit 4 codebases containing such JUnit 4 rules by minimizing the effort needed to run such legacy tests under JUnit Platform. By using one of the two provided class-level extensions on a test class such rules in legacy code bases can be left unchanged including the JUnit 4 rule import statements. However, if you intend to develop a *new* extension for JUnit please use the new extension model of JUnit Jupiter instead of the rule-based model of JUnit 4. ================================================ FILE: junit-jupiter-migrationsupport/junit-jupiter-migrationsupport.gradle.kts ================================================ plugins { id("junitbuild.java-library-conventions") id("junitbuild.junit4-compatibility") } description = "JUnit Jupiter Migration Support" dependencies { api(platform(projects.junitBom)) api(libs.junit4) api(projects.junitJupiterApi) compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) osgiVerification(projects.junitJupiterEngine) osgiVerification(projects.junitPlatformLauncher) } tasks { compileJava { options.compilerArgs.add("-Xlint:-requires-automatic,-requires-transitive-automatic") // JUnit 4 } jar { bundle { val importAPIGuardian: String by extra val importJSpecify: String by extra val importCommonsLogging: String by extra bnd(""" # Import JUnit4 packages with a version Import-Package: \ $importAPIGuardian,\ $importJSpecify,\ $importCommonsLogging,\ org.junit;version="[${libs.versions.junit4Min.get()},5)",\ org.junit.rules;version="[${libs.versions.junit4Min.get()},5)",\ * """) } } } ================================================ FILE: junit-jupiter-migrationsupport/src/main/java/module-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Support for migrating from JUnit 4 to JUnit Jupiter. * * @since 5.0 * @deprecated Please migrate to the corresponding APIs and extensions provided * by JUnit Jupiter. */ @Deprecated(forRemoval = true) module org.junit.jupiter.migrationsupport { requires static transitive org.apiguardian.api; requires static transitive org.jspecify; requires transitive junit; // 4 requires transitive org.junit.jupiter.api; requires org.junit.platform.commons; exports org.junit.jupiter.migrationsupport; exports org.junit.jupiter.migrationsupport.conditions; exports org.junit.jupiter.migrationsupport.rules; exports org.junit.jupiter.migrationsupport.rules.adapter; exports org.junit.jupiter.migrationsupport.rules.member; } ================================================ FILE: junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/EnableJUnit4MigrationSupport.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.migrationsupport; import static org.apiguardian.api.API.Status.DEPRECATED; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.migrationsupport.conditions.IgnoreCondition; import org.junit.jupiter.migrationsupport.rules.EnableRuleMigrationSupport; import org.junit.jupiter.migrationsupport.rules.ExpectedExceptionSupport; import org.junit.jupiter.migrationsupport.rules.ExternalResourceSupport; import org.junit.jupiter.migrationsupport.rules.VerifierSupport; /** * {@code EnableJUnit4MigrationSupport} is a class-level annotation that * enables all JUnit 4 migration support within JUnit Jupiter. * *

Specifically, this annotation registers all extensions supported by * {@link EnableRuleMigrationSupport @EnableRuleMigrationSupport} and provides * support for JUnit 4's {@link org.junit.Ignore @Ignore} annotation for * disabling test classes and test methods. * *

Technically speaking, {@code @EnableJUnit4MigrationSupport} is a composed * annotation which registers all of the following migration extensions: * {@link VerifierSupport}, {@link ExternalResourceSupport}, * {@link ExpectedExceptionSupport}, and {@link IgnoreCondition}. Note, however, * that you can optionally register one or more of these extensions explicitly * without the use of this composed annotation. * * @since 5.4 * @see ExternalResourceSupport * @see VerifierSupport * @see ExpectedExceptionSupport * @see IgnoreCondition * @see EnableRuleMigrationSupport * @deprecated Please migrate to Jupiter extensions and use * {@link org.junit.jupiter.api.Disabled @Disabled} instead. */ @SuppressWarnings("removal") @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @API(status = DEPRECATED, since = "6.0") @Deprecated(since = "6.0", forRemoval = true) @EnableRuleMigrationSupport @ExtendWith(IgnoreCondition.class) public @interface EnableJUnit4MigrationSupport { } ================================================ FILE: junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/conditions/IgnoreCondition.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.migrationsupport.conditions; import static org.apiguardian.api.API.Status.DEPRECATED; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import java.lang.reflect.AnnotatedElement; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.Ignore; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.platform.commons.util.StringUtils; /** * {@link ExecutionCondition} that supports JUnit 4's {@link Ignore @Ignore} * annotation. * * @since 5.4 * @see org.junit.Ignore @Ignore * @see org.junit.jupiter.api.Disabled @Disabled * @see #evaluateExecutionCondition(ExtensionContext) * @see org.junit.jupiter.migrationsupport.EnableJUnit4MigrationSupport * @deprecated Please use {@link org.junit.jupiter.api.Disabled @Disabled} instead. */ @API(status = DEPRECATED, since = "6.0") @Deprecated(since = "6.0", forRemoval = true) public class IgnoreCondition implements ExecutionCondition { private static final ConditionEvaluationResult ENABLED = // ConditionEvaluationResult.enabled("@org.junit.Ignore is not present"); public IgnoreCondition() { } /** * Containers/tests are disabled if {@link Ignore @Ignore} is present on * the test class or method. */ @Override public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { AnnotatedElement element = context.getElement().orElse(null); return findAnnotation(element, Ignore.class) // .map(annotation -> toResult(element, annotation)) // .orElse(ENABLED); } private ConditionEvaluationResult toResult(@Nullable AnnotatedElement element, Ignore annotation) { String value = annotation.value(); String reason = StringUtils.isNotBlank(value) ? value : element + " is disabled via @org.junit.Ignore"; return ConditionEvaluationResult.disabled(reason); } } ================================================ FILE: junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/conditions/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Deprecated extensions that provide support for conditional test * execution features of JUnit 4 (e.g., the {@link org.junit.Ignore @Ignore} * annotation) within JUnit Jupiter. */ @NullMarked package org.junit.jupiter.migrationsupport.conditions; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Deprecated support for migrating from JUnit 4 to JUnit Jupiter. */ @NullMarked package org.junit.jupiter.migrationsupport; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupport.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.migrationsupport.rules; import static org.apiguardian.api.API.Status.DEPRECATED; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ExtendWith; /** * This class-level annotation enables native JUnit 4 rule support * within JUnit Jupiter. * *

Currently, rules of type {@code Verifier}, {@code ExternalResource}, * and {@code ExpectedException} rules are supported. * *

{@code @EnableRuleMigrationSupport} is a composed annotation which * enables all supported extensions: {@link VerifierSupport}, * {@link ExternalResourceSupport}, and {@link ExpectedExceptionSupport}. * * @since 5.0 * @see ExternalResourceSupport * @see VerifierSupport * @see ExpectedExceptionSupport * @see org.junit.jupiter.migrationsupport.EnableJUnit4MigrationSupport * @deprecated Please migrate to Jupiter extensions. */ @SuppressWarnings("removal") @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @API(status = DEPRECATED, since = "6.0") @Deprecated(since = "6.0", forRemoval = true) @ExtendWith(ExternalResourceSupport.class) @ExtendWith(VerifierSupport.class) @ExtendWith(ExpectedExceptionSupport.class) public @interface EnableRuleMigrationSupport { } ================================================ FILE: junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/ExpectedExceptionSupport.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.migrationsupport.rules; import static org.apiguardian.api.API.Status.DEPRECATED; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Namespace; import org.junit.jupiter.api.extension.ExtensionContext.Store; import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.migrationsupport.rules.adapter.ExpectedExceptionAdapter; import org.junit.rules.ExpectedException; /** * This {@code Extension} provides native support for the * {@link ExpectedException} rule from JUnit 4. * *

By using this class-level extension on a test class, * {@code ExpectedException} can continue to be used. * *

However, you should rather switch to * {@link org.junit.jupiter.api.Assertions#assertThrows} for new code. * * @since 5.0 * @see org.junit.jupiter.api.Assertions#assertThrows * @see org.junit.rules.ExpectedException * @see org.junit.rules.TestRule * @see org.junit.Rule * @deprecated Please use * {@link org.junit.jupiter.api.Assertions#assertThrows(Class, Executable)} * instead. */ @SuppressWarnings("removal") @API(status = DEPRECATED, since = "6.0") @Deprecated(since = "6.0", forRemoval = true) public class ExpectedExceptionSupport implements AfterEachCallback, TestExecutionExceptionHandler { private static final String EXCEPTION_WAS_HANDLED = "exceptionWasHandled"; private final TestRuleSupport support = new TestRuleSupport(ExpectedExceptionAdapter::new, ExpectedException.class); public ExpectedExceptionSupport() { } @Override public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { getStore(context).put(EXCEPTION_WAS_HANDLED, true); this.support.handleTestExecutionException(context, throwable); } @Override public void afterEach(ExtensionContext context) throws Exception { boolean handled = getStore(context).computeIfAbsent(EXCEPTION_WAS_HANDLED, key -> false, Boolean.class); if (!handled) { this.support.afterEach(context); } } private Store getStore(ExtensionContext context) { return context.getStore(Namespace.create(getClass(), context.getUniqueId())); } } ================================================ FILE: junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupport.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.migrationsupport.rules; import static org.apiguardian.api.API.Status.DEPRECATED; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.migrationsupport.rules.adapter.ExternalResourceAdapter; import org.junit.rules.ExternalResource; /** * This {@code Extension} provides native support for subclasses of * the {@link ExternalResource} rule from JUnit 4. * *

{@code @Rule}-annotated fields as well as methods are supported. * *

By using this class-level extension on a test class such * {@code ExternalResource} implementations in legacy code bases * can be left unchanged including the JUnit 4 rule import statements. * *

However, if you intend to develop a new extension for * JUnit please use the new extension model of JUnit Jupiter instead * of the rule-based model of JUnit 4. * * @since 5.0 * @see org.junit.rules.ExternalResource * @see org.junit.rules.TestRule * @see org.junit.Rule * @deprecated Please use {@link org.junit.jupiter.api.AutoClose @AutoClose} instead. */ @SuppressWarnings("removal") @API(status = DEPRECATED, since = "6.0") @Deprecated(since = "6.0", forRemoval = true) public class ExternalResourceSupport implements BeforeEachCallback, AfterEachCallback { private final TestRuleSupport support = new TestRuleSupport(ExternalResourceAdapter::new, ExternalResource.class); public ExternalResourceSupport() { } @Override public void beforeEach(ExtensionContext context) throws Exception { this.support.beforeEach(context); } @Override public void afterEach(ExtensionContext context) throws Exception { this.support.afterEach(context); } } ================================================ FILE: junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/TestRuleSupport.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.migrationsupport.rules; import static java.util.Collections.unmodifiableList; import static org.junit.platform.commons.support.AnnotationSupport.findPublicAnnotatedFields; import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated; import static org.junit.platform.commons.support.HierarchyTraversalMode.TOP_DOWN; import static org.junit.platform.commons.support.ReflectionSupport.findMethods; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.function.Predicate; import org.junit.Rule; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Namespace; import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; import org.junit.jupiter.migrationsupport.rules.adapter.AbstractTestRuleAdapter; import org.junit.jupiter.migrationsupport.rules.adapter.GenericBeforeAndAfterAdvice; import org.junit.jupiter.migrationsupport.rules.member.TestRuleAnnotatedField; import org.junit.jupiter.migrationsupport.rules.member.TestRuleAnnotatedMember; import org.junit.jupiter.migrationsupport.rules.member.TestRuleAnnotatedMethod; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.rules.TestRule; /** * @since 5.0 */ class TestRuleSupport implements BeforeEachCallback, TestExecutionExceptionHandler, AfterEachCallback { private final Class ruleType; private final Function adapterGenerator; TestRuleSupport(Function adapterGenerator, Class ruleType) { this.adapterGenerator = adapterGenerator; this.ruleType = ruleType; } /** * @see org.junit.runners.BlockJUnit4ClassRunner#withRules * @see org.junit.rules.RunRules */ @SuppressWarnings("JavadocReference") private List findRuleAnnotatedMembers(Object testInstance) { List result = new ArrayList<>(); // @formatter:off // Instantiate rules from methods by calling them findAnnotatedMethods(testInstance).stream() .map(method -> new TestRuleAnnotatedMethod(testInstance, method)) .forEach(result::add); // Fields are already instantiated because we have a test instance findAnnotatedFields(testInstance).stream() .map(field -> new TestRuleAnnotatedField(testInstance, field)) .forEach(result::add); // @formatter:on // Due to how rules are applied (see RunRules), the last rule gets called first. // Rules from fields get called before those from methods. // Thus, we first add methods and then fields and reverse the list in the end. Collections.reverse(result); return unmodifiableList(result); } private List findAnnotatedMethods(Object testInstance) { Predicate isRuleMethod = method -> isAnnotated(method, Rule.class); Predicate hasCorrectReturnType = method -> TestRule.class.isAssignableFrom(method.getReturnType()); return findMethods(testInstance.getClass(), isRuleMethod.and(hasCorrectReturnType), TOP_DOWN); } private List findAnnotatedFields(Object testInstance) { return findPublicAnnotatedFields(testInstance.getClass(), TestRule.class, Rule.class); } @Override public void beforeEach(ExtensionContext context) { invokeAppropriateMethodOnRuleAnnotatedMembers(context, false, GenericBeforeAndAfterAdvice::before); } @Override public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { int numRuleAnnotatedMembers = invokeAppropriateMethodOnRuleAnnotatedMembers(context, true, advice -> advice.handleTestExecutionException(throwable)); // If no appropriate @Rule annotated members were discovered, we then // have to rethrow the exception in order not to silently swallow it. // Fixes bug: https://github.com/junit-team/junit-framework/issues/1069 if (numRuleAnnotatedMembers == 0) { throw throwable; } } @Override public void afterEach(ExtensionContext context) { invokeAppropriateMethodOnRuleAnnotatedMembers(context, true, GenericBeforeAndAfterAdvice::after); } /** * @return the number of appropriate rule-annotated members that were discovered */ private int invokeAppropriateMethodOnRuleAnnotatedMembers(ExtensionContext context, boolean reverseOrder, AdviceInvoker adviceInvoker) { List ruleAnnotatedMembers = getRuleAnnotatedMembers(context); if (reverseOrder) { Collections.reverse(ruleAnnotatedMembers); } AtomicInteger counter = new AtomicInteger(); // @formatter:off ruleAnnotatedMembers.stream() .filter(annotatedMember -> this.ruleType.isInstance(annotatedMember.getTestRule())) .map(this.adapterGenerator) .forEach(advice -> { adviceInvoker.invokeAndMaskCheckedExceptions(advice); counter.incrementAndGet(); }); // @formatter:on return counter.get(); } /** * @return a modifiable copy of the list of rule-annotated members */ @SuppressWarnings("unchecked") private List getRuleAnnotatedMembers(ExtensionContext context) { Object testInstance = context.getRequiredTestInstance(); Namespace namespace = Namespace.create(TestRuleSupport.class, context.getRequiredTestClass()); // @formatter:off return new ArrayList<>(context.getStore(namespace) .computeIfAbsent("rule-annotated-members", key -> findRuleAnnotatedMembers(testInstance), List.class)); // @formatter:on } @FunctionalInterface private interface AdviceInvoker { default void invokeAndMaskCheckedExceptions(GenericBeforeAndAfterAdvice advice) { try { invoke(advice); } catch (Throwable t) { throw ExceptionUtils.throwAsUncheckedException(t); } } void invoke(GenericBeforeAndAfterAdvice advice) throws Throwable; } } ================================================ FILE: junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/VerifierSupport.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.migrationsupport.rules; import static org.apiguardian.api.API.Status.DEPRECATED; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.migrationsupport.rules.adapter.VerifierAdapter; import org.junit.rules.Verifier; /** * This {@code Extension} provides native support for subclasses of * the {@link Verifier} rule from JUnit 4. * *

{@code @Rule}-annotated fields as well as methods are supported. * *

By using this class-level extension on a test class such * {@code Verifier} implementations in legacy code bases * can be left unchanged including the JUnit 4 rule import statements. * *

However, if you intend to develop a new extension for * JUnit please use the new extension model of JUnit Jupiter instead * of the rule-based model of JUnit 4. * * @since 5.0 * @see org.junit.rules.Verifier * @see org.junit.rules.TestRule * @see org.junit.Rule * @deprecated Please implement {@link org.junit.jupiter.api.extension.AfterTestExecutionCallback} instead. */ @SuppressWarnings("removal") @API(status = DEPRECATED, since = "6.0") @Deprecated(since = "6.0", forRemoval = true) public class VerifierSupport implements AfterEachCallback { private final TestRuleSupport support = new TestRuleSupport(VerifierAdapter::new, Verifier.class); public VerifierSupport() { } @Override public void afterEach(ExtensionContext context) throws Exception { this.support.afterEach(context); } } ================================================ FILE: junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/AbstractTestRuleAdapter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.migrationsupport.rules.adapter; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.commons.support.ReflectionSupport.findMethod; import static org.junit.platform.commons.support.ReflectionSupport.invokeMethod; import java.lang.reflect.Method; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.migrationsupport.rules.member.TestRuleAnnotatedMember; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.ClassUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.rules.TestRule; /** * @since 5.0 */ @API(status = INTERNAL, since = "5.0") public abstract class AbstractTestRuleAdapter implements GenericBeforeAndAfterAdvice { private final TestRule target; public AbstractTestRuleAdapter(TestRuleAnnotatedMember annotatedMember, Class adapteeClass) { this.target = annotatedMember.getTestRule(); Preconditions.condition(adapteeClass.isAssignableFrom(this.target.getClass()), () -> adapteeClass + " is not assignable from " + this.target.getClass()); } protected @Nullable Object executeMethod(String name) { return executeMethod(name, new Class[0]); } protected @Nullable Object executeMethod(String methodName, Class[] parameterTypes, Object... arguments) { Method method = findMethod(this.target.getClass(), methodName, parameterTypes).orElseThrow( () -> new JUnitException("Failed to find method %s(%s) in class %s".formatted(methodName, ClassUtils.nullSafeToString(parameterTypes), this.target.getClass().getName()))); return invokeMethod(method, this.target, arguments); } } ================================================ FILE: junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/ExpectedExceptionAdapter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.migrationsupport.rules.adapter; import static java.lang.Boolean.TRUE; import static org.apiguardian.api.API.Status.INTERNAL; import org.apiguardian.api.API; import org.junit.jupiter.migrationsupport.rules.member.TestRuleAnnotatedMember; import org.junit.rules.ExpectedException; /** * @since 5.0 */ @API(status = INTERNAL, since = "5.0") public class ExpectedExceptionAdapter extends AbstractTestRuleAdapter { public ExpectedExceptionAdapter(TestRuleAnnotatedMember annotatedMember) { super(annotatedMember, ExpectedException.class); } @Override public void handleTestExecutionException(Throwable cause) throws Throwable { executeMethod("handleException", new Class[] { Throwable.class }, cause); } @Override public void after() { if (TRUE.equals(executeMethod("isAnyExceptionExpected"))) { executeMethod("failDueToMissingException"); } } } ================================================ FILE: junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/ExternalResourceAdapter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.migrationsupport.rules.adapter; import static org.apiguardian.api.API.Status.INTERNAL; import org.apiguardian.api.API; import org.junit.jupiter.migrationsupport.rules.member.TestRuleAnnotatedMember; import org.junit.rules.ExternalResource; /** * @since 5.0 */ @API(status = INTERNAL, since = "5.0") public class ExternalResourceAdapter extends AbstractTestRuleAdapter { public ExternalResourceAdapter(TestRuleAnnotatedMember annotatedMember) { super(annotatedMember, ExternalResource.class); } @Override public void before() { executeMethod("before"); } @Override public void after() { executeMethod("after"); } } ================================================ FILE: junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/GenericBeforeAndAfterAdvice.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.migrationsupport.rules.adapter; import static org.apiguardian.api.API.Status.INTERNAL; import org.apiguardian.api.API; /** * @since 5.0 */ @API(status = INTERNAL, since = "5.0") public interface GenericBeforeAndAfterAdvice { default void before() { } default void handleTestExecutionException(Throwable cause) throws Throwable { } default void after() { } } ================================================ FILE: junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/VerifierAdapter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.migrationsupport.rules.adapter; import static org.apiguardian.api.API.Status.INTERNAL; import org.apiguardian.api.API; import org.junit.jupiter.migrationsupport.rules.member.TestRuleAnnotatedMember; import org.junit.rules.Verifier; /** * @since 5.0 */ @API(status = INTERNAL, since = "5.0") public class VerifierAdapter extends AbstractTestRuleAdapter { public VerifierAdapter(TestRuleAnnotatedMember annotatedMember) { super(annotatedMember, Verifier.class); } @Override public void after() { executeMethod("verify"); } } ================================================ FILE: junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Internal wrappers for JUnit 4 rules to overcome visibility * limitations of the JUnit 4 implementations. */ @NullMarked package org.junit.jupiter.migrationsupport.rules.adapter; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/AbstractTestRuleAnnotatedMember.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.migrationsupport.rules.member; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; import org.junit.rules.TestRule; /** * @since 5.0 */ abstract class AbstractTestRuleAnnotatedMember implements TestRuleAnnotatedMember { private final TestRule testRule; AbstractTestRuleAnnotatedMember(@Nullable TestRule testRule) { this.testRule = Preconditions.notNull(testRule, "TestRule must not be null"); } @Override public TestRule getTestRule() { return this.testRule; } } ================================================ FILE: junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedField.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.migrationsupport.rules.member; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.commons.support.ReflectionSupport.makeAccessible; import java.lang.reflect.Field; import org.apiguardian.api.API; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.rules.TestRule; /** * @since 5.0 */ @API(status = INTERNAL, since = "5.1") public final class TestRuleAnnotatedField extends AbstractTestRuleAnnotatedMember { public TestRuleAnnotatedField(Object testInstance, Field field) { super(retrieveTestRule(testInstance, field)); } private static TestRule retrieveTestRule(Object testInstance, Field field) { try { return (TestRule) makeAccessible(field).get(testInstance); } catch (IllegalAccessException exception) { throw ExceptionUtils.throwAsUncheckedException(exception); } } } ================================================ FILE: junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedMember.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.migrationsupport.rules.member; import static org.apiguardian.api.API.Status.INTERNAL; import org.apiguardian.api.API; import org.junit.rules.TestRule; /** * @since 5.0 */ @API(status = INTERNAL, since = "5.1") public interface TestRuleAnnotatedMember { TestRule getTestRule(); } ================================================ FILE: junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedMethod.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.migrationsupport.rules.member; import static org.apiguardian.api.API.Status.INTERNAL; import java.lang.reflect.Method; import org.apiguardian.api.API; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.rules.TestRule; /** * @since 5.0 */ @API(status = INTERNAL, since = "5.1") public final class TestRuleAnnotatedMethod extends AbstractTestRuleAnnotatedMember { public TestRuleAnnotatedMethod(Object testInstance, Method method) { super((TestRule) ReflectionSupport.invokeMethod(method, testInstance)); } } ================================================ FILE: junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Internal abstractions for members which can be targets of JUnit 4 rule annotations. */ @NullMarked package org.junit.jupiter.migrationsupport.rules.member; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Deprecated extensions which provide (limited) support for JUnit 4 * rules within JUnit Jupiter. */ @NullMarked package org.junit.jupiter.migrationsupport.rules; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-jupiter-migrationsupport/src/test/README.md ================================================ For compatibility with the Eclipse IDE, the test for this module are in the `jupiter-tests` project. ================================================ FILE: junit-jupiter-params/junit-jupiter-params.gradle.kts ================================================ import junitbuild.extensions.javaModuleName import net.ltgt.gradle.errorprone.errorprone import net.ltgt.gradle.nullaway.nullaway plugins { id("junitbuild.kotlin-library-conventions") id("junitbuild.shadow-conventions") id("junitbuild.jmh-conventions") `java-test-fixtures` } description = "JUnit Jupiter Params" dependencies { api(platform(projects.junitBom)) api(projects.junitJupiterApi) compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) shadowed(libs.fastcsv) compileOnly(kotlin("stdlib")) osgiVerification(projects.junitJupiterEngine) osgiVerification(projects.junitPlatformLauncher) } tasks { jar { bundle { val version = project.version bnd(""" Require-Capability:\ org.junit.platform.engine;\ filter:='(&(org.junit.platform.engine=junit-jupiter)(version>=${'$'}{version_cleanup;$version})(!(version>=${'$'}{versionmask;+;${'$'}{version_cleanup;$version}})))';\ effective:=active """) } } val extractFastCSVLicense by registering(Sync::class) { from(zipTree(configurations.shadowedClasspath.flatMap { it.elements }.map { it.single { file -> file.asFile.name.contains("fastcsv") } })) { include("META-INF/LICENSE") rename { "LICENSE-fastcsv" } } into(layout.buildDirectory.dir("fastcsv")) } shadowJar { relocate("de.siegmar.fastcsv", "org.junit.jupiter.params.shadow.de.siegmar.fastcsv") exclude("META-INF/LICENSE") from(extractFastCSVLicense) } compileJava { options.compilerArgs.addAll(listOf( "--add-modules", "de.siegmar.fastcsv", "--add-reads", "${javaModuleName}=de.siegmar.fastcsv" )) } compileJmhJava { options.compilerArgs.add("-Xlint:-processing") options.errorprone.nullaway { customInitializerAnnotations.add( "org.openjdk.jmh.annotations.Setup", ) } } javadoc { (options as StandardJavadocDocletOptions).apply { addStringOption("-add-modules", "de.siegmar.fastcsv") addStringOption("-add-reads", "${javaModuleName}=de.siegmar.fastcsv") } } } ================================================ FILE: junit-jupiter-params/src/jmh/java/org/junit/jupiter/params/ParameterizedInvocationNameFormatterBenchmarks.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import static java.util.Objects.requireNonNull; import static org.junit.jupiter.params.ParameterizedInvocationConstants.DEFAULT_DISPLAY_NAME; import static org.junit.jupiter.params.ParameterizedInvocationConstants.DISPLAY_NAME_PLACEHOLDER; import java.util.List; import java.util.stream.IntStream; import org.junit.jupiter.params.provider.Arguments; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Param; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.infra.Blackhole; @State(Scope.Benchmark) @Fork(1) @Warmup(iterations = 1, time = 2) @Measurement(iterations = 3, time = 2) public class ParameterizedInvocationNameFormatterBenchmarks { @Param({ "1", "2", "4", "10", "100", "1000" }) private int numberOfParameters; List argumentsList; @Setup public void setUp() { argumentsList = IntStream.range(0, numberOfParameters) // .mapToObj(i -> Arguments.argumentSet(String.valueOf(i), i)) // .toList(); } @Benchmark public void formatTestNames(Blackhole blackhole) throws Exception { var method = TestCase.class.getDeclaredMethod("parameterizedTest", int.class); var formatter = new ParameterizedInvocationNameFormatter( DISPLAY_NAME_PLACEHOLDER + " " + DEFAULT_DISPLAY_NAME + " ({0})", "displayName", new ParameterizedTestContext(TestCase.class, method, requireNonNull(method.getAnnotation(ParameterizedTest.class))), 512); for (int i = 0; i < argumentsList.size(); i++) { Arguments arguments = argumentsList.get(i); blackhole.consume(formatter.format(i, EvaluatedArgumentSet.allOf(arguments), false)); } } @SuppressWarnings("JUnitMalformedDeclaration") static class TestCase { @SuppressWarnings("unused") @ParameterizedTest void parameterizedTest(int param) { } } } ================================================ FILE: junit-jupiter-params/src/main/java/module-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * JUnit Jupiter extension for parameterized tests. * * @since 5.0 */ module org.junit.jupiter.params { requires static transitive org.apiguardian.api; requires static transitive org.jspecify; requires transitive org.junit.jupiter.api; requires transitive org.junit.platform.commons; exports org.junit.jupiter.params; exports org.junit.jupiter.params.aggregator; exports org.junit.jupiter.params.converter; exports org.junit.jupiter.params.provider; exports org.junit.jupiter.params.support; opens org.junit.jupiter.params to org.junit.platform.commons; opens org.junit.jupiter.params.converter to org.junit.platform.commons; opens org.junit.jupiter.params.provider to org.junit.platform.commons; } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/AbstractParameterizedClassInvocationLifecycleMethodInvoker.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import static java.util.Objects.requireNonNull; import org.junit.jupiter.api.extension.ExecutableInvoker; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.extension.ParameterResolver; /** * @since 5.13 */ abstract class AbstractParameterizedClassInvocationLifecycleMethodInvoker implements ParameterResolver { private final ParameterizedClassContext declarationContext; private final EvaluatedArgumentSet arguments; private final int invocationIndex; private final ResolutionCache resolutionCache; private final ArgumentSetLifecycleMethod lifecycleMethod; AbstractParameterizedClassInvocationLifecycleMethodInvoker(ParameterizedClassContext declarationContext, EvaluatedArgumentSet arguments, int invocationIndex, ResolutionCache resolutionCache, ArgumentSetLifecycleMethod lifecycleMethod) { this.declarationContext = declarationContext; this.arguments = arguments; this.invocationIndex = invocationIndex; this.resolutionCache = resolutionCache; this.lifecycleMethod = lifecycleMethod; } @Override public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { return ExtensionContextScope.TEST_METHOD; } @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return parameterContext.getDeclaringExecutable().equals(this.lifecycleMethod.method) // && this.lifecycleMethod.parameterResolver.supports(parameterContext); } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return requireNonNull(this.lifecycleMethod.parameterResolver // .resolve(parameterContext, extensionContext, this.arguments, this.invocationIndex, this.resolutionCache)); } protected void invoke(ExtensionContext context) { if (isCorrectTestClass(context)) { ExecutableInvoker executableInvoker = context.getExecutableInvoker(); Object testInstance = context.getTestInstance().orElse(null); executableInvoker.invoke(this.lifecycleMethod.method, testInstance); } } private boolean isCorrectTestClass(ExtensionContext context) { return this.declarationContext.getAnnotatedElement().equals(context.getTestClass().orElse(null)); } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/AfterParameterizedClassInvocation.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ClassTemplateInvocationLifecycleMethod; import org.junit.jupiter.params.aggregator.AggregateWith; import org.junit.jupiter.params.aggregator.ArgumentsAccessor; /** * {@code @AfterParameterizedClassInvocation} is used to signal that the * annotated method should be executed before each * invocation of the current {@link ParameterizedClass @ParameterizedClass}. * *

Declaring {@code @AfterParameterizedClassInvocation} methods in a regular, * non-parameterized test class has no effect and will be ignored. * *

Method Signatures

* *

{@code @AfterParameterizedClassInvocation} methods must have a * {@code void} return type, must not be private, and must be {@code static} * unless the test class is annotated with * {@link org.junit.jupiter.api.TestInstance @TestInstance(Lifecycle.PER_CLASS)}. * *

Method Arguments

* *

{@code @AfterParameterizedClassInvocation} methods may optionally declare * parameters that are resolved depending on the setting of the * {@link #injectArguments()} attribute. * *

If {@link #injectArguments()} is set to {@code false}, the parameters must * be resolved by other registered * {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolvers}. * *

If {@link #injectArguments()} is set to {@code true} (the default), the * method must declare the same parameters, in the same order, as the * indexed parameters (see * {@link ParameterizedClass @ParameterizedClass}) of the parameterized test * class. It may declare a subset of the indexed parameters starting from the * first argument. Additionally, the method may declare custom aggregator * parameters (see {@link ParameterizedClass @ParameterizedClass}) at the * end of its parameter list. If the method declares additional parameters after * these aggregator parameters, or more parameters than the class has indexed * parameters, they may be resolved by other * {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolvers}. * *

For example, given a {@link ParameterizedClass @ParameterizedClass} with * indexed parameters of type {@code int} and {@code String}, the * following method signatures are valid: * *

{@code
 * @AfterParameterizedClassInvocation
 * void afterInvocation() { ... }
 *
 * @AfterParameterizedClassInvocation
 * void afterInvocation(int number) { ... }
 *
 * @AfterParameterizedClassInvocation
 * void afterInvocation(int number, String text) { ... }
 *
 * @AfterParameterizedClassInvocation
 * void afterInvocation(int number, String text, TestInfo testInfo) { ... }
 *
 * @AfterParameterizedClassInvocation
 * void afterInvocation(ArgumentsAccessor accessor) { ... }
 *
 * @AfterParameterizedClassInvocation
 * void afterInvocation(ArgumentsAccessor accessor, TestInfo testInfo) { ... }
 *
 * @AfterParameterizedClassInvocation
 * void afterInvocation(int number, String text, ArgumentsAccessor accessor) { ... }
 *
 * @AfterParameterizedClassInvocation
 * void afterInvocation(int number, String text, ArgumentsAccessor accessor, TestInfo testInfo) { ... }
 * }
* *

In the snippet above,{@link ArgumentsAccessor} is used as an example of an * aggregator parameter but the same applies to any parameter annotated with * {@link AggregateWith @AggregateWith}. The parameter of type * {@link org.junit.jupiter.api.TestInfo TestInfo} is used as an example of a * parameter that is resolved by another * {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolver}. * *

Inheritance and Execution Order

* *

{@code @AfterParameterizedClassInvocation} methods are inherited from * superclasses as long as they are not overridden according to the * visibility rules of the Java language. Furthermore, * {@code @AfterParameterizedClassInvocation} methods from superclasses will be * executed before {@code @AfterParameterizedClassInvocation} methods in * subclasses. * *

Similarly, {@code @AfterParameterizedClassInvocation} methods declared in * an interface are inherited as long as they are not overridden, and * {@code @AfterParameterizedClassInvocation} methods from an interface will be * executed before {@code @AfterParameterizedClassInvocation} methods in the * class that implements the interface. * *

JUnit Jupiter does not guarantee the execution order of multiple * {@code @AfterParameterizedClassInvocation} methods that are declared within a * single parameterized test class or test interface. While it may at times * appear that these methods are invoked in alphabetical order, they are in fact * sorted using an algorithm that is deterministic but intentionally * non-obvious. * *

In addition, {@code @AfterParameterizedClassInvocation} methods are in no * way linked to {@code @BeforeParameterizedClassInvocation} methods. * Consequently, there are no guarantees with regard to their wrapping * behavior. For example, given two {@code @AfterParameterizedClassInvocation} * methods {@code createA()} and {@code createB()} as well as two * {@code @BeforeParameterizedClassInvocation} methods {@code destroyA()} and * {@code destroyB()}, the order in which the * {@code @AfterParameterizedClassInvocation} methods are executed (e.g. * {@code createA()} before {@code createB()}) does not imply any order for the * seemingly corresponding {@code @BeforeParameterizedClassInvocation} methods. * In other words, {@code destroyA()} might be called before or after * {@code destroyB()}. The JUnit Team therefore recommends that developers * declare at most one {@code @AfterParameterizedClassInvocation} method and at * most one {@code @BeforeParameterizedClassInvocation} method per test class or * test interface unless there are no dependencies between the * {@code @AfterParameterizedClassInvocation} methods or between the * {@code @BeforeParameterizedClassInvocation} methods. * *

Composition

* *

{@code @AfterParameterizedClassInvocation} may be used as a * meta-annotation in order to create a custom composed annotation that * inherits the semantics of {@code @AfterParameterizedClassInvocation}. * * @since 5.13 * @see ParameterizedClass * @see BeforeParameterizedClassInvocation * @see org.junit.jupiter.api.TestInstance */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = EXPERIMENTAL, since = "6.0") @ClassTemplateInvocationLifecycleMethod(classTemplateAnnotation = ParameterizedClass.class, lifecycleMethodAnnotation = AfterParameterizedClassInvocation.class) public @interface AfterParameterizedClassInvocation { /** * Whether the arguments of the parameterized test class should be injected * into the annotated method (defaults to {@code true}). */ boolean injectArguments() default true; } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/AfterParameterizedClassInvocationMethodInvoker.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import org.junit.jupiter.api.extension.AfterClassTemplateInvocationCallback; import org.junit.jupiter.api.extension.ExtensionContext; /** * @since 5.13 */ class AfterParameterizedClassInvocationMethodInvoker extends AbstractParameterizedClassInvocationLifecycleMethodInvoker implements AfterClassTemplateInvocationCallback { AfterParameterizedClassInvocationMethodInvoker(ParameterizedClassContext declarationContext, EvaluatedArgumentSet arguments, int invocationIndex, ResolutionCache resolutionCache, ArgumentSetLifecycleMethod lifecycleMethod) { super(declarationContext, arguments, invocationIndex, resolutionCache, lifecycleMethod); } @Override public void afterClassTemplateInvocation(ExtensionContext context) { invoke(context); } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/ArgumentCountValidationMode.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import static org.apiguardian.api.API.Status.MAINTAINED; import org.apiguardian.api.API; import org.junit.jupiter.params.provider.ArgumentsSource; /** * Enumeration of argument count validation modes for * {@link ParameterizedClass @ParameterizedClass} and * {@link ParameterizedTest @ParameterizedTest}. * *

When an {@link ArgumentsSource} provides more arguments than declared by * the parameterized class or method, there might be a bug in the class/method * or the {@link ArgumentsSource}. By default, the additional arguments are * ignored. {@link ArgumentCountValidationMode} allows you to control how * additional arguments are handled. * * @since 5.12 * @see ParameterizedClass#argumentCountValidation() * @see ParameterizedTest#argumentCountValidation() */ @API(status = MAINTAINED, since = "5.13.3") public enum ArgumentCountValidationMode { /** * Use the default validation mode. * *

The default validation mode may be changed via the * {@value ArgumentCountValidator#ARGUMENT_COUNT_VALIDATION_KEY} * configuration parameter (see the User Guide for details on configuration * parameters). */ DEFAULT, /** * Use the "none" argument count validation mode. * *

When there are more arguments provided than declared by the * parameterized class or method, these additional arguments are ignored. */ NONE, /** * Use the strict argument count validation mode. * *

When there are more arguments provided than declared by the * parameterized class or method, this raises an error. */ STRICT } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/ArgumentCountValidator.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import java.util.Arrays; import java.util.Optional; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExtensionConfigurationException; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Namespace; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.Preconditions; class ArgumentCountValidator { private static final Logger logger = LoggerFactory.getLogger(ArgumentCountValidator.class); static final String ARGUMENT_COUNT_VALIDATION_KEY = "junit.jupiter.params.argumentCountValidation"; private static final Namespace NAMESPACE = Namespace.create(ArgumentCountValidator.class); private final ParameterizedDeclarationContext declarationContext; private final EvaluatedArgumentSet arguments; ArgumentCountValidator(ParameterizedDeclarationContext declarationContext, EvaluatedArgumentSet arguments) { this.declarationContext = declarationContext; this.arguments = arguments; } void validate(ExtensionContext extensionContext) { validateRequiredArgumentsArePresent(); ArgumentCountValidationMode argumentCountValidationMode = getArgumentCountValidationMode(extensionContext); switch (argumentCountValidationMode) { case DEFAULT, NONE -> { } case STRICT -> { int consumedCount = this.declarationContext.getResolverFacade().determineConsumedArgumentCount( this.arguments); int totalCount = this.arguments.getTotalLength(); Preconditions.condition(consumedCount == totalCount, () -> wrongNumberOfArgumentsMessages("consumes", consumedCount, null, null)); } default -> throw new ExtensionConfigurationException( "Unsupported argument count validation mode: " + argumentCountValidationMode); } } private void validateRequiredArgumentsArePresent() { var requiredParameterCount = this.declarationContext.getResolverFacade().getRequiredParameterCount(); if (requiredParameterCount != null) { var totalCount = this.arguments.getTotalLength(); Preconditions.condition(requiredParameterCount.value() <= totalCount, () -> wrongNumberOfArgumentsMessages("has", requiredParameterCount.value(), "required", requiredParameterCount.reason())); } } private String wrongNumberOfArgumentsMessages(String verb, int actualCount, @Nullable String parameterAdjective, @Nullable String reason) { int totalCount = this.arguments.getTotalLength(); return "Configuration error: @%s %s %s %s%s%s but there %s %s %s provided.%nNote: the provided arguments were %s".formatted( this.declarationContext.getAnnotationName(), verb, actualCount, parameterAdjective == null ? "" : parameterAdjective + " ", pluralize(actualCount, "parameter", "parameters"), reason == null ? "" : " (due to %s)".formatted(reason), pluralize(totalCount, "was", "were"), totalCount, pluralize(totalCount, "argument", "arguments"), Arrays.toString(this.arguments.getAllPayloads())); } private ArgumentCountValidationMode getArgumentCountValidationMode(ExtensionContext extensionContext) { ArgumentCountValidationMode mode = declarationContext.getArgumentCountValidationMode(); if (mode != ArgumentCountValidationMode.DEFAULT) { return mode; } else { return getArgumentCountValidationModeConfiguration(extensionContext); } } private ArgumentCountValidationMode getArgumentCountValidationModeConfiguration(ExtensionContext extensionContext) { String key = ARGUMENT_COUNT_VALIDATION_KEY; ArgumentCountValidationMode fallback = ArgumentCountValidationMode.NONE; ExtensionContext.Store store = getStore(extensionContext); return store.computeIfAbsent(key, __ -> { Optional optionalConfigValue = extensionContext.getConfigurationParameter(key); if (optionalConfigValue.isPresent()) { String configValue = optionalConfigValue.get(); Optional enumValue = Arrays.stream( ArgumentCountValidationMode.values()).filter( mode -> mode.name().equalsIgnoreCase(configValue)).findFirst(); if (enumValue.isPresent()) { logger.config( () -> "Using ArgumentCountValidationMode '%s' set via the '%s' configuration parameter.".formatted( enumValue.get().name(), key)); return enumValue.get(); } else { logger.warn(() -> """ Invalid ArgumentCountValidationMode '%s' set via the '%s' configuration parameter. \ Falling back to the %s default value.""".formatted(configValue, key, fallback.name())); return fallback; } } else { return fallback; } }, ArgumentCountValidationMode.class); } private static String pluralize(int count, String singular, String plural) { return count == 1 ? singular : plural; } private ExtensionContext.Store getStore(ExtensionContext context) { return context.getRoot().getStore(NAMESPACE); } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/ArgumentSetLifecycleMethod.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import java.lang.reflect.Method; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.Preconditions; /** * @since 5.13 */ class ArgumentSetLifecycleMethod { final Method method; final ParameterResolver parameterResolver; ArgumentSetLifecycleMethod(Method method) { this(method, ParameterResolver.DISABLED); } ArgumentSetLifecycleMethod(Method method, ParameterResolver parameterResolver) { this.method = Preconditions.notNull(method, "method must not be null"); this.parameterResolver = Preconditions.notNull(parameterResolver, "parameterResolver must not be null"); } interface ParameterResolver { ParameterResolver DISABLED = new ParameterResolver() { @Override public boolean supports(ParameterContext parameterContext) { return false; } @Override public Object resolve(ParameterContext parameterContext, ExtensionContext extensionContext, EvaluatedArgumentSet arguments, int invocationIndex, ResolutionCache resolutionCache) { throw new JUnitException("Parameter resolution is disabled"); } }; boolean supports(ParameterContext parameterContext); @Nullable Object resolve(ParameterContext parameterContext, ExtensionContext extensionContext, EvaluatedArgumentSet arguments, int invocationIndex, ResolutionCache resolutionCache); } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/BeforeClassTemplateInvocationFieldInjector.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import org.junit.jupiter.api.extension.BeforeClassTemplateInvocationCallback; import org.junit.jupiter.api.extension.ExtensionContext; class BeforeClassTemplateInvocationFieldInjector implements BeforeClassTemplateInvocationCallback { private final ResolverFacade resolverFacade; private final EvaluatedArgumentSet arguments; private final int invocationIndex; private final ResolutionCache resolutionCache; BeforeClassTemplateInvocationFieldInjector(ResolverFacade resolverFacade, EvaluatedArgumentSet arguments, int invocationIndex, ResolutionCache resolutionCache) { this.resolverFacade = resolverFacade; this.arguments = arguments; this.invocationIndex = invocationIndex; this.resolutionCache = resolutionCache; } @Override public void beforeClassTemplateInvocation(ExtensionContext extensionContext) { extensionContext.getTestInstance() // .ifPresent(testInstance -> this.resolverFacade // .resolveAndInjectFields(testInstance, extensionContext, this.arguments, this.invocationIndex, this.resolutionCache)); } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/BeforeParameterizedClassInvocation.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ClassTemplateInvocationLifecycleMethod; import org.junit.jupiter.params.aggregator.AggregateWith; import org.junit.jupiter.params.aggregator.ArgumentsAccessor; /** * {@code @BeforeParameterizedClassInvocation} is used to signal that the * annotated method should be executed before each * invocation of the current {@link ParameterizedClass @ParameterizedClass}. * *

Declaring {@code @BeforeParameterizedClassInvocation} methods in a * regular, non-parameterized test class has no effect and will be ignored. * *

Method Signatures

* *

{@code @BeforeParameterizedClassInvocation} methods must have a * {@code void} return type, must not be private, and must be {@code static} * unless the test class is annotated with * {@link org.junit.jupiter.api.TestInstance @TestInstance(Lifecycle.PER_CLASS)}. * *

Method Arguments

* *

{@code @BeforeParameterizedClassInvocation} methods may optionally declare * parameters that are resolved depending on the setting of the * {@link #injectArguments()} attribute. * *

If {@link #injectArguments()} is set to {@code false}, the parameters must * be resolved by other registered * {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolvers}. * *

If {@link #injectArguments()} is set to {@code true} (the default), the * method must declare the same parameters, in the same order, as the * indexed parameters (see * {@link ParameterizedClass @ParameterizedClass}) of the parameterized test * class. It may declare a subset of the indexed parameters starting from the * first argument. Additionally, the method may declare custom aggregator * parameters (see {@link ParameterizedClass @ParameterizedClass}) at the * end of its parameter list. If the method declares additional parameters after * these aggregator parameters, or more parameters than the class has indexed * parameters, they may be resolved by other * {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolvers}. * *

For example, given a {@link ParameterizedClass @ParameterizedClass} with * indexed parameters of type {@code int} and {@code String}, the * following method signatures are valid: * *

{@code
 * @BeforeParameterizedClassInvocation
 * void beforeInvocation() { ... }
 *
 * @BeforeParameterizedClassInvocation
 * void beforeInvocation(int number) { ... }
 *
 * @BeforeParameterizedClassInvocation
 * void beforeInvocation(int number, String text) { ... }
 *
 * @BeforeParameterizedClassInvocation
 * void beforeInvocation(int number, String text, TestInfo testInfo) { ... }
 *
 * @BeforeParameterizedClassInvocation
 * void beforeInvocation(ArgumentsAccessor accessor) { ... }
 *
 * @BeforeParameterizedClassInvocation
 * void beforeInvocation(ArgumentsAccessor accessor, TestInfo testInfo) { ... }
 *
 * @BeforeParameterizedClassInvocation
 * void beforeInvocation(int number, String text, ArgumentsAccessor accessor) { ... }
 *
 * @BeforeParameterizedClassInvocation
 * void beforeInvocation(int number, String text, ArgumentsAccessor accessor, TestInfo testInfo) { ... }
 * }
* *

In the snippet above,{@link ArgumentsAccessor} is used as an example of an * aggregator parameter but the same applies to any parameter annotated with * {@link AggregateWith @AggregateWith}. The parameter of type * {@link org.junit.jupiter.api.TestInfo TestInfo} is used as an example of a * parameter that is resolved by another * {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolver}. * *

Inheritance and Execution Order

* *

{@code @BeforeParameterizedClassInvocation} methods are inherited from * superclasses as long as they are not overridden according to the * visibility rules of the Java language. Furthermore, * {@code @BeforeParameterizedClassInvocation} methods from superclasses will be * executed before {@code @BeforeParameterizedClassInvocation} methods in * subclasses. * *

Similarly, {@code @BeforeParameterizedClassInvocation} methods declared in * an interface are inherited as long as they are not overridden, and * {@code @BeforeParameterizedClassInvocation} methods from an interface will be * executed before {@code @BeforeParameterizedClassInvocation} methods in the * class that implements the interface. * *

JUnit Jupiter does not guarantee the execution order of multiple * {@code @BeforeParameterizedClassInvocation} methods that are declared within * a single parameterized test class or test interface. While it may at times * appear that these methods are invoked in alphabetical order, they are in fact * sorted using an algorithm that is deterministic but intentionally * non-obvious. * *

In addition, {@code @BeforeParameterizedClassInvocation} methods are in no * way linked to {@code @AfterParameterizedClassInvocation} methods. * Consequently, there are no guarantees with regard to their wrapping * behavior. For example, given two {@code @BeforeParameterizedClassInvocation} * methods {@code createA()} and {@code createB()} as well as two * {@code @AfterParameterizedClassInvocation} methods {@code destroyA()} and * {@code destroyB()}, the order in which the * {@code @BeforeParameterizedClassInvocation} methods are executed (e.g. * {@code createA()} before {@code createB()}) does not imply any order for the * seemingly corresponding {@code @AfterParameterizedClassInvocation} methods. * In other words, {@code destroyA()} might be called before or after * {@code destroyB()}. The JUnit Team therefore recommends that developers * declare at most one {@code @BeforeParameterizedClassInvocation} method and at * most one {@code @AfterParameterizedClassInvocation} method per test class or * test interface unless there are no dependencies between the * {@code @BeforeParameterizedClassInvocation} methods or between the * {@code @AfterParameterizedClassInvocation} methods. * *

Composition

* *

{@code @BeforeParameterizedClassInvocation} may be used as a * meta-annotation in order to create a custom composed annotation that * inherits the semantics of {@code @BeforeParameterizedClassInvocation}. * * @since 5.13 * @see ParameterizedClass * @see AfterParameterizedClassInvocation * @see org.junit.jupiter.api.TestInstance */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = EXPERIMENTAL, since = "6.0") @ClassTemplateInvocationLifecycleMethod(classTemplateAnnotation = ParameterizedClass.class, lifecycleMethodAnnotation = BeforeParameterizedClassInvocation.class) public @interface BeforeParameterizedClassInvocation { /** * Whether the arguments of the parameterized test class should be injected * into the annotated method (defaults to {@code true}). */ boolean injectArguments() default true; } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/BeforeParameterizedClassInvocationMethodInvoker.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import org.junit.jupiter.api.extension.BeforeClassTemplateInvocationCallback; import org.junit.jupiter.api.extension.ExtensionContext; /** * @since 5.13 */ class BeforeParameterizedClassInvocationMethodInvoker extends AbstractParameterizedClassInvocationLifecycleMethodInvoker implements BeforeClassTemplateInvocationCallback { BeforeParameterizedClassInvocationMethodInvoker(ParameterizedClassContext declarationContext, EvaluatedArgumentSet arguments, int invocationIndex, ResolutionCache resolutionCache, ArgumentSetLifecycleMethod lifecycleMethod) { super(declarationContext, arguments, invocationIndex, resolutionCache, lifecycleMethod); } @Override public void beforeClassTemplateInvocation(ExtensionContext context) { invoke(context); } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/ClassTemplateConstructorParameterResolver.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import org.junit.jupiter.api.extension.ExtensionContext; /** * @since 5.13 */ class ClassTemplateConstructorParameterResolver extends ParameterizedInvocationParameterResolver { private final Class classTemplateClass; ClassTemplateConstructorParameterResolver(ParameterizedClassContext classContext, EvaluatedArgumentSet arguments, int invocationIndex, ResolutionCache resolutionCache) { super(classContext.getResolverFacade(), arguments, invocationIndex, resolutionCache); this.classTemplateClass = classContext.getAnnotatedElement(); } @Override protected boolean isSupportedOnConstructorOrMethod(Executable declaringExecutable, ExtensionContext extensionContext) { return declaringExecutable instanceof Constructor // && this.classTemplateClass.equals(declaringExecutable.getDeclaringClass()); } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/DefaultParameterInfo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.aggregator.ArgumentsAccessor; import org.junit.jupiter.params.support.ParameterDeclarations; /** * @since 5.13 */ @SuppressWarnings("removal") record DefaultParameterInfo(ParameterDeclarations declarations, ArgumentsAccessor arguments) implements org.junit.jupiter.params.support.ParameterInfo { @Override public ParameterDeclarations getDeclarations() { return this.declarations; } @Override public ArgumentsAccessor getArguments() { return this.arguments; } void store(ExtensionContext context) { context.getStore(org.junit.jupiter.params.ParameterInfo.NAMESPACE) // .put(org.junit.jupiter.params.ParameterInfo.KEY, this); context.getStore(org.junit.jupiter.params.support.ParameterInfo.NAMESPACE) // .put(org.junit.jupiter.params.support.ParameterInfo.KEY, this); } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/EvaluatedArgumentSet.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import java.util.Arrays; import java.util.Optional; import java.util.function.Function; import java.util.function.IntUnaryOperator; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Named; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments.ArgumentSet; import org.junit.platform.commons.util.Preconditions; /** * Encapsulates the evaluation of an {@link Arguments} instance (so it happens * only once) and access to the resulting argument values. * *

The provided accessor methods are focused on the different use cases and * make it less error-prone to access the argument values. * * @since 5.13 */ class EvaluatedArgumentSet { static EvaluatedArgumentSet allOf(Arguments arguments) { @Nullable Object[] all = arguments.get(); return create(all, all, arguments); } static EvaluatedArgumentSet of(Arguments arguments, IntUnaryOperator consumedLengthComputer) { @Nullable Object[] all = arguments.get(); @Nullable Object[] consumed = dropSurplus(all, consumedLengthComputer.applyAsInt(all.length)); return create(all, consumed, arguments); } private static EvaluatedArgumentSet create(@Nullable Object[] all, @Nullable Object[] consumed, Arguments arguments) { return new EvaluatedArgumentSet(all, consumed, determineName(arguments)); } private final @Nullable Object[] all; private final @Nullable Object[] consumed; private final Optional name; private EvaluatedArgumentSet(@Nullable Object[] all, @Nullable Object[] consumed, Optional name) { this.all = all; this.consumed = consumed; this.name = name; } int getTotalLength() { return this.all.length; } @Nullable Object[] getAllPayloads() { return extractFromNamed(this.all, Named::getPayload); } int getConsumedLength() { return this.consumed.length; } @Nullable Object[] getConsumedArguments() { return this.consumed; } @Nullable Object[] getConsumedPayloads() { return extractFromNamed(this.consumed, Named::getPayload); } @Nullable Object getConsumedPayload(int index) { return extractFromNamed(this.consumed[index], Named::getPayload); } Optional getName() { return this.name; } private static @Nullable Object[] dropSurplus(@Nullable Object[] arguments, int newLength) { Preconditions.condition(newLength <= arguments.length, () -> "New length %d must be less than or equal to the total length %d".formatted(newLength, arguments.length)); return arguments.length > newLength ? Arrays.copyOf(arguments, newLength) : arguments; } private static Optional determineName(Arguments arguments) { if (arguments instanceof ArgumentSet set) { return Optional.of(set.getName()); } return Optional.empty(); } private static @Nullable Object[] extractFromNamed(@Nullable Object[] arguments, Function, @Nullable Object> mapper) { return Arrays.stream(arguments) // .map(argument -> extractFromNamed(argument, mapper)) // .toArray(); } private static @Nullable Object extractFromNamed(@Nullable Object argument, Function, @Nullable Object> mapper) { return argument instanceof Named named ? mapper.apply(named) : argument; } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/InstancePostProcessingClassTemplateFieldInjector.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestInstancePostProcessor; class InstancePostProcessingClassTemplateFieldInjector implements TestInstancePostProcessor { private final ResolverFacade resolverFacade; private final EvaluatedArgumentSet arguments; private final int invocationIndex; private final ResolutionCache resolutionCache; InstancePostProcessingClassTemplateFieldInjector(ResolverFacade resolverFacade, EvaluatedArgumentSet arguments, int invocationIndex, ResolutionCache resolutionCache) { this.resolverFacade = resolverFacade; this.arguments = arguments; this.invocationIndex = invocationIndex; this.resolutionCache = resolutionCache; } @Override public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { return ExtensionContextScope.TEST_METHOD; } @Override public void postProcessTestInstance(Object testInstance, ExtensionContext extensionContext) { this.resolverFacade.resolveAndInjectFields(testInstance, extensionContext, this.arguments, this.invocationIndex, this.resolutionCache); } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/Parameter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.params.aggregator.AggregateWith; import org.junit.jupiter.params.aggregator.ArgumentsAccessor; /** * {@code @Parameter} is used to signal that a field in a * {@code @ParameterizedClass} constitutes a parameter and marks it for * field injection. * *

{@code @Parameter} may also be used as a meta-annotation in order to * create a custom composed annotation that inherits the semantics of * {@code @Parameter}. * * @since 5.13 * @see ParameterizedClass * @see ArgumentsAccessor * @see AggregateWith * @see org.junit.jupiter.params.converter.ArgumentConverter * @see org.junit.jupiter.params.converter.ConvertWith */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.ANNOTATION_TYPE, ElementType.FIELD }) @Documented @API(status = EXPERIMENTAL, since = "6.0") public @interface Parameter { /** * Constant that indicates that the index of the parameter is unset. */ int UNSET_INDEX = -1; /** * {@return the index of the parameter in the list of parameters} * *

Must be {@value #UNSET_INDEX} (the default) for aggregators, * that is any field of type {@link ArgumentsAccessor} or any field * annotated with {@link AggregateWith @AggregateWith}. * *

May be omitted if there's a single indexed parameter. * Otherwise, must be unique among all indexed parameters of the * parameterized class and its superclasses. */ int value() default UNSET_INDEX; } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterInfo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Namespace; import org.junit.jupiter.params.aggregator.ArgumentsAccessor; import org.junit.jupiter.params.support.ParameterDeclarations; /** * {@code ParameterInfo} is used to provide information about the current * invocation of a parameterized class or test. * *

Registered {@link Extension} implementations may retrieve the current * {@code ParameterInfo} instance by calling * {@link ExtensionContext#getStore(Namespace)} with {@link #NAMESPACE} and * {@link ExtensionContext.Store#get(Object, Class) Store.get(...)} with * {@link #KEY}. Alternatively, the {@link #get(ExtensionContext)} method may * be used to retrieve the {@code ParameterInfo} instance for the supplied * {@code ExtensionContext}. Extensions must not modify any entries in the * {@link ExtensionContext.Store Store} for {@link #NAMESPACE}. * *

When a {@link ParameterizedTest @ParameterizedTest} method is declared * inside a {@link ParameterizedClass @ParameterizedClass} or a * {@link Nested @Nested} {@link ParameterizedClass @ParameterizedClass} is * declared inside an enclosing {@link ParameterizedClass @ParameterizedClass}, * there will be multiple {@code ParameterInfo} instances available on different * levels of the {@link ExtensionContext} hierarchy. In such cases, please use * {@link ExtensionContext#getParent()} to navigate to the right level before * retrieving the {@code ParameterInfo} instance from the * {@link ExtensionContext.Store Store}. * * @since 5.14 * @see ParameterizedClass * @see ParameterizedTest */ @API(status = EXPERIMENTAL, since = "5.14") public interface ParameterInfo { /** * The {@link Namespace} for accessing the * {@link ExtensionContext.Store Store} for {@code ParameterInfo}. */ Namespace NAMESPACE = Namespace.create(ParameterInfo.class); /** * The key for retrieving the {@code ParameterInfo} instance from the * {@link ExtensionContext.Store Store}. */ Object KEY = ParameterInfo.class; /** * {@return the closest {@code ParameterInfo} instance for the supplied * {@code ExtensionContext}; potentially {@code null}} */ static @Nullable ParameterInfo get(ExtensionContext context) { return context.getStore(NAMESPACE).get(KEY, ParameterInfo.class); } /** * {@return the declarations of all indexed parameters} */ ParameterDeclarations getDeclarations(); /** * {@return an accessor for the arguments of the current invocation} */ ArgumentsAccessor getArguments(); } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedClass.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.api.ClassTemplate; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.provider.ArgumentsSource; /** * {@code @ParameterizedClass} is used to signal that the annotated class is * a parameterized test class. * *

Arguments Providers and Sources

* *

A {@code @ParameterizedClass} must specify at least one * {@link org.junit.jupiter.params.provider.ArgumentsProvider ArgumentsProvider} * via {@link org.junit.jupiter.params.provider.ArgumentsSource @ArgumentsSource} * or a corresponding composed annotation (such as {@code @ValueSource}, * {@code @CsvSource}, etc.). The provider is responsible for providing a * {@link java.util.stream.Stream Stream} of * {@link org.junit.jupiter.params.provider.Arguments Arguments} that will be * used to invoke the parameterized class. * *

Field or Constructor Injection

* *

The provided arguments can either be injected into fields annotated with * {@link Parameter @Parameter} or passed to the unique constructor of the * parameterized class. If a {@code @Parameter}-annotated field is declared in * the parameterized class or one of its superclasses, field injection will be * used. Otherwise, constructor injection will be used. * *

Constructor Injection

* *

A {@code @ParameterizedClass} constructor may declare additional * parameters at the end of its parameter list to be resolved by other * {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolvers} * (such as {@code TestInfo}, {@code TestReporter}, etc.). Specifically, such a * constructor must declare formal parameters according to the following rules. * *

    *
  1. Zero or more indexed parameters must be declared first.
  2. *
  3. Zero or more aggregators must be declared next.
  4. *
  5. Zero or more parameters supplied by other {@code ParameterResolver} * implementations must be declared last.
  6. *
* *

In this context, an indexed parameter is an argument for a given * index in the {@code Arguments} provided by an {@code ArgumentsProvider} that * is supplied as an argument to the parameterized class at the same index in * the constructor's formal parameter list. An aggregator is any * parameter of type * {@link org.junit.jupiter.params.aggregator.ArgumentsAccessor ArgumentsAccessor} * or any parameter annotated with * {@link org.junit.jupiter.params.aggregator.AggregateWith @AggregateWith}. * *

Field injection

* *

Fields annotated with {@code @Parameter} must be declared according to the * following rules. * *

    *
  1. Zero or more indexed parameters may be declared; each must have * a unique index specified in its {@code @Parameter(index)} annotation. The * index may be omitted if there is only one indexed parameter. If there are at * least two indexed parameter declarations, there must be declarations for all * indexes from 0 to the largest declared index.
  2. *
  3. Zero or more aggregators may be declared; each without * specifying an index in its {@code @Parameter} annotation.
  4. *
  5. Zero or more other fields may be declared as usual as long as they're not * annotated with {@code @Parameter}.
  6. *
* *

In this context, an indexed parameter is an argument for a given * index in the {@code Arguments} provided by an {@code ArgumentsProvider} that * is injected into a field annotated with {@code @Parameter(index)}. An * aggregator is any {@code @Parameter}-annotated field of type * {@link org.junit.jupiter.params.aggregator.ArgumentsAccessor ArgumentsAccessor} * or any field annotated with * {@link org.junit.jupiter.params.aggregator.AggregateWith @AggregateWith}. * *

Argument Conversion

* *

{@code @Parameter}-annotated fields or constructor parameters may be * annotated with * {@link org.junit.jupiter.params.converter.ConvertWith @ConvertWith} * or a corresponding composed annotation to specify an explicit * {@link org.junit.jupiter.params.converter.ArgumentConverter ArgumentConverter}. * Otherwise, JUnit Jupiter will attempt to perform an implicit * conversion to the target type automatically (see the User Guide for further * details). * *

Lifecycle Methods

* *

If you wish to execute custom code before or after each invocation of the * parameterized class, you may declare methods annotated with * {@link BeforeParameterizedClassInvocation @BeforeParameterizedClassInvocation} * or {@link AfterParameterizedClassInvocation @AfterParameterizedClassInvocation}. * This can, for example, be useful to initialize the arguments before they are * used. * *

Composed Annotations

* *

{@code @ParameterizedClass} may also be used as a meta-annotation in * order to create a custom composed annotation that inherits the * semantics of {@code @ParameterizedClass}. * *

Inheritance

* *

This annotation is {@linkplain Inherited inherited} within class hierarchies. * * @since 5.13 * @see Parameter * @see BeforeParameterizedClassInvocation * @see AfterParameterizedClassInvocation * @see ParameterizedTest * @see org.junit.jupiter.params.provider.Arguments * @see org.junit.jupiter.params.provider.ArgumentsProvider * @see org.junit.jupiter.params.provider.ArgumentsSource * @see org.junit.jupiter.params.provider.CsvFileSource * @see org.junit.jupiter.params.provider.CsvSource * @see org.junit.jupiter.params.provider.EnumSource * @see org.junit.jupiter.params.provider.MethodSource * @see org.junit.jupiter.params.provider.ValueSource * @see org.junit.jupiter.params.aggregator.ArgumentsAccessor * @see org.junit.jupiter.params.aggregator.AggregateWith * @see org.junit.jupiter.params.converter.ArgumentConverter * @see org.junit.jupiter.params.converter.ConvertWith */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @API(status = EXPERIMENTAL, since = "6.0") @ClassTemplate @ExtendWith(ParameterizedClassExtension.class) @SuppressWarnings("exports") public @interface ParameterizedClass { /** * The display name to be used for individual invocations of the * parameterized class; never blank or consisting solely of whitespace. * *

Defaults to * {@value ParameterizedInvocationNameFormatter#DEFAULT_DISPLAY_NAME}. * *

If the default display name flag * ({@value ParameterizedInvocationNameFormatter#DEFAULT_DISPLAY_NAME}) * is not overridden, JUnit will: *

    *
  • Look up the {@value ParameterizedInvocationNameFormatter#DISPLAY_NAME_PATTERN_KEY} * configuration parameter and use it if available. The configuration * parameter can be supplied via the {@code Launcher} API, build tools (e.g., * Gradle and Maven), a JVM system property, or the JUnit Platform configuration * file (i.e., a file named {@code junit-platform.properties} in the root of * the class path). Consult the User Guide for further information.
  • *
  • Otherwise, * {@value ParameterizedInvocationConstants#DEFAULT_DISPLAY_NAME} * will be used.
  • *
* *

Supported placeholders

*
    *
  • {@value ParameterizedInvocationConstants#DISPLAY_NAME_PLACEHOLDER}
  • *
  • {@value ParameterizedInvocationConstants#INDEX_PLACEHOLDER}
  • *
  • {@value ParameterizedInvocationConstants#ARGUMENT_SET_NAME_PLACEHOLDER}
  • *
  • {@value ParameterizedInvocationConstants#ARGUMENTS_PLACEHOLDER}
  • *
  • {@value ParameterizedInvocationConstants#ARGUMENTS_WITH_NAMES_PLACEHOLDER}
  • *
  • {@value ParameterizedInvocationConstants#ARGUMENT_SET_NAME_OR_ARGUMENTS_WITH_NAMES_PLACEHOLDER}
  • *
  • "{0}", "{1}", etc.: an individual argument (0-based)
  • *
* *

For the latter, you may use {@link java.text.MessageFormat} patterns * to customize formatting (for example, {@code {0,number,#.###}}). Please * note that the original arguments are passed when formatting, regardless * of any implicit or explicit argument conversions. * *

Note that * {@value ParameterizedInvocationNameFormatter#DEFAULT_DISPLAY_NAME} is * a flag rather than a placeholder. * * @see java.text.MessageFormat * @see #quoteTextArguments() */ String name() default ParameterizedInvocationNameFormatter.DEFAULT_DISPLAY_NAME; /** * Configure whether to enclose text-based argument values in quotes within * display names. * *

Defaults to {@code true}. * *

In this context, any {@link CharSequence} (such as a {@link String}) * or {@link Character} is considered text. A {@code CharSequence} is wrapped * in double quotes ("), and a {@code Character} is wrapped in single quotes * ('). * *

Special characters in Java strings and characters will be escaped in the * quoted text — for example, carriage returns and line feeds will be * escaped as {@code \\r} and {@code \\n}, respectively. In addition, any * {@linkplain Character#isISOControl(char) ISO control character} will be * represented as a question mark (?) in the quoted text. * *

For example, given a string argument {@code "line 1\nline 2"}, the * representation in the display name would be {@code "\"line 1\\nline 2\""} * (printed as {@code "line 1\nline 2"}) with the newline character escaped as * {@code "\\n"}. Similarly, given a string argument {@code "\t"}, the * representation in the display name would be {@code "\"\\t\""} (printed as * {@code "\t"}) instead of a blank string or invisible tab * character. The same applies for a character argument {@code '\t'}, whose * representation in the display name would be {@code "'\\t'"} (printed as * {@code '\t'}). * *

Please note that original source arguments are quoted when generating * a display name, before any implicit or explicit argument conversion is * performed. For example, if a parameterized class accepts {@code 3.14} as a * {@code float} argument that was converted from {@code "3.14"} as an input * string, {@code "3.14"} will be present in the display name instead of * {@code 3.14}. * * @since 6.0 * @see #name() */ @API(status = EXPERIMENTAL, since = "6.0") boolean quoteTextArguments() default true; /** * Configure whether all arguments of the parameterized class that implement * {@link AutoCloseable} will be closed after their corresponding * invocation. * *

Defaults to {@code true}. * *

WARNING: if an argument that implements * {@code AutoCloseable} is reused for multiple invocations of the same * parameterized class, you must set {@code autoCloseArguments} to * {@code false} to ensure that the argument is not closed between * invocations. * * @see java.lang.AutoCloseable */ boolean autoCloseArguments() default true; /** * Configure whether zero invocations are allowed for this * parameterized class. * *

Set this attribute to {@code true} if the absence of invocations is * expected in some cases and should not cause a test failure. * *

Defaults to {@code false}. */ boolean allowZeroInvocations() default false; /** * Configure how the number of arguments provided by an * {@link ArgumentsSource} are validated. * *

Defaults to {@link ArgumentCountValidationMode#DEFAULT}. * *

When an {@link ArgumentsSource} provides more arguments than declared * by the parameterized class constructor or {@link Parameter}-annotated * fields, there might be a bug in the parameterized class or the * {@link ArgumentsSource}. By default, the additional arguments are * ignored. {@code argumentCountValidation} allows you to control how * additional arguments are handled. The default can be configured via the * {@value ArgumentCountValidator#ARGUMENT_COUNT_VALIDATION_KEY} * configuration parameter (see the User Guide for details on configuration * parameters). * * @see ArgumentCountValidationMode */ ArgumentCountValidationMode argumentCountValidation() default ArgumentCountValidationMode.DEFAULT; } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedClassContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import static java.util.Collections.emptyList; import static java.util.Collections.reverse; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotatedMethods; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated; import static org.junit.platform.commons.support.HierarchyTraversalMode.BOTTOM_UP; import static org.junit.platform.commons.support.HierarchyTraversalMode.TOP_DOWN; import static org.junit.platform.commons.support.ReflectionSupport.findFields; import static org.junit.platform.commons.util.ReflectionUtils.isRecordClass; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.function.Predicate; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.params.provider.Arguments; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.support.HierarchyTraversalMode; import org.junit.platform.commons.util.ReflectionUtils; class ParameterizedClassContext implements ParameterizedDeclarationContext { private final Class testClass; private final ParameterizedClass annotation; private final TestInstance.Lifecycle testInstanceLifecycle; private final ResolverFacade resolverFacade; private final InjectionType injectionType; private final List beforeMethods; private final List afterMethods; ParameterizedClassContext(Class testClass, ParameterizedClass annotation, TestInstance.Lifecycle testInstanceLifecycle) { this.testClass = testClass; this.annotation = annotation; this.testInstanceLifecycle = testInstanceLifecycle; List fields = findParameterAnnotatedFields(testClass); if (fields.isEmpty()) { this.resolverFacade = ResolverFacade.create(ReflectionUtils.getDeclaredConstructor(testClass), annotation); this.injectionType = InjectionType.CONSTRUCTOR; } else { this.resolverFacade = ResolverFacade.create(testClass, fields); this.injectionType = InjectionType.FIELDS; } this.beforeMethods = findLifecycleMethodsAndAssertStaticAndNonPrivate(testClass, TOP_DOWN, BeforeParameterizedClassInvocation.class, BeforeParameterizedClassInvocation::injectArguments, this.resolverFacade); // Make a local copy since findAnnotatedMethods() returns an immutable list. this.afterMethods = new ArrayList<>(findLifecycleMethodsAndAssertStaticAndNonPrivate(testClass, BOTTOM_UP, AfterParameterizedClassInvocation.class, AfterParameterizedClassInvocation::injectArguments, this.resolverFacade)); // Since the bottom-up ordering of afterMethods will later be reversed when the // AfterParameterizedClassInvocationMethodInvoker extensions are executed within // ClassTemplateInvocationTestDescriptor, we have to reverse them to put them // in top-down order before we register them as extensions. reverse(afterMethods); } private static List findParameterAnnotatedFields(Class clazz) { if (isRecordClass(clazz)) { return emptyList(); } return findFields(clazz, it -> isAnnotated(it, Parameter.class), BOTTOM_UP); } @Override public Class getTestClass() { return this.testClass; } @Override public ParameterizedClass getAnnotation() { return this.annotation; } @Override public Class getAnnotatedElement() { return this.testClass; } @Override public String getDisplayNamePattern() { return this.annotation.name(); } @Override public boolean quoteTextArguments() { return this.annotation.quoteTextArguments(); } @Override public boolean isAutoClosingArguments() { return this.annotation.autoCloseArguments(); } @Override public boolean isAllowingZeroInvocations() { return this.annotation.allowZeroInvocations(); } @Override public ArgumentCountValidationMode getArgumentCountValidationMode() { return this.annotation.argumentCountValidation(); } @Override public ResolverFacade getResolverFacade() { return this.resolverFacade; } @Override public ParameterizedClassInvocationContext createInvocationContext(ParameterizedInvocationNameFormatter formatter, Arguments arguments, int invocationIndex) { return new ParameterizedClassInvocationContext(this, formatter, arguments, invocationIndex); } TestInstance.Lifecycle getTestInstanceLifecycle() { return testInstanceLifecycle; } InjectionType getInjectionType() { return injectionType; } List getBeforeMethods() { return beforeMethods; } List getAfterMethods() { return afterMethods; } private static List findLifecycleMethodsAndAssertStaticAndNonPrivate( Class testClass, HierarchyTraversalMode traversalMode, Class annotationType, Predicate injectArgumentsPredicate, ResolverFacade resolverFacade) { List methods = findAnnotatedMethods(testClass, annotationType, traversalMode); return methods.stream() // .map(method -> { A annotation = getAnnotation(method, annotationType); if (injectArgumentsPredicate.test(annotation)) { return new ArgumentSetLifecycleMethod(method, resolverFacade.createLifecycleMethodParameterResolver(method, annotation)); } return new ArgumentSetLifecycleMethod(method); }) // .toList(); } private static A getAnnotation(Method method, Class annotationType) { return findAnnotation(method, annotationType) // .orElseThrow(() -> new JUnitException("Method not annotated with @" + annotationType.getSimpleName())); } enum InjectionType { CONSTRUCTOR, FIELDS } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedClassExtension.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import static java.util.Objects.requireNonNull; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.params.ParameterizedClassContext.InjectionType.CONSTRUCTOR; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.util.Optional; import java.util.stream.Stream; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.extension.ClassTemplateInvocationContextProvider; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Namespace; import org.junit.jupiter.api.extension.ExtensionContext.Store; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.PreconditionViolationException; /** * @since 5.13 */ class ParameterizedClassExtension extends ParameterizedInvocationContextProvider implements ClassTemplateInvocationContextProvider, ParameterResolver { private static final String DECLARATION_CONTEXT_KEY = "context"; @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { // This method always returns `false` because it is not intended to be used as a parameter resolver. // Instead, it is used to provide a better error message when `TestInstance.Lifecycle.PER_CLASS` is // attempted to be combined with constructor injection of parameters. if (isDeclaredOnTestClassConstructor(parameterContext, extensionContext)) { validateAndStoreClassContext(extensionContext); } return false; } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { // Should never be called (see comment above). throw new JUnitException("Unexpected call to resolveParameter"); } @Override public boolean supportsClassTemplate(ExtensionContext extensionContext) { return validateAndStoreClassContext(extensionContext); } @Override public Stream provideClassTemplateInvocationContexts( ExtensionContext extensionContext) { return provideInvocationContexts(extensionContext, getDeclarationContext(extensionContext)); } @Override public boolean mayReturnZeroClassTemplateInvocationContexts(ExtensionContext extensionContext) { return getDeclarationContext(extensionContext).isAllowingZeroInvocations(); } private static boolean isDeclaredOnTestClassConstructor(ParameterContext parameterContext, ExtensionContext extensionContext) { Executable declaringExecutable = parameterContext.getDeclaringExecutable(); return declaringExecutable instanceof Constructor // && declaringExecutable.getDeclaringClass().equals(extensionContext.getTestClass().orElse(null)); } private boolean validateAndStoreClassContext(ExtensionContext extensionContext) { Store store = getStore(extensionContext); if (store.get(DECLARATION_CONTEXT_KEY) != null) { return true; } Optional annotation = findAnnotation(extensionContext.getTestClass(), ParameterizedClass.class); if (annotation.isEmpty()) { return false; } store.put(DECLARATION_CONTEXT_KEY, createClassContext(extensionContext, extensionContext.getRequiredTestClass(), annotation.get())); return true; } private static ParameterizedClassContext createClassContext(ExtensionContext extensionContext, Class testClass, ParameterizedClass annotation) { TestInstance.Lifecycle lifecycle = extensionContext.getTestInstanceLifecycle() // .orElseThrow(() -> new PreconditionViolationException("TestInstance.Lifecycle not present")); ParameterizedClassContext classContext = new ParameterizedClassContext(testClass, annotation, lifecycle); if (lifecycle == PER_CLASS && classContext.getInjectionType() == CONSTRUCTOR) { throw new PreconditionViolationException( "Constructor injection is not supported for @ParameterizedClass classes with @TestInstance(Lifecycle.PER_CLASS)"); } return classContext; } private ParameterizedClassContext getDeclarationContext(ExtensionContext extensionContext) { return requireNonNull(getStore(extensionContext)// .get(DECLARATION_CONTEXT_KEY, ParameterizedClassContext.class)); } private Store getStore(ExtensionContext context) { return context.getStore(Namespace.create(ParameterizedClassExtension.class, context.getRequiredTestClass())); } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedClassInvocationContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import java.util.List; import java.util.stream.Stream; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.extension.ClassTemplateInvocationContext; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedClassContext.InjectionType; import org.junit.jupiter.params.provider.Arguments; import org.junit.platform.commons.util.Preconditions; class ParameterizedClassInvocationContext extends ParameterizedInvocationContext implements ClassTemplateInvocationContext { private final ResolutionCache resolutionCache = ResolutionCache.enabled(); ParameterizedClassInvocationContext(ParameterizedClassContext classContext, ParameterizedInvocationNameFormatter formatter, Arguments arguments, int invocationIndex) { super(classContext, formatter, arguments, invocationIndex); } @Override public String getDisplayName(int invocationIndex) { return super.getDisplayName(invocationIndex); } @Override public List getAdditionalExtensions() { return Stream.concat(Stream.of(createParameterInjector()), createLifecycleMethodInvokers()) // .toList(); } @Override public void prepareInvocation(ExtensionContext context) { super.prepareInvocation(context); } private Extension createParameterInjector() { InjectionType injectionType = this.declarationContext.getInjectionType(); return switch (injectionType) { case CONSTRUCTOR -> createExtensionForConstructorInjection(); case FIELDS -> createExtensionForFieldInjection(); }; } private ClassTemplateConstructorParameterResolver createExtensionForConstructorInjection() { Preconditions.condition(this.declarationContext.getTestInstanceLifecycle() == PER_METHOD, "Constructor injection is only supported for lifecycle PER_METHOD"); return new ClassTemplateConstructorParameterResolver(this.declarationContext, this.arguments, this.invocationIndex, this.resolutionCache); } private Extension createExtensionForFieldInjection() { ResolverFacade resolverFacade = this.declarationContext.getResolverFacade(); TestInstance.Lifecycle lifecycle = this.declarationContext.getTestInstanceLifecycle(); return switch (lifecycle) { case PER_CLASS -> new BeforeClassTemplateInvocationFieldInjector(resolverFacade, this.arguments, this.invocationIndex, this.resolutionCache); case PER_METHOD -> new InstancePostProcessingClassTemplateFieldInjector(resolverFacade, this.arguments, this.invocationIndex, this.resolutionCache); }; } private Stream createLifecycleMethodInvokers() { return Stream.concat( // this.declarationContext.getBeforeMethods().stream().map( this::createBeforeParameterizedClassInvocationMethodInvoker), // this.declarationContext.getAfterMethods().stream().map( this::createAfterParameterizedClassInvocationMethodInvoker) // ); } private BeforeParameterizedClassInvocationMethodInvoker createBeforeParameterizedClassInvocationMethodInvoker( ArgumentSetLifecycleMethod method) { return new BeforeParameterizedClassInvocationMethodInvoker(this.declarationContext, this.arguments, this.invocationIndex, this.resolutionCache, method); } private AfterParameterizedClassInvocationMethodInvoker createAfterParameterizedClassInvocationMethodInvoker( ArgumentSetLifecycleMethod method) { return new AfterParameterizedClassInvocationMethodInvoker(this.declarationContext, this.arguments, this.invocationIndex, this.resolutionCache, method); } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedDeclarationContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import org.junit.jupiter.params.provider.Arguments; /** * @since 5.13 */ interface ParameterizedDeclarationContext { Class getTestClass(); Annotation getAnnotation(); AnnotatedElement getAnnotatedElement(); String getDisplayNamePattern(); boolean quoteTextArguments(); boolean isAutoClosingArguments(); boolean isAllowingZeroInvocations(); ArgumentCountValidationMode getArgumentCountValidationMode(); default String getAnnotationName() { return getAnnotation().annotationType().getSimpleName(); } ResolverFacade getResolverFacade(); C createInvocationContext(ParameterizedInvocationNameFormatter formatter, Arguments arguments, int invocationIndex); } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedInvocationConstants.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import static org.apiguardian.api.API.Status.MAINTAINED; import org.apiguardian.api.API; /** * Constants for the use with the * {@link ParameterizedClass @ParameterizedClass} and * {@link ParameterizedTest @ParameterizedTest} annotations. * * @since 5.13 */ @API(status = MAINTAINED, since = "5.13") public class ParameterizedInvocationConstants { /** * Placeholder for the {@linkplain org.junit.jupiter.api.TestInfo#getDisplayName * display name} of a {@code @ParameterizedTest} method: {displayName} * * @since 5.3 * @see ParameterizedClass#name() * @see ParameterizedTest#name() */ public static final String DISPLAY_NAME_PLACEHOLDER = "{displayName}"; /** * Placeholder for the current invocation index of a {@code @ParameterizedTest} * method (1-based): {index} * * @since 5.3 * @see ParameterizedClass#name() * @see ParameterizedTest#name() * @see #DEFAULT_DISPLAY_NAME */ public static final String INDEX_PLACEHOLDER = "{index}"; /** * Placeholder for the complete, comma-separated arguments list of the * current invocation of a {@code @ParameterizedTest} method: * {arguments} * * @since 5.3 * @see ParameterizedClass#name() * @see ParameterizedTest#name() */ public static final String ARGUMENTS_PLACEHOLDER = "{arguments}"; /** * Placeholder for the complete, comma-separated named arguments list * of the current invocation of a {@code @ParameterizedTest} method: * {argumentsWithNames} * *

Argument names will be retrieved via the {@link java.lang.reflect.Parameter#getName()} * API if the byte code contains parameter names — for example, if * the code was compiled with the {@code -parameters} command line argument * for {@code javac}. * * @since 5.6 * @see ParameterizedClass#name() * @see ParameterizedTest#name() * @see #ARGUMENT_SET_NAME_OR_ARGUMENTS_WITH_NAMES_PLACEHOLDER */ public static final String ARGUMENTS_WITH_NAMES_PLACEHOLDER = "{argumentsWithNames}"; /** * Placeholder for the name of the argument set for the current invocation * of a {@code @ParameterizedTest} method: {argumentSetName}. * *

This placeholder can be used when the current set of arguments was created via * {@link org.junit.jupiter.params.provider.Arguments#argumentSet(String, Object...) * argumentSet()}. * * @since 5.11 * @see ParameterizedClass#name() * @see ParameterizedTest#name() * @see #ARGUMENT_SET_NAME_OR_ARGUMENTS_WITH_NAMES_PLACEHOLDER * @see org.junit.jupiter.params.provider.Arguments#argumentSet(String, Object...) */ @API(status = MAINTAINED, since = "5.13.3") public static final String ARGUMENT_SET_NAME_PLACEHOLDER = "{argumentSetName}"; /** * Placeholder for either {@link #ARGUMENT_SET_NAME_PLACEHOLDER} or * {@link #ARGUMENTS_WITH_NAMES_PLACEHOLDER}, depending on whether the * current set of arguments was created via * {@link org.junit.jupiter.params.provider.Arguments#argumentSet(String, Object...) * argumentSet()}: {argumentSetNameOrArgumentsWithNames}. * * @since 5.11 * @see ParameterizedClass#name() * @see ParameterizedTest#name() * @see #ARGUMENT_SET_NAME_PLACEHOLDER * @see #ARGUMENTS_WITH_NAMES_PLACEHOLDER * @see #DEFAULT_DISPLAY_NAME * @see org.junit.jupiter.params.provider.Arguments#argumentSet(String, Object...) */ @API(status = MAINTAINED, since = "5.13.3") public static final String ARGUMENT_SET_NAME_OR_ARGUMENTS_WITH_NAMES_PLACEHOLDER = "{argumentSetNameOrArgumentsWithNames}"; /** * Default display name pattern for the current invocation of a * {@code @ParameterizedTest} method: {@value} * *

Note that the default pattern does not include the * {@linkplain #DISPLAY_NAME_PLACEHOLDER display name} of the * {@code @ParameterizedTest} method. * * @since 5.3 * @see ParameterizedClass#name() * @see ParameterizedTest#name() * @see #DISPLAY_NAME_PLACEHOLDER * @see #INDEX_PLACEHOLDER * @see #ARGUMENT_SET_NAME_OR_ARGUMENTS_WITH_NAMES_PLACEHOLDER */ public static final String DEFAULT_DISPLAY_NAME = ParameterizedInvocationNameFormatter.DEFAULT_DISPLAY_NAME_PATTERN; private ParameterizedInvocationConstants() { } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedInvocationContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import static org.junit.platform.commons.util.ClassLoaderUtils.getClassLoader; import java.util.Arrays; import java.util.concurrent.atomic.AtomicInteger; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Namespace; import org.junit.jupiter.params.aggregator.ArgumentsAccessor; import org.junit.jupiter.params.aggregator.DefaultArgumentsAccessor; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.support.ParameterDeclarations; class ParameterizedInvocationContext> { private static final Namespace NAMESPACE = Namespace.create(ParameterizedTestInvocationContext.class); protected final T declarationContext; private final ParameterizedInvocationNameFormatter formatter; protected final EvaluatedArgumentSet arguments; protected final int invocationIndex; ParameterizedInvocationContext(T declarationContext, ParameterizedInvocationNameFormatter formatter, Arguments arguments, int invocationIndex) { this.declarationContext = declarationContext; this.formatter = formatter; ResolverFacade resolverFacade = this.declarationContext.getResolverFacade(); this.arguments = EvaluatedArgumentSet.of(arguments, resolverFacade::determineConsumedArgumentLength); this.invocationIndex = invocationIndex; } public String getDisplayName(int invocationIndex) { return this.formatter.format(invocationIndex, this.arguments, this.declarationContext.quoteTextArguments()); } public void prepareInvocation(ExtensionContext context) { if (this.declarationContext.isAutoClosingArguments()) { registerAutoCloseableArgumentsInStoreForClosing(context); } validateArgumentCount(context); storeParameterInfo(context); } private void registerAutoCloseableArgumentsInStoreForClosing(ExtensionContext context) { ExtensionContext.Store store = context.getStore(NAMESPACE); AtomicInteger argumentIndex = new AtomicInteger(); Arrays.stream(this.arguments.getAllPayloads()) // .filter(AutoCloseable.class::isInstance) // .map(AutoCloseable.class::cast) // .map(CloseableArgument::new) // .forEach(closeable -> store.put(argumentIndex.incrementAndGet(), closeable)); } private void validateArgumentCount(ExtensionContext context) { new ArgumentCountValidator(this.declarationContext, this.arguments).validate(context); } private void storeParameterInfo(ExtensionContext context) { ParameterDeclarations declarations = this.declarationContext.getResolverFacade().getIndexedParameterDeclarations(); ClassLoader classLoader = getClassLoader(this.declarationContext.getTestClass()); @Nullable Object[] arguments = this.arguments.getConsumedPayloads(); ArgumentsAccessor accessor = DefaultArgumentsAccessor.create(invocationIndex, classLoader, arguments); new DefaultParameterInfo(declarations, accessor).store(context); } @SuppressWarnings({ "deprecation", "try" }) private record CloseableArgument(AutoCloseable autoCloseable) implements ExtensionContext.Store.CloseableResource, AutoCloseable { @Override public void close() throws Exception { this.autoCloseable.close(); } } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedInvocationContextProvider.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import static org.junit.platform.commons.support.AnnotationSupport.findRepeatableAnnotations; import java.util.List; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Stream; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TemplateInvocationValidationException; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.ArgumentsProvider; import org.junit.jupiter.params.provider.ArgumentsSource; import org.junit.jupiter.params.support.AnnotationConsumerInitializer; import org.junit.jupiter.params.support.ParameterDeclarations; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.commons.util.Preconditions; class ParameterizedInvocationContextProvider { protected Stream provideInvocationContexts(ExtensionContext extensionContext, ParameterizedDeclarationContext declarationContext) { List argumentsSources = collectArgumentSources(declarationContext); ParameterDeclarations parameters = declarationContext.getResolverFacade().getIndexedParameterDeclarations(); ParameterizedInvocationNameFormatter formatter = ParameterizedInvocationNameFormatter.create(extensionContext, declarationContext); AtomicLong invocationCount = new AtomicLong(0); return argumentsSources.stream() // .map(ArgumentsSource::value) // .map(clazz -> ParameterizedTestSpiInstantiator.instantiate(ArgumentsProvider.class, clazz, extensionContext)) // .map(provider -> AnnotationConsumerInitializer.initialize(declarationContext.getAnnotatedElement(), provider)) // .flatMap(provider -> arguments(provider, parameters, extensionContext)) // .map(arguments -> { invocationCount.incrementAndGet(); return declarationContext.createInvocationContext(formatter, arguments, invocationCount.intValue()); }) // .onClose(() -> validateInvokedAtLeastOnce(invocationCount.get(), declarationContext)); } private static void validateInvokedAtLeastOnce(long invocationCount, ParameterizedDeclarationContext declarationContext) { if (invocationCount == 0 && !declarationContext.isAllowingZeroInvocations()) { String message = "Configuration error: You must configure at least one set of arguments for this @%s".formatted( declarationContext.getAnnotationName()); throw new TemplateInvocationValidationException(message); } } private static List collectArgumentSources(ParameterizedDeclarationContext declarationContext) { List argumentsSources = findRepeatableAnnotations(declarationContext.getAnnotatedElement(), ArgumentsSource.class); Preconditions.notEmpty(argumentsSources, () -> "Configuration error: You must configure at least one arguments source for this @%s".formatted( declarationContext.getAnnotationName())); return argumentsSources; } protected static Stream arguments(ArgumentsProvider provider, ParameterDeclarations parameters, ExtensionContext context) { try { return provider.provideArguments(parameters, context); } catch (Exception e) { throw ExceptionUtils.throwAsUncheckedException(e); } } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedInvocationNameFormatter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import static java.util.Objects.requireNonNull; import static org.junit.jupiter.params.ParameterizedInvocationConstants.ARGUMENTS_PLACEHOLDER; import static org.junit.jupiter.params.ParameterizedInvocationConstants.ARGUMENTS_WITH_NAMES_PLACEHOLDER; import static org.junit.jupiter.params.ParameterizedInvocationConstants.ARGUMENT_SET_NAME_OR_ARGUMENTS_WITH_NAMES_PLACEHOLDER; import static org.junit.jupiter.params.ParameterizedInvocationConstants.ARGUMENT_SET_NAME_PLACEHOLDER; import static org.junit.jupiter.params.ParameterizedInvocationConstants.DISPLAY_NAME_PLACEHOLDER; import static org.junit.jupiter.params.ParameterizedInvocationConstants.INDEX_PLACEHOLDER; import static org.junit.platform.commons.util.StringUtils.isNotBlank; import java.text.FieldPosition; import java.text.Format; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.StringJoiner; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.function.Function; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExtensionConfigurationException; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.support.ParameterNameAndArgument; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.StringUtils; /** * @since 5.0 */ class ParameterizedInvocationNameFormatter { /** * Global cache for {arguments} pattern strings, keyed by the number of arguments. * @since 6.0 */ private static final Map argumentsPatternCache = new ConcurrentHashMap<>(8); static final String DEFAULT_DISPLAY_NAME = "{default_display_name}"; static final String DEFAULT_DISPLAY_NAME_PATTERN = "[" + INDEX_PLACEHOLDER + "] " + ARGUMENT_SET_NAME_OR_ARGUMENTS_WITH_NAMES_PLACEHOLDER; static final String DISPLAY_NAME_PATTERN_KEY = "junit.jupiter.params.displayname.default"; static final String ARGUMENT_MAX_LENGTH_KEY = "junit.jupiter.params.displayname.argument.maxlength"; static ParameterizedInvocationNameFormatter create(ExtensionContext extensionContext, ParameterizedDeclarationContext declarationContext) { String name = declarationContext.getDisplayNamePattern(); String pattern = DEFAULT_DISPLAY_NAME.equals(name) ? extensionContext.getConfigurationParameter(DISPLAY_NAME_PATTERN_KEY) // .orElse(DEFAULT_DISPLAY_NAME_PATTERN) : name; pattern = Preconditions.notBlank(pattern.strip(), () -> "Configuration error: @%s on %s must be declared with a non-empty name.".formatted( declarationContext.getAnnotationName(), declarationContext.getResolverFacade().getIndexedParameterDeclarations().getSourceElementDescription())); int argumentMaxLength = extensionContext.getConfigurationParameter(ARGUMENT_MAX_LENGTH_KEY, Integer::parseInt) // .orElse(512); return new ParameterizedInvocationNameFormatter(pattern, extensionContext.getDisplayName(), declarationContext, argumentMaxLength); } private final PartialFormatter[] partialFormatters; ParameterizedInvocationNameFormatter(String pattern, String displayName, ParameterizedDeclarationContext declarationContext, int argumentMaxLength) { try { this.partialFormatters = parse(pattern, displayName, declarationContext, argumentMaxLength); } catch (Exception ex) { String message = "The display name pattern defined for the parameterized test is invalid. " + "See nested exception for further details."; throw new JUnitException(message, ex); } } String format(int invocationIndex, EvaluatedArgumentSet arguments, boolean quoteTextArguments) { try { return formatSafely(invocationIndex, arguments, quoteTextArguments); } catch (Exception ex) { String message = "Failed to format display name for parameterized test. " + "See nested exception for further details."; throw new JUnitException(message, ex); } } @SuppressWarnings("JdkObsolete") private String formatSafely(int invocationIndex, EvaluatedArgumentSet arguments, boolean quoteTextArguments) { ArgumentsContext context = new ArgumentsContext(invocationIndex, arguments.getConsumedArguments(), arguments.getName(), quoteTextArguments); StringBuffer result = new StringBuffer(); // used instead of StringBuilder so MessageFormat can append directly for (PartialFormatter partialFormatter : this.partialFormatters) { partialFormatter.append(context, result); } return result.toString(); } private PartialFormatter[] parse(String pattern, String displayName, ParameterizedDeclarationContext declarationContext, int argumentMaxLength) { List result = new ArrayList<>(); PartialFormatters formatters = createPartialFormatters(displayName, declarationContext, argumentMaxLength); String unparsedSegment = pattern; while (isNotBlank(unparsedSegment)) { PlaceholderPosition position = findFirstPlaceholder(formatters, unparsedSegment); if (position == null) { result.add(determineNonPlaceholderFormatter(unparsedSegment, argumentMaxLength)); break; } if (position.index > 0) { String before = unparsedSegment.substring(0, position.index); result.add(determineNonPlaceholderFormatter(before, argumentMaxLength)); } result.add(formatters.get(position.placeholder)); unparsedSegment = unparsedSegment.substring(position.index + position.placeholder.length()); } return result.toArray(new PartialFormatter[0]); } private static @Nullable PlaceholderPosition findFirstPlaceholder(PartialFormatters formatters, String segment) { if (segment.length() < formatters.minimumPlaceholderLength) { return null; } PlaceholderPosition minimum = null; for (String placeholder : formatters.placeholders()) { int index = segment.indexOf(placeholder); if (index >= 0) { if (index < formatters.minimumPlaceholderLength) { return new PlaceholderPosition(index, placeholder); } else if (minimum == null || index < minimum.index) { minimum = new PlaceholderPosition(index, placeholder); } } } return minimum; } private static PartialFormatter determineNonPlaceholderFormatter(String segment, int argumentMaxLength) { return segment.contains("{") // ? new MessageFormatPartialFormatter(segment, argumentMaxLength) // : (context, result) -> result.append(segment); } private PartialFormatters createPartialFormatters(String displayName, ParameterizedDeclarationContext declarationContext, int argumentMaxLength) { PartialFormatter argumentsWithNamesFormatter = new CachingByArgumentsLengthPartialFormatter( length -> new MessageFormatPartialFormatter(argumentsPattern(length), argumentMaxLength, true, declarationContext.getResolverFacade())); PartialFormatter argumentSetNameFormatter = new ArgumentSetNameFormatter( declarationContext.getAnnotationName()); PartialFormatters formatters = new PartialFormatters(); formatters.put(INDEX_PLACEHOLDER, PartialFormatter.INDEX); formatters.put(DISPLAY_NAME_PLACEHOLDER, (context, result) -> result.append(displayName)); formatters.put(ARGUMENT_SET_NAME_PLACEHOLDER, argumentSetNameFormatter); formatters.put(ARGUMENTS_WITH_NAMES_PLACEHOLDER, argumentsWithNamesFormatter); formatters.put(ARGUMENTS_PLACEHOLDER, new CachingByArgumentsLengthPartialFormatter( length -> new MessageFormatPartialFormatter(argumentsPattern(length), argumentMaxLength))); formatters.put(ARGUMENT_SET_NAME_OR_ARGUMENTS_WITH_NAMES_PLACEHOLDER, (context, result) -> { PartialFormatter formatterToUse = context.argumentSetName.isPresent() // ? argumentSetNameFormatter // : argumentsWithNamesFormatter; formatterToUse.append(context, result); }); return formatters; } private static String argumentsPattern(int length) { return argumentsPatternCache.computeIfAbsent(length, // key -> { StringJoiner sj = new StringJoiner(", "); for (int i = 0; i < length; i++) { sj.add("{" + i + "}"); } return sj.toString(); }); } private record PlaceholderPosition(int index, String placeholder) { } @SuppressWarnings("ArrayRecordComponent") private record ArgumentsContext(int invocationIndex, @Nullable Object[] consumedArguments, Optional argumentSetName, boolean quoteTextArguments) { } @FunctionalInterface private interface PartialFormatter { PartialFormatter INDEX = (context, result) -> result.append(context.invocationIndex); void append(ArgumentsContext context, StringBuffer result); } private record ArgumentSetNameFormatter(String annotationName) implements PartialFormatter { @Override public void append(ArgumentsContext context, StringBuffer result) { if (context.argumentSetName.isPresent()) { result.append(context.argumentSetName.get()); return; } throw new ExtensionConfigurationException( "When the display name pattern for a @%s contains %s, the arguments must be supplied as an ArgumentSet.".formatted( this.annotationName, ARGUMENT_SET_NAME_PLACEHOLDER)); } } private static class MessageFormatPartialFormatter implements PartialFormatter { @SuppressWarnings("UnnecessaryUnicodeEscape") private static final char ELLIPSIS = '\u2026'; private final MessageFormat messageFormat; private final int argumentMaxLength; private final boolean generateNameValuePairs; private final @Nullable ResolverFacade resolverFacade; MessageFormatPartialFormatter(String pattern, int argumentMaxLength) { this(pattern, argumentMaxLength, false, null); } MessageFormatPartialFormatter(String pattern, int argumentMaxLength, boolean generateNameValuePairs, @Nullable ResolverFacade resolverFacade) { this.messageFormat = new MessageFormat(pattern); this.argumentMaxLength = argumentMaxLength; this.generateNameValuePairs = generateNameValuePairs; this.resolverFacade = resolverFacade; } // synchronized because MessageFormat is not thread-safe @Override public synchronized void append(ArgumentsContext context, StringBuffer result) { this.messageFormat.format(makeReadable(context.consumedArguments, context.quoteTextArguments), result, new FieldPosition(0)); } private @Nullable Object[] makeReadable(@Nullable Object[] arguments, boolean quoteTextArguments) { @Nullable Format[] formats = messageFormat.getFormatsByArgumentIndex(); @Nullable Object[] result = Arrays.copyOf(arguments, Math.min(arguments.length, formats.length), Object[].class); for (int i = 0; i < result.length; i++) { if (formats[i] == null) { Object argument = arguments[i]; String prefix = ""; if (argument instanceof ParameterNameAndArgument parameterNameAndArgument) { // This supports the useHeadersInDisplayName attributes in @CsvSource and @CsvFileSource. prefix = parameterNameAndArgument.getName() + " = "; argument = parameterNameAndArgument.getPayload(); } else if (this.generateNameValuePairs && this.resolverFacade != null) { Optional parameterName = this.resolverFacade.getParameterName(i); if (parameterName.isPresent()) { // This supports the {argumentsWithNames} pattern. prefix = parameterName.get() + " = "; } } if (argument instanceof Character ch) { result[i] = prefix + (quoteTextArguments ? QuoteUtils.quote(ch) : ch); } else { String argumentText = (argument == null ? "null" : truncateIfExceedsMaxLength(StringUtils.nullSafeToString(argument))); result[i] = prefix + (quoteTextArguments && argument instanceof CharSequence// ? QuoteUtils.quote(argumentText) : argumentText); } } } return result; } private String truncateIfExceedsMaxLength(String argument) { if (argument.length() > this.argumentMaxLength) { return argument.substring(0, this.argumentMaxLength - 1) + ELLIPSIS; } return argument; } } /** * Caches formatters by the length of the consumed arguments which * may differ from the number of declared parameters. * *

For example, when using multiple providers or a provider that returns * argument arrays of different length, such as: * *

	 * @ParameterizedTest
	 * @CsvSource({"a", "a,b", "a,b,c"})
	 * void test(ArgumentsAccessor accessor) {}
	 * 
*/ private static class CachingByArgumentsLengthPartialFormatter implements PartialFormatter { private final ConcurrentMap cache = new ConcurrentHashMap<>(1); private final Function factory; CachingByArgumentsLengthPartialFormatter(Function factory) { this.factory = factory; } @Override public void append(ArgumentsContext context, StringBuffer result) { cache.computeIfAbsent(context.consumedArguments.length, factory).append(context, result); } } private static class PartialFormatters { private final Map formattersByPlaceholder = new LinkedHashMap<>(); private int minimumPlaceholderLength = Integer.MAX_VALUE; void put(String placeholder, PartialFormatter formatter) { formattersByPlaceholder.put(placeholder, formatter); int newPlaceholderLength = placeholder.length(); if (newPlaceholderLength < minimumPlaceholderLength) { minimumPlaceholderLength = newPlaceholderLength; } } PartialFormatter get(String placeholder) { return requireNonNull(formattersByPlaceholder.get(placeholder)); } Set placeholders() { return formattersByPlaceholder.keySet(); } } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedInvocationParameterResolver.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import java.lang.reflect.Executable; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.extension.ParameterResolver; /** * @since 5.13 */ abstract class ParameterizedInvocationParameterResolver implements ParameterResolver { private final ResolverFacade resolverFacade; private final EvaluatedArgumentSet arguments; private final int invocationIndex; private final ResolutionCache resolutionCache; ParameterizedInvocationParameterResolver(ResolverFacade resolverFacade, EvaluatedArgumentSet arguments, int invocationIndex, ResolutionCache resolutionCache) { this.resolverFacade = resolverFacade; this.arguments = arguments; this.invocationIndex = invocationIndex; this.resolutionCache = resolutionCache; } @Override public final ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { return ExtensionContextScope.TEST_METHOD; } @Override public final boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return isSupportedOnConstructorOrMethod(parameterContext.getDeclaringExecutable(), extensionContext) // && this.resolverFacade.isSupportedParameter(parameterContext, this.arguments); } @Override public final @Nullable Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return this.resolverFacade.resolve(parameterContext, extensionContext, this.arguments, this.invocationIndex, this.resolutionCache); } protected abstract boolean isSupportedOnConstructorOrMethod(Executable declaringExecutable, ExtensionContext extensionContext); } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTest.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.provider.ArgumentsSource; /** * {@code @ParameterizedTest} is used to signal that the annotated method is a * parameterized test method. * *

Such methods must not be {@code private} or {@code static}. * *

Arguments Providers and Sources

* *

{@code @ParameterizedTest} methods must specify at least one * {@link org.junit.jupiter.params.provider.ArgumentsProvider ArgumentsProvider} * via {@link org.junit.jupiter.params.provider.ArgumentsSource @ArgumentsSource} * or a corresponding composed annotation (e.g., {@code @ValueSource}, * {@code @CsvSource}, etc.). The provider is responsible for providing a * {@link java.util.stream.Stream Stream} of * {@link org.junit.jupiter.params.provider.Arguments Arguments} that will be * used to invoke the parameterized test method. * *

Formal Parameter List

* *

A {@code @ParameterizedTest} method may declare additional parameters at * the end of the method's parameter list to be resolved by other * {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolvers} * (e.g., {@code TestInfo}, {@code TestReporter}, etc.). Specifically, a * parameterized test method must declare formal parameters according to the * following rules. * *

    *
  1. Zero or more indexed parameters must be declared first.
  2. *
  3. Zero or more aggregators must be declared next.
  4. *
  5. Zero or more parameters supplied by other {@code ParameterResolver} * implementations must be declared last.
  6. *
* *

In this context, an indexed parameter is an argument for a given * index in the {@code Arguments} provided by an {@code ArgumentsProvider} that * is passed as an argument to the parameterized method at the same index in the * method's formal parameter list. An aggregator is any parameter of type * {@link org.junit.jupiter.params.aggregator.ArgumentsAccessor ArgumentsAccessor} * or any parameter annotated with * {@link org.junit.jupiter.params.aggregator.AggregateWith @AggregateWith}. * *

Argument Conversion

* *

Method parameters may be annotated with * {@link org.junit.jupiter.params.converter.ConvertWith @ConvertWith} * or a corresponding composed annotation to specify an explicit * {@link org.junit.jupiter.params.converter.ArgumentConverter ArgumentConverter}. * Otherwise, JUnit Jupiter will attempt to perform an implicit * conversion to the target type automatically (see the User Guide for further * details). * *

Composed Annotations

* *

{@code @ParameterizedTest} may also be used as a meta-annotation in order * to create a custom composed annotation that inherits the semantics * of {@code @ParameterizedTest}. * *

Inheritance

* *

{@code @ParameterizedTest} methods are inherited from superclasses as long * as they are not overridden according to the visibility rules of the * Java language. Similarly, {@code @ParameterizedTest} methods declared as * interface default methods are inherited as long as they are not * overridden. * *

Test Execution Order

* *

By default, test methods will be ordered using an algorithm that is * deterministic but intentionally nonobvious. This ensures that subsequent runs * of a test suite execute test methods in the same order, thereby allowing for * repeatable builds. In this context, a test method is any instance * method that is directly annotated or meta-annotated with {@code @Test}, * {@code @RepeatedTest}, {@code @ParameterizedTest}, {@code @TestFactory}, or * {@code @TestTemplate}. * *

Although true unit tests typically should not rely on the order * in which they are executed, there are times when it is necessary to enforce * a specific test method execution order — for example, when writing * integration tests or functional tests where the sequence of * the tests is important, especially in conjunction with * {@link org.junit.jupiter.api.TestInstance @TestInstance(Lifecycle.PER_CLASS)}. * *

To control the order in which test methods are executed, annotate your * test class or test interface with * {@link org.junit.jupiter.api.TestMethodOrder @TestMethodOrder} and specify * the desired {@link org.junit.jupiter.api.MethodOrderer MethodOrderer} * implementation. * * @since 5.0 * @see ParameterizedClass * @see org.junit.jupiter.params.provider.Arguments * @see org.junit.jupiter.params.provider.ArgumentsProvider * @see org.junit.jupiter.params.provider.ArgumentsSource * @see org.junit.jupiter.params.provider.CsvFileSource * @see org.junit.jupiter.params.provider.CsvSource * @see org.junit.jupiter.params.provider.EnumSource * @see org.junit.jupiter.params.provider.MethodSource * @see org.junit.jupiter.params.provider.ValueSource * @see org.junit.jupiter.params.aggregator.ArgumentsAccessor * @see org.junit.jupiter.params.aggregator.AggregateWith * @see org.junit.jupiter.params.converter.ArgumentConverter * @see org.junit.jupiter.params.converter.ConvertWith */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = STABLE, since = "5.7") @TestTemplate @ExtendWith(ParameterizedTestExtension.class) @SuppressWarnings("exports") public @interface ParameterizedTest { /** * See {@link ParameterizedInvocationConstants#DISPLAY_NAME_PLACEHOLDER}. * * @since 5.3 * @see #name * @deprecated Please reference * {@link ParameterizedInvocationConstants#DISPLAY_NAME_PLACEHOLDER} * instead. */ @API(status = DEPRECATED, since = "5.13") @Deprecated(since = "5.13") String DISPLAY_NAME_PLACEHOLDER = ParameterizedInvocationConstants.DISPLAY_NAME_PLACEHOLDER; /** * See {@link ParameterizedInvocationConstants#INDEX_PLACEHOLDER}. * * @since 5.3 * @see #name * @see ParameterizedInvocationConstants#DEFAULT_DISPLAY_NAME * @deprecated Please reference * {@link ParameterizedInvocationConstants#INDEX_PLACEHOLDER} instead. */ @API(status = DEPRECATED, since = "5.13") @Deprecated(since = "5.13") String INDEX_PLACEHOLDER = ParameterizedInvocationConstants.INDEX_PLACEHOLDER; /** * See {@link ParameterizedInvocationConstants#ARGUMENTS_PLACEHOLDER}. * * @since 5.3 * @see #name * @deprecated Please reference * {@link ParameterizedInvocationConstants#ARGUMENTS_PLACEHOLDER} instead. */ @API(status = DEPRECATED, since = "5.13") @Deprecated(since = "5.13") String ARGUMENTS_PLACEHOLDER = ParameterizedInvocationConstants.ARGUMENTS_PLACEHOLDER; /** * See * {@link ParameterizedInvocationConstants#ARGUMENTS_WITH_NAMES_PLACEHOLDER}. * * @since 5.6 * @see #name * @see ParameterizedInvocationConstants#ARGUMENT_SET_NAME_OR_ARGUMENTS_WITH_NAMES_PLACEHOLDER * @deprecated Please reference * {@link ParameterizedInvocationConstants#ARGUMENTS_WITH_NAMES_PLACEHOLDER} * instead. */ @API(status = DEPRECATED, since = "5.13") @Deprecated(since = "5.13") String ARGUMENTS_WITH_NAMES_PLACEHOLDER = ParameterizedInvocationConstants.ARGUMENTS_WITH_NAMES_PLACEHOLDER; /** * See * {@link ParameterizedInvocationConstants#ARGUMENT_SET_NAME_PLACEHOLDER}. * * @since 5.11 * @see #name * @see ParameterizedInvocationConstants#ARGUMENT_SET_NAME_OR_ARGUMENTS_WITH_NAMES_PLACEHOLDER * @see org.junit.jupiter.params.provider.Arguments#argumentSet(String, Object...) * @deprecated Please reference * {@link ParameterizedInvocationConstants#ARGUMENT_SET_NAME_PLACEHOLDER} * instead. */ @API(status = DEPRECATED, since = "5.13") @Deprecated(since = "5.13") String ARGUMENT_SET_NAME_PLACEHOLDER = ParameterizedInvocationConstants.ARGUMENT_SET_NAME_PLACEHOLDER; /** * See * {@link ParameterizedInvocationConstants#ARGUMENT_SET_NAME_OR_ARGUMENTS_WITH_NAMES_PLACEHOLDER}. * * @since 5.11 * @see #name * @see ParameterizedInvocationConstants#ARGUMENT_SET_NAME_PLACEHOLDER * @see ParameterizedInvocationConstants#ARGUMENTS_WITH_NAMES_PLACEHOLDER * @see ParameterizedInvocationConstants#DEFAULT_DISPLAY_NAME * @see org.junit.jupiter.params.provider.Arguments#argumentSet(String, Object...) * @deprecated Please reference * {@link ParameterizedInvocationConstants#ARGUMENT_SET_NAME_OR_ARGUMENTS_WITH_NAMES_PLACEHOLDER} * instead. */ @API(status = DEPRECATED, since = "5.13") @Deprecated(since = "5.13") String ARGUMENT_SET_NAME_OR_ARGUMENTS_WITH_NAMES_PLACEHOLDER = // ParameterizedInvocationConstants.ARGUMENT_SET_NAME_OR_ARGUMENTS_WITH_NAMES_PLACEHOLDER; /** * See * {@link ParameterizedInvocationConstants#DEFAULT_DISPLAY_NAME}. * * @since 5.3 * @see #name * @see ParameterizedInvocationConstants#DISPLAY_NAME_PLACEHOLDER * @see ParameterizedInvocationConstants#INDEX_PLACEHOLDER * @see ParameterizedInvocationConstants#ARGUMENT_SET_NAME_OR_ARGUMENTS_WITH_NAMES_PLACEHOLDER * @deprecated Please reference * {@link ParameterizedInvocationConstants#DEFAULT_DISPLAY_NAME} instead. */ @API(status = DEPRECATED, since = "5.13") @Deprecated(since = "5.13") String DEFAULT_DISPLAY_NAME = ParameterizedInvocationConstants.DEFAULT_DISPLAY_NAME; /** * The display name to be used for individual invocations of the * parameterized test; never blank or consisting solely of whitespace. * *

Defaults to {@value ParameterizedInvocationNameFormatter#DEFAULT_DISPLAY_NAME}. * *

If the default display name flag * ({@value ParameterizedInvocationNameFormatter#DEFAULT_DISPLAY_NAME}) * is not overridden, JUnit will: *

    *
  • Look up the {@value ParameterizedInvocationNameFormatter#DISPLAY_NAME_PATTERN_KEY} * configuration parameter and use it if available. The configuration * parameter can be supplied via the {@code Launcher} API, build tools (e.g., * Gradle and Maven), a JVM system property, or the JUnit Platform configuration * file (i.e., a file named {@code junit-platform.properties} in the root of * the class path). Consult the User Guide for further information.
  • *
  • Otherwise, {@value ParameterizedInvocationConstants#DEFAULT_DISPLAY_NAME} will be used.
  • *
* *

Supported placeholders

*
    *
  • {@value ParameterizedInvocationConstants#DISPLAY_NAME_PLACEHOLDER}
  • *
  • {@value ParameterizedInvocationConstants#INDEX_PLACEHOLDER}
  • *
  • {@value ParameterizedInvocationConstants#ARGUMENT_SET_NAME_PLACEHOLDER}
  • *
  • {@value ParameterizedInvocationConstants#ARGUMENTS_PLACEHOLDER}
  • *
  • {@value ParameterizedInvocationConstants#ARGUMENTS_WITH_NAMES_PLACEHOLDER}
  • *
  • {@value ParameterizedInvocationConstants#ARGUMENT_SET_NAME_OR_ARGUMENTS_WITH_NAMES_PLACEHOLDER}
  • *
  • "{0}", "{1}", etc.: an individual argument (0-based)
  • *
* *

For the latter, you may use {@link java.text.MessageFormat} patterns * to customize formatting (for example, {@code {0,number,#.###}}). Please * note that the original arguments are passed when formatting, regardless * of any implicit or explicit argument conversions. * *

Note that * {@value ParameterizedInvocationNameFormatter#DEFAULT_DISPLAY_NAME} is * a flag rather than a placeholder. * * @see java.text.MessageFormat * @see #quoteTextArguments() */ String name() default ParameterizedInvocationNameFormatter.DEFAULT_DISPLAY_NAME; /** * Configure whether to enclose text-based argument values in quotes within * display names. * *

Defaults to {@code true}. * *

In this context, any {@link CharSequence} (such as a {@link String}) * or {@link Character} is considered text. A {@code CharSequence} is wrapped * in double quotes ("), and a {@code Character} is wrapped in single quotes * ('). * *

Special characters in Java strings and characters will be escaped in the * quoted text — for example, carriage returns and line feeds will be * escaped as {@code \\r} and {@code \\n}, respectively. In addition, any * {@linkplain Character#isISOControl(char) ISO control character} will be * represented as a question mark (?) in the quoted text. * *

For example, given a string argument {@code "line 1\nline 2"}, the * representation in the display name would be {@code "\"line 1\\nline 2\""} * (printed as {@code "line 1\nline 2"}) with the newline character escaped as * {@code "\\n"}. Similarly, given a string argument {@code "\t"}, the * representation in the display name would be {@code "\"\\t\""} (printed as * {@code "\t"}) instead of a blank string or invisible tab * character. The same applies for a character argument {@code '\t'}, whose * representation in the display name would be {@code "'\\t'"} (printed as * {@code '\t'}). * *

Please note that original source arguments are quoted when generating * a display name, before any implicit or explicit argument conversion is * performed. For example, if a parameterized test accepts {@code 3.14} as a * {@code float} argument that was converted from {@code "3.14"} as an input * string, {@code "3.14"} will be present in the display name instead of * {@code 3.14}. * * @since 6.0 * @see #name() */ @API(status = EXPERIMENTAL, since = "6.0") boolean quoteTextArguments() default true; /** * Configure whether all arguments of the parameterized test that implement * {@link AutoCloseable} will be closed after their corresponding * invocation. * *

Defaults to {@code true}. * *

WARNING: if an argument that implements * {@code AutoCloseable} is reused for multiple invocations of the same * parameterized test method, you must set {@code autoCloseArguments} to * {@code false} to ensure that the argument is not closed between * invocations. * * @since 5.8 * @see java.lang.AutoCloseable */ @API(status = STABLE, since = "5.10") boolean autoCloseArguments() default true; /** * Configure whether zero invocations are allowed for this * parameterized test. * *

Set this attribute to {@code true} if the absence of invocations is * expected in some cases and should not cause a test failure. * *

Defaults to {@code false}. * * @since 5.12 */ @API(status = MAINTAINED, since = "5.13.3") boolean allowZeroInvocations() default false; /** * Configure how the number of arguments provided by an * {@link ArgumentsSource} are validated. * *

Defaults to {@link ArgumentCountValidationMode#DEFAULT}. * *

When an {@link ArgumentsSource} provides more arguments than declared * by the parameterized test method, there might be a bug in the method or * the {@link ArgumentsSource}. By default, the additional arguments are * ignored. {@code argumentCountValidation} allows you to control how * additional arguments are handled. The default can be configured via the * {@value ArgumentCountValidator#ARGUMENT_COUNT_VALIDATION_KEY} * configuration parameter (see the User Guide for details on configuration * parameters). * * @since 5.12 * @see ArgumentCountValidationMode */ @API(status = MAINTAINED, since = "5.13.3") ArgumentCountValidationMode argumentCountValidation() default ArgumentCountValidationMode.DEFAULT; } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import java.lang.reflect.Method; import org.junit.jupiter.params.provider.Arguments; import org.junit.platform.commons.util.Preconditions; /** * Encapsulates access to the parameters of a parameterized test method and * caches the converters and aggregators used to resolve them. * * @since 5.3 */ class ParameterizedTestContext implements ParameterizedDeclarationContext { private final Class testClass; private final Method method; private final ParameterizedTest annotation; private final ResolverFacade resolverFacade; ParameterizedTestContext(Class testClass, Method method, ParameterizedTest annotation) { this.testClass = testClass; this.method = Preconditions.notNull(method, "method must not be null"); this.annotation = Preconditions.notNull(annotation, "annotation must not be null"); this.resolverFacade = ResolverFacade.create(method, annotation); } @Override public Class getTestClass() { return this.testClass; } @Override public ParameterizedTest getAnnotation() { return this.annotation; } @Override public Method getAnnotatedElement() { return this.method; } @Override public String getDisplayNamePattern() { return this.annotation.name(); } @Override public boolean quoteTextArguments() { return this.annotation.quoteTextArguments(); } @Override public boolean isAutoClosingArguments() { return this.annotation.autoCloseArguments(); } @Override public boolean isAllowingZeroInvocations() { return this.annotation.allowZeroInvocations(); } @Override public ArgumentCountValidationMode getArgumentCountValidationMode() { return this.annotation.argumentCountValidation(); } @Override public ResolverFacade getResolverFacade() { return this.resolverFacade; } @Override public ParameterizedTestInvocationContext createInvocationContext(ParameterizedInvocationNameFormatter formatter, Arguments arguments, int invocationIndex) { return new ParameterizedTestInvocationContext(this, formatter, arguments, invocationIndex); } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import static java.util.Objects.requireNonNull; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import java.util.Optional; import java.util.stream.Stream; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Namespace; import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; /** * @since 5.0 */ class ParameterizedTestExtension extends ParameterizedInvocationContextProvider implements TestTemplateInvocationContextProvider { static final String DECLARATION_CONTEXT_KEY = "context"; @Override public boolean supportsTestTemplate(ExtensionContext context) { Optional annotation = findAnnotation(context.getTestMethod(), ParameterizedTest.class); if (annotation.isEmpty()) { return false; } ParameterizedTestContext methodContext = new ParameterizedTestContext(context.getRequiredTestClass(), context.getRequiredTestMethod(), annotation.get()); getStore(context).put(DECLARATION_CONTEXT_KEY, methodContext); return true; } @Override public Stream provideTestTemplateInvocationContexts( ExtensionContext extensionContext) { return provideInvocationContexts(extensionContext, getDeclarationContext(extensionContext)); } @Override public boolean mayReturnZeroTestTemplateInvocationContexts(ExtensionContext extensionContext) { return getDeclarationContext(extensionContext).isAllowingZeroInvocations(); } private ParameterizedTestContext getDeclarationContext(ExtensionContext extensionContext) { return requireNonNull(getStore(extensionContext)// .get(DECLARATION_CONTEXT_KEY, ParameterizedTestContext.class)); } private ExtensionContext.Store getStore(ExtensionContext context) { return context.getStore(Namespace.create(ParameterizedTestExtension.class, context.getRequiredTestMethod())); } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestInvocationContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import java.util.List; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.TestTemplateInvocationContext; import org.junit.jupiter.params.provider.Arguments; /** * @since 5.0 */ class ParameterizedTestInvocationContext extends ParameterizedInvocationContext implements TestTemplateInvocationContext { ParameterizedTestInvocationContext(ParameterizedTestContext methodContext, ParameterizedInvocationNameFormatter formatter, Arguments arguments, int invocationIndex) { super(methodContext, formatter, arguments, invocationIndex); } @Override public List getAdditionalExtensions() { return List.of( // new ParameterizedTestMethodParameterResolver(this.declarationContext, this.arguments, this.invocationIndex) // ); } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestMethodParameterResolver.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import java.lang.reflect.Executable; import java.lang.reflect.Method; import org.junit.jupiter.api.extension.ExtensionContext; /** * @since 5.0 */ class ParameterizedTestMethodParameterResolver extends ParameterizedInvocationParameterResolver { private final Method testTemplateMethod; ParameterizedTestMethodParameterResolver(ParameterizedTestContext methodContext, EvaluatedArgumentSet arguments, int invocationIndex) { super(methodContext.getResolverFacade(), arguments, invocationIndex, ResolutionCache.DISABLED); this.testTemplateMethod = methodContext.getAnnotatedElement(); } @Override protected boolean isSupportedOnConstructorOrMethod(Executable declaringExecutable, ExtensionContext extensionContext) { return this.testTemplateMethod.equals(declaringExecutable); } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestSpiInstantiator.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import java.lang.reflect.Constructor; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ReflectionUtils; /** * @since 5.12 */ class ParameterizedTestSpiInstantiator { static T instantiate(Class spiInterface, Class implementationClass, ExtensionContext extensionContext) { return extensionContext.getExecutableInvoker() // .invoke(findConstructor(spiInterface, implementationClass)); } @SuppressWarnings("unchecked") private static Constructor findConstructor(Class spiInterface, Class implementationClass) { return (Constructor) findBestConstructor(spiInterface, implementationClass); } /** * Find the "best" constructor for the supplied implementation class. * *

For backward compatibility, it first checks for a single constructor * and returns that. If there are multiple constructors, it checks for a * default constructor which takes precedence over any other constructors. * Otherwise, this method throws an exception stating that it failed to * find a suitable constructor. */ private static Constructor findBestConstructor(Class spiInterface, Class implementationClass) { Preconditions.condition(!ReflectionUtils.isInnerClass(implementationClass), () -> "The %s [%s] must be either a top-level class or a static nested class".formatted( spiInterface.getSimpleName(), implementationClass.getName())); Constructor[] constructors = implementationClass.getDeclaredConstructors(); // Single constructor? if (constructors.length == 1) { return constructors[0]; } // Find default constructor. for (Constructor constructor : constructors) { if (constructor.getParameterCount() == 0) { return constructor; } } // Otherwise... String message = """ Failed to find constructor for %s [%s]. \ Please ensure that a no-argument or a single constructor exists.""".formatted( spiInterface.getSimpleName(), implementationClass.getName()); throw new JUnitException(message); } private ParameterizedTestSpiInstantiator() { } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/QuoteUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; /** * Collection of utilities for quoting text. * * @since 6.0 */ final class QuoteUtils { private QuoteUtils() { /* no-op */ } public static String quote(CharSequence text) { if (text.isEmpty()) { return "\"\""; } StringBuilder builder = new StringBuilder(); builder.append('"'); for (int i = 0; i < text.length(); i++) { builder.append(escape(text.charAt(i), true)); } builder.append('"'); return builder.toString(); } public static String quote(char ch) { return '\'' + escape(ch, false) + '\''; } private static String escape(char ch, boolean withinString) { return switch (ch) { case '"' -> withinString ? "\\\"" : "\""; case '\'' -> withinString ? "'" : "\\'"; case '\\' -> "\\\\"; case '\b' -> "\\b"; case '\f' -> "\\f"; case '\t' -> "\\t"; case '\r' -> "\\r"; case '\n' -> "\\n"; default -> String.valueOf(ch); }; } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/ResolutionCache.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; import org.jspecify.annotations.Nullable; import org.junit.jupiter.params.support.ParameterDeclaration; /** * @since 5.13 */ interface ResolutionCache { static ResolutionCache enabled() { return new Concurrent(); } ResolutionCache DISABLED = (__, resolver) -> resolver.get(); @Nullable Object resolve(ParameterDeclaration declaration, Supplier resolver); class Concurrent implements ResolutionCache { private final Map cache = new ConcurrentHashMap<>(); @Override public @Nullable Object resolve(ParameterDeclaration declaration, Supplier resolver) { return cache.computeIfAbsent(declaration, __ -> resolver.get()); } } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/ResolverFacade.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import static java.lang.System.lineSeparator; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toMap; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated; import static org.junit.platform.commons.support.ReflectionSupport.makeAccessible; import static org.junit.platform.commons.util.KotlinReflectionUtils.getKotlinSuspendingFunctionParameters; import static org.junit.platform.commons.util.KotlinReflectionUtils.isKotlinSuspendingFunction; import static org.junit.platform.commons.util.ReflectionUtils.isInnerClass; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.NavigableMap; import java.util.Optional; import java.util.Set; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.AnnotatedElementContext; import org.junit.jupiter.api.extension.ExtensionConfigurationException; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.params.aggregator.AggregateWith; import org.junit.jupiter.params.aggregator.ArgumentsAccessor; import org.junit.jupiter.params.aggregator.ArgumentsAggregationException; import org.junit.jupiter.params.aggregator.ArgumentsAggregator; import org.junit.jupiter.params.aggregator.SimpleArgumentsAggregator; import org.junit.jupiter.params.converter.ArgumentConverter; import org.junit.jupiter.params.converter.ConvertWith; import org.junit.jupiter.params.converter.DefaultArgumentConverter; import org.junit.jupiter.params.support.AnnotationConsumerInitializer; import org.junit.jupiter.params.support.FieldContext; import org.junit.jupiter.params.support.ParameterDeclaration; import org.junit.jupiter.params.support.ParameterDeclarations; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.function.Try; import org.junit.platform.commons.support.ModifierSupport; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.StringUtils; class ResolverFacade { static ResolverFacade create(Class clazz, List fields) { Preconditions.notEmpty(fields, "Fields must not be empty"); NavigableMap> allIndexedParameters = new TreeMap<>(); Set aggregatorParameters = new LinkedHashSet<>(); for (Field field : fields) { Parameter annotation = findAnnotation(field, Parameter.class) // .orElseThrow(() -> new JUnitException("No @Parameter annotation present")); int index = annotation.value(); FieldParameterDeclaration declaration = new FieldParameterDeclaration(field, annotation.value()); if (declaration.isAggregator()) { aggregatorParameters.add(declaration); } else { if (fields.size() == 1 && index == Parameter.UNSET_INDEX) { index = 0; declaration = new FieldParameterDeclaration(field, 0); } allIndexedParameters.computeIfAbsent(index, __ -> new ArrayList<>()) // .add(declaration); } } NavigableMap uniqueIndexedParameters = validateFieldDeclarations( allIndexedParameters, aggregatorParameters); Stream.concat(uniqueIndexedParameters.values().stream(), aggregatorParameters.stream()) // .forEach(declaration -> makeAccessible(declaration.getField())); var requiredParameterCount = new RequiredParameterCount(uniqueIndexedParameters.size(), "field injection"); return new ResolverFacade(clazz, uniqueIndexedParameters, aggregatorParameters, 0, requiredParameterCount); } static ResolverFacade create(Constructor constructor, ParameterizedClass annotation) { // Inner classes get the outer instance as first (implicit) parameter int implicitParameters = isInnerClass(constructor.getDeclaringClass()) ? 1 : 0; return create(constructor, annotation, implicitParameters); } static ResolverFacade create(Method method, Annotation annotation) { if (isKotlinSuspendingFunction(method)) { return create(method, annotation, 0, getKotlinSuspendingFunctionParameters(method)); } return create(method, annotation, 0); } /** * Create a new {@link ResolverFacade} for the supplied {@link Executable}. * *

This method takes a best-effort approach at enforcing the following * policy for parameterized class constructors and parameterized test * methods that accept aggregators as arguments. *

    *
  1. zero or more indexed arguments come first.
  2. *
  3. zero or more aggregators come next.
  4. *
  5. zero or more arguments supplied by other {@code ParameterResolver} * implementations come last.
  6. *
*/ private static ResolverFacade create(Executable executable, Annotation annotation, int indexOffset) { return create(executable, annotation, indexOffset, executable.getParameters()); } private static ResolverFacade create(Executable executable, Annotation annotation, int indexOffset, java.lang.reflect.Parameter[] parameters) { NavigableMap indexedParameters = new TreeMap<>(); NavigableMap aggregatorParameters = new TreeMap<>(); for (int index = indexOffset; index < parameters.length; index++) { ExecutableParameterDeclaration declaration = new ExecutableParameterDeclaration(parameters[index], index, indexOffset); if (declaration.isAggregator()) { Preconditions.condition( aggregatorParameters.isEmpty() || aggregatorParameters.lastKey() == declaration.getParameterIndex() - 1, () -> """ @%s %s declares formal parameters in an invalid order: \ argument aggregators must be declared after any indexed arguments \ and before any arguments resolved by another ParameterResolver.""".formatted( annotation.annotationType().getSimpleName(), DefaultParameterDeclarations.describe(executable))); aggregatorParameters.put(declaration.getParameterIndex(), declaration); } else if (aggregatorParameters.isEmpty()) { indexedParameters.put(declaration.getParameterIndex(), declaration); } } return new ResolverFacade(executable, indexedParameters, new LinkedHashSet<>(aggregatorParameters.values()), indexOffset, null); } private final int parameterIndexOffset; private final Map resolvers; private final DefaultParameterDeclarations indexedParameterDeclarations; private final Set aggregatorParameters; private final @Nullable RequiredParameterCount requiredParameterCount; private ResolverFacade(AnnotatedElement sourceElement, NavigableMap indexedParameters, Set aggregatorParameters, int parameterIndexOffset, @Nullable RequiredParameterCount requiredParameterCount) { this.aggregatorParameters = aggregatorParameters; this.parameterIndexOffset = parameterIndexOffset; this.resolvers = new ConcurrentHashMap<>(indexedParameters.size() + aggregatorParameters.size()); this.indexedParameterDeclarations = new DefaultParameterDeclarations(sourceElement, indexedParameters); this.requiredParameterCount = requiredParameterCount; } ParameterDeclarations getIndexedParameterDeclarations() { return this.indexedParameterDeclarations; } @Nullable RequiredParameterCount getRequiredParameterCount() { return this.requiredParameterCount; } boolean isSupportedParameter(ParameterContext parameterContext, EvaluatedArgumentSet arguments) { int index = toLogicalIndex(parameterContext); if (this.indexedParameterDeclarations.get(index).isPresent()) { return index < arguments.getConsumedLength(); } return !this.aggregatorParameters.isEmpty() && this.aggregatorParameters.stream().anyMatch(it -> it.getParameterIndex() == index); } /** * Get the name of the parameter with the supplied index, if it is present * and declared before the aggregators. * * @return an {@code Optional} containing the name of the parameter */ Optional getParameterName(int parameterIndex) { return this.indexedParameterDeclarations.get(parameterIndex) // .flatMap(ParameterDeclaration::getParameterName); } /** * Determine the length of the arguments array that is considered consumed * by the parameter declarations in this resolver. * *

If an aggregator is present, all arguments are considered consumed. * Otherwise, the consumed argument length is the minimum of the total * length and the number of indexed parameter declarations. */ int determineConsumedArgumentLength(int totalLength) { NavigableMap declarationsByIndex = this.indexedParameterDeclarations.declarationsByIndex; return this.aggregatorParameters.isEmpty() // ? Math.min(totalLength, declarationsByIndex.isEmpty() ? 0 : declarationsByIndex.lastKey() + 1) // : totalLength; } /** * Determine the number of arguments that are considered consumed by the * parameter declarations in this resolver. * *

If an aggregator is present, all arguments are considered consumed. * Otherwise, the consumed argument count, is the number of indexes that * correspond to indexed parameter declarations. */ int determineConsumedArgumentCount(EvaluatedArgumentSet arguments) { if (this.aggregatorParameters.isEmpty()) { return this.indexedParameterDeclarations.declarationsByIndex.subMap(0, arguments.getConsumedLength()).size(); } return arguments.getTotalLength(); } ArgumentSetLifecycleMethod.ParameterResolver createLifecycleMethodParameterResolver(Method method, Annotation annotation) { ResolverFacade originalResolverFacade = this; ResolverFacade lifecycleMethodResolverFacade = create(method, annotation); Map parameterDeclarationMapping = new HashMap<>(); List errors = validateLifecycleMethodParameters(originalResolverFacade, lifecycleMethodResolverFacade, parameterDeclarationMapping); return Try // .call(() -> configurationErrorOrSuccess(errors, () -> new DefaultArgumentSetLifecycleMethodParameterResolver(originalResolverFacade, lifecycleMethodResolverFacade, parameterDeclarationMapping))) // .getNonNullOrThrow(cause -> new ExtensionConfigurationException( "Invalid @%s lifecycle method declaration: %s".formatted( annotation.annotationType().getSimpleName(), method.toGenericString()), cause)); } /** * Resolve the parameter for the supplied context using the supplied * arguments. */ @Nullable Object resolve(ParameterContext parameterContext, ExtensionContext extensionContext, EvaluatedArgumentSet arguments, int invocationIndex, ResolutionCache resolutionCache) { int parameterIndex = toLogicalIndex(parameterContext); ResolvableParameterDeclaration declaration = findDeclaration(parameterIndex) // .orElseThrow( () -> new ParameterResolutionException("Parameter index out of bounds: " + parameterIndex)); return resolutionCache.resolve(declaration, () -> resolve(declaration, extensionContext, arguments, invocationIndex, Optional.of(parameterContext))); } private Optional findDeclaration(int parameterIndex) { ResolvableParameterDeclaration declaration = this.indexedParameterDeclarations.declarationsByIndex // .get(parameterIndex); if (declaration == null) { return this.aggregatorParameters.stream() // .filter(it -> it.getParameterIndex() == parameterIndex) // .findFirst(); } return Optional.of(declaration); } void resolveAndInjectFields(Object testInstance, ExtensionContext extensionContext, EvaluatedArgumentSet arguments, int invocationIndex, ResolutionCache resolutionCache) { if (this.indexedParameterDeclarations.sourceElement.equals(testInstance.getClass())) { getAllParameterDeclarations() // .filter(FieldParameterDeclaration.class::isInstance) // .map(FieldParameterDeclaration.class::cast) // .forEach(declaration -> setField(testInstance, declaration, extensionContext, arguments, invocationIndex, resolutionCache)); } } private Stream getAllParameterDeclarations() { return Stream.concat(this.indexedParameterDeclarations.declarationsByIndex.values().stream(), aggregatorParameters.stream()); } private void setField(Object testInstance, FieldParameterDeclaration declaration, ExtensionContext extensionContext, EvaluatedArgumentSet arguments, int invocationIndex, ResolutionCache resolutionCache) { Object argument = resolutionCache.resolve(declaration, () -> resolve(declaration, extensionContext, arguments, invocationIndex, Optional.empty())); try { declaration.getField().set(testInstance, argument); } catch (Exception e) { throw new JUnitException("Failed to inject parameter value into field: " + declaration.getField(), e); } } private @Nullable Object resolve(ResolvableParameterDeclaration parameterDeclaration, ExtensionContext extensionContext, EvaluatedArgumentSet arguments, int invocationIndex, Optional parameterContext) { Resolver resolver = getResolver(extensionContext, parameterDeclaration); return parameterDeclaration.resolve(resolver, extensionContext, arguments, invocationIndex, parameterContext); } private Resolver getResolver(ExtensionContext extensionContext, ResolvableParameterDeclaration declaration) { return this.resolvers.computeIfAbsent(declaration, __ -> this.aggregatorParameters.contains(declaration) // ? createAggregator(declaration, extensionContext) // : createConverter(declaration, extensionContext)); } private int toLogicalIndex(ParameterContext parameterContext) { int index = parameterContext.getIndex() - this.parameterIndexOffset; Preconditions.condition(index >= 0, () -> "Parameter index must be greater than or equal to zero"); return index; } private static NavigableMap validateFieldDeclarations( NavigableMap> indexedParameters, Set aggregatorParameters) { List errors = new ArrayList<>(); validateIndexedParameters(indexedParameters, errors); validateAggregatorParameters(aggregatorParameters, errors); return configurationErrorOrSuccess(errors, () -> indexedParameters.entrySet().stream() // .collect(toMap(Map.Entry::getKey, entry -> entry.getValue().get(0), (d, __) -> d, TreeMap::new))); } private static List validateLifecycleMethodParameters(ResolverFacade originalResolverFacade, ResolverFacade lifecycleMethodResolverFacade, Map parameterDeclarationMapping) { List actualDeclarations = lifecycleMethodResolverFacade.indexedParameterDeclarations.getAll(); List errors = new ArrayList<>(); for (int parameterIndex = 0; parameterIndex < actualDeclarations.size(); parameterIndex++) { ParameterDeclaration actualDeclaration = actualDeclarations.get(parameterIndex); ResolvableParameterDeclaration originalDeclaration = originalResolverFacade.indexedParameterDeclarations.declarationsByIndex // .get(parameterIndex); if (originalDeclaration == null) { break; } if (!actualDeclaration.getParameterType().equals(originalDeclaration.getParameterType())) { errors.add( "parameter%s with index %d is incompatible with the parameter declared on the parameterized class: expected type '%s' but found '%s'".formatted( parameterName(actualDeclaration), parameterIndex, originalDeclaration.getParameterType(), actualDeclaration.getParameterType())); } else if (findAnnotation(actualDeclaration.getAnnotatedElement(), ConvertWith.class).isPresent()) { errors.add("parameter%s with index %d must not be annotated with @ConvertWith".formatted( parameterName(actualDeclaration), parameterIndex)); } else if (errors.isEmpty()) { parameterDeclarationMapping.put(actualDeclaration, originalDeclaration); } } return errors; } private static String parameterName(ParameterDeclaration actualDeclaration) { return actualDeclaration.getParameterName().map(name -> " '" + name + "'").orElse(""); } private static T configurationErrorOrSuccess(List errors, Supplier successfulResult) { if (errors.isEmpty()) { return successfulResult.get(); } else if (errors.size() == 1) { throw new PreconditionViolationException("Configuration error: " + errors.get(0) + "."); } else { throw new PreconditionViolationException("%d configuration errors:%n%s".formatted(errors.size(), errors.stream().collect(joining(lineSeparator() + "- ", "- ", "")))); } } private static void validateIndexedParameters( NavigableMap> indexedParameters, List errors) { if (indexedParameters.isEmpty()) { return; } indexedParameters.forEach( (index, declarations) -> validateIndexedParameterDeclarations(index, declarations, errors)); for (int index = 0; index <= indexedParameters.lastKey(); index++) { if (!indexedParameters.containsKey(index)) { errors.add("no field annotated with @Parameter(%d) declared".formatted(index)); } } } private static void validateIndexedParameterDeclarations(int index, List declarations, List errors) { List fields = declarations.stream().map(FieldParameterDeclaration::getField).toList(); if (index < 0) { declarations.stream() // .map( declaration -> "index must be greater than or equal to zero in @Parameter(%d) annotation on field [%s]".formatted( index, declaration.getField())) // .forEach(errors::add); } else if (declarations.size() > 1) { errors.add("duplicate index declared in @Parameter(%d) annotation on fields %s".formatted(index, fields)); } fields.stream() // .filter(ModifierSupport::isFinal) // .map("@Parameter field [%s] must not be declared as final"::formatted) // .forEach(errors::add); } private static void validateAggregatorParameters(Set aggregatorParameters, List errors) { aggregatorParameters.stream() // .filter(declaration -> declaration.getParameterIndex() != Parameter.UNSET_INDEX) // .map( declaration -> "no index may be declared in @Parameter(%d) annotation on aggregator field [%s]".formatted( declaration.getParameterIndex(), declaration.getField())) // .forEach(errors::add); } private static Converter createConverter(ParameterDeclaration declaration, ExtensionContext extensionContext) { try { // @formatter:off return findAnnotation(declaration.getAnnotatedElement(), ConvertWith.class) .map(ConvertWith::value) .map(clazz -> ParameterizedTestSpiInstantiator.instantiate(ArgumentConverter.class, clazz, extensionContext)) .map(converter -> AnnotationConsumerInitializer.initialize(declaration.getAnnotatedElement(), converter)) .map(Converter::new) .orElse(Converter.DEFAULT); } // @formatter:on catch (Exception ex) { throw parameterResolutionException("Error creating ArgumentConverter", ex, declaration.getParameterIndex()); } } private static Aggregator createAggregator(ParameterDeclaration declaration, ExtensionContext extensionContext) { try { // @formatter:off return findAnnotation(declaration.getAnnotatedElement(), AggregateWith.class) .map(AggregateWith::value) .map(clazz -> ParameterizedTestSpiInstantiator.instantiate(ArgumentsAggregator.class, clazz, extensionContext)) .map(Aggregator::new) .orElse(Aggregator.DEFAULT); } // @formatter:on catch (Exception ex) { throw parameterResolutionException("Error creating ArgumentsAggregator", ex, declaration.getParameterIndex()); } } private static ParameterResolutionException parameterResolutionException(String message, Exception cause, int index) { String fullMessage = message + " at index " + index; if (StringUtils.isNotBlank(cause.getMessage())) { fullMessage += ": " + cause.getMessage(); } return new ParameterResolutionException(fullMessage, cause); } private interface Resolver { @Nullable Object resolve(ParameterContext parameterContext, int parameterIndex, ExtensionContext extensionContext, EvaluatedArgumentSet arguments, int invocationIndex); @Nullable Object resolve(FieldContext fieldContext, ExtensionContext extensionContext, EvaluatedArgumentSet arguments, int invocationIndex); } private record Converter(ArgumentConverter argumentConverter) implements Resolver { static final Converter DEFAULT = new Converter(DefaultArgumentConverter.INSTANCE); @Override public @Nullable Object resolve(ParameterContext parameterContext, int parameterIndex, ExtensionContext extensionContext, EvaluatedArgumentSet arguments, int invocationIndex) { Object argument = arguments.getConsumedPayload(parameterIndex); try { return this.argumentConverter.convert(argument, parameterContext); } catch (Exception ex) { throw parameterResolutionException("Error converting parameter", ex, parameterContext.getIndex()); } } @Override public @Nullable Object resolve(FieldContext fieldContext, ExtensionContext extensionContext, EvaluatedArgumentSet arguments, int invocationIndex) { Object argument = arguments.getConsumedPayload(fieldContext.getParameterIndex()); try { return this.argumentConverter.convert(argument, fieldContext); } catch (Exception ex) { throw parameterResolutionException("Error converting parameter", ex, fieldContext.getParameterIndex()); } } } private record Aggregator(ArgumentsAggregator argumentsAggregator) implements Resolver { private static final Aggregator DEFAULT = new Aggregator(new SimpleArgumentsAggregator() { @Override protected Object aggregateArguments(ArgumentsAccessor accessor, Class targetType, AnnotatedElementContext context, int parameterIndex) throws ArgumentsAggregationException { return accessor; } }); @Override public @Nullable Object resolve(ParameterContext parameterContext, int parameterIndex, ExtensionContext extensionContext, EvaluatedArgumentSet arguments, int invocationIndex) { ArgumentsAccessor accessor = requireNonNull(ParameterInfo.get(extensionContext)).getArguments(); try { return this.argumentsAggregator.aggregateArguments(accessor, parameterContext); } catch (Exception ex) { throw parameterResolutionException("Error aggregating arguments for parameter", ex, parameterContext.getIndex()); } } @Override public @Nullable Object resolve(FieldContext fieldContext, ExtensionContext extensionContext, EvaluatedArgumentSet arguments, int invocationIndex) { ArgumentsAccessor accessor = requireNonNull(ParameterInfo.get(extensionContext)).getArguments(); try { return this.argumentsAggregator.aggregateArguments(accessor, fieldContext); } catch (Exception ex) { throw parameterResolutionException("Error aggregating arguments for parameter", ex, fieldContext.getParameterIndex()); } } } private record DefaultParameterDeclarations(AnnotatedElement sourceElement, NavigableMap declarationsByIndex) implements ParameterDeclarations { @Override public AnnotatedElement getSourceElement() { return this.sourceElement; } @Override public Optional getFirst() { return this.declarationsByIndex.isEmpty() // ? Optional.empty() // : Optional.of(this.declarationsByIndex.firstEntry().getValue()); } @Override public List getAll() { return List.copyOf(this.declarationsByIndex.values()); } @Override public Optional get(int parameterIndex) { return Optional.ofNullable(this.declarationsByIndex.get(parameterIndex)); } @Override public String getSourceElementDescription() { return describe(this.sourceElement); } static String describe(AnnotatedElement sourceElement) { if (sourceElement instanceof Method method) { return "method [%s]".formatted(method.toGenericString()); } if (sourceElement instanceof Constructor constructor) { return "constructor [%s]".formatted(constructor.toGenericString()); } if (sourceElement instanceof Class clazz) { return "class [%s]".formatted(clazz.getName()); } return sourceElement.toString(); } } private abstract static class ResolvableParameterDeclaration implements ParameterDeclaration { /** * Determine if the supplied {@link Parameter} is an aggregator (i.e., of * type {@link ArgumentsAccessor} or annotated with {@link AggregateWith}). * * @return {@code true} if the parameter is an aggregator */ boolean isAggregator() { return ArgumentsAccessor.class.isAssignableFrom(getParameterType()) || isAnnotated(getAnnotatedElement(), AggregateWith.class); } abstract @Nullable Object resolve(Resolver resolver, ExtensionContext extensionContext, EvaluatedArgumentSet arguments, int invocationIndex, Optional originalParameterContext); } private static class FieldParameterDeclaration extends ResolvableParameterDeclaration implements FieldContext { private final Field field; private final int index; FieldParameterDeclaration(Field field, int index) { this.field = field; this.index = index; } @Override public Field getField() { return this.field; } @Override public Field getAnnotatedElement() { return this.field; } @Override public Class getParameterType() { return this.field.getType(); } @Override public int getParameterIndex() { return index; } @Override public Optional getParameterName() { return Optional.of(this.field.getName()); } @Override public @Nullable Object resolve(Resolver resolver, ExtensionContext extensionContext, EvaluatedArgumentSet arguments, int invocationIndex, Optional originalParameterContext) { return resolver.resolve(this, extensionContext, arguments, invocationIndex); } } private static class ExecutableParameterDeclaration extends ResolvableParameterDeclaration { private final java.lang.reflect.Parameter parameter; private final int index; private final int indexOffset; ExecutableParameterDeclaration(java.lang.reflect.Parameter parameter, int index, int indexOffset) { this.parameter = parameter; this.index = index; this.indexOffset = indexOffset; } @Override public java.lang.reflect.Parameter getAnnotatedElement() { return this.parameter; } @Override public Class getParameterType() { return this.parameter.getType(); } @Override public int getParameterIndex() { return this.index - this.indexOffset; } @Override public Optional getParameterName() { return this.parameter.isNamePresent() ? Optional.of(this.parameter.getName()) : Optional.empty(); } @Override public @Nullable Object resolve(Resolver resolver, ExtensionContext extensionContext, EvaluatedArgumentSet arguments, int invocationIndex, Optional originalParameterContext) { ParameterContext parameterContext = originalParameterContext // .filter(it -> it.getParameter().equals(this.parameter)) // .orElseGet(() -> toParameterContext(extensionContext, originalParameterContext)); return resolver.resolve(parameterContext, getParameterIndex(), extensionContext, arguments, invocationIndex); } private ParameterContext toParameterContext(ExtensionContext extensionContext, Optional originalParameterContext) { Optional target = originalParameterContext.flatMap(ParameterContext::getTarget); if (target.isEmpty()) { target = extensionContext.getTestInstance(); } return toParameterContext(target); } private ParameterContext toParameterContext(Optional target) { return new ParameterContext() { @Override public java.lang.reflect.Parameter getParameter() { return ExecutableParameterDeclaration.this.parameter; } @Override public int getIndex() { return ExecutableParameterDeclaration.this.index; } @Override public Optional getTarget() { return target; } }; } } private record DefaultArgumentSetLifecycleMethodParameterResolver(ResolverFacade originalResolverFacade, ResolverFacade lifecycleMethodResolverFacade, Map parameterDeclarationMapping) implements ArgumentSetLifecycleMethod.ParameterResolver { @Override public boolean supports(ParameterContext parameterContext) { return this.lifecycleMethodResolverFacade.findDeclaration(parameterContext.getIndex()) // .filter(it -> this.parameterDeclarationMapping.containsKey(it) || it.isAggregator()) // .isPresent(); } @Override public @Nullable Object resolve(ParameterContext parameterContext, ExtensionContext extensionContext, EvaluatedArgumentSet arguments, int invocationIndex, ResolutionCache resolutionCache) { ResolvableParameterDeclaration actualDeclaration = this.lifecycleMethodResolverFacade // .findDeclaration(parameterContext.getIndex()) // .orElseThrow(() -> new ParameterResolutionException( "Parameter index out of bounds: " + parameterContext.getIndex())); ResolvableParameterDeclaration originalDeclaration = this.parameterDeclarationMapping // .get(actualDeclaration); if (originalDeclaration == null) { return this.lifecycleMethodResolverFacade.resolve(actualDeclaration, extensionContext, arguments, invocationIndex, Optional.of(parameterContext)); } return resolutionCache.resolve(originalDeclaration, () -> this.originalResolverFacade.resolve(originalDeclaration, extensionContext, arguments, invocationIndex, Optional.of(parameterContext))); } } record RequiredParameterCount(int value, String reason) { } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/AggregateWith.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.aggregator; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @AggregateWith} is an annotation that allows one to specify an * {@link ArgumentsAggregator}. * *

This annotation may be applied to parameters of a * {@link org.junit.jupiter.params.ParameterizedClass @ParameterizedClass} * constructor or its * {@link org.junit.jupiter.params.Parameter @Parameter}-annotated fields, or to * parameters of a * {@link org.junit.jupiter.params.ParameterizedTest @ParameterizedTest} method * in order for an aggregated value to be resolved for the annotated parameter * when the parameterized class or method is invoked. * *

{@code @AggregateWith} may also be used as a meta-annotation in order to * create a custom composed annotation that inherits the semantics * of {@code @AggregateWith}. * * @since 5.2 * @see ArgumentsAggregator * @see org.junit.jupiter.params.ParameterizedTest */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.ANNOTATION_TYPE, ElementType.PARAMETER, ElementType.FIELD }) @Documented @API(status = STABLE, since = "5.7") public @interface AggregateWith { Class value(); } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentAccessException.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.aggregator; import static org.apiguardian.api.API.Status.STABLE; import java.io.Serial; import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; /** * {@code ArgumentAccessException} is an exception thrown by an * {@link ArgumentsAccessor} if an error occurs while accessing * or converting an argument. * * @since 5.2 * @see ArgumentsAccessor */ @API(status = STABLE, since = "5.7") public class ArgumentAccessException extends JUnitException { @Serial private static final long serialVersionUID = 1L; public ArgumentAccessException(String message) { super(message); } public ArgumentAccessException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAccessor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.aggregator; import static org.apiguardian.api.API.Status.STABLE; import java.util.List; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; /** * {@code ArgumentsAccessor} defines the public API for accessing arguments provided * by an {@link org.junit.jupiter.params.provider.ArgumentsProvider ArgumentsProvider} * for a single invocation of a * {@link org.junit.jupiter.params.ParameterizedClass @ParameterizedClass} or * {@link org.junit.jupiter.params.ParameterizedTest @ParameterizedTest}. * *

Specifically, an {@code ArgumentsAccessor} aggregates a set of * arguments for a given invocation of a parameterized class or parameterized * test and provides convenience methods for accessing those arguments in a * type-safe manner with support for automatic type conversion. * *

An instance of {@code ArgumentsAccessor} will be automatically supplied * for any parameter of type {@code ArgumentsAccessor} in a parameterized class * or parameterized test. In addition, {@link ArgumentsAggregator} implementations * are given access to an {@code ArgumentsAccessor}. * *

This interface is not intended to be implemented by clients. * *

Additional Kotlin arguments accessors can be * found as extension functions in the {@link org.junit.jupiter.params.aggregator} * package. * * @since 5.2 * @see ArgumentsAggregator * @see org.junit.jupiter.params.ParameterizedTest */ @API(status = STABLE, since = "5.7") public interface ArgumentsAccessor { /** * Get the value of the argument at the given index as an {@link Object}. * * @param index the index of the argument to get; must be greater than or * equal to zero and less than {@link #size} * @return the value at the given index, potentially {@code null} */ @Nullable Object get(int index) throws ArgumentAccessException; /** * Get the value of the argument at the given index as an instance of the * required type. * * @param index the index of the argument to get; must be greater than or * equal to zero and less than {@link #size} * @param requiredType the required type of the value; never {@code null} * @return the value at the given index, potentially {@code null} */ @Nullable T get(int index, Class requiredType) throws ArgumentAccessException; /** * Get the value of the argument at the given index as a {@link Character}, * performing automatic type conversion as necessary. * * @param index the index of the argument to get; must be greater than or * equal to zero and less than {@link #size} * @return the value at the given index, potentially {@code null} * @throws ArgumentAccessException if the value cannot be accessed * or converted to the desired type */ @Nullable Character getCharacter(int index) throws ArgumentAccessException; /** * Get the value of the argument at the given index as a {@link Boolean}, * performing automatic type conversion as necessary. * * @param index the index of the argument to get; must be greater than or * equal to zero and less than {@link #size} * @return the value at the given index, potentially {@code null} * @throws ArgumentAccessException if the value cannot be accessed * or converted to the desired type */ @Nullable Boolean getBoolean(int index) throws ArgumentAccessException; /** * Get the value of the argument at the given index as a {@link Byte}, * performing automatic type conversion as necessary. * * @param index the index of the argument to get; must be greater than or * equal to zero and less than {@link #size} * @return the value at the given index, potentially {@code null} * @throws ArgumentAccessException if the value cannot be accessed * or converted to the desired type */ @Nullable Byte getByte(int index) throws ArgumentAccessException; /** * Get the value of the argument at the given index as a {@link Short}, * performing automatic type conversion as necessary. * * @param index the index of the argument to get; must be greater than or * equal to zero and less than {@link #size} * @return the value at the given index, potentially {@code null} * @throws ArgumentAccessException if the value cannot be accessed * or converted to the desired type */ @Nullable Short getShort(int index) throws ArgumentAccessException; /** * Get the value of the argument at the given index as a {@link Integer}, * performing automatic type conversion as necessary. * * @param index the index of the argument to get; must be greater than or * equal to zero and less than {@link #size} * @return the value at the given index, potentially {@code null} * @throws ArgumentAccessException if the value cannot be accessed * or converted to the desired type */ @Nullable Integer getInteger(int index) throws ArgumentAccessException; /** * Get the value of the argument at the given index as a {@link Long}, * performing automatic type conversion as necessary. * * @param index the index of the argument to get; must be greater than or * equal to zero and less than {@link #size} * @return the value at the given index, potentially {@code null} * @throws ArgumentAccessException if the value cannot be accessed * or converted to the desired type */ @Nullable Long getLong(int index) throws ArgumentAccessException; /** * Get the value of the argument at the given index as a {@link Float}, * performing automatic type conversion as necessary. * * @param index the index of the argument to get; must be greater than or * equal to zero and less than {@link #size} * @return the value at the given index, potentially {@code null} * @throws ArgumentAccessException if the value cannot be accessed * or converted to the desired type */ @Nullable Float getFloat(int index) throws ArgumentAccessException; /** * Get the value of the argument at the given index as a {@link Double}, * performing automatic type conversion as necessary. * * @param index the index of the argument to get; must be greater than or * equal to zero and less than {@link #size} * @return the value at the given index, potentially {@code null} * @throws ArgumentAccessException if the value cannot be accessed * or converted to the desired type */ @Nullable Double getDouble(int index) throws ArgumentAccessException; /** * Get the value of the argument at the given index as a {@link String}, * performing automatic type conversion as necessary. * * @param index the index of the argument to get; must be greater than or * equal to zero and less than {@link #size} * @return the value at the given index, potentially {@code null} * @throws ArgumentAccessException if the value cannot be accessed * or converted to the desired type */ @Nullable String getString(int index) throws ArgumentAccessException; /** * Get the number of arguments in this accessor. */ int size(); /** * Get all arguments in this accessor as an array. */ @Nullable Object[] toArray(); /** * Get all arguments in this accessor as an immutable list. */ List<@Nullable Object> toList(); /** * Get the index of the current invocation. */ int getInvocationIndex(); } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAggregationException.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.aggregator; import static org.apiguardian.api.API.Status.STABLE; import java.io.Serial; import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; /** * {@code ArgumentsAggregationException} is an exception thrown by an * {@link ArgumentsAggregator} when an error occurs while aggregating * arguments. * * @since 5.2 * @see ArgumentsAggregator */ @API(status = STABLE, since = "5.7") public class ArgumentsAggregationException extends JUnitException { @Serial private static final long serialVersionUID = 1L; public ArgumentsAggregationException(String message) { super(message); } public ArgumentsAggregationException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAggregator.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.aggregator; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.jupiter.params.support.FieldContext; import org.junit.platform.commons.JUnitException; /** * {@code ArgumentsAggregator} is an abstraction for the aggregation of arguments * provided by an {@link org.junit.jupiter.params.provider.ArgumentsProvider * ArgumentsProvider} for a single invocation of a * {@link org.junit.jupiter.params.ParameterizedTest @ParameterizedTest} method * into a single object. * *

An {@code ArgumentsAggregator} is applied to a method parameter of a * {@code @ParameterizedTest} method via the {@link AggregateWith @AggregateWith} * annotation. * *

The result of the aggregation will be passed as an argument to the * {@code @ParameterizedTest} method for the annotated parameter. * *

A common use case is the aggregation of multiple columns from a single line * in a CSV file into a domain object such as a {@code Person}, {@code Address}, * {@code Order}, etc. * *

Implementations must provide a no-args constructor or a single unambiguous * constructor to use {@linkplain ParameterResolver parameter resolution}. They * should not make any assumptions regarding when they are instantiated or how * often they are called. Since instances may potentially be cached and called * from different threads, they should be thread-safe. * * @since 5.2 * @see AggregateWith * @see ArgumentsAccessor * @see SimpleArgumentsAggregator * @see org.junit.jupiter.params.ParameterizedTest */ @API(status = STABLE, since = "5.7") public interface ArgumentsAggregator { /** * Aggregate the arguments contained in the supplied {@code accessor} into a * single object. * * @param accessor an {@link ArgumentsAccessor} containing the arguments to be * aggregated; never {@code null} * @param context the parameter context where the aggregated result is to be * supplied; never {@code null} * @return the aggregated result; may be {@code null} but only if the target * type is a reference type * @throws ArgumentsAggregationException if an error occurs during the * aggregation */ @Nullable Object aggregateArguments(ArgumentsAccessor accessor, ParameterContext context) throws ArgumentsAggregationException; /** * Aggregate the arguments contained in the supplied {@code accessor} into a * single object. * * @param accessor an {@link ArgumentsAccessor} containing the arguments to be * aggregated; never {@code null} * @param context the field context where the aggregated result is to be * injected; never {@code null} * @return the aggregated result; may be {@code null} but only if the target * type is a reference type * @throws ArgumentsAggregationException if an error occurs during the * aggregation * @since 5.13 */ @API(status = EXPERIMENTAL, since = "6.0") default @Nullable Object aggregateArguments(ArgumentsAccessor accessor, FieldContext context) throws ArgumentsAggregationException { throw new JUnitException(""" ArgumentsAggregator does not override the convert(ArgumentsAccessor, FieldContext) method. \ Please report this issue to the maintainers of %s.""".formatted(getClass().getName())); } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.aggregator; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.function.BiFunction; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.params.converter.DefaultArgumentConverter; import org.junit.platform.commons.util.ClassUtils; import org.junit.platform.commons.util.Preconditions; /** * Default implementation of the {@link ArgumentsAccessor} API. * *

Delegates conversion to {@link DefaultArgumentConverter}. * * @since 5.2 * @see ArgumentsAccessor * @see DefaultArgumentConverter * @see org.junit.jupiter.params.ParameterizedTest */ @API(status = INTERNAL, since = "5.2") public class DefaultArgumentsAccessor implements ArgumentsAccessor { private final int invocationIndex; private final @Nullable Object[] arguments; private final BiFunction<@Nullable Object, Class, @Nullable Object> converter; public static DefaultArgumentsAccessor create(int invocationIndex, ClassLoader classLoader, @Nullable Object[] arguments) { Preconditions.notNull(classLoader, "ClassLoader must not be null"); BiFunction<@Nullable Object, Class, @Nullable Object> converter = (source, targetType) -> DefaultArgumentConverter.INSTANCE.convert(source, targetType, classLoader); return new DefaultArgumentsAccessor(converter, invocationIndex, arguments); } private DefaultArgumentsAccessor(BiFunction<@Nullable Object, Class, @Nullable Object> converter, int invocationIndex, @Nullable Object... arguments) { Preconditions.notNull(converter, "Converter must not be null"); Preconditions.condition(invocationIndex >= 1, () -> "Invocation index must be >= 1"); Preconditions.notNull(arguments, "Arguments array must not be null"); this.converter = converter; this.invocationIndex = invocationIndex; this.arguments = arguments; } @Override public @Nullable Object get(int index) { Preconditions.condition(index >= 0 && index < this.arguments.length, () -> "index must be >= 0 and < %d".formatted(this.arguments.length)); return this.arguments[index]; } @Override public @Nullable T get(int index, Class requiredType) { Preconditions.notNull(requiredType, "requiredType must not be null"); Object value = get(index); try { Object convertedValue = converter.apply(value, requiredType); return requiredType.cast(convertedValue); } catch (Exception ex) { String message = "Argument at index [%d] with value [%s] and type [%s] could not be converted or cast to type [%s].".formatted( index, value, ClassUtils.nullSafeToString(value == null ? null : value.getClass()), requiredType.getName()); throw new ArgumentAccessException(message, ex); } } @Override public @Nullable Character getCharacter(int index) { return get(index, Character.class); } @Override public @Nullable Boolean getBoolean(int index) { return get(index, Boolean.class); } @Override public @Nullable Byte getByte(int index) { return get(index, Byte.class); } @Override public @Nullable Short getShort(int index) { return get(index, Short.class); } @Override public @Nullable Integer getInteger(int index) { return get(index, Integer.class); } @Override public @Nullable Long getLong(int index) { return get(index, Long.class); } @Override public @Nullable Float getFloat(int index) { return get(index, Float.class); } @Override public @Nullable Double getDouble(int index) { return get(index, Double.class); } @Override public @Nullable String getString(int index) { return get(index, String.class); } @Override public int size() { return this.arguments.length; } @Override public @Nullable Object[] toArray() { return Arrays.copyOf(this.arguments, this.arguments.length); } @Override public List<@Nullable Object> toList() { return Collections.<@Nullable Object> unmodifiableList(Arrays.asList(this.arguments)); } @Override public int getInvocationIndex() { return this.invocationIndex; } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/SimpleArgumentsAggregator.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.aggregator; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.AnnotatedElementContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.params.support.FieldContext; /** * {@code SimpleArgumentsAggregator} is an abstract base class for * {@link ArgumentsAggregator} implementations that do not need to distinguish * between fields and method/constructor parameters. * * @since 5.0 * @see ArgumentsAggregator */ @API(status = EXPERIMENTAL, since = "6.0") public abstract class SimpleArgumentsAggregator implements ArgumentsAggregator { public SimpleArgumentsAggregator() { } @Override public @Nullable Object aggregateArguments(ArgumentsAccessor accessor, ParameterContext context) throws ArgumentsAggregationException { return aggregateArguments(accessor, context.getParameter().getType(), context, context.getIndex()); } @Override public @Nullable Object aggregateArguments(ArgumentsAccessor accessor, FieldContext context) throws ArgumentsAggregationException { return aggregateArguments(accessor, context.getField().getType(), context, context.getParameterIndex()); } protected abstract @Nullable Object aggregateArguments(ArgumentsAccessor accessor, Class targetType, AnnotatedElementContext context, int parameterIndex) throws ArgumentsAggregationException; } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * The {@link org.junit.jupiter.params.aggregator.ArgumentsAggregator} and * {@link org.junit.jupiter.params.aggregator.ArgumentsAccessor} interfaces and the * {@link org.junit.jupiter.params.aggregator.AggregateWith} annotation. */ @NullMarked package org.junit.jupiter.params.aggregator; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/AnnotationBasedArgumentConverter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.converter; import static java.util.Objects.requireNonNull; import static org.apiguardian.api.API.Status.MAINTAINED; import java.lang.annotation.Annotation; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.params.support.AnnotationConsumer; import org.junit.jupiter.params.support.FieldContext; import org.junit.platform.commons.util.Preconditions; /** * {@code AnnotationBasedArgumentConverter} is an abstract base class for * {@link ArgumentConverter} implementations that also need to consume an * annotation in order to perform the conversion. * * @since 5.10 * @see ArgumentConverter * @see AnnotationConsumer * @see SimpleArgumentConverter */ @API(status = MAINTAINED, since = "5.13.3") public abstract class AnnotationBasedArgumentConverter implements ArgumentConverter, AnnotationConsumer { private @Nullable A annotation; public AnnotationBasedArgumentConverter() { } @Override public final void accept(A annotation) { this.annotation = Preconditions.notNull(annotation, "annotation must not be null"); } @Override public final @Nullable Object convert(@Nullable Object source, ParameterContext context) throws ArgumentConversionException { return convert(source, context.getParameter().getType(), requireNonNull(this.annotation)); } @Override public final @Nullable Object convert(@Nullable Object source, FieldContext context) throws ArgumentConversionException { return convert(source, context.getField().getType(), requireNonNull(this.annotation)); } /** * Convert the supplied {@code source} object into the supplied {@code targetType}, * based on metadata in the provided annotation. * * @param source the source object to convert; may be {@code null} * @param targetType the target type the source object should be converted * into; never {@code null} * @param annotation the annotation to process; never {@code null} * @return the converted object; may be {@code null} but only if the target * type is a reference type * @throws ArgumentConversionException in case an error occurs during the * conversion */ protected abstract @Nullable Object convert(@Nullable Object source, Class targetType, A annotation) throws ArgumentConversionException; } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConversionException.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.converter; import static org.apiguardian.api.API.Status.STABLE; import java.io.Serial; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; /** * {@code ArgumentConversionException} is an exception that can occur when an * object is converted to another object by an implementation of an * {@link ArgumentConverter}. * * @since 5.0 * @see ArgumentConverter */ @API(status = STABLE, since = "5.7") public class ArgumentConversionException extends JUnitException { @Serial private static final long serialVersionUID = 1L; public ArgumentConversionException(@Nullable String message) { super(message); } public ArgumentConversionException(@Nullable String message, @Nullable Throwable cause) { super(message, cause); } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConverter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.converter; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.jupiter.params.support.FieldContext; import org.junit.platform.commons.JUnitException; /** * {@code ArgumentConverter} is an abstraction that allows an input object to * be converted to an instance of a different class. * *

Such an {@code ArgumentConverter} is applied to the method parameter * of a {@link org.junit.jupiter.params.ParameterizedTest @ParameterizedTest} * or a constructor parameter or * {@link org.junit.jupiter.params.Parameter @Parameter}-annotated field of a * {@link org.junit.jupiter.params.ParameterizedClass @ParameterizedClass} with * the help of a * {@link org.junit.jupiter.params.converter.ConvertWith @ConvertWith} * annotation. * *

Implementations must provide a no-args constructor or a single unambiguous * constructor to use {@linkplain ParameterResolver parameter resolution}. They * should not make any assumptions regarding when they are instantiated or how * often they are called. Since instances may potentially be cached and called * from different threads, they should be thread-safe. * *

Extend {@link SimpleArgumentConverter} if your implementation only needs * to know the target type and does not need access to the {@link ParameterContext} * to perform the conversion. * *

Extend {@link TypedArgumentConverter} if your implementation always converts * from a given source type into a given target type and does not need access to * the {@link ParameterContext} to perform the conversion. * * @since 5.0 * @see org.junit.jupiter.params.ParameterizedTest * @see org.junit.jupiter.params.converter.ConvertWith * @see org.junit.jupiter.params.support.AnnotationConsumer * @see SimpleArgumentConverter * @see TypedArgumentConverter */ @API(status = STABLE, since = "5.7") public interface ArgumentConverter { /** * Convert the supplied {@code source} object according to the supplied * {@code context}. * * @param source the source object to convert; may be {@code null} * @param context the parameter context where the converted object will be * supplied; never {@code null} * @return the converted object; may be {@code null} but only if the target * type is a reference type * @throws ArgumentConversionException if an error occurs during the * conversion */ @Nullable Object convert(@Nullable Object source, ParameterContext context) throws ArgumentConversionException; /** * Convert the supplied {@code source} object according to the supplied * {@code context}. * * @param source the source object to convert; may be {@code null} * @param context the field context where the converted object will be * injected; never {@code null} * @return the converted object; may be {@code null} but only if the target * type is a reference type * @throws ArgumentConversionException if an error occurs during the * conversion * @since 5.13 */ @API(status = EXPERIMENTAL, since = "6.0") default @Nullable Object convert(@Nullable Object source, FieldContext context) throws ArgumentConversionException { throw new JUnitException(""" ArgumentConverter does not override the convert(Object, FieldContext) method. \ Please report this issue to the maintainers of %s.""".formatted(getClass().getName())); } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ConvertWith.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.converter; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @ConvertWith} is an annotation that allows one to specify an explicit * {@link ArgumentConverter}. * *

This annotation may be applied to parameters of a * {@link org.junit.jupiter.params.ParameterizedClass @ParameterizedClass} * constructor or its * {@link org.junit.jupiter.params.Parameter @Parameter}-annotated fields, or to * parameters of a * {@link org.junit.jupiter.params.ParameterizedTest @ParameterizedTest} method * which need to have their {@code Arguments} converted before consuming them. * * @since 5.0 * @see org.junit.jupiter.params.ParameterizedTest * @see org.junit.jupiter.params.converter.ArgumentConverter */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.PARAMETER, ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = STABLE, since = "5.7") public @interface ConvertWith { /** * The type of {@link ArgumentConverter} to use. */ Class value(); } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/DefaultArgumentConverter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.converter; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.commons.util.ClassLoaderUtils.getClassLoader; import java.io.File; import java.math.BigDecimal; import java.math.BigInteger; import java.net.URI; import java.net.URL; import java.util.Currency; import java.util.Locale; import java.util.UUID; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.params.support.FieldContext; import org.junit.platform.commons.support.conversion.ConversionException; import org.junit.platform.commons.support.conversion.ConversionSupport; import org.junit.platform.commons.util.ReflectionUtils; /** * {@code DefaultArgumentConverter} is the default implementation of the * {@link ArgumentConverter} API. * *

The {@code DefaultArgumentConverter} is able to convert from strings to a * number of primitive types and their corresponding wrapper types (Byte, Short, * Integer, Long, Float, and Double), date and time types from the * {@code java.time} package, and some additional common Java types such as * {@link File}, {@link BigDecimal}, {@link BigInteger}, {@link Currency}, * {@link Locale}, {@link URI}, {@link URL}, {@link UUID}, etc. * *

If the source and target types are identical the source object will not * be modified. * * @since 5.0 * @see org.junit.jupiter.params.converter.ArgumentConverter * @see org.junit.platform.commons.support.conversion.ConversionSupport */ @API(status = INTERNAL, since = "5.0") public class DefaultArgumentConverter implements ArgumentConverter { public static final DefaultArgumentConverter INSTANCE = new DefaultArgumentConverter(); private DefaultArgumentConverter() { } @Override public final @Nullable Object convert(@Nullable Object source, ParameterContext context) { Class targetType = context.getParameter().getType(); ClassLoader classLoader = getClassLoader(context.getDeclaringExecutable().getDeclaringClass()); return convert(source, targetType, classLoader); } @Override public final @Nullable Object convert(@Nullable Object source, FieldContext context) throws ArgumentConversionException { Class targetType = context.getField().getType(); ClassLoader classLoader = getClassLoader(context.getField().getDeclaringClass()); return convert(source, targetType, classLoader); } public final @Nullable Object convert(@Nullable Object source, Class targetType, ClassLoader classLoader) { if (source == null) { if (targetType.isPrimitive()) { throw new ArgumentConversionException( "Cannot convert null to primitive value of type " + targetType.getTypeName()); } return null; } if (ReflectionUtils.isAssignableTo(source, targetType)) { return source; } if (source instanceof String string) { try { return convert(string, targetType, classLoader); } catch (ConversionException ex) { throw new ArgumentConversionException(ex.getMessage(), ex); } } throw new ArgumentConversionException("No built-in converter for source type %s and target type %s".formatted( source.getClass().getTypeName(), targetType.getTypeName())); } @Nullable Object convert(@Nullable String source, Class targetType, ClassLoader classLoader) { return ConversionSupport.convert(source, targetType, classLoader); } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.converter; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetDateTime; import java.time.OffsetTime; import java.time.Year; import java.time.YearMonth; import java.time.ZonedDateTime; import java.time.chrono.ChronoLocalDate; import java.time.chrono.ChronoLocalDateTime; import java.time.chrono.ChronoZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.TemporalQuery; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import org.jspecify.annotations.Nullable; /** * @since 5.0 */ class JavaTimeArgumentConverter extends AnnotationBasedArgumentConverter { private static final Map, TemporalQuery> TEMPORAL_QUERIES; static { Map, TemporalQuery> queries = new LinkedHashMap<>(); queries.put(ChronoLocalDate.class, ChronoLocalDate::from); queries.put(ChronoLocalDateTime.class, ChronoLocalDateTime::from); queries.put(ChronoZonedDateTime.class, ChronoZonedDateTime::from); queries.put(LocalDate.class, LocalDate::from); queries.put(LocalDateTime.class, LocalDateTime::from); queries.put(LocalTime.class, LocalTime::from); queries.put(OffsetDateTime.class, OffsetDateTime::from); queries.put(OffsetTime.class, OffsetTime::from); queries.put(Year.class, Year::from); queries.put(YearMonth.class, YearMonth::from); queries.put(ZonedDateTime.class, ZonedDateTime::from); TEMPORAL_QUERIES = Collections.unmodifiableMap(queries); } @Override protected @Nullable Object convert(@Nullable Object input, Class targetClass, JavaTimeConversionPattern annotation) { if (input == null) { if (annotation.nullable()) { return null; } throw new ArgumentConversionException( "Cannot convert null to " + targetClass.getName() + "; consider setting 'nullable = true'"); } TemporalQuery temporalQuery = TEMPORAL_QUERIES.get(targetClass); if (temporalQuery == null) { throw new ArgumentConversionException("Cannot convert to " + targetClass.getName() + ": " + input); } String pattern = annotation.value(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); return formatter.parse(input.toString(), temporalQuery); } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/JavaTimeConversionPattern.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.converter; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @JavaTimeConversionPattern} is an annotation that allows a date/time * conversion pattern to be specified on a parameter of a * {@link org.junit.jupiter.params.ParameterizedClass @ParameterizedClass} * or * {@link org.junit.jupiter.params.ParameterizedTest @ParameterizedTest}. * * @since 5.0 * @see ConvertWith * @see org.junit.jupiter.params.ParameterizedClass * @see org.junit.jupiter.params.ParameterizedTest * @see java.time.format.DateTimeFormatterBuilder#appendPattern(String) */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.PARAMETER, ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = STABLE, since = "5.7") @ConvertWith(JavaTimeArgumentConverter.class) @SuppressWarnings("exports") public @interface JavaTimeConversionPattern { /** * The date/time conversion pattern. * * @see java.time.format.DateTimeFormatterBuilder#appendPattern(String) */ String value(); /** * Whether {@code null} argument values are allowed. * *

Defaults to {@code false}, in which case a {@code null} value will result in * an exception. * * @since 5.12 */ @API(status = MAINTAINED, since = "5.13.3") boolean nullable() default false; } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/SimpleArgumentConverter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.converter; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.params.support.FieldContext; /** * {@code SimpleArgumentConverter} is an abstract base class for * {@link ArgumentConverter} implementations that only need to know the target * type and do not need access to the {@link ParameterContext} to perform the * conversion. * * @since 5.0 * @see ArgumentConverter * @see TypedArgumentConverter */ @API(status = STABLE, since = "5.7") public abstract class SimpleArgumentConverter implements ArgumentConverter { public SimpleArgumentConverter() { } @Override public final @Nullable Object convert(@Nullable Object source, ParameterContext context) throws ArgumentConversionException { return convert(source, context.getParameter().getType()); } @Override public final @Nullable Object convert(@Nullable Object source, FieldContext context) throws ArgumentConversionException { return convert(source, context.getField().getType()); } /** * Convert the supplied {@code source} object into the supplied * {@code targetType}. * * @param source the source object to convert; may be {@code null} * @param targetType the target type the source object should be converted * into; never {@code null} * @return the converted object; may be {@code null} but only if the target * type is a reference type * @throws ArgumentConversionException in case an error occurs during the * conversion */ protected abstract @Nullable Object convert(@Nullable Object source, Class targetType) throws ArgumentConversionException; } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/TypedArgumentConverter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.converter; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.params.support.FieldContext; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ReflectionUtils; /** * {@code TypedArgumentConverter} is an abstract base class for * {@link ArgumentConverter} implementations that always convert objects of a * given source type into a given target type. * * @param the type of the source argument to convert * @param the type of the target object to create from the source * @since 5.7 * @see ArgumentConverter * @see SimpleArgumentConverter */ @API(status = STABLE, since = "5.10") public abstract class TypedArgumentConverter implements ArgumentConverter { private final Class sourceType; private final Class targetType; /** * Create a new {@code TypedArgumentConverter}. * * @param sourceType the type of the argument to convert; never {@code null} * @param targetType the type of the target object to create from the source; * never {@code null} */ protected TypedArgumentConverter(Class sourceType, @SuppressWarnings("NullableProblems") Class<@NonNull T> targetType) { this.sourceType = Preconditions.notNull(sourceType, "sourceType must not be null"); this.targetType = Preconditions.notNull(targetType, "targetType must not be null"); } @Override public final @Nullable Object convert(@Nullable Object source, ParameterContext context) throws ArgumentConversionException { return convert(source, context.getParameter().getType()); } @Override public final @Nullable Object convert(@Nullable Object source, FieldContext context) throws ArgumentConversionException { return convert(source, context.getField().getType()); } private T convert(@Nullable Object source, Class actualTargetType) { if (source == null) { return convert(null); } if (!this.sourceType.isInstance(source)) { String message = "%s cannot convert objects of type [%s]. Only source objects of type [%s] are supported.".formatted( getClass().getSimpleName(), source.getClass().getTypeName(), this.sourceType.getTypeName()); throw new ArgumentConversionException(message); } if (!ReflectionUtils.isAssignableTo(this.targetType, actualTargetType)) { String message = "%s cannot convert to type [%s]. Only target type [%s] is supported.".formatted( getClass().getSimpleName(), actualTargetType.getTypeName(), this.targetType.getTypeName()); throw new ArgumentConversionException(message); } return convert(this.sourceType.cast(source)); } /** * Convert the supplied {@code source} object of type {@code S} into an object * of type {@code T}. * * @param source the source object to convert; may be {@code null} * @return the converted object; may be {@code null} but only if the target * type is a reference type * @throws ArgumentConversionException if an error occurs during the * conversion */ protected abstract T convert(@Nullable S source) throws ArgumentConversionException; } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * {@link org.junit.jupiter.params.converter.ArgumentConverter ArgumentConverter} * implementations and the corresponding * {@link org.junit.jupiter.params.converter.ConvertWith @ConvertWith} annotation. */ @NullMarked package org.junit.jupiter.params.converter; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * JUnit Jupiter extension for parameterized tests. */ @NullMarked package org.junit.jupiter.params; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProvider.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.MAINTAINED; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.support.AnnotationConsumer; import org.junit.jupiter.params.support.ParameterDeclarations; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.Preconditions; /** * {@code AnnotationBasedArgumentsProvider} is an abstract base class for * {@link ArgumentsProvider} implementations that also need to consume an * annotation in order to provide the arguments. * * @since 5.10 * @see org.junit.jupiter.params.ParameterizedClass * @see org.junit.jupiter.params.ParameterizedTest * @see org.junit.jupiter.params.provider.ArgumentsSource * @see org.junit.jupiter.params.provider.Arguments * @see org.junit.jupiter.params.provider.ArgumentsProvider * @see org.junit.jupiter.params.support.AnnotationConsumer */ @API(status = MAINTAINED, since = "5.13.3") public abstract class AnnotationBasedArgumentsProvider implements ArgumentsProvider, AnnotationConsumer { public AnnotationBasedArgumentsProvider() { } private final List annotations = new ArrayList<>(); @Override public final void accept(A annotation) { Preconditions.notNull(annotation, "annotation must not be null"); annotations.add(annotation); } @Override public Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context) { return annotations.stream().flatMap(annotation -> provideArguments(parameters, context, annotation)); } /** * Provide a {@link Stream} of {@link Arguments} — based on metadata in the * provided annotation — to be passed to a {@code @ParameterizedTest} method. * * @param context the current extension context; never {@code null} * @param annotation the annotation to process; never {@code null} * @return a stream of arguments; never {@code null} * @deprecated Please implement * {@link #provideArguments(ParameterDeclarations, ExtensionContext, Annotation)} * instead. */ @Deprecated(since = "5.13") @API(status = DEPRECATED, since = "5.13") protected Stream provideArguments(ExtensionContext context, A annotation) { throw new JUnitException(""" AnnotationBasedArgumentsProvider does not override the \ provideArguments(ParameterDeclarations, ExtensionContext, Annotation) method. \ Please report this issue to the maintainers of %s.""".formatted(getClass().getName())); } /** * The returned {@code Stream} will be {@link Stream#close() properly closed} * by the default implementation of * {@link #provideArguments(ParameterDeclarations, ExtensionContext)}, * making it safe to use a resource such as * {@link java.nio.file.Files#lines(java.nio.file.Path) Files.lines()}. */ protected Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context, A annotation) { return provideArguments(context, annotation); } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/Arguments.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; /** * {@code Arguments} is an abstraction that provides access to an array of * objects to be used for the invocation of a {@code @ParameterizedClass} or * {@code @ParameterizedTest} method. * *

A {@link java.util.stream.Stream} of such {@code Arguments} will * typically be provided by an {@link ArgumentsProvider}. * * @apiNote

This interface is specifically designed as a simple holder of * arguments for a parameterized test. Therefore, if you end up * {@linkplain java.util.stream.Stream#map(java.util.function.Function) transforming} or * {@linkplain java.util.stream.Stream#filter(java.util.function.Predicate) filtering} * the arguments, you should consider using one of the following in intermediate steps: * *

* *

Alternatively, you can use an * {@link org.junit.jupiter.params.converter.ArgumentConverter ArgumentConverter} * to convert some of the arguments from one type to another. * * @since 5.0 * @see ArgumentSet * @see org.junit.jupiter.params.ParameterizedTest * @see org.junit.jupiter.params.provider.ArgumentsSource * @see org.junit.jupiter.params.provider.ArgumentsProvider * @see org.junit.jupiter.params.converter.ArgumentConverter */ @FunctionalInterface @API(status = STABLE, since = "5.7") public interface Arguments { /** * Get the arguments used for an invocation of the {@code @ParameterizedClass} * or {@code @ParameterizedTest} method. * * @apiNote If you need a type-safe way to access some or all of the * arguments, please read the {@linkplain Arguments class-level API note}. * * @return the arguments; never {@code null} but may contain {@code null} */ @Nullable Object[] get(); /** * Convert the arguments to a new mutable {@link List} containing the same * elements as {@link #get()}. * *

This is useful for test logic that benefits from {@code List} * operations such as filtering, transformation, or assertions. * * @return a mutable List of arguments; never {@code null} but may contain * {@code null} * @since 6.1 */ @API(status = EXPERIMENTAL, since = "6.1") default List<@Nullable Object> toList() { // We could return List here but the unbounded wildcard is painful // to work with. return new ArrayList<>(Arrays.asList(get())); } /** * Factory method for creating an instance of {@code Arguments} based on * the supplied {@code arguments}. * * @param arguments the arguments to be used for an invocation of the test * method; must not be {@code null} but may contain {@code null} * @return an instance of {@code Arguments}; never {@code null} * @see #from(Iterable) * @see #arguments(Object...) * @see #argumentSet(String, Object...) */ static Arguments of(@Nullable Object... arguments) { Preconditions.notNull(arguments, "arguments array must not be null"); return () -> arguments; } /** * Factory method for creating an instance of {@code Arguments} based on * the supplied {@link Iterable} of {@code arguments}. * *

The iterable supplied to this method should be a finite collection * and have a reliable iteration order to provide arguments in a consistent * order to tests. It is therefore recommended that the iterable be a * {@link java.util.SequencedCollection} (on Java 21 or higher), * {@link java.util.List}, or similar. * * @param arguments the arguments to be used for an invocation of the test * method; must not be {@code null} but may contain {@code null} * @return an instance of {@code Arguments}; never {@code null} * @since 6.1 * @see #argumentsFrom(Iterable) */ @API(status = EXPERIMENTAL, since = "6.1") static Arguments from(Iterable arguments) { Preconditions.notNull(arguments, "arguments must not be null"); return of(toArray(arguments)); } /** * Factory method for creating an instance of {@code Arguments} based on * the supplied {@code arguments}. * *

This method is an alias for {@link Arguments#of} and is * intended to be used when statically imported — for example, via: * {@code import static org.junit.jupiter.params.provider.Arguments.arguments;} * * @param arguments the arguments to be used for an invocation of the test * method; must not be {@code null} but may contain {@code null} * @return an instance of {@code Arguments}; never {@code null} * @since 5.3 * @see #argumentSet(String, Object...) * @see #argumentsFrom(Iterable) */ static Arguments arguments(@Nullable Object... arguments) { return of(arguments); } /** * Factory method for creating an instance of {@code Arguments} based on * the supplied {@link Iterable} of {@code arguments}. * *

This method is an alias for {@link Arguments#from} and is * intended to be used when statically imported — for example, via: * {@code import static org.junit.jupiter.params.provider.Arguments.argumentsFrom;} * *

The iterable supplied to this method should be a finite collection * and have a reliable iteration order to provide arguments in a consistent * order to tests. It is therefore recommended that the iterable be a * {@link java.util.SequencedCollection} (on Java 21 or higher), * {@link java.util.List}, or similar. * * @param arguments the arguments to be used for an invocation of the test * method; must not be {@code null} but may contain {@code null} * @return an instance of {@code Arguments}; never {@code null} * @since 6.1 * @see #arguments(Object...) * @see #argumentSetFrom(String, Iterable) */ @API(status = EXPERIMENTAL, since = "6.1") static Arguments argumentsFrom(Iterable arguments) { return from(arguments); } /** * Factory method for creating an {@link ArgumentSet} based on the supplied * {@code name} and {@code arguments}. * *

Favor this method over {@link Arguments#of Arguments.of(...)} and * {@link Arguments#arguments arguments(...)} when you wish to assign a name * to the entire set of arguments. * *

This method is well suited to be used as a static import — for * example, via: * {@code import static org.junit.jupiter.params.provider.Arguments.argumentSet;}. * * @param name the name of the argument set; must not be {@code null} or blank * @param arguments the arguments to be used for an invocation of the test * method; must not be {@code null} but may contain {@code null} * @return an {@code ArgumentSet}; never {@code null} * @since 5.11 * @see ArgumentSet * @see #argumentSetFrom(String, Iterable) * @see org.junit.jupiter.params.ParameterizedInvocationConstants#ARGUMENT_SET_NAME_PLACEHOLDER * @see org.junit.jupiter.params.ParameterizedInvocationConstants#ARGUMENT_SET_NAME_OR_ARGUMENTS_WITH_NAMES_PLACEHOLDER */ @API(status = MAINTAINED, since = "5.13.3") static ArgumentSet argumentSet(String name, @Nullable Object... arguments) { return new ArgumentSet(name, arguments); } /** * Factory method for creating an {@link ArgumentSet} based on the supplied * {@code name} and {@link Iterable} of {@code arguments}. * *

Favor this method over {@link Arguments#from(Iterable) Arguments.from(...)} * and {@link Arguments#argumentsFrom(Iterable) argumentsFrom(...)} when you * wish to assign a name to the entire set of arguments. * *

This method is well suited to be used as a static import — for * example, via: * {@code import static org.junit.jupiter.params.provider.Arguments.argumentSetFrom;}. * *

The iterable supplied to this method should be a finite collection * and have a reliable iteration order to provide arguments in a consistent * order to tests. It is therefore recommended that the iterable be a * {@link java.util.SequencedCollection} (on Java 21 or higher), * {@link java.util.List}, or similar. * * @param name the name of the argument set; must not be {@code null} or blank * @param arguments the arguments to be used for an invocation of the test * method; must not be {@code null} but may contain {@code null} * @return an {@code ArgumentSet}; never {@code null} * @since 6.1 * @see ArgumentSet * @see #argumentSet(String, Object...) * @see org.junit.jupiter.params.ParameterizedInvocationConstants#ARGUMENT_SET_NAME_PLACEHOLDER * @see org.junit.jupiter.params.ParameterizedInvocationConstants#ARGUMENT_SET_NAME_OR_ARGUMENTS_WITH_NAMES_PLACEHOLDER */ @API(status = EXPERIMENTAL, since = "6.1") static ArgumentSet argumentSetFrom(String name, Iterable arguments) { Preconditions.notNull(arguments, "arguments must not be null"); return argumentSet(name, toArray(arguments)); } private static Object[] toArray(Iterable arguments) { if (arguments instanceof Collection collection) { return collection.toArray(); } var list = new ArrayList<>(); arguments.forEach(list::add); return list.toArray(); } /** * Specialization of {@link Arguments} that associates a {@link #getName() name} * with a set of {@link #get() arguments}. * * @since 5.11 * @see Arguments#argumentSet(String, Object...) * @see Arguments#argumentSetFrom(String, Iterable) * @see org.junit.jupiter.params.ParameterizedInvocationConstants#ARGUMENT_SET_NAME_PLACEHOLDER * @see org.junit.jupiter.params.ParameterizedInvocationConstants#ARGUMENT_SET_NAME_OR_ARGUMENTS_WITH_NAMES_PLACEHOLDER */ @API(status = MAINTAINED, since = "5.13.3") final class ArgumentSet implements Arguments { private final String name; private final @Nullable Object[] arguments; private ArgumentSet(String name, @Nullable Object[] arguments) { Preconditions.notBlank(name, "name must not be null or blank"); Preconditions.notNull(arguments, "arguments array must not be null"); this.name = name; this.arguments = arguments; } /** * Get the name of this {@code ArgumentSet}. * @return the name of this {@code ArgumentSet}; never {@code null} or blank */ public String getName() { return this.name; } @Override public @Nullable Object[] get() { return this.arguments; } /** * Return the {@link #getName() name} of this {@code ArgumentSet}. * @return the name of this {@code ArgumentSet} */ @Override public String toString() { return getName(); } } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsProvider.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.util.stream.Stream; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.jupiter.params.support.ParameterDeclarations; import org.junit.platform.commons.JUnitException; /** * An {@code ArgumentsProvider} is responsible for * {@linkplain #provideArguments(ParameterDeclarations, ExtensionContext) providing} * a stream of arguments to be passed to a * {@link org.junit.jupiter.params.ParameterizedClass @ParameterizedClass} or * {@link org.junit.jupiter.params.ParameterizedTest @ParameterizedTest}. * *

An {@code ArgumentsProvider} can be registered via the * {@link ArgumentsSource @ArgumentsSource} annotation. * *

Implementations must provide a no-args constructor or a single unambiguous * constructor to use {@linkplain ParameterResolver parameter resolution}. * * @since 5.0 * @see org.junit.jupiter.params.ParameterizedClass * @see org.junit.jupiter.params.ParameterizedTest * @see org.junit.jupiter.params.provider.ArgumentsSource * @see org.junit.jupiter.params.provider.Arguments * @see org.junit.jupiter.params.support.AnnotationConsumer */ @API(status = STABLE, since = "5.7") public interface ArgumentsProvider { /** * Provide a {@link Stream} of {@link Arguments} to be passed to a * {@code @ParameterizedTest} method. * * @param context the current extension context; never {@code null} * @return a stream of arguments; never {@code null} * @deprecated Please implement * {@link #provideArguments(ParameterDeclarations, ExtensionContext)} instead. */ @Deprecated(since = "5.13") @API(status = DEPRECATED, since = "5.13") default Stream provideArguments(@SuppressWarnings("unused") ExtensionContext context) throws Exception { throw new UnsupportedOperationException( "Please implement provideArguments(ParameterDeclarations, ExtensionContext) instead."); } /** * Provide a {@link Stream} of {@link Arguments} to be passed to a * {@code @ParameterizedClass} or {@code @ParameterizedTest}. * * @param parameters the parameter declarations for the parameterized * class or test; never {@code null} * @param context the current extension context; never {@code null} * @return a stream of arguments; never {@code null} * @since 5.13 */ @API(status = MAINTAINED, since = "6.0.2") default Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context) throws Exception { try { return provideArguments(context); } catch (Exception e) { String message = """ ArgumentsProvider does not override the provideArguments(ParameterDeclarations, ExtensionContext) method. \ Please report this issue to the maintainers of %s.""".formatted( getClass().getName()); throw new JUnitException(message, e); } } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsSource.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @ArgumentsSource} is a {@linkplain Repeatable repeatable} annotation * that is used to register {@linkplain ArgumentsProvider arguments providers} * for the annotated class or method. * *

{@code @ArgumentsSource} may also be used as a meta-annotation in order to * create a custom composed annotation that inherits the semantics * of {@code @ArgumentsSource}. * *

Inheritance

* *

This annotation is {@linkplain Inherited inherited} within class hierarchies. * * @since 5.0 * @see org.junit.jupiter.params.provider.ArgumentsProvider * @see org.junit.jupiter.params.ParameterizedClass * @see org.junit.jupiter.params.ParameterizedTest */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Repeatable(ArgumentsSources.class) @API(status = STABLE, since = "5.7") public @interface ArgumentsSource { /** * The type of {@link ArgumentsProvider} to be used. */ Class value(); } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsSources.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @ArgumentsSources} is a simple container for one or more * {@link ArgumentsSource @ArgumentsSource} annotations. * *

Note, however, that use of the {@code @ArgumentsSources} container is completely * optional since {@code @ArgumentsSource} is a {@linkplain java.lang.annotation.Repeatable * repeatable} annotation. * *

Inheritance

* *

This annotation is {@linkplain Inherited inherited} within class hierarchies. * * @since 5.0 * @see org.junit.jupiter.params.provider.ArgumentsSource */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @API(status = STABLE, since = "5.7") public @interface ArgumentsSources { /** * An array of one or more {@link ArgumentsSource @ArgumentsSource} * annotations. */ ArgumentsSource[] value(); } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static org.junit.jupiter.params.provider.Arguments.arguments; import org.junit.platform.commons.util.ReflectionUtils; /** * Collection of utilities for working with {@link Arguments}. * * @since 5.11, when it was extracted from {@link MethodArgumentsProvider} */ final class ArgumentsUtils { private ArgumentsUtils() { /* no-op */ } /** * Convert the supplied object into an {@link Arguments} instance. */ static Arguments toArguments(Object item) { // Nothing to do except cast. if (item instanceof Arguments arguments) { return arguments; } // Pass all multidimensional arrays "as is", in contrast to Object[]. // See https://github.com/junit-team/junit-framework/issues/1665 if (ReflectionUtils.isMultidimensionalArray(item)) { return arguments(item); } // Special treatment for one-dimensional reference arrays. // See https://github.com/junit-team/junit-framework/issues/1665 if (item instanceof Object[] objects) { return arguments(objects); } // Pass everything else "as is". return arguments(item); } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; import de.siegmar.fastcsv.reader.CsvRecord; import de.siegmar.fastcsv.reader.NamedCsvRecord; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Named; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.support.ParameterDeclarations; import org.junit.jupiter.params.support.ParameterNameAndArgument; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.UnrecoverableExceptions; /** * @since 5.0 */ class CsvArgumentsProvider extends AnnotationBasedArgumentsProvider { @Override protected Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context, CsvSource csvSource) { CsvReaderFactory.validate(csvSource); List arguments = new ArrayList<>(); try (var reader = CsvReaderFactory.createReaderFor(csvSource, getData(csvSource))) { boolean useHeadersInDisplayName = csvSource.useHeadersInDisplayName(); for (CsvRecord record : reader) { arguments.add(processCsvRecord(record, useHeadersInDisplayName)); } } catch (Throwable throwable) { throw handleCsvException(throwable, csvSource); } return arguments.stream(); } private static String getData(CsvSource csvSource) { var values = csvSource.value(); Preconditions.condition(values.length > 0 ^ !csvSource.textBlock().isEmpty(), () -> "@CsvSource must be declared with either `value` or `textBlock` but not both"); if (!csvSource.textBlock().isEmpty()) { return csvSource.textBlock(); } else { for (int i = 0; i < values.length; i++) { int finalI = i; Preconditions.notBlank(values[i], () -> "CSV record at index %d must not be blank".formatted(finalI + 1)); } return String.join("\n", values); } } /** * Processes custom null values, supports wrapping of column values in * {@link Named} if necessary (for CSV header support), and returns the * CSV record wrapped in an {@link Arguments} instance. */ static Arguments processCsvRecord(CsvRecord record, boolean useHeadersInDisplayName) { List fields = record.getFields(); List headers = useHeadersInDisplayName ? getHeaders(record) : List.of(); Preconditions.condition(!useHeadersInDisplayName || fields.size() <= headers.size(), // () -> "The number of columns (%d) exceeds the number of supplied headers (%d) in CSV record: %s".formatted( // fields.size(), headers.size(), fields)); // @Nullable Object[] arguments = new Object[fields.size()]; for (int i = 0; i < fields.size(); i++) { Object argument = resolveNullMarker(fields.get(i)); if (useHeadersInDisplayName) { String header = resolveNullMarker(headers.get(i)); argument = new ParameterNameAndArgument(String.valueOf(header), argument); } arguments[i] = argument; } return Arguments.of(arguments); } private static List getHeaders(CsvRecord record) { return ((NamedCsvRecord) record).getHeader(); } @SuppressWarnings({ "ReferenceEquality", "StringEquality" }) private static @Nullable String resolveNullMarker(String record) { return record == CsvReaderFactory.DefaultFieldModifier.NULL_MARKER ? null : record; } /** * @return this method always throws an exception and therefore never * returns anything; the return type is merely present to allow this * method to be supplied as the operand in a {@code throw} statement */ static RuntimeException handleCsvException(Throwable throwable, Annotation annotation) { UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); if (throwable instanceof PreconditionViolationException exception) { throw exception; } throw new CsvParsingException("Failed to parse CSV input configured via " + annotation, throwable); } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; import java.util.List; import java.util.Spliterator; import java.util.function.Consumer; import java.util.stream.Stream; import java.util.stream.StreamSupport; import de.siegmar.fastcsv.reader.CsvReader; import de.siegmar.fastcsv.reader.CsvRecord; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.support.ParameterDeclarations; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.Preconditions; /** * @since 5.0 */ class CsvFileArgumentsProvider extends AnnotationBasedArgumentsProvider { private final InputStreamProvider inputStreamProvider; CsvFileArgumentsProvider() { this(DefaultInputStreamProvider.INSTANCE); } CsvFileArgumentsProvider(InputStreamProvider inputStreamProvider) { this.inputStreamProvider = inputStreamProvider; } @Override protected Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context, CsvFileSource csvFileSource) { Charset charset = getCharsetFrom(csvFileSource); CsvReaderFactory.validate(csvFileSource); Stream resources = Arrays.stream(csvFileSource.resources()).map(inputStreamProvider::classpathResource); Stream files = Arrays.stream(csvFileSource.files()).map(inputStreamProvider::file); List sources = Stream.concat(resources, files).toList(); // @formatter:off return Preconditions.notEmpty(sources, "Resources or files must not be empty") .stream() .map(source -> source.open(context)) .map(inputStream -> CsvReaderFactory.createReaderFor(csvFileSource, inputStream, charset)) .flatMap(reader -> toStream(reader, csvFileSource)); // @formatter:on } private static Charset getCharsetFrom(CsvFileSource csvFileSource) { try { return Charset.forName(csvFileSource.encoding()); } catch (Exception ex) { throw new PreconditionViolationException("The charset supplied in " + csvFileSource + " is invalid", ex); } } private static Stream toStream(CsvReader reader, CsvFileSource csvFileSource) { var spliterator = CsvExceptionHandlingSpliterator.delegatingTo(reader.spliterator(), csvFileSource); boolean useHeadersInDisplayName = csvFileSource.useHeadersInDisplayName(); // @formatter:off return StreamSupport.stream(spliterator, false) .skip(csvFileSource.numLinesToSkip()) .map(record -> CsvArgumentsProvider.processCsvRecord( record, useHeadersInDisplayName) ) .onClose(() -> { try { reader.close(); } catch (Throwable throwable) { throw CsvArgumentsProvider.handleCsvException(throwable, csvFileSource); } }); // @formatter:on } private record CsvExceptionHandlingSpliterator(Spliterator delegate, CsvFileSource csvFileSource) implements Spliterator { static CsvExceptionHandlingSpliterator delegatingTo(Spliterator delegate, CsvFileSource csvFileSource) { return new CsvExceptionHandlingSpliterator<>(delegate, csvFileSource); } @Override public boolean tryAdvance(final Consumer action) { try { return delegate.tryAdvance(action); } catch (Throwable throwable) { throw CsvArgumentsProvider.handleCsvException(throwable, csvFileSource); } } @Override public Spliterator trySplit() { return delegate.trySplit(); } @Override public long estimateSize() { return delegate.estimateSize(); } @Override public int characteristics() { return delegate.characteristics(); } } @FunctionalInterface interface Source { InputStream open(ExtensionContext context); } interface InputStreamProvider { InputStream openClasspathResource(Class baseClass, String path); InputStream openFile(String path); default Source classpathResource(String path) { return context -> openClasspathResource(context.getRequiredTestClass(), path); } default Source file(String path) { return __ -> openFile(path); } } private static class DefaultInputStreamProvider implements InputStreamProvider { private static final DefaultInputStreamProvider INSTANCE = new DefaultInputStreamProvider(); @Override public InputStream openClasspathResource(Class baseClass, String path) { Preconditions.notBlank(path, () -> "Classpath resource [" + path + "] must not be null or blank"); //noinspection resource (closed elsewhere) InputStream inputStream = baseClass.getResourceAsStream(path); return Preconditions.notNull(inputStream, () -> "Classpath resource [" + path + "] does not exist"); } @Override public InputStream openFile(String path) { Preconditions.notBlank(path, () -> "File [" + path + "] must not be null or blank"); try { return Files.newInputStream(Path.of(path)); } catch (IOException e) { throw new JUnitException("File [" + path + "] could not be read", e); } } } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSource.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.params.ParameterizedInvocationConstants; /** * {@code @CsvFileSource} is a {@linkplain Repeatable repeatable} * {@link ArgumentsSource} which is used to load comma-separated value (CSV) * files from one or more classpath {@link #resources} or {@link #files}. * *

The CSV records parsed from these resources and files will be provided as * arguments to the annotated * {@link org.junit.jupiter.params.ParameterizedClass @ParameterizedClass} or * {@link org.junit.jupiter.params.ParameterizedTest @ParameterizedTest}. Note * that the first record may optionally be used to supply CSV headers (see * {@link #useHeadersInDisplayName}). * *

Any line beginning with a {@link #commentCharacter} * will be interpreted as a comment and will be ignored. * *

The column delimiter (which defaults to a comma ({@code ,})) can be customized * via either {@link #delimiter} or {@link #delimiterString}. * *

The line separator is detected automatically, meaning that any of * {@code "\r"}, {@code "\n"}, or {@code "\r\n"} is treated as a line separator. * *

In contrast to the default syntax used in {@code @CsvSource}, {@code @CsvFileSource} * uses a double quote ({@code "}) as its quote character by default, but this can * be changed via {@link #quoteCharacter}. An empty, quoted value ({@code ""}) * results in an empty {@link String} unless the {@link #emptyValue} attribute is * set; whereas, an entirely empty value is interpreted as a {@code null} * reference. By specifying one or more {@link #nullValues} a custom value can be * interpreted as a {@code null} reference (see the User Guide for an example). An * {@link org.junit.jupiter.params.converter.ArgumentConversionException * ArgumentConversionException} is thrown if the target type of a {@code null} * reference is a primitive type. * *

NOTE: An unquoted empty value will always be converted to a * {@code null} reference regardless of any custom values configured via the * {@link #nullValues} attribute. * *

Except within a quoted string, leading and trailing whitespace in a CSV * column is trimmed by default. This behavior can be changed by setting the * {@link #ignoreLeadingAndTrailingWhitespace} attribute to {@code true}. * *

Note that {@link #delimiter} (or {@link #delimiterString}), * {@link #quoteCharacter}, and {@link #commentCharacter} are treated as * control characters and must all be distinct. * *

Inheritance

* *

This annotation is {@linkplain Inherited inherited} within class hierarchies. * * @since 5.0 * @see CsvSource * @see org.junit.jupiter.params.provider.ArgumentsSource * @see org.junit.jupiter.params.ParameterizedClass * @see org.junit.jupiter.params.ParameterizedTest */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Repeatable(CsvFileSources.class) @API(status = STABLE, since = "5.7") @ArgumentsSource(CsvFileArgumentsProvider.class) @SuppressWarnings("exports") public @interface CsvFileSource { /** * The CSV classpath resources to use as the sources of arguments; must not * be empty unless {@link #files} is non-empty. */ String[] resources() default {}; /** * The CSV files to use as the sources of arguments; must not be empty * unless {@link #resources} is non-empty. */ String[] files() default {}; /** * The encoding to use when reading the CSV files; must be a valid charset. * *

Defaults to {@code "UTF-8"}. * * @see java.nio.charset.StandardCharsets */ String encoding() default "UTF-8"; /** * Configures whether the first CSV record should be treated as header names * for columns. * *

When set to {@code true}, the header names will be used in the * generated display name for each {@code @ParameterizedClass} or * {@code @ParameterizedTest} invocation. When using this feature, you must * ensure that the display name pattern for {@code @ParameterizedClass} or * {@code @ParameterizedTest} includes * {@value ParameterizedInvocationConstants#ARGUMENTS_PLACEHOLDER} instead of * {@value ParameterizedInvocationConstants#ARGUMENTS_WITH_NAMES_PLACEHOLDER} * as demonstrated in the example below. * *

Defaults to {@code false}. * *

Example

*
	 * {@literal @}ParameterizedTest(name = "[{index}] {arguments}")
	 * {@literal @}CsvFileSource(resources = "fruits.csv", useHeadersInDisplayName = true)
	 * void test(String fruit, int rank) {
	 *     // ...
	 * }
* * @since 5.8.2 */ @API(status = STABLE, since = "5.10") boolean useHeadersInDisplayName() default false; /** * The quote character to use for quoted strings. * *

Defaults to a double quote ({@code "}). * *

You may change the quote character to anything that makes sense for * your use case. * * @since 5.8.2 */ @API(status = STABLE, since = "5.10") char quoteCharacter() default '"'; /** * The column delimiter character to use when reading the CSV files. * *

This is an alternative to {@link #delimiterString} and cannot be * used in conjunction with {@link #delimiterString}. * *

Defaults implicitly to {@code ','}, if neither delimiter attribute is * explicitly set. */ char delimiter() default '\0'; /** * The column delimiter string to use when reading the CSV files. * *

This is an alternative to {@link #delimiter} and cannot be used in * conjunction with {@link #delimiter}. * *

Defaults implicitly to {@code ","}, if neither delimiter attribute is * explicitly set. * * @since 5.6 */ String delimiterString() default ""; /** * The number of lines to skip when reading the CSV files. * *

Typically used to skip header lines. * *

Defaults to {@code 0}. * * @since 5.1 */ int numLinesToSkip() default 0; /** * The empty value to use when reading the CSV files. * *

This value replaces quoted empty strings read from the input. * *

Defaults to {@code ""}. * * @since 5.5 */ String emptyValue() default ""; /** * A list of strings that should be interpreted as {@code null} references. * *

For example, you may wish for certain values such as {@code "N/A"} or * {@code "NIL"} to be converted to {@code null} references. * *

Please note that unquoted empty values will always be converted * to {@code null} references regardless of the value of this {@code nullValues} * attribute; whereas, a quoted empty string will be treated as an * {@link #emptyValue}. * *

Defaults to {@code {}}. * * @since 5.6 */ String[] nullValues() default {}; /** * The maximum number of characters allowed per CSV column. * *

Must be a positive number or {@code -1} to allow an unlimited number * of characters. * *

Defaults to {@code 4096}. * * @since 5.7 */ @API(status = STABLE, since = "5.10") int maxCharsPerColumn() default 4096; /** * Controls whether leading and trailing whitespace characters of unquoted * CSV columns should be ignored. * *

Whitespace refers to characters with Unicode code points less than * or equal to {@code U+0020}, as defined by {@link String#trim()}. * *

Defaults to {@code true}. * * @since 5.8 */ @API(status = STABLE, since = "5.10") boolean ignoreLeadingAndTrailingWhitespace() default true; /** * The character used to denote comments when reading the CSV files. * *

Any line that begins with this character will be treated as a comment * and ignored during parsing. Note that there is one exception to this rule: * if the comment character appears within a quoted field, it loses its * special meaning. * *

The comment character must be the first character on the line without * any leading whitespace. * *

Defaults to {@code '#'}. * * @since 6.0.1 */ @API(status = EXPERIMENTAL, since = "6.0.1") char commentCharacter() default '#'; } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSources.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @CsvFileSources} is a simple container for one or more * {@link CsvFileSource @CsvFileSource} annotations. * *

Note, however, that use of the {@code @CsvFileSources} container is completely * optional since {@code @CsvFileSource} is a {@linkplain java.lang.annotation.Repeatable * repeatable} annotation. * *

Inheritance

* *

This annotation is {@linkplain Inherited inherited} within class hierarchies. * * @since 5.11 * @see CsvFileSource * @see java.lang.annotation.Repeatable */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @API(status = STABLE, since = "5.11") public @interface CsvFileSources { /** * An array of one or more {@link CsvFileSource @CsvFileSource} * annotations. */ CsvFileSource[] value(); } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvParsingException.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static org.apiguardian.api.API.Status.STABLE; import java.io.Serial; import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; /** * Thrown if an error is encountered while parsing CSV input. * * @since 5.3 * @see CsvSource * @see CsvFileSource */ @API(status = STABLE, since = "5.7") public class CsvParsingException extends JUnitException { @Serial private static final long serialVersionUID = 1L; public CsvParsingException(String message) { super(message); } public CsvParsingException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static de.siegmar.fastcsv.reader.CommentStrategy.NONE; import static de.siegmar.fastcsv.reader.CommentStrategy.SKIP; import java.io.InputStream; import java.lang.annotation.Annotation; import java.nio.charset.Charset; import java.util.Set; import java.util.UUID; import java.util.stream.Stream; import de.siegmar.fastcsv.reader.CommentStrategy; import de.siegmar.fastcsv.reader.CsvCallbackHandler; import de.siegmar.fastcsv.reader.CsvReader; import de.siegmar.fastcsv.reader.CsvRecord; import de.siegmar.fastcsv.reader.CsvRecordHandler; import de.siegmar.fastcsv.reader.FieldMismatchStrategy; import de.siegmar.fastcsv.reader.FieldModifier; import de.siegmar.fastcsv.reader.NamedCsvRecordHandler; import org.junit.platform.commons.util.Preconditions; /** * @since 6.0 */ class CsvReaderFactory { private static final String DEFAULT_DELIMITER = ","; private static final char EMPTY_CHAR = '\0'; private static final boolean SKIP_EMPTY_LINES = true; private static final boolean TRIM_WHITESPACES_AROUND_QUOTES = true; private static final FieldMismatchStrategy ALLOW_EXTRA_FIELDS = FieldMismatchStrategy.IGNORE; private static final FieldMismatchStrategy ALLOW_MISSING_FIELDS = FieldMismatchStrategy.IGNORE; private static final boolean ALLOW_DUPLICATE_HEADER_FIELDS = true; private static final int MAX_FIELDS = 512; private static final int MAX_RECORD_SIZE = Integer.MAX_VALUE; static void validate(CsvSource csvSource) { validateMaxCharsPerColumn(csvSource.maxCharsPerColumn()); validateDelimiter(csvSource.delimiter(), csvSource.delimiterString(), csvSource); } static void validate(CsvFileSource csvFileSource) { validateMaxCharsPerColumn(csvFileSource.maxCharsPerColumn()); validateDelimiter(csvFileSource.delimiter(), csvFileSource.delimiterString(), csvFileSource); } private static void validateMaxCharsPerColumn(int maxCharsPerColumn) { Preconditions.condition(maxCharsPerColumn > 0 || maxCharsPerColumn == -1, () -> "maxCharsPerColumn must be a positive number or -1: " + maxCharsPerColumn); } private static void validateDelimiter(char delimiter, String delimiterString, Annotation annotation) { Preconditions.condition(delimiter == EMPTY_CHAR || delimiterString.isEmpty(), () -> "The delimiter and delimiterString attributes cannot be set simultaneously in " + annotation); } static CsvReader createReaderFor(CsvSource csvSource, String data) { String delimiter = selectDelimiter(csvSource.delimiter(), csvSource.delimiterString()); var commentStrategy = csvSource.textBlock().isEmpty() ? NONE : SKIP; // @formatter:off validateControlCharactersDiffer( delimiter, csvSource.quoteCharacter(), csvSource.commentCharacter(), commentStrategy); var builder = CsvReader.builder() .skipEmptyLines(SKIP_EMPTY_LINES) .trimWhitespacesAroundQuotes(TRIM_WHITESPACES_AROUND_QUOTES) .extraFieldStrategy(ALLOW_EXTRA_FIELDS) .missingFieldStrategy(ALLOW_MISSING_FIELDS) .fieldSeparator(delimiter) .quoteCharacter(csvSource.quoteCharacter()) .commentStrategy(commentStrategy) .commentCharacter(csvSource.commentCharacter()); var callbackHandler = createCallbackHandler( csvSource.emptyValue(), Set.of(csvSource.nullValues()), csvSource.ignoreLeadingAndTrailingWhitespace(), csvSource.maxCharsPerColumn(), csvSource.useHeadersInDisplayName() ); // @formatter:on return builder.build(callbackHandler, data); } static CsvReader createReaderFor(CsvFileSource csvFileSource, InputStream inputStream, Charset charset) { String delimiter = selectDelimiter(csvFileSource.delimiter(), csvFileSource.delimiterString()); var commentStrategy = SKIP; // @formatter:off validateControlCharactersDiffer( delimiter, csvFileSource.quoteCharacter(), csvFileSource.commentCharacter(), commentStrategy); var builder = CsvReader.builder() .skipEmptyLines(SKIP_EMPTY_LINES) .trimWhitespacesAroundQuotes(TRIM_WHITESPACES_AROUND_QUOTES) .extraFieldStrategy(ALLOW_EXTRA_FIELDS) .missingFieldStrategy(ALLOW_MISSING_FIELDS) .fieldSeparator(delimiter) .quoteCharacter(csvFileSource.quoteCharacter()) .commentStrategy(commentStrategy) .commentCharacter(csvFileSource.commentCharacter()); var callbackHandler = createCallbackHandler( csvFileSource.emptyValue(), Set.of(csvFileSource.nullValues()), csvFileSource.ignoreLeadingAndTrailingWhitespace(), csvFileSource.maxCharsPerColumn(), csvFileSource.useHeadersInDisplayName() ); // @formatter:on return builder.build(callbackHandler, inputStream, charset); } private static String selectDelimiter(char delimiter, String delimiterString) { if (delimiter != EMPTY_CHAR) { return String.valueOf(delimiter); } if (!delimiterString.isEmpty()) { return delimiterString; } return DEFAULT_DELIMITER; } private static void validateControlCharactersDiffer(String delimiter, char quoteCharacter, char commentCharacter, CommentStrategy commentStrategy) { if (commentStrategy == NONE) { Preconditions.condition(stringValuesUnique(delimiter, quoteCharacter), () -> ("delimiter or delimiterString: '%s' and quoteCharacter: '%s' " + // "must differ").formatted(delimiter, quoteCharacter)); } else { Preconditions.condition(stringValuesUnique(delimiter, quoteCharacter, commentCharacter), () -> ("delimiter or delimiterString: '%s', quoteCharacter: '%s', and commentCharacter: '%s' " + // "must all differ").formatted(delimiter, quoteCharacter, commentCharacter)); } } private static boolean stringValuesUnique(Object... values) { long uniqueCount = Stream.of(values).map(String::valueOf).distinct().count(); return uniqueCount == values.length; } private static CsvCallbackHandler createCallbackHandler(String emptyValue, Set nullValues, boolean ignoreLeadingAndTrailingWhitespaces, int maxCharsPerColumn, boolean useHeadersInDisplayName) { int maxFieldSize = maxCharsPerColumn == -1 ? Integer.MAX_VALUE : maxCharsPerColumn; FieldModifier modifier = new DefaultFieldModifier(emptyValue, nullValues, ignoreLeadingAndTrailingWhitespaces); // @formatter:off if (useHeadersInDisplayName) { return NamedCsvRecordHandler.builder() .allowDuplicateHeaderFields(ALLOW_DUPLICATE_HEADER_FIELDS) .maxFields(MAX_FIELDS) .maxRecordSize(MAX_RECORD_SIZE) .maxFieldSize(maxFieldSize) .fieldModifier(modifier) .build(); } return CsvRecordHandler.builder() .maxFields(MAX_FIELDS) .maxRecordSize(MAX_RECORD_SIZE) .maxFieldSize(maxFieldSize) .fieldModifier(modifier) .build(); // @formatter:on } record DefaultFieldModifier(String emptyValue, Set nullValues, boolean ignoreLeadingAndTrailingWhitespaces) implements FieldModifier { /** * Represents a {@code null} value and serves as a workaround since FastCSV * does not allow the modified field value to be {@code null}. * *

The marker is generated with a unique ID to ensure it cannot conflict * with actual CSV content. */ static final String NULL_MARKER = "".formatted(UUID.randomUUID()); @Override public String modify(long unusedStartingLineNumber, int unusedFieldIdx, boolean quoted, String field) { if (quoted && field.isEmpty() && !emptyValue.isEmpty()) { return emptyValue; } if (!quoted && field.isBlank()) { return NULL_MARKER; } String modifiedField = (!quoted && ignoreLeadingAndTrailingWhitespaces) ? field.trim() : field; if (nullValues.contains(modifiedField)) { return NULL_MARKER; } return modifiedField; } } private CsvReaderFactory() { } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSource.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.params.ParameterizedInvocationConstants; /** * {@code @CsvSource} is a {@linkplain Repeatable repeatable} * {@link ArgumentsSource} which reads comma-separated values (CSV) from one * or more CSV records supplied via the {@link #value} attribute or * {@link #textBlock} attribute. * *

The supplied values will be provided as arguments to the annotated * {@link org.junit.jupiter.params.ParameterizedClass @ParameterizedClass} or * {@link org.junit.jupiter.params.ParameterizedTest @ParameterizedTest}. * *

The column delimiter (which defaults to a comma ({@code ,})) can be customized * via either {@link #delimiter} or {@link #delimiterString}. * *

By default, {@code @CsvSource} uses a single quote ({@code '}) as its quote * character, but this can be changed via {@link #quoteCharacter}. See the * {@code 'lemon, lime'} examples in the documentation for the {@link #value} * and {@link #textBlock} attributes. An empty, quoted value ({@code ''}) results * in an empty {@link String} unless the {@link #emptyValue} attribute is set; * whereas, an entirely empty value is interpreted as a {@code null} reference. * By specifying one or more {@link #nullValues} a custom value can be interpreted * as a {@code null} reference (see the User Guide for an example). An * {@link org.junit.jupiter.params.converter.ArgumentConversionException * ArgumentConversionException} is thrown if the target type of a {@code null} * reference is a primitive type. * *

NOTE: An unquoted empty value will always be converted to a * {@code null} reference regardless of any custom values configured via the * {@link #nullValues} attribute. * *

Except within a quoted string, leading and trailing whitespace in a CSV * column is trimmed by default. This behavior can be changed by setting the * {@link #ignoreLeadingAndTrailingWhitespace} attribute to {@code true}. * *

In general, CSV records should not contain explicit newlines ({@code \n}) * unless they are placed within quoted strings. Note that CSV records supplied * via {@link #textBlock} will implicitly contain newlines at the end of each * physical line within the text block. Thus, if a CSV column wraps across a * new line in a text block, the column must be a quoted string. * *

Note that {@link #delimiter} (or {@link #delimiterString}), * {@link #quoteCharacter}, and {@link #commentCharacter} (when * {@link #textBlock} is used) are treated as control characters. * *

    *
  • {@link #delimiter} and {@link #quoteCharacter} must always be distinct.
  • *
  • {@link #commentCharacter} must be distinct from the others only when * {@link #textBlock} is used.
  • *
* *

Inheritance

* *

This annotation is {@linkplain Inherited inherited} within class hierarchies. * * @since 5.0 * @see CsvFileSource * @see org.junit.jupiter.params.provider.ArgumentsSource * @see org.junit.jupiter.params.ParameterizedClass * @see org.junit.jupiter.params.ParameterizedTest */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Repeatable(CsvSources.class) @Documented @Inherited @API(status = STABLE, since = "5.7") @ArgumentsSource(CsvArgumentsProvider.class) @SuppressWarnings("exports") public @interface CsvSource { /** * The CSV records to use as the source of arguments; must not be empty. * *

Defaults to an empty array. You therefore must supply CSV content * via this attribute or the {@link #textBlock} attribute. * *

Each value corresponds to a record in a CSV file and will be split using * the specified {@link #delimiter} or {@link #delimiterString}. Note that * the first value may optionally be used to supply CSV headers (see * {@link #useHeadersInDisplayName}). Moreover, each specified value must * not be blank. * *

If text block syntax is supported by your programming language, * you may find it more convenient to declare your CSV content via the * {@link #textBlock} attribute. * *

Example

*
	 * {@literal @}ParameterizedTest
	 * {@literal @}CsvSource({
	 *     "apple,         1",
	 *     "banana,        2",
	 *     "'lemon, lime', 0xF1",
	 *     "strawberry,    700_000"
	 * })
	 * void test(String fruit, int rank) {
	 *     // ...
	 * }
* * @see #textBlock */ String[] value() default {}; /** * The CSV records to use as the source of arguments, supplied as a single * text block; must not be empty. * *

Defaults to an empty string. You therefore must supply CSV content * via this attribute or the {@link #value} attribute. * *

Text block syntax is supported by various languages on the JVM * including Java SE. If text blocks are not supported, you * should declare your CSV content via the {@link #value} attribute. * *

Each record in the text block corresponds to a record in a CSV file and will * be split using the specified {@link #delimiter} or {@link #delimiterString}. * Note that the first record may optionally be used to supply CSV headers (see * {@link #useHeadersInDisplayName}). * *

In contrast to CSV records supplied via {@link #value}, a text block * can contain comments. Any line beginning with a {@link #commentCharacter} * will be treated as a comment and ignored. Note that there is one exception * to this rule: if the comment character appears within a quoted field, * it loses its special meaning. * *

The comment character must be the first character on the line without * any leading whitespace. It is therefore recommended that the closing text block * delimiter {@code """} be placed either at the end of the last line of * input or on the following line, vertically aligned with the rest of the * input (as can be seen in the example below). * *

Java's text block * feature automatically removes incidental whitespace when the code * is compiled. However, other JVM languages such as Groovy and Kotlin do not. * Thus, if you are using a programming language other than Java and your text * block contains comments or new lines within quoted strings, you will need * to ensure that there is no leading whitespace within your text block. * *

Example

*
	 * {@literal @}ParameterizedTest
	 * {@literal @}CsvSource(quoteCharacter = '"', textBlock = """
	 *     # FRUIT,       RANK
	 *     apple,         1
	 *     banana,        2
	 *     "lemon, lime", 0xF1
	 *     strawberry,    700_000
	 *     """)
	 * void test(String fruit, int rank) {
	 *     // ...
	 * }
* * @since 5.8.1 * @see #value * @see #quoteCharacter */ @API(status = STABLE, since = "5.10") String textBlock() default ""; /** * Configures whether the first CSV record should be treated as header names * for columns. * *

When set to {@code true}, the header names will be used in the * generated display name for each {@code @ParameterizedClass} or * {@code @ParameterizedTest} invocation. When using this feature, you must * ensure that the display name pattern for {@code @ParameterizedClass} or * {@code @ParameterizedTest} includes * {@value ParameterizedInvocationConstants#ARGUMENTS_PLACEHOLDER} instead of * {@value ParameterizedInvocationConstants#ARGUMENTS_WITH_NAMES_PLACEHOLDER} * as demonstrated in the example below. * *

Defaults to {@code false}. * *

Example

*
	 * {@literal @}ParameterizedTest(name = "[{index}] {arguments}")
	 * {@literal @}CsvSource(useHeadersInDisplayName = true, textBlock = """
	 *     FRUIT,         RANK
	 *     apple,         1
	 *     banana,        2
	 *     'lemon, lime', 0xF1
	 *     strawberry,    700_000
	 *     """)
	 * void test(String fruit, int rank) {
	 *     // ...
	 * }
* * @since 5.8.2 */ @API(status = STABLE, since = "5.10") boolean useHeadersInDisplayName() default false; /** * The quote character to use for quoted strings. * *

Defaults to a single quote ({@code '}). * *

You may change the quote character to anything that makes sense for * your use case; however, the primary use case is to allow you to use double * quotes in {@link #textBlock}. * * @since 5.8.2 * @see #textBlock */ @API(status = STABLE, since = "5.10") char quoteCharacter() default '\''; /** * The column delimiter character to use when reading the {@linkplain #value records}. * *

This is an alternative to {@link #delimiterString} and cannot be * used in conjunction with {@link #delimiterString}. * *

Defaults implicitly to {@code ','}, if neither delimiter attribute is * explicitly set. */ char delimiter() default '\0'; /** * The column delimiter string to use when reading the {@linkplain #value records}. * *

This is an alternative to {@link #delimiter} and cannot be used in * conjunction with {@link #delimiter}. * *

Defaults implicitly to {@code ","}, if neither delimiter attribute is * explicitly set. * * @since 5.6 */ String delimiterString() default ""; /** * The empty value to use when reading the {@linkplain #value records}. * *

This value replaces quoted empty strings read from the input. * *

Defaults to {@code ""}. * * @since 5.5 */ String emptyValue() default ""; /** * A list of strings that should be interpreted as {@code null} references. * *

For example, you may wish for certain values such as {@code "N/A"} or * {@code "NIL"} to be converted to {@code null} references. * *

Please note that unquoted empty values will always be converted * to {@code null} references regardless of the value of this {@code nullValues} * attribute; whereas, a quoted empty string will be treated as an * {@link #emptyValue}. * *

Defaults to {@code {}}. * * @since 5.6 */ String[] nullValues() default {}; /** * The maximum number of characters allowed per CSV column. * *

Must be a positive number or {@code -1} to allow an unlimited number * of characters. * *

Defaults to {@code 4096}. * * @since 5.7 */ @API(status = STABLE, since = "5.10") int maxCharsPerColumn() default 4096; /** * Controls whether leading and trailing whitespace characters of unquoted * CSV columns should be ignored. * *

Whitespace refers to characters with Unicode code points less than * or equal to {@code U+0020}, as defined by {@link String#trim()}. * *

Defaults to {@code true}. * * @since 5.8 */ @API(status = STABLE, since = "5.10") boolean ignoreLeadingAndTrailingWhitespace() default true; /** * The character used to denote comments in a {@linkplain #textBlock text block}. * *

Any line that begins with this character will be treated as a comment * and ignored during parsing. Note that there is one exception to this rule: * if the comment character appears within a quoted field, it loses its * special meaning. * *

The comment character must be the first character on the line without * any leading whitespace. * *

Defaults to {@code '#'}. * * @since 6.0.1 */ @API(status = EXPERIMENTAL, since = "6.0.1") char commentCharacter() default '#'; } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSources.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @CsvSources} is a simple container for one or more * {@link CsvSource @CsvSource} annotations. * *

Note, however, that use of the {@code @CsvSources} container is completely * optional since {@code @CsvSource} is a {@linkplain java.lang.annotation.Repeatable * repeatable} annotation. * *

Inheritance

* *

This annotation is {@linkplain Inherited inherited} within class hierarchies. * * @since 5.11 * @see CsvSource * @see java.lang.annotation.Repeatable */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @API(status = STABLE, since = "5.11") public @interface CsvSources { /** * An array of one or more {@link CsvSource @CsvSource} * annotations. */ CsvSource[] value(); } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EmptyArgumentsProvider.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.junit.platform.commons.util.ReflectionUtils.newInstance; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.NavigableMap; import java.util.NavigableSet; import java.util.NoSuchElementException; import java.util.Optional; import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; import java.util.function.Supplier; import java.util.stream.Stream; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.support.ParameterDeclaration; import org.junit.jupiter.params.support.ParameterDeclarations; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.Preconditions; /** * @since 5.4 * @see EmptySource */ class EmptyArgumentsProvider extends AnnotationBasedArgumentsProvider { @Override protected Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context, EmptySource annotation) { var explicitType = annotation.type(); if (explicitType.equals(Derived.class)) { Optional firstParameter = parameters.getFirst(); Preconditions.condition(firstParameter.isPresent(), () -> "@EmptySource cannot provide an empty argument to %s: no formal parameters declared.".formatted( parameters.getSourceElementDescription())); return provideEmptyArgument(firstParameter.get().getParameterType(), () -> "to " + parameters.getSourceElementDescription()); } return provideEmptyArgument(explicitType, () -> "for 'type'"); } private static Stream provideEmptyArgument(Class parameterType, Supplier errorDetailsSupplier) { if (String.class.equals(parameterType)) { return Stream.of(arguments("")); } if (Iterable.class.equals(parameterType)) { return Stream.of(arguments(EmptyIterable.INSTANCE)); } if (Iterator.class.equals(parameterType)) { return Stream.of(arguments(EmptyIterator.INSTANCE)); } if (ListIterator.class.equals(parameterType)) { return Stream.of(arguments(EmptyListIterator.INSTANCE)); } if (Collection.class.equals(parameterType)) { return Stream.of(arguments(Collections.emptySet())); } if (List.class.equals(parameterType)) { return Stream.of(arguments(Collections.emptyList())); } if (Set.class.equals(parameterType)) { return Stream.of(arguments(Collections.emptySet())); } if (SortedSet.class.equals(parameterType)) { return Stream.of(arguments(Collections.emptySortedSet())); } if (NavigableSet.class.equals(parameterType)) { return Stream.of(arguments(Collections.emptyNavigableSet())); } if (Map.class.equals(parameterType)) { return Stream.of(arguments(Collections.emptyMap())); } if (SortedMap.class.equals(parameterType)) { return Stream.of(arguments(Collections.emptySortedMap())); } if (NavigableMap.class.equals(parameterType)) { return Stream.of(arguments(Collections.emptyNavigableMap())); } if (Collection.class.isAssignableFrom(parameterType) || Map.class.isAssignableFrom(parameterType)) { Optional> defaultConstructor = getDefaultConstructor(parameterType); if (defaultConstructor.isPresent()) { return Stream.of(arguments(newInstance(defaultConstructor.get()))); } } if (parameterType.isArray()) { Object array = Array.newInstance(parameterType.getComponentType(), 0); return Stream.of(arguments(array)); } // else throw new PreconditionViolationException( "@EmptySource cannot provide an empty argument %s: [%s] is not a supported type.".formatted( errorDetailsSupplier.get(), parameterType.getName())); } private static Optional> getDefaultConstructor(Class clazz) { try { return Optional.of(clazz.getConstructor()); } catch (NoSuchMethodException e) { return Optional.empty(); } } /** * @since 6.1 */ private static class EmptyIterable implements Iterable { private static final EmptyIterable INSTANCE = new EmptyIterable<>(); @Override @SuppressWarnings("unchecked") public Iterator iterator() { return (Iterator) EmptyIterator.INSTANCE; } @Override public String toString() { return "[]"; } } /** * @since 6.1 */ private static sealed class EmptyIterator implements Iterator permits EmptyListIterator { private static final EmptyIterator INSTANCE = new EmptyIterator<>(); @Override public boolean hasNext() { return false; } @Override public E next() { throw new NoSuchElementException(); } @Override public void remove() { throw new UnsupportedOperationException(); } @Override public String toString() { return "[]"; } } /** * @since 6.1 */ private static final class EmptyListIterator extends EmptyIterator implements ListIterator { private static final EmptyListIterator INSTANCE = new EmptyListIterator<>(); @Override public boolean hasPrevious() { return false; } @Override public E previous() { throw new NoSuchElementException(); } @Override public int nextIndex() { return 0; } @Override public int previousIndex() { return -1; } @Override public void set(E e) { throw new UnsupportedOperationException(); } @Override public void add(E e) { throw new UnsupportedOperationException(); } } static final class Derived { private Derived() { throw new JUnitException("Must not be instantiated"); } } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EmptySource.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.jupiter.params.converter.ArgumentConverter; /** * {@code @EmptySource} is an {@link ArgumentsSource} which provides a single * empty argument to the annotated {@code @ParameterizedClass} * or {@code @ParameterizedTest}. * *

Supported Parameter Types

* *

This argument source will only provide an empty argument for the following * parameter types. * *

    *
  • {@link java.lang.String}
  • *
  • {@link java.lang.Iterable}
  • *
  • {@link java.util.Iterator}
  • *
  • {@link java.util.ListIterator}
  • *
  • {@link java.util.Collection} and concrete subtypes with a public no-arg constructor
  • *
  • {@link java.util.List}
  • *
  • {@link java.util.Set}
  • *
  • {@link java.util.SortedSet}
  • *
  • {@link java.util.NavigableSet}
  • *
  • {@link java.util.Map} and concrete subtypes with a public no-arg constructor
  • *
  • {@link java.util.SortedMap}
  • *
  • {@link java.util.NavigableMap}
  • *
  • primitive arrays — for example {@code int[]}, {@code char[][]}, etc.
  • *
  • object arrays — for example {@code String[]}, {@code Integer[][]}, etc.
  • *
* *

Unless the {@link #type()} is set, the parameter type is derived from * the first (and only) parameter of the annotated {@code @ParameterizedClass} * or {@code @ParameterizedTest}. * *

Inheritance

* *

This annotation is {@linkplain Inherited inherited} within class hierarchies. * * @since 5.4 * @see org.junit.jupiter.params.provider.ArgumentsSource * @see org.junit.jupiter.params.ParameterizedClass * @see org.junit.jupiter.params.ParameterizedTest * @see NullSource * @see NullAndEmptySource */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @API(status = STABLE, since = "5.7") @ArgumentsSource(EmptyArgumentsProvider.class) @SuppressWarnings("exports") public @interface EmptySource { /** * The type of the empty argument to provide. * *

Must be one of the * supported parameter types. * *

Setting this attribute is usually not necessary because the type will * be derived from the first (and only) parameter. Setting it explicitly * allows using an {@link ArgumentConverter} to convert from the specified * type to the actual parameter type. */ @API(status = EXPERIMENTAL, since = "6.1") Class type() default EmptyArgumentsProvider.Derived.class; } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumArgumentsProvider.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static java.util.Arrays.stream; import static java.util.stream.Collectors.toSet; import java.util.EnumSet; import java.util.Set; import java.util.stream.Stream; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.support.ParameterDeclaration; import org.junit.jupiter.params.support.ParameterDeclarations; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.Preconditions; /** * @since 5.0 */ class EnumArgumentsProvider extends AnnotationBasedArgumentsProvider { @Override protected Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context, EnumSource enumSource) { Set> constants = getEnumConstants(parameters, enumSource); EnumSource.Mode mode = enumSource.mode(); String[] declaredConstantNames = enumSource.names(); if (declaredConstantNames.length > 0) { Set uniqueNames = stream(declaredConstantNames).collect(toSet()); Preconditions.condition(uniqueNames.size() == declaredConstantNames.length, () -> "Duplicate enum constant name(s) found in " + enumSource); mode.validate(enumSource, constants, uniqueNames); constants.removeIf(constant -> !mode.select(constant, uniqueNames)); } return constants.stream().map(Arguments::of); } private > Set getEnumConstants(ParameterDeclarations parameters, EnumSource enumSource) { Class enumClass = determineEnumClass(parameters, enumSource); E[] constants = enumClass.getEnumConstants(); if (constants.length == 0) { Preconditions.condition(enumSource.from().isEmpty() && enumSource.to().isEmpty(), "No enum constant in " + enumClass.getSimpleName() + ", but 'from' or 'to' is not empty."); return EnumSet.noneOf(enumClass); } E from = enumSource.from().isEmpty() ? constants[0] : Enum.valueOf(enumClass, enumSource.from()); E to = enumSource.to().isEmpty() ? constants[constants.length - 1] : Enum.valueOf(enumClass, enumSource.to()); Preconditions.condition(from.compareTo(to) <= 0, () -> "Invalid enum range: 'from' (%s) must come before 'to' (%s) in the natural order of enum constants.".formatted( from, to)); return EnumSet.range(from, to); } @SuppressWarnings({ "unchecked", "rawtypes" }) private > Class determineEnumClass(ParameterDeclarations parameters, EnumSource enumSource) { Class enumClass = enumSource.value(); if (enumClass.equals(NullEnum.class)) { enumClass = parameters.getFirst() // .map(ParameterDeclaration::getParameterType).map(parameterType -> { Preconditions.condition(Enum.class.isAssignableFrom(parameterType), () -> "First parameter must reference an Enum type (alternatively, use the annotation's 'value' attribute to specify the type explicitly): " + parameters.getSourceElementDescription()); return (Class) parameterType; }).orElseThrow( () -> new PreconditionViolationException("There must be at least one declared parameter for " + parameters.getSourceElementDescription())); } return enumClass; } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSource.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static java.util.stream.Collectors.toSet; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.Set; import java.util.function.BiPredicate; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import org.apiguardian.api.API; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.Preconditions; /** * {@code @EnumSource} is a {@linkplain Repeatable repeatable} * {@link ArgumentsSource} for constants of an {@link Enum}. * *

The enum constants will be provided as arguments to the annotated * {@code @ParameterizedClass} or {@code @ParameterizedTest}. * *

The enum type can be specified explicitly using the {@link #value} * attribute. Otherwise, the declared type of the first parameter of the * {@code @ParameterizedClass} or {@code @ParameterizedTest} is used. * *

The set of enum constants can be restricted via the {@link #names}, * {@link #from}, {@link #to} and {@link #mode} attributes. * *

Inheritance

* *

This annotation is {@linkplain Inherited inherited} within class hierarchies. * * @since 5.0 * @see org.junit.jupiter.params.provider.ArgumentsSource * @see org.junit.jupiter.params.ParameterizedClass * @see org.junit.jupiter.params.ParameterizedTest */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Repeatable(EnumSources.class) @API(status = STABLE, since = "5.7") @ArgumentsSource(EnumArgumentsProvider.class) @SuppressWarnings("exports") public @interface EnumSource { /** * The enum type that serves as the source of the enum constants. * *

If this attribute is not set explicitly, the declared type of the * first parameter of the {@code @ParameterizedTest} method is used. * * @see #names * @see #from * @see #to * @see #mode */ Class> value() default NullEnum.class; /** * The names of enum constants to provide, or regular expressions to select * the names of enum constants to provide. * *

If no names or regular expressions are specified, and neither {@link #from} * nor {@link #to} are specified, all enum constants declared in the specified * {@linkplain #value enum type} will be provided. * *

If {@link #from} or {@link #to} are specified, the elements in names must * fall within the range defined by {@link #from} and {@link #to}. * *

The {@link #mode} determines how the names are interpreted. * * @see #value * @see #from * @see #to * @see #mode */ String[] names() default {}; /** * The starting enum constant of the range to be included. * *

Defaults to an empty string, where the range starts from the first enum * constant of the specified {@linkplain #value enum type}. * * @see #value * @see #names * @see #to * @see #mode * * @since 5.12 */ @API(status = MAINTAINED, since = "5.13.3") String from() default ""; /** * The ending enum constant of the range to be included. * *

Defaults to an empty string, where the range ends at the last enum * constant of the specified {@linkplain #value enum type}. * * @see #value * @see #names * @see #from * @see #mode * * @since 5.12 */ @API(status = MAINTAINED, since = "5.13.3") String to() default ""; /** * The enum constant selection mode. * *

The mode only applies to the {@link #names} attribute and does not change * the behavior of {@link #from} and {@link #to}, which always define a range * based on the natural order of the enum constants. * *

Defaults to {@link Mode#INCLUDE INCLUDE}. * * @see Mode#INCLUDE * @see Mode#EXCLUDE * @see Mode#MATCH_ALL * @see Mode#MATCH_ANY * @see Mode#MATCH_NONE * @see #names * @see #from * @see #to */ Mode mode() default Mode.INCLUDE; /** * Enumeration of modes for selecting enum constants by name. */ enum Mode { /** * Select only those enum constants whose names are supplied via the * {@link EnumSource#names} attribute. */ INCLUDE(Mode::validateNames, (name, names) -> names.contains(name)), /** * Select all declared enum constants except those supplied via the * {@link EnumSource#names} attribute. */ EXCLUDE(Mode::validateNames, (name, names) -> !names.contains(name)), /** * Select only those enum constants whose names match all patterns supplied * via the {@link EnumSource#names} attribute. * * @see java.util.stream.Stream#allMatch(java.util.function.Predicate) */ MATCH_ALL(Mode::validatePatterns, (name, patterns) -> patterns.stream().allMatch(name::matches)), /** * Select only those enum constants whose names match any pattern supplied * via the {@link EnumSource#names} attribute. * * @see java.util.stream.Stream#anyMatch(java.util.function.Predicate) */ MATCH_ANY(Mode::validatePatterns, (name, patterns) -> patterns.stream().anyMatch(name::matches)), /** * Select only those enum constants whose names match none of the patterns supplied * via the {@link EnumSource#names} attribute. * * @since 5.9 * @see java.util.stream.Stream#noneMatch(java.util.function.Predicate) */ @API(status = MAINTAINED, since = "5.13.3") MATCH_NONE(Mode::validatePatterns, (name, patterns) -> patterns.stream().noneMatch(name::matches)); private final Validator validator; private final BiPredicate> selector; Mode(Validator validator, BiPredicate> selector) { this.validator = validator; this.selector = selector; } void validate(EnumSource enumSource, Set> constants, Set names) { Preconditions.notNull(enumSource, "EnumSource must not be null"); Preconditions.notNull(names, "names must not be null"); validator.validate(enumSource, constants, names); } boolean select(Enum constant, Set names) { Preconditions.notNull(constant, "Enum constant must not be null"); Preconditions.notNull(names, "names must not be null"); return selector.test(constant.name(), names); } private static void validateNames(EnumSource enumSource, Set> constants, Set names) { Set allNames = constants.stream().map(Enum::name).collect(toSet()); Preconditions.condition(allNames.containsAll(names), () -> "Invalid enum constant name(s) in " + enumSource + ". Valid names include: " + allNames); } private static void validatePatterns(EnumSource enumSource, Set> constants, Set names) { try { names.forEach(Pattern::compile); } catch (PatternSyntaxException e) { throw new PreconditionViolationException( "Pattern compilation failed for a regular expression supplied in " + enumSource, e); } } private interface Validator { void validate(EnumSource enumSource, Set> constants, Set names); } } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSources.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @EnumSources} is a simple container for one or more * {@link EnumSource @EnumSource} annotations. * *

Note, however, that use of the {@code @EnumSources} container is completely * optional since {@code @EnumSource} is a {@linkplain java.lang.annotation.Repeatable * repeatable} annotation. * *

Inheritance

* *

This annotation is {@linkplain Inherited inherited} within class hierarchies. * * @since 5.11 * @see EnumSource * @see java.lang.annotation.Repeatable */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @API(status = STABLE, since = "5.11") public @interface EnumSources { /** * An array of one or more {@link EnumSource @EnumSource} * annotations. */ EnumSource[] value(); } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldArgumentsProvider.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static java.util.Arrays.stream; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Iterator; import java.util.Optional; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.BaseStream; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.support.ParameterDeclarations; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.support.ModifierSupport; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.ClassLoaderUtils; import org.junit.platform.commons.util.CollectionUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode; /** * {@link ArgumentsProvider} for {@link FieldSource @FieldSource}. * * @since 5.11 */ class FieldArgumentsProvider extends AnnotationBasedArgumentsProvider { @Override protected Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context, FieldSource fieldSource) { Class testClass = context.getRequiredTestClass(); Object testInstance = context.getTestInstance().orElse(null); String[] fieldNames = fieldSource.value(); if (fieldNames.length == 0) { Optional testMethod = context.getTestMethod(); Preconditions.condition(testMethod.isPresent(), "You must specify a field name when using @FieldSource with @ParameterizedClass"); fieldNames = new String[] { testMethod.get().getName() }; } // @formatter:off return stream(fieldNames) .map(fieldName -> findField(testClass, fieldName)) .map(field -> validateField(field, testInstance)) .map(field -> readField(field, testInstance)) .flatMap(fieldValue -> { if (fieldValue instanceof Supplier supplier) { fieldValue = supplier.get(); } return CollectionUtils.toStream(fieldValue); }) .map(ArgumentsUtils::toArguments); // @formatter:on } // package-private for testing static Field findField(Class testClass, String fieldName) { Preconditions.notBlank(fieldName, "Field name must not be blank"); fieldName = fieldName.strip(); Class clazz = testClass; if (fieldName.contains("#") || fieldName.contains(".")) { String[] fieldParts = ReflectionUtils.parseFullyQualifiedFieldName(fieldName); String className = fieldParts[0]; fieldName = fieldParts[1]; ClassLoader classLoader = ClassLoaderUtils.getClassLoader(testClass); clazz = ReflectionUtils.loadRequiredClass(className, classLoader); } Class resolvedClass = clazz; String resolvedFieldName = fieldName; Predicate nameMatches = field -> field.getName().equals(resolvedFieldName); Field field = ReflectionUtils.streamFields(resolvedClass, nameMatches, HierarchyTraversalMode.BOTTOM_UP)// .findFirst()// .orElse(null); return Preconditions.notNull(field, () -> "Could not find field named [%s] in class [%s]".formatted(resolvedFieldName, resolvedClass.getName())); } private static Field validateField(Field field, @Nullable Object testInstance) { Preconditions.condition(field.getDeclaringClass().isInstance(testInstance) || ModifierSupport.isStatic(field), () -> """ Field '%s' must be static: local @FieldSource fields must be static \ unless the PER_CLASS @TestInstance lifecycle mode is used; \ external @FieldSource fields must always be static.""".formatted(field.toGenericString())); return field; } private static Object readField(Field field, @Nullable Object testInstance) { Object value = ReflectionSupport.tryToReadFieldValue(field, testInstance).getOrThrow( cause -> new JUnitException("Could not read field [%s]".formatted(field.getName()), cause)); String fieldName = field.getName(); String declaringClass = field.getDeclaringClass().getName(); Preconditions.notNull(value, () -> "The value of field [%s] in class [%s] must not be null".formatted(fieldName, declaringClass)); Preconditions.condition(!(value instanceof BaseStream), () -> "The value of field [%s] in class [%s] must not be a stream".formatted(fieldName, declaringClass)); Preconditions.condition(!(value instanceof Iterator), () -> "The value of field [%s] in class [%s] must not be an Iterator".formatted(fieldName, declaringClass)); Preconditions.condition(isConvertibleToStream(field, value), () -> "The value of field [%s] in class [%s] must be convertible to a Stream".formatted(fieldName, declaringClass)); return value; } /** * Determine if the supplied value can be converted into a {@code Stream} or * if the declared type of the supplied field is a {@link Supplier} of a type * that can be converted into a {@code Stream}. */ private static boolean isConvertibleToStream(Field field, Object value) { // Check actual value type. if (CollectionUtils.isConvertibleToStream(value.getClass())) { return true; } // Check declared type T of Supplier. if (Supplier.class.isAssignableFrom(field.getType())) { Type genericType = field.getGenericType(); if (genericType instanceof ParameterizedType parameterizedType) { Type[] typeArguments = parameterizedType.getActualTypeArguments(); if (typeArguments.length == 1) { Type type = typeArguments[0]; // Handle cases such as Supplier if (type instanceof Class clazz) { return CollectionUtils.isConvertibleToStream(clazz); } // Handle cases such as Supplier> if (type instanceof ParameterizedType innerParameterizedType) { Type rawType = innerParameterizedType.getRawType(); if (rawType instanceof Class clazz) { return CollectionUtils.isConvertibleToStream(clazz); } } } } } return false; } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSource.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static org.apiguardian.api.API.Status.MAINTAINED; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @FieldSource} is a {@linkplain Repeatable repeatable} * {@link ArgumentsSource} which provides access to values of * {@linkplain #value() fields} of the class in which this annotation is declared * or from static fields in external classes referenced by fully qualified * field name. * *

Each field must be able to supply a stream of arguments, * and each set of "arguments" within the "stream" will be provided as the physical * arguments for individual invocations of the annotated * {@link org.junit.jupiter.params.ParameterizedClass @ParameterizedClass} or * {@link org.junit.jupiter.params.ParameterizedTest @ParameterizedTest}. * *

In this context, a "stream" is anything that JUnit can reliably convert to * a {@link java.util.stream.Stream Stream}; however, the actual concrete field * type can take on many forms. Generally speaking this translates to a * {@link java.util.Collection Collection}, an {@link Iterable}, a * {@link java.util.function.Supplier Supplier} of a stream * ({@link java.util.stream.Stream Stream}, * {@link java.util.stream.DoubleStream DoubleStream}, * {@link java.util.stream.LongStream LongStream}, or * {@link java.util.stream.IntStream IntStream}), a {@code Supplier} of an * {@link java.util.Iterator Iterator}, an array of objects or primitives, or * any type that provides an {@link java.util.Iterator Iterator}-returning * {@code iterator()} method (such as, for example, a * {@code kotlin.sequences.Sequence}). Each set of "arguments" within the * "stream" can be supplied as an instance of {@link Arguments}, an array of * objects (for example, {@code Object[]}, {@code String[]}, etc.), or a single * value if the parameterized class or test accepts a single argument. * *

In contrast to the supported return types for {@link MethodSource @MethodSource} * factory methods, the value of a {@code @FieldSource} field cannot be an instance of * {@link java.util.stream.Stream Stream}, * {@link java.util.stream.DoubleStream DoubleStream}, * {@link java.util.stream.LongStream LongStream}, * {@link java.util.stream.IntStream IntStream}, or * {@link java.util.Iterator Iterator}, since the values of such types are * consumed the first time they are processed. However, if you wish to * use one of these types, you can wrap it in a {@code Supplier} — for * example, {@code Supplier}. * *

If the {@code Supplier} return type is {@code Stream} or * one of the primitive streams, JUnit will properly close it by calling * {@link java.util.stream.BaseStream#close() BaseStream.close()}, * making it safe to use a resource such as * {@link java.nio.file.Files#lines(java.nio.file.Path) Files.lines()}. * *

Please note that a one-dimensional array of objects supplied as a set of * "arguments" will be handled differently than other types of arguments. * Specifically, all of the elements of a one-dimensional array of objects will * be passed as individual physical arguments to the {@code @ParameterizedTest} * method. This behavior can be seen in the table below for the * {@code Supplier> objectArrayStreamSupplier} field: the * {@code @ParameterizedTest} method accepts individual {@code String} and * {@code int} arguments rather than a single {@code Object[]} array. In contrast, * any multidimensional array supplied as a set of "arguments" will be passed as * a single physical argument to the {@code @ParameterizedTest} method without * modification. This behavior can be seen in the table below for the * {@code Supplier> twoDimensionalIntArrayStreamSupplier} and * {@code Supplier> twoDimensionalObjectArrayStreamSupplier} * fields: the {@code @ParameterizedTest} methods for those fields accept individual * {@code int[][]} and {@code Object[][]} arguments, respectively. * *

Examples

* *

The following table displays compatible method signatures for parameterized * test methods and their corresponding {@code @FieldSource} fields. * * * * * * * * * * * * * * * * * *
Compatible method signatures and field declarations
{@code @ParameterizedTest} method{@code @FieldSource} field
{@code void test(String)}{@code static List listOfStrings}
{@code void test(String)}{@code static String[] arrayOfStrings}
{@code void test(int)}{@code static int[] intArray}
{@code void test(int[])}{@code static int[][] twoDimensionalIntArray}
{@code void test(String, String)}{@code static String[][] twoDimensionalStringArray}
{@code void test(String, int)}{@code static Object[][] twoDimensionalObjectArray}
{@code void test(int)}{@code static Supplier intStreamSupplier}
{@code void test(String)}{@code static Supplier> stringStreamSupplier}
{@code void test(String, int)}{@code static Supplier> objectArrayStreamSupplier}
{@code void test(String, int)}{@code static Supplier> argumentsStreamSupplier}
{@code void test(int[])}{@code static Supplier> intArrayStreamSupplier}
{@code void test(int[][])}{@code static Supplier> twoDimensionalIntArrayStreamSupplier}
{@code void test(Object[][])}{@code static Supplier> twoDimensionalObjectArrayStreamSupplier}
* *

Fields within the test class must be {@code static} unless the * {@link org.junit.jupiter.api.TestInstance.Lifecycle#PER_CLASS PER_CLASS} * test instance lifecycle mode is used; whereas, fields in external classes must * always be {@code static}. * *

This behavior and the above examples also apply to parameters of a * {@link org.junit.jupiter.params.ParameterizedClass @ParameterizedClass}, * regardless whether field or constructor injection is used. * *

Inheritance

* *

This annotation is {@linkplain Inherited inherited} within class hierarchies. * * @since 5.11 * @see MethodSource * @see Arguments * @see ArgumentsSource * @see org.junit.jupiter.params.ParameterizedClass * @see org.junit.jupiter.params.ParameterizedTest * @see org.junit.jupiter.api.TestInstance */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Repeatable(FieldSources.class) @API(status = MAINTAINED, since = "5.13.3") @ArgumentsSource(FieldArgumentsProvider.class) @SuppressWarnings("exports") public @interface FieldSource { /** * The names of fields within the test class or in external classes to use * as sources for arguments. * *

Fields in external classes must be referenced by fully qualified * field name — for example, * {@code "com.example.WebUtils#httpMethodNames"} or * {@code "com.example.TopLevelClass$NestedClass#numbers"} for a field in a * static nested class. * *

If no field names are declared, a field within the test class that has * the same name as the test method will be used as the field by default in * case this annotation is applied to a {@code @ParameterizedTest} method. * For a {@code @ParameterizedClass}, at least one field name must be * declared explicitly. * *

For further information, see the {@linkplain FieldSource class-level Javadoc}. */ String[] value() default {}; } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSources.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static org.apiguardian.api.API.Status.MAINTAINED; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @FieldSources} is a simple container for one or more * {@link FieldSource @FieldSource} annotations. * *

Note, however, that use of the {@code @FieldSources} container is completely * optional since {@code @FieldSource} is a {@linkplain java.lang.annotation.Repeatable * repeatable} annotation. * *

Inheritance

* *

This annotation is {@linkplain Inherited inherited} within class hierarchies. * * @since 5.11 * @see FieldSource * @see java.lang.annotation.Repeatable */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @API(status = MAINTAINED, since = "5.13.3") public @interface FieldSources { /** * An array of one or more {@link FieldSource @FieldSource} * annotations. */ FieldSource[] value(); } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static java.util.Arrays.stream; import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated; import static org.junit.platform.commons.util.CollectionUtils.isConvertibleToStream; import java.lang.reflect.Method; import java.util.List; import java.util.Optional; import java.util.function.Predicate; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.support.ParameterDeclarations; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.ClassLoaderUtils; import org.junit.platform.commons.util.CollectionUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.commons.util.StringUtils; /** * @since 5.0 */ class MethodArgumentsProvider extends AnnotationBasedArgumentsProvider { private static final Predicate isFactoryMethod = // method -> isConvertibleToStream(method.getReturnType()) && !isTestMethod(method); @Override protected Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context, MethodSource methodSource) { Class testClass = context.getRequiredTestClass(); Optional testMethod = context.getTestMethod(); Object testInstance = context.getTestInstance().orElse(null); String[] methodNames = methodSource.value(); // @formatter:off return stream(methodNames) .map(factoryMethodName -> findFactoryMethod(testClass, testMethod, factoryMethodName)) .map(factoryMethod -> validateFactoryMethod(factoryMethod, testInstance)) .map(factoryMethod -> Preconditions.notNull(context.getExecutableInvoker().invoke(factoryMethod, testInstance), () -> "@MethodSource-referenced method [%s] must not return null".formatted(factoryMethod.toGenericString()))) .flatMap(CollectionUtils::toStream) .map(ArgumentsUtils::toArguments); // @formatter:on } private static Method findFactoryMethod(Class testClass, Optional testMethod, String factoryMethodName) { String originalFactoryMethodName = factoryMethodName; // If the user did not provide a factory method name, find a "default" local // factory method with the same name as the parameterized test method. if (StringUtils.isBlank(factoryMethodName)) { Preconditions.condition(testMethod.isPresent(), "You must specify a method name when using @MethodSource with @ParameterizedClass"); factoryMethodName = testMethod.get().getName(); return findFactoryMethodBySimpleName(testClass, testMethod, factoryMethodName); } // Convert local factory method name to fully qualified method name. if (!looksLikeAFullyQualifiedMethodName(factoryMethodName)) { factoryMethodName = testClass.getName() + "#" + factoryMethodName; } // Find factory method using fully qualified name. Method factoryMethod = findFactoryMethodByFullyQualifiedName(testClass, testMethod, factoryMethodName); // Ensure factory method has a valid return type and is not a test method. Preconditions.condition(isFactoryMethod.test(factoryMethod), () -> "Could not find valid factory method [%s] for test class [%s] but found the following invalid candidate: %s".formatted( originalFactoryMethodName, testClass.getName(), factoryMethod)); return factoryMethod; } private static boolean looksLikeAFullyQualifiedMethodName(String factoryMethodName) { if (factoryMethodName.contains("#")) { return true; } int indexOfFirstDot = factoryMethodName.indexOf('.'); if (indexOfFirstDot == -1) { return false; } int indexOfLastOpeningParenthesis = factoryMethodName.lastIndexOf('('); if (indexOfLastOpeningParenthesis > 0) { // Exclude simple/local method names with parameters return indexOfFirstDot < indexOfLastOpeningParenthesis; } // If we get this far, we conclude the supplied factory method name "looks" // like it was intended to be a fully qualified method name, even if the // syntax is invalid. We do this in order to provide better diagnostics for // the user when a fully qualified method name is in fact invalid. return true; } // package-private for testing static Method findFactoryMethodByFullyQualifiedName(Class testClass, Optional testMethod, String fullyQualifiedMethodName) { String[] methodParts = ReflectionUtils.parseFullyQualifiedMethodName(fullyQualifiedMethodName); String className = methodParts[0]; String methodName = methodParts[1]; String methodParameters = methodParts[2]; ClassLoader classLoader = ClassLoaderUtils.getClassLoader(testClass); Class clazz = ReflectionUtils.loadRequiredClass(className, classLoader); // Attempt to find an exact match first. Method factoryMethod = ReflectionUtils.findMethod(clazz, methodName, methodParameters).orElse(null); if (factoryMethod != null) { return factoryMethod; } boolean explicitParameterListSpecified = // StringUtils.isNotBlank(methodParameters) || fullyQualifiedMethodName.endsWith("()"); // If we didn't find an exact match but an explicit parameter list was specified, // that's a user configuration error. Preconditions.condition(!explicitParameterListSpecified, () -> "Could not find factory method [%s(%s)] in class [%s]".formatted(methodName, methodParameters, className)); // Otherwise, fall back to the same lenient search semantics that are used // to locate a "default" local factory method. return findFactoryMethodBySimpleName(clazz, testMethod, methodName); } /** * Find the factory method by searching for all methods in the given {@code clazz} * with the desired {@code factoryMethodName} which have return types that can be * converted to a {@link Stream}, ignoring the {@code testMethod} itself as well * as any {@code @Test}, {@code @TestTemplate}, or {@code @TestFactory} methods * with the same name. * @return the single factory method matching the search criteria * @throws PreconditionViolationException if the factory method was not found or * multiple competing factory methods with the same name were found */ private static Method findFactoryMethodBySimpleName(Class clazz, Optional testMethod, String factoryMethodName) { Predicate isCandidate = candidate -> factoryMethodName.equals(candidate.getName()) && !candidate.equals(testMethod.orElse(null)); List candidates = ReflectionUtils.findMethods(clazz, isCandidate); List factoryMethods = candidates.stream().filter(isFactoryMethod).toList(); Preconditions.notEmpty(factoryMethods, () -> { if (candidates.isEmpty()) { // Report that we didn't find anything. return "Could not find factory method [%s] in class [%s]".formatted(factoryMethodName, clazz.getName()); } // If we didn't find the factory method using the isFactoryMethod Predicate, perhaps // the specified factory method has an invalid return type or is a test method. // In that case, we report the invalid candidates that were found. return "Could not find valid factory method [%s] in class [%s] but found the following invalid candidates: %s".formatted( factoryMethodName, clazz.getName(), candidates); }); Preconditions.condition(factoryMethods.size() == 1, () -> "%d factory methods named [%s] were found in class [%s]: %s".formatted(factoryMethods.size(), factoryMethodName, clazz.getName(), factoryMethods)); return factoryMethods.get(0); } private static boolean isTestMethod(Method candidate) { return isAnnotated(candidate, Test.class) || isAnnotated(candidate, TestTemplate.class) || isAnnotated(candidate, TestFactory.class); } private static Method validateFactoryMethod(Method factoryMethod, @Nullable Object testInstance) { Preconditions.condition( factoryMethod.getDeclaringClass().isInstance(testInstance) || ReflectionUtils.isStatic(factoryMethod), () -> """ Method '%s' must be static: local factory methods must be static \ unless the PER_CLASS @TestInstance lifecycle mode is used; \ external factory methods must always be static.""".formatted(factoryMethod.toGenericString())); return factoryMethod; } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSource.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @MethodSource} is a {@linkplain Repeatable repeatable} * {@link ArgumentsSource} which provides access to values returned from * {@linkplain #value() factory methods} of the class in which this annotation * is declared or from static factory methods in external classes referenced * by fully qualified method name. * *

Each factory method must generate a stream of arguments, * and each set of "arguments" within the "stream" will be provided as the * physical arguments for individual invocations of the annotated * {@code org.junit.jupiter.params.ParameterizedClass @ParameterizedClass} or * {@link org.junit.jupiter.params.ParameterizedTest @ParameterizedTest}. * Generally speaking this translates to a {@link java.util.stream.Stream Stream} * of {@link Arguments} (i.e., {@code Stream}); however, the actual * concrete return type can take on many forms. In this context, a "stream" is * anything that JUnit can reliably convert into a {@code Stream}, such as * {@link java.util.stream.Stream Stream}, * {@link java.util.stream.DoubleStream DoubleStream}, * {@link java.util.stream.LongStream LongStream}, * {@link java.util.stream.IntStream IntStream}, * {@link java.util.Collection Collection}, * {@link java.util.Iterator Iterator}, an array of objects or primitives, or * any type that provides an {@link java.util.Iterator Iterator}-returning * {@code iterator()} method (such as, for example, a * {@code kotlin.sequences.Sequence}). Each set of "arguments" within the * "stream" can be supplied as an instance of {@link Arguments}, an array of * objects (e.g., {@code Object[]}, {@code String[]}, etc.), or a single * value if the parameterized test method accepts a single argument. * *

If the return type is {@code Stream} or * one of the primitive streams, JUnit will properly close it by calling * {@link java.util.stream.BaseStream#close() BaseStream.close()}, * making it safe to use a resource such as * {@link java.nio.file.Files#lines(java.nio.file.Path) Files.lines()}. * *

Please note that a one-dimensional array of objects supplied as a set of * "arguments" will be handled differently than other types of arguments. * Specifically, all of the elements of a one-dimensional array of objects will * be passed as individual physical arguments to the {@code @ParameterizedTest} * method. This behavior can be seen in the table below for the * {@code static Stream factory()} method: the {@code @ParameterizedTest} * method accepts individual {@code String} and {@code int} arguments rather than * a single {@code Object[]} array. In contrast, any multidimensional array * supplied as a set of "arguments" will be passed as a single physical argument * to the {@code @ParameterizedTest} method without modification. This behavior * can be seen in the table below for the {@code static Stream factory()} * and {@code static Stream factory()} methods: the * {@code @ParameterizedTest} methods for those factories accept individual * {@code int[][]} and {@code Object[][]} arguments, respectively. * *

Examples

* *

The following table displays compatible method signatures for parameterized * test methods and their corresponding factory methods. * * * * * * * * * * * * * * * * * *
Compatible method signatures and factory methods
{@code @ParameterizedTest} methodFactory method
{@code void test(int)}{@code static int[] factory()}
{@code void test(int)}{@code static IntStream factory()}
{@code void test(String)}{@code static String[] factory()}
{@code void test(String)}{@code static List factory()}
{@code void test(String)}{@code static Stream factory()}
{@code void test(String, String)}{@code static String[][] factory()}
{@code void test(String, int)}{@code static Object[][] factory()}
{@code void test(String, int)}{@code static Stream factory()}
{@code void test(String, int)}{@code static Stream factory()}
{@code void test(int[])}{@code static int[][] factory()}
{@code void test(int[])}{@code static Stream factory()}
{@code void test(int[][])}{@code static Stream factory()}
{@code void test(Object[][])}{@code static Stream factory()}
* *

Factory methods within the test class must be {@code static} unless the * {@link org.junit.jupiter.api.TestInstance.Lifecycle#PER_CLASS PER_CLASS} * test instance lifecycle mode is used; whereas, factory methods in external * classes must always be {@code static}. * *

This behavior and the above examples also apply to parameters of a * {@link org.junit.jupiter.params.ParameterizedClass @ParameterizedClass}, * regardless whether field or constructor injection is used. * *

Factory methods can declare parameters, which will be provided by registered * implementations of {@link org.junit.jupiter.api.extension.ParameterResolver}. * *

Inheritance

* *

This annotation is {@linkplain Inherited inherited} within class hierarchies. * * @since 5.0 * @see FieldSource * @see Arguments * @see ArgumentsSource * @see org.junit.jupiter.params.ParameterizedClass * @see org.junit.jupiter.params.ParameterizedTest * @see org.junit.jupiter.api.TestInstance */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Repeatable(MethodSources.class) @API(status = STABLE, since = "5.7") @ArgumentsSource(MethodArgumentsProvider.class) @SuppressWarnings("exports") public @interface MethodSource { /** * The names of factory methods within the test class or in external classes * to use as sources for arguments. * *

Factory methods in external classes must be referenced by * fully qualified method name — for example, * {@code "com.example.StringsProviders#blankStrings"} or * {@code "com.example.TopLevelClass$NestedClass#classMethod"} for a factory * method in a static nested class. * *

If a factory method accepts arguments that are provided by a * {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolver}, * you can supply the formal parameter list in the qualified method name to * disambiguate between overloaded variants of the factory method. For example, * {@code "blankStrings(int)"} for a local qualified method name or * {@code "com.example.StringsProviders#blankStrings(int)"} for a fully qualified * method name. * *

If no factory method names are declared, a method within the test class * that has the same name as the test method will be used as the factory * method by default in case this annotation is applied to a * {@code @ParameterizedTest} method. For a {@code @ParameterizedClass}, at * least one method name must be declared explicitly. * *

For further information, see the {@linkplain MethodSource class-level Javadoc}. */ String[] value() default ""; } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSources.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @MethodSources} is a simple container for one or more * {@link MethodSource @MethodSource} annotations. * *

Note, however, that use of the {@code @MethodSources} container is completely * optional since {@code @MethodSource} is a {@linkplain java.lang.annotation.Repeatable * repeatable} annotation. * *

Inheritance

* *

This annotation is {@linkplain Inherited inherited} within class hierarchies. * * @since 5.11 * @see MethodSource * @see java.lang.annotation.Repeatable */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @API(status = STABLE, since = "5.11") public @interface MethodSources { /** * An array of one or more {@link MethodSource @MethodSource} * annotations. */ MethodSource[] value(); } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullAndEmptySource.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @NullAndEmptySource} is a composed annotation that combines * the functionality of {@link NullSource @NullSource} and * {@link EmptySource @EmptySource}. * *

Annotating a {@code @ParameterizedClass} or {@code @ParameterizedTest} * with {@code @NullAndEmptySource} is equivalent to annotating the method with * both {@code @NullSource} and {@code @EmptySource}. * *

Inheritance

* *

This annotation is {@linkplain Inherited inherited} within class hierarchies. * * @since 5.4 * @see org.junit.jupiter.params.ParameterizedClass * @see org.junit.jupiter.params.ParameterizedTest * @see NullSource * @see EmptySource */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @API(status = STABLE, since = "5.7") @NullSource @EmptySource public @interface NullAndEmptySource { } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullArgumentsProvider.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static org.junit.jupiter.params.provider.Arguments.arguments; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.support.ParameterDeclarations; import org.junit.platform.commons.util.Preconditions; /** * @since 5.4 * @see NullSource */ class NullArgumentsProvider implements ArgumentsProvider { private static final Arguments nullArguments = arguments(new @Nullable Object[] { null }); @Override public Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context) { Preconditions.condition(parameters.getFirst().isPresent(), () -> "@NullSource cannot provide a null argument to %s: no formal parameters declared.".formatted( parameters.getSourceElementDescription())); return Stream.of(nullArguments); } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullEnum.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static org.apiguardian.api.API.Status.INTERNAL; import org.apiguardian.api.API; /** * Dummy enum class used as the default value for optional attributes of annotations. * * @since 5.6 * @see EnumSource#value() */ @API(status = INTERNAL, since = "5.7") public enum NullEnum { } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullSource.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @NullSource} is an {@link ArgumentsSource} which provides a single * {@code null} argument to the annotated {@code @ParameterizedClass} or * {@code @ParameterizedTest}. * *

Note that {@code @NullSource} cannot be used for an argument that has * a primitive type, unless the argument is converted to a corresponding wrapper * type with an {@link org.junit.jupiter.params.converter.ArgumentConverter}. * *

Inheritance

* *

This annotation is {@linkplain Inherited inherited} within class hierarchies. * * @since 5.4 * @see org.junit.jupiter.params.provider.ArgumentsSource * @see org.junit.jupiter.params.ParameterizedClass * @see org.junit.jupiter.params.ParameterizedTest * @see EmptySource * @see NullAndEmptySource */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @API(status = STABLE, since = "5.7") @ArgumentsSource(NullArgumentsProvider.class) @SuppressWarnings("exports") public @interface NullSource { } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueArgumentsProvider.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import java.lang.reflect.Array; import java.util.Arrays; import java.util.List; import java.util.stream.IntStream; import java.util.stream.Stream; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.support.ParameterDeclarations; import org.junit.platform.commons.util.Preconditions; /** * @since 5.0 */ class ValueArgumentsProvider extends AnnotationBasedArgumentsProvider { @Override protected Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context, ValueSource valueSource) { Object[] arguments = getArgumentsFromSource(valueSource); return Arrays.stream(arguments).map(Arguments::of); } private Object[] getArgumentsFromSource(ValueSource valueSource) { // @formatter:off List arrays = Stream.of( valueSource.shorts(), valueSource.bytes(), valueSource.ints(), valueSource.longs(), valueSource.floats(), valueSource.doubles(), valueSource.chars(), valueSource.booleans(), valueSource.strings(), valueSource.classes() ) .filter(array -> Array.getLength(array) > 0) .toList(); // @formatter:on Preconditions.condition(arrays.size() == 1, () -> "Exactly one type of input must be provided in the @" + ValueSource.class.getSimpleName() + " annotation, but there were " + arrays.size()); Object originalArray = arrays.get(0); return IntStream.range(0, Array.getLength(originalArray)) // .mapToObj(index -> Array.get(originalArray, index)) // .toArray(); } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSource.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @ValueSource} is a {@linkplain Repeatable repeatable} * {@link ArgumentsSource} which provides access to an array of literal values. * *

Supported types include {@link #shorts}, {@link #bytes}, {@link #ints}, * {@link #longs}, {@link #floats}, {@link #doubles}, {@link #chars}, * {@link #booleans}, {@link #strings}, and {@link #classes}. Note, however, * that only one of the supported types may be specified per * {@code @ValueSource} declaration. * *

The supplied literal values will be provided as arguments to the * annotated {@code @ParameterizedClass} or {@code @ParameterizedTest}. * *

Inheritance

* *

This annotation is {@linkplain Inherited inherited} within class hierarchies. * * @since 5.0 * @see org.junit.jupiter.params.provider.ArgumentsSource * @see org.junit.jupiter.params.ParameterizedClass * @see org.junit.jupiter.params.ParameterizedTest */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Repeatable(ValueSources.class) @API(status = STABLE, since = "5.7") @ArgumentsSource(ValueArgumentsProvider.class) @SuppressWarnings("exports") public @interface ValueSource { /** * The {@code short} values to use as sources of arguments; must not be empty. * * @since 5.1 */ short[] shorts() default {}; /** * The {@code byte} values to use as sources of arguments; must not be empty. * * @since 5.1 */ byte[] bytes() default {}; /** * The {@code int} values to use as sources of arguments; must not be empty. */ int[] ints() default {}; /** * The {@code long} values to use as sources of arguments; must not be empty. */ long[] longs() default {}; /** * The {@code float} values to use as sources of arguments; must not be empty. * * @since 5.1 */ float[] floats() default {}; /** * The {@code double} values to use as sources of arguments; must not be empty. */ double[] doubles() default {}; /** * The {@code char} values to use as sources of arguments; must not be empty. * * @since 5.1 */ char[] chars() default {}; /** * The {@code boolean} values to use as sources of arguments; must not be empty. * * @since 5.5 */ boolean[] booleans() default {}; /** * The {@link String} values to use as sources of arguments; must not be empty. */ String[] strings() default {}; /** * The {@link Class} values to use as sources of arguments; must not be empty. * * @since 5.1 */ Class[] classes() default {}; } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSources.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @ValueSources} is a simple container for one or more * {@link ValueSource @ValueSource} annotations. * *

Note, however, that use of the {@code @ValueSources} container is completely * optional since {@code @ValueSource} is a {@linkplain java.lang.annotation.Repeatable * repeatable} annotation. * *

Inheritance

* *

This annotation is {@linkplain Inherited inherited} within class hierarchies. * * @since 5.11 * @see ValueSource * @see java.lang.annotation.Repeatable */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @API(status = STABLE, since = "5.11") public @interface ValueSources { /** * An array of one or more {@link ValueSource @ValueSource} * annotations. */ ValueSource[] value(); } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * {@link org.junit.jupiter.params.provider.ArgumentsProvider ArgumentsProvider} * implementations and their corresponding * {@link org.junit.jupiter.params.provider.ArgumentsSource ArgumentsSource} * annotations. */ @NullMarked package org.junit.jupiter.params.provider; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/AnnotationConsumer.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.support; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Annotation; import java.util.function.Consumer; import org.apiguardian.api.API; /** * {@code AnnotationConsumer} is a {@linkplain FunctionalInterface functional * interface} for consuming annotations. * *

It is typically implemented by implementations of * {@link org.junit.jupiter.params.provider.ArgumentsProvider ArgumentsProvider} * and {@link org.junit.jupiter.params.converter.ArgumentConverter ArgumentConverter} * in order to signal that they can {@link #accept accept} a certain annotation. * * @since 5.0 */ @FunctionalInterface @API(status = STABLE, since = "5.7") public interface AnnotationConsumer extends Consumer { } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/AnnotationConsumerInitializer.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.support; import static java.util.Collections.emptyList; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import static org.junit.platform.commons.support.AnnotationSupport.findRepeatableAnnotations; import static org.junit.platform.commons.support.HierarchyTraversalMode.BOTTOM_UP; import static org.junit.platform.commons.support.ReflectionSupport.findMethods; import java.lang.annotation.Annotation; import java.lang.annotation.Repeatable; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.util.Collections; import java.util.List; import java.util.function.Predicate; import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; /** * {@code AnnotationConsumerInitializer} is an internal helper class for * initializing {@link AnnotationConsumer AnnotationConsumers}. * * @since 5.0 */ @API(status = INTERNAL, since = "5.0") public final class AnnotationConsumerInitializer { private static final List methodSignatures = List.of( // new MethodSignature("accept", 1, 0), // new MethodSignature("provideArguments", 3, 2), // new MethodSignature("provideArguments", 2, 1), // new MethodSignature("convert", 3, 2)); private static final Predicate consumesAnnotation = methodSignatures.stream() // .map(signature -> (Predicate) signature::matches) // .reduce(method -> false, Predicate::or); private AnnotationConsumerInitializer() { /* no-op */ } @SuppressWarnings({ "unchecked", "rawtypes" }) public static T initialize(AnnotatedElement annotatedElement, T annotationConsumerInstance) { if (annotationConsumerInstance instanceof AnnotationConsumer consumer) { Class annotationType = findConsumedAnnotationType(annotationConsumerInstance); List annotations = findAnnotations(annotatedElement, annotationType); if (annotations.isEmpty()) { throw new JUnitException(annotationConsumerInstance.getClass().getName() + " must be used with an annotation of type " + annotationType.getName()); } annotations.forEach(annotation -> initializeAnnotationConsumer(consumer, annotation)); } return annotationConsumerInstance; } private static List findAnnotations(AnnotatedElement annotatedElement, Class annotationType) { return annotationType.isAnnotationPresent(Repeatable.class) ? findRepeatableAnnotations(annotatedElement, annotationType) : findAnnotation(annotatedElement, annotationType).map(Collections::singletonList).orElse(emptyList()); } private static Class findConsumedAnnotationType(T annotationConsumerInstance) { Method method = findMethods(annotationConsumerInstance.getClass(), consumesAnnotation, BOTTOM_UP).get(0); return getAnnotationType(method); } @SuppressWarnings("unchecked") private static Class getAnnotationType(Method method) { int annotationIndex = methodSignatures.stream() // .filter(signature -> signature.matches(method)) // .findFirst() // .map(MethodSignature::annotationParameterIndex) // .orElse(0); return (Class) method.getParameterTypes()[annotationIndex]; } private static void initializeAnnotationConsumer(AnnotationConsumer instance, A annotation) { try { instance.accept(annotation); } catch (Exception ex) { throw new JUnitException("Failed to initialize AnnotationConsumer: " + instance, ex); } } /** * Annotation-consuming method signature. */ private record MethodSignature(String methodName, int parameterCount, int annotationParameterIndex) { boolean matches(Method method) { return method.getName().equals(methodName) // && method.getParameterCount() == parameterCount // && method.getParameterTypes()[annotationParameterIndex].isAnnotation(); } } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/FieldContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.support; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import java.lang.reflect.Field; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.AnnotatedElementContext; import org.junit.jupiter.params.Parameter; import org.junit.jupiter.params.ParameterizedClass; /** * {@code FieldContext} encapsulates the context in which an * {@link Parameter @Parameter}-annotated {@link Field} is declared in a * {@link ParameterizedClass @ParameterizedClass}. * * @since 5.13 * @see ParameterizedClass * @see Parameter */ @API(status = EXPERIMENTAL, since = "6.0") public interface FieldContext extends AnnotatedElementContext { /** * {@return the field for this context; never {@code null}} */ Field getField(); /** * {@return the index of the parameter} * *

This method returns {@value Parameter#UNSET_INDEX} for aggregator * fields and a value greater than or equal to zero for indexed * parameters. * * @see Parameter#value() */ int getParameterIndex(); } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/ParameterDeclaration.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.support; import static org.apiguardian.api.API.Status.MAINTAINED; import java.lang.reflect.AnnotatedElement; import java.util.Optional; import org.apiguardian.api.API; /** * {@code ParameterDeclaration} encapsulates the declaration of an * indexed {@code @ParameterizedClass} or {@code @ParameterizedTest} parameter. * * @since 5.13 * @see ParameterDeclarations */ @API(status = MAINTAINED, since = "6.0.2") public interface ParameterDeclaration { /** * {@return the {@link AnnotatedElement} that declares the parameter; never * {@code null}} * *

This is either a {@link java.lang.reflect.Parameter} or a * {@link java.lang.reflect.Field}. */ AnnotatedElement getAnnotatedElement(); /** * {@return the type of the parameter; never {@code null}} */ Class getParameterType(); /** * {@return the index of the parameter} */ int getParameterIndex(); /** * {@return the name of the parameter, if available; never {@code null} but * potentially empty} */ Optional getParameterName(); } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/ParameterDeclarations.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.support; import static org.apiguardian.api.API.Status.MAINTAINED; import java.lang.reflect.AnnotatedElement; import java.util.List; import java.util.Optional; import org.apiguardian.api.API; import org.junit.jupiter.params.aggregator.ArgumentsAccessor; /** * {@code ParameterDeclarations} encapsulates the combined declarations * of all indexed parameters for a {@code @ParameterizedClass} or * {@code @ParameterizedTest}. * *

For a {@code @ParameterizedTest}, the parameter declarations are derived * from the method signature. For a {@code @ParameterizedClass}, they may be * derived from the constructor or * {@link org.junit.jupiter.params.Parameter @Parameter}-annotated fields. * *

Aggregators — parameters of type {@link ArgumentsAccessor ArgumentsAccessor} * or parameters annotated with * {@link org.junit.jupiter.params.aggregator.AggregateWith @AggregateWith} — * are not indexed and thus not included in the list of parameter * declarations. * * @since 5.13 * @see ParameterDeclaration * @see org.junit.jupiter.params.ParameterizedClass * @see org.junit.jupiter.params.ParameterizedTest */ @API(status = MAINTAINED, since = "6.0.2") public interface ParameterDeclarations { /** * {@return all indexed parameter declarations, sorted by index; * never {@code null}, but potentially empty} */ List getAll(); /** * {@return the first indexed parameter declaration, if available; * never {@code null}, but potentially empty} */ Optional getFirst(); /** * {@return the indexed parameter declaration for the supplied * index, if available; never {@code null}, but potentially empty} */ Optional get(int parameterIndex); /** * {@return the source element of all parameter declarations} * *

For {@code @ParameterizedTest}, this always corresponds to the * parameterized test method. For {@code @ParameterizedClass}, this * corresponds to the parameterized test class constructor, if constructor * injection is used; or the test class itself, if field injection is used. */ AnnotatedElement getSourceElement(); /** * {@return a human-readable description of the source element} * *

This may, for example, be used in error messages. * * @see #getSourceElement() */ String getSourceElementDescription(); } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/ParameterInfo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.support; import static org.apiguardian.api.API.Status.DEPRECATED; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Namespace; import org.junit.jupiter.params.ParameterizedClass; import org.junit.jupiter.params.ParameterizedTest; /** * {@code ParameterInfo} is used to provide information about the current * invocation of a parameterized class or test. * *

Registered {@link Extension} implementations may retrieve the current * {@code ParameterInfo} instance by calling * {@link ExtensionContext#getStore(Namespace)} with {@link #NAMESPACE} and * {@link ExtensionContext.Store#get(Object, Class) Store.get(...)} with * {@link #KEY}. Alternatively, the {@link #get(ExtensionContext)} method may * be used to retrieve the {@code ParameterInfo} instance for the supplied * {@code ExtensionContext}. Extensions must not modify any entries in the * {@link ExtensionContext.Store Store} for {@link #NAMESPACE}. * *

When a {@link ParameterizedTest @ParameterizedTest} method is declared * inside a {@link ParameterizedClass @ParameterizedClass} or a * {@link Nested @Nested} {@link ParameterizedClass @ParameterizedClass} is * declared inside an enclosing {@link ParameterizedClass @ParameterizedClass}, * there will be multiple {@code ParameterInfo} instances available on different * levels of the {@link ExtensionContext} hierarchy. In such cases, please use * {@link ExtensionContext#getParent()} to navigate to the right level before * retrieving the {@code ParameterInfo} instance from the * {@link ExtensionContext.Store Store}. * * @since 5.13 * @see ParameterizedClass * @see ParameterizedTest * @deprecated Please use {@link org.junit.jupiter.params.ParameterInfo} instead */ @Deprecated(since = "5.14", forRemoval = true) @API(status = DEPRECATED, since = "5.14") public interface ParameterInfo extends org.junit.jupiter.params.ParameterInfo { /** * The {@link Namespace} for accessing the * {@link ExtensionContext.Store Store} for {@code ParameterInfo}. * @deprecated Please use * {@link org.junit.jupiter.params.ParameterInfo#NAMESPACE} instead */ @Deprecated(since = "5.14", forRemoval = true) @API(status = DEPRECATED, since = "5.14") Namespace NAMESPACE = Namespace.create(ParameterInfo.class); /** * The key for retrieving the {@code ParameterInfo} instance from the * {@link ExtensionContext.Store Store}. * @deprecated Please use * {@link org.junit.jupiter.params.ParameterInfo#KEY} instead */ @Deprecated(since = "5.14", forRemoval = true) @API(status = DEPRECATED, since = "5.14") Object KEY = ParameterInfo.class; /** * {@return the closest {@code ParameterInfo} instance for the supplied * {@code ExtensionContext}; potentially {@code null}} * @deprecated Please use * {@link org.junit.jupiter.params.ParameterInfo#get(ExtensionContext)} * instead */ @Deprecated(since = "5.14", forRemoval = true) @API(status = DEPRECATED, since = "5.14") static @Nullable ParameterInfo get(ExtensionContext context) { return context.getStore(NAMESPACE).get(KEY, ParameterInfo.class); } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/ParameterNameAndArgument.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.support; import static org.apiguardian.api.API.Status.INTERNAL; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Named; /** * Customized parameter name and its associated argument value. * *

Although this class implements {@link Named} for technical reasons, it * serves a different purpose than {@link Named#of(String, Object)} and is only * used for internal display name processing. * * @since 6.0 */ @API(status = INTERNAL, since = "6.0") public class ParameterNameAndArgument implements Named<@Nullable Object> { private final String name; private final @Nullable Object argument; public ParameterNameAndArgument(String name, @Nullable Object argument) { this.name = name; this.argument = argument; } /** * Get the customized name of the parameter. */ @Override public String getName() { return this.name; } /** * Get the argument for the parameter. */ @Override public @Nullable Object getPayload() { return this.argument; } @Override public String toString() { return "ParameterNameAndArgument[name = %s, argument = %s]".formatted(this.name, this.argument); } } ================================================ FILE: junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Support classes for building * {@linkplain org.junit.jupiter.params.provider.ArgumentsProvider providers} * and * {@linkplain org.junit.jupiter.params.converter.ArgumentConverter converters} * for arguments. */ @NullMarked package org.junit.jupiter.params.support; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-jupiter-params/src/main/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessor.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ @file:API(status = API.Status.STABLE, since = "5.7") package org.junit.jupiter.params.aggregator import org.apiguardian.api.API /** * Get the value of the argument at the given index as an instance of the * reified type. * * @param index the index of the argument to get; must be greater than or * equal to zero and less than {@link #size} * @return the value at the given index, potentially {@code null} * @since 5.3 * @receiver[ArgumentsAccessor] * @see ArgumentsAccessor.get(Int, Class!) */ @Suppress("EXTENSION_SHADOWED_BY_MEMBER") // method is in fact not shadowed due to reified type inline fun ArgumentsAccessor.get(index: Int): T = this.get(index, T::class.java)!! ================================================ FILE: junit-jupiter-params/src/test/README.md ================================================ For compatibility with the Eclipse IDE, the test for this module are in the `jupiter-tests` project. ================================================ FILE: junit-jupiter-params/src/testFixtures/java/org/junit/jupiter/params/provider/RecordArguments.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import java.util.Arrays; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.support.ReflectionSupport; public interface RecordArguments extends Arguments { @Override default @Nullable Object[] get() { return Arrays.stream(getClass().getRecordComponents()) // .map(component -> ReflectionSupport.invokeMethod(component.getAccessor(), this)) // .toArray(); } } ================================================ FILE: junit-platform-commons/junit-platform-commons.gradle.kts ================================================ import junitbuild.extensions.javaModuleName plugins { id("junitbuild.kotlin-library-conventions") `java-test-fixtures` } description = "JUnit Platform Commons" dependencies { api(platform(projects.junitBom)) compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) compileOnly(kotlin("stdlib")) compileOnly(kotlin("reflect")) compileOnly(libs.kotlinx.coroutines.core) testFixturesImplementation(libs.assertj) } javadocConventions { addExtraModuleReferences(projects.junitPlatformEngine) } eclipseConventions { hideModularity = false } tasks.compileJava { options.compilerArgs.add("-Xlint:-module") // due to qualified exports val moduleName = javaModuleName val mainOutput = files(sourceSets.main.get().output) options.compilerArgumentProviders.add(CommandLineArgumentProvider { listOf("--patch-module", "${moduleName}=${mainOutput.asPath}") }) } tasks.jar { bundle { val importAPIGuardian: String by extra val importJSpecify: String by extra bnd(""" Import-Package: \ $importAPIGuardian,\ $importJSpecify,\ kotlin.*;resolution:="optional",\ kotlinx.*;resolution:="optional",\ * """) } } ================================================ FILE: junit-platform-commons/src/main/java/module-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Common APIs and support utilities for the JUnit Platform. * * @since 1.0 */ module org.junit.platform.commons { requires java.logging; requires java.management; // needed by RuntimeUtils to determine input arguments requires static transitive org.apiguardian.api; requires static transitive org.jspecify; requires static kotlin.stdlib; requires static kotlin.reflect; requires static kotlinx.coroutines.core; exports org.junit.platform.commons; exports org.junit.platform.commons.annotation; exports org.junit.platform.commons.function; exports org.junit.platform.commons.io; exports org.junit.platform.commons.logging to org.junit.jupiter.api, org.junit.jupiter.engine, org.junit.jupiter.migrationsupport, org.junit.jupiter.params, org.junit.platform.console, org.junit.platform.engine, org.junit.platform.launcher, org.junit.platform.reporting, org.junit.platform.suite.api, org.junit.platform.suite.engine, org.junit.platform.testkit, org.junit.vintage.engine; exports org.junit.platform.commons.support; exports org.junit.platform.commons.support.conversion; exports org.junit.platform.commons.support.scanning; exports org.junit.platform.commons.util to org.junit.jupiter.api, org.junit.jupiter.engine, org.junit.jupiter.migrationsupport, org.junit.jupiter.params, org.junit.platform.console, org.junit.platform.engine, org.junit.platform.launcher, org.junit.platform.reporting, org.junit.platform.suite.api, org.junit.platform.suite.engine, org.junit.platform.testkit, org.junit.vintage.engine; uses org.junit.platform.commons.support.scanning.ClasspathScanner; } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/JUnitException.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.io.Serial; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; /** * Base class for all {@link RuntimeException RuntimeExceptions} thrown * by JUnit. * * @since 1.0 */ @API(status = STABLE, since = "1.5") public class JUnitException extends RuntimeException { @Serial private static final long serialVersionUID = 1L; public JUnitException(@Nullable String message) { super(message); } public JUnitException(@Nullable String message, @Nullable Throwable cause) { super(message, cause); } /** * @since 1.13 */ @API(status = MAINTAINED, since = "1.13") protected JUnitException(@Nullable String message, @Nullable Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/PreconditionViolationException.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons; import static org.apiguardian.api.API.Status.STABLE; import java.io.Serial; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; /** * Thrown if a precondition is violated. * * @since 1.5 */ @API(status = STABLE, since = "1.5") public class PreconditionViolationException extends JUnitException { @Serial private static final long serialVersionUID = 1L; public PreconditionViolationException(String message) { super(message); } public PreconditionViolationException(String message, @Nullable Throwable cause) { super(message, cause); } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/Contract.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.annotation; import static org.apiguardian.api.API.Status.INTERNAL; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * Specifies some aspects of the annotated method's behavior to be used by tools * for data flow analysis. * * @since 6.0 * @see org.jetbrains.annotations.Contract * @see NullAway custom contract annotations */ @Documented @Target(ElementType.METHOD) @API(status = INTERNAL, since = "6.0") public @interface Contract { /** * Contains the contract clauses describing causal relations between call * arguments and the returned value. */ String value(); } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/Testable.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.annotation; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import org.apiguardian.api.API; /** * {@code @Testable} is used to signal to IDEs and tooling vendors that the * annotated or meta-annotated element is testable. * *

In this context, the term "testable" means that the annotated element * (typically a method, field, or class) can be executed by a {@code TestEngine} * as a test or test container on the JUnit Platform. * *

Motivation for {@code @Testable}

*

Some clients of the JUnit Platform, notably IDEs such as IntelliJ IDEA, * operate only on sources for test discovery. Thus, they cannot use the full * runtime discovery mechanism of the JUnit Platform since it relies on compiled * classes. {@code @Testable} therefore serves as an alternative mechanism for * IDEs to discover potential tests by analyzing the source code only. * *

Common Use Cases

*

{@code @Testable} will typically be used as a meta-annotation in order to * create a custom composed annotation that inherits the semantics * of {@code @Testable}. For example, the {@code @Test} and {@code @TestFactory} * annotations in JUnit Jupiter are meta-annotated with {@code @Testable}. *

For test programming models that do not rely on annotations, test classes, * test methods, or test fields may be directly annotated with {@code @Testable}. * Alternatively, if concrete test classes extend from a base class, the base class * can be annotated with {@code @Testable}. Note that {@code @Testable} is an * {@link Inherited @Inherited} annotation. * *

Requirements for IDEs and Tooling Vendors

*
    *
  • If a top-level class, static nested class, or inner class is not * annotated or meta-annotated with {@code @Testable} but contains a method or field * that is annotated or meta-annotated with {@code @Testable}, the class must * be considered to be a testable class.
  • *
  • If annotation hierarchies containing {@code @Testable} are present on * classes, methods, or fields in compiled byte code (e.g., in JARs in the user's * classpath), IDEs and tooling vendors must also take such annotation * hierarchies into consideration when performing annotation processing for * source code.
  • *
* *

Restrictions for TestEngine Implementations

*

A {@code TestEngine} must not in any way perform * discovery based on the presence of {@code @Testable}. In terms of * discovery, the presence of {@code @Testable} should only be meaningful to * clients such as IDEs and tooling vendors. A {@code TestEngine} implementation * is therefore required to discover tests based on information specific to * that test engine (e.g., annotations specific to that test engine). * *

Supported Target Elements

*

{@code @Testable} may target any declaration * {@linkplain java.lang.annotation.ElementType element type}. This includes the * aforementioned method, field, and class elements. * * @since 1.0 */ @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @API(status = STABLE, since = "1.0") public @interface Testable { } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Common annotations for the JUnit Platform. */ @NullMarked package org.junit.platform.commons.annotation; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/function/Try.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.function; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import java.util.Objects; import java.util.Optional; import java.util.concurrent.Callable; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; import org.apiguardian.api.API; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.annotation.Contract; /** * A container object which may either contain a nullable value in case of * success or an exception in case of failure. * *

Instances of this class should be returned by methods instead of * {@link Optional} when callers might want to report the exception via logging * or by wrapping it in another exception at a later point in time, e.g. via * {@link #getOrThrow(Function)}. * *

Moreover, it makes it particularly convenient to attach follow-up actions * should the {@code Try} have been successful (cf. {@link #andThen} and * {@link #andThenTry}) or fallback actions should it not have been (cf. * {@link #orElse} and {@link #orElseTry}). * * @since 1.4 */ @API(status = MAINTAINED, since = "1.4") public abstract class Try { /** * Call the supplied {@link Callable} and return a successful {@code Try} * that contains the returned value or, in case an exception was thrown, a * failed {@code Try} that contains the exception. * * @param action the action to try; must not be {@code null} * @return a succeeded or failed {@code Try} depending on the outcome of the * supplied action; never {@code null} * @see #success(Object) * @see #failure(Exception) */ public static Try call(Callable action) { checkNotNull(action, "action"); return Try.of(() -> success(action.call())); } /** * Convert the supplied value into a succeeded {@code Try}. * * @param value the value to wrap; potentially {@code null} * @return a succeeded {@code Try} that contains the supplied value; never * {@code null} */ public static Try success(V value) { return new Success<>(value); } /** * Convert the supplied exception into a failed {@code Try}. * * @param cause the exception to wrap; must not be {@code null} * @return a failed {@code Try} that contains the supplied value; never * {@code null} */ public static Try failure(Exception cause) { return new Failure<>(checkNotNull(cause, "cause")); } // Cannot use Preconditions due to package cycle @Contract("null, _ -> fail; !null, _ -> param1") private static T checkNotNull(@Nullable T input, String title) { if (input == null) { // Cannot use PreconditionViolationException due to package cycle throw new JUnitException(title + " must not be null"); } return input; } private static Try of(Callable> action) { try { return action.call(); } catch (Exception e) { return failure(e); } } private Try() { /* no-op */ } /** * If this {@code Try} is a success, apply the supplied transformer to its * value and return a new successful or failed {@code Try} depending on the * transformer's outcome; if this {@code Try} is a failure, do nothing. * * @param transformer the transformer to try; must not be {@code null} * @return a succeeded or failed {@code Try}; never {@code null} */ public abstract Try andThenTry(Transformer transformer); /** * If this {@code Try} is a success, apply the supplied function to its * value and return the resulting {@code Try}; if this {@code Try} is a * failure, do nothing. * * @param function the function to apply; must not be {@code null} * @return a succeeded or failed {@code Try}; never {@code null} */ public abstract Try andThen(Function> function); /** * If this {@code Try} is a failure, call the supplied action and return a * new successful or failed {@code Try} depending on the action's outcome; * if this {@code Try} is a success, do nothing. * * @param action the action to try; must not be {@code null} * @return a succeeded or failed {@code Try}; never {@code null} */ public abstract Try orElseTry(Callable action); /** * If this {@code Try} is a failure, call the supplied supplier and return * the resulting {@code Try}; if this {@code Try} is a success, do nothing. * * @param supplier the supplier to call; must not be {@code null} * @return a succeeded or failed {@code Try}; never {@code null} */ public abstract Try orElse(Supplier> supplier); /** * If this {@code Try} is a success, get the contained value; if this * {@code Try} is a failure, throw the contained exception. * * @return the contained value, if available; potentially {@code null} * @throws Exception if this {@code Try} is a failure */ public abstract V get() throws Exception; /** * If this {@code Try} is a success, get the contained value; if this * {@code Try} is a failure, throw the contained exception. * * @return the contained value, if available * @throws Exception if this {@code Try} is a failure or the contained value * is {@code null} * * @since 6.0 */ @API(status = EXPERIMENTAL, since = "6.0") public final @NonNull V getNonNull() throws Exception { var value = get(); return checkNotNull(value, "value"); } /** * If this {@code Try} is a success, get the contained value; if this * {@code Try} is a failure, call the supplied {@link Function} with the * contained exception and throw the resulting {@link Exception}. * * @param exceptionTransformer the transformer to be called with the * contained exception, if available; must not be {@code null} * @return the contained value, if available * @throws E if this {@code Try} is a failure */ public abstract V getOrThrow(Function exceptionTransformer) throws E; /** * If this {@code Try} is a success, get the contained value; if this * {@code Try} is a failure, call the supplied {@link Function} with the * contained exception and throw the resulting {@link Exception}. * * @param exceptionTransformer the transformer to be called with the * contained exception, if available; must not be {@code null} * @return the contained value, if available and not {@code null} * @throws E if this {@code Try} is a failure or the contained value * is {@code null} */ @API(status = EXPERIMENTAL, since = "6.0") public final @NonNull V getNonNullOrThrow( Function<@Nullable Exception, E> exceptionTransformer) throws E { var value = getOrThrow(exceptionTransformer); if (value == null) { throw exceptionTransformer.apply(null); } return value; } /** * If this {@code Try} is a success, call the supplied {@link Consumer} with * the contained value; otherwise, do nothing. * * @param valueConsumer the consumer to be called with the contained value, * if available; must not be {@code null} * @return the same {@code Try} for method chaining */ public abstract Try ifSuccess(Consumer valueConsumer); /** * If this {@code Try} is a failure, call the supplied {@link Consumer} with * the contained exception; otherwise, do nothing. * * @param causeConsumer the consumer to be called with the contained * exception, if available; must not be {@code null} * @return the same {@code Try} for method chaining */ public abstract Try ifFailure(Consumer causeConsumer); /** * If this {@code Try} is a failure, return an empty {@link Optional}; if * this {@code Try} is a success, wrap the contained value using * {@link Optional#ofNullable(Object)}. * * @return an {@link Optional}; never {@code null} but potentially * empty */ public abstract Optional toOptional(); /** * A transformer for values of type {@code S} to type {@code T}. * *

The {@code Transformer} interface is similar to {@link Function}, * except that a {@code Transformer} may throw an exception. */ @FunctionalInterface public interface Transformer { /** * Apply this transformer to the supplied value. * * @throws Exception if the transformation fails */ T apply(S value) throws Exception; } private static final class Success extends Try { private final V value; Success(V value) { this.value = value; } @Override public Try andThenTry(Transformer transformer) { checkNotNull(transformer, "transformer"); return Try.call(() -> transformer.apply(this.value)); } @Override public Try andThen(Function> function) { checkNotNull(function, "function"); return Try.of(() -> function.apply(this.value)); } @Override public Try orElseTry(Callable action) { // don't call action because this Try is a success return this; } @Override public Try orElse(Supplier> supplier) { // don't call supplier because this Try is a success return this; } @Override public V get() { return this.value; } @Override public V getOrThrow(Function exceptionTransformer) { // don't call exceptionTransformer because this Try is a success return this.value; } @Override public Try ifSuccess(Consumer valueConsumer) { checkNotNull(valueConsumer, "valueConsumer"); valueConsumer.accept(this.value); return this; } @Override public Try ifFailure(Consumer causeConsumer) { // don't call causeConsumer because this Try was a success return this; } @Override public Optional toOptional() { return Optional.ofNullable(this.value); } @Override public boolean equals(Object that) { if (this == that) { return true; } if (that == null || this.getClass() != that.getClass()) { return false; } return Objects.equals(this.value, ((Success) that).value); } @Override public int hashCode() { return Objects.hash(value); } } private static final class Failure extends Try { private final Exception cause; Failure(Exception cause) { this.cause = cause; } @Override public Try andThenTry(Transformer transformer) { // don't call transformer because this Try is a failure return uncheckedCast(); } @Override public Try andThen(Function> function) { // don't call function because this Try is a failure return uncheckedCast(); } @SuppressWarnings("unchecked") private Try uncheckedCast() { return (Try) this; } @Override public Try orElseTry(Callable action) { checkNotNull(action, "action"); return Try.call(action); } @Override public Try orElse(Supplier> supplier) { checkNotNull(supplier, "supplier"); return Try.of(supplier::get); } @Contract(" -> fail") @Override public V get() throws Exception { throw this.cause; } @Contract("_ -> fail") @Override public V getOrThrow(Function exceptionTransformer) throws E { checkNotNull(exceptionTransformer, "exceptionTransformer"); throw exceptionTransformer.apply(this.cause); } @Override public Try ifSuccess(Consumer valueConsumer) { // don't call valueConsumer because this Try is a failure return this; } @Override public Try ifFailure(Consumer causeConsumer) { checkNotNull(causeConsumer, "causeConsumer"); causeConsumer.accept(this.cause); return this; } @Override public Optional toOptional() { return Optional.empty(); } @Override public boolean equals(Object that) { if (this == that) { return true; } if (that == null || this.getClass() != that.getClass()) { return false; } return Objects.equals(this.cause, ((Failure) that).cause); } @Override public int hashCode() { return Objects.hash(cause); } } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/function/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Functional interfaces and support classes. */ @NullMarked package org.junit.platform.commons.function; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/io/DefaultResource.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.io; import java.net.URI; import java.util.Objects; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.annotation.Contract; /** * Default implementation of {@link Resource}. * * @since 1.14 */ record DefaultResource(String name, URI uri) implements Resource { DefaultResource { checkNotNull(name, "name"); checkNotNull(uri, "uri"); } @Override public String getName() { return this.name; } @Override public URI getUri() { return this.uri; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj instanceof org.junit.platform.commons.io.Resource that) { return this.name.equals(that.getName()) // && this.uri.equals(that.getUri()); } return false; } @Override public int hashCode() { return Objects.hash(name, uri); } // Cannot use Preconditions due to package cycle @Contract("null, _ -> fail; !null, _ -> param1") private static void checkNotNull(@Nullable T input, String title) { if (input == null) { throw new PreconditionViolationException(title + " must not be null"); } } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/io/Resource.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.io; import static org.apiguardian.api.API.Status.MAINTAINED; import java.io.IOException; import java.io.InputStream; import java.net.URI; import org.apiguardian.api.API; /** * {@code Resource} represents a resource on the classpath. * *

WARNING: a {@code Resource} must provide correct * {@link Object#equals(Object) equals} and {@link Object#hashCode() hashCode} * implementations since a {@code Resource} may potentially be stored in a * collection or map. * * @since 1.14 * @see org.junit.platform.commons.support.ResourceSupport */ @API(status = MAINTAINED, since = "1.14") public interface Resource { /** * Create a new {@link Resource} with the given name and URI. * * @param name the name of the resource; never {@code null} * @param uri the URI of the resource; never {@code null} * @return a new {@code Resource} * @since 1.14 */ static Resource of(String name, URI uri) { return new DefaultResource(name, uri); } /** * Get the name of this resource. * *

The resource name is a {@code /}-separated path. The path is relative * to the classpath root in which the resource is located. * * @return the resource name; never {@code null} */ String getName(); /** * Get the URI of this resource. * * @return the URI of the resource; never {@code null} */ URI getUri(); /** * Get an {@link InputStream} for reading this resource. * *

The default implementation delegates to {@link java.net.URL#openStream()} * for this resource's {@link #getUri() URI}. * * @return an input stream for this resource; never {@code null} * @throws IOException if an I/O exception occurs */ default InputStream getInputStream() throws IOException { return getUri().toURL().openStream(); } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/io/ResourceFilter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.io; import static org.apiguardian.api.API.Status.MAINTAINED; import java.util.function.Predicate; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.annotation.Contract; /** * Resource filter used by reflection and classpath scanning support. * * @since 1.14 * @see Resource */ @API(status = MAINTAINED, since = "1.14") public class ResourceFilter { /** * Create a {@link ResourceFilter} instance from a predicate. * * @param resourcePredicate the resource predicate; never {@code null} * @return an instance of {@code ResourceFilter}; never {@code null} */ public static ResourceFilter of(Predicate resourcePredicate) { return new ResourceFilter(checkNotNull(resourcePredicate, "resourcePredicate")); } private final Predicate predicate; private ResourceFilter(Predicate predicate) { this.predicate = predicate; } /** * Test whether the given resource matches this filter. * * @param resource the resource to test; never {@code null} * @return {@code true} if the resource matches this filter, otherwise * {@code false} */ public boolean match(Resource resource) { return predicate.test(checkNotNull(resource, "resource")); } // Cannot use Preconditions due to package cycle @Contract("null, _ -> fail; !null, _ -> param1") private static T checkNotNull(@Nullable T input, String title) { if (input == null) { throw new PreconditionViolationException(title + " must not be null"); } return input; } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/io/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * IO-related interfaces and support classes */ @NullMarked package org.junit.platform.commons.io; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LogRecordListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.logging; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.stream.Stream; import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; /** * {@code LogRecordListener} is only intended for testing purposes within * JUnit's own test suite. * * @since 1.1 */ @API(status = INTERNAL, since = "1.1") public class LogRecordListener { // capture log records by thread to support parallel test execution private final ThreadLocal> logRecords = ThreadLocal.withInitial(ArrayList::new); /** * Inform the listener of a {@link LogRecord} that was submitted to JUL for * processing. */ public void logRecordSubmitted(LogRecord logRecord) { this.logRecords.get().add(logRecord); } /** * Get a stream of {@link LogRecord log records} that have been * {@linkplain #logRecordSubmitted submitted} to this listener by the * current thread. * *

As stated in the Javadoc for {@code LogRecord}, a submitted * {@code LogRecord} should not be updated by the client application. Thus, * the {@code LogRecords} in the returned stream should only be inspected for * testing purposes and not modified in any way. * * @see #stream(Level) * @see #stream(Class) * @see #stream(Class, Level) */ public Stream stream() { return this.logRecords.get().stream(); } /** * Get a stream of {@link LogRecord log records} that have been * {@linkplain #logRecordSubmitted submitted} to this listener by the current * thread at the given log level. * *

As stated in the Javadoc for {@code LogRecord}, a submitted * {@code LogRecord} should not be updated by the client application. Thus, * the {@code LogRecords} in the returned stream should only be inspected for * testing purposes and not modified in any way. * * @param level the log level for which to get the log records; never {@code null} * @since 1.4 * @see #stream() * @see #stream(Class) * @see #stream(Class, Level) */ @SuppressWarnings({ "ConstantValue", "ReferenceEquality" }) public Stream stream(Level level) { // NOTE: we cannot use org.junit.platform.commons.util.Preconditions here // since that would introduce a package cycle. if (level == null) { throw new JUnitException("Level must not be null"); } return stream().filter(logRecord -> logRecord.getLevel() == level); } /** * Get a stream of {@link LogRecord log records} that have been * {@linkplain #logRecordSubmitted submitted} to this listener by the current * thread for the logger name equal to the name of the given class. * *

As stated in the Javadoc for {@code LogRecord}, a submitted * {@code LogRecord} should not be updated by the client application. Thus, * the {@code LogRecords} in the returned stream should only be inspected for * testing purposes and not modified in any way. * * @param clazz the class for which to get the log records; never {@code null} * @see #stream() * @see #stream(Level) * @see #stream(Class, Level) */ @SuppressWarnings("ConstantValue") public Stream stream(Class clazz) { // NOTE: we cannot use org.junit.platform.commons.util.Preconditions here // since that would introduce a package cycle. if (clazz == null) { throw new JUnitException("Class must not be null"); } return stream().filter(logRecord -> logRecord.getLoggerName().equals(clazz.getName())); } /** * Get a stream of {@link LogRecord log records} that have been * {@linkplain #logRecordSubmitted submitted} to this listener by the current * thread for the logger name equal to the name of the given class at the given * log level. * *

As stated in the Javadoc for {@code LogRecord}, a submitted * {@code LogRecord} should not be updated by the client application. Thus, * the {@code LogRecords} in the returned stream should only be inspected for * testing purposes and not modified in any way. * * @param clazz the class for which to get the log records; never {@code null} * @param level the log level for which to get the log records; never {@code null} * @see #stream() * @see #stream(Level) * @see #stream(Class) */ @SuppressWarnings({ "ConstantValue", "ReferenceEquality" }) public Stream stream(Class clazz, Level level) { // NOTE: we cannot use org.junit.platform.commons.util.Preconditions here // since that would introduce a package cycle. if (level == null) { throw new JUnitException("Level must not be null"); } return stream(clazz).filter(logRecord -> logRecord.getLevel() == level); } /** * Clear all existing {@link LogRecord log records} that have been * {@linkplain #logRecordSubmitted submitted} to this listener by the * current thread. */ public void clear() { this.logRecords.get().clear(); } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/logging/Logger.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.logging; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.function.Supplier; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; /** * The {@code Logger} API serves as a simple logging facade for * {@code java.util.logging} (JUL). * * @since 1.0 */ @API(status = INTERNAL, since = "1.0") public interface Logger { /** * Log the message from the provided {@code messageSupplier} at error level. * *

Maps to {@link java.util.logging.Level#SEVERE} in JUL. */ void error(Supplier messageSupplier); /** * Log the provided {@code Throwable} and message from the provided * {@code messageSupplier} at error level. * *

Maps to {@link java.util.logging.Level#SEVERE} in JUL. */ void error(@Nullable Throwable throwable, Supplier messageSupplier); /** * Log the message from the provided {@code messageSupplier} at warning level. * *

Maps to {@link java.util.logging.Level#WARNING} in JUL. */ void warn(Supplier messageSupplier); /** * Log the provided {@code Throwable} and message from the provided * {@code messageSupplier} at warning level. * *

Maps to {@link java.util.logging.Level#WARNING} in JUL. */ void warn(@Nullable Throwable throwable, Supplier messageSupplier); /** * Log the message from the provided {@code messageSupplier} at info level. * *

Maps to {@link java.util.logging.Level#INFO} in JUL. */ void info(Supplier messageSupplier); /** * Log the provided {@code Throwable} and message from the provided * {@code messageSupplier} at info level. * *

Maps to {@link java.util.logging.Level#INFO} in JUL. */ void info(@Nullable Throwable throwable, Supplier messageSupplier); /** * Log the message from the provided {@code messageSupplier} at config level. * *

Maps to {@link java.util.logging.Level#CONFIG} in JUL. */ void config(Supplier messageSupplier); /** * Log the provided {@code Throwable} and message from the provided * {@code messageSupplier} at config level. * *

Maps to {@link java.util.logging.Level#CONFIG} in JUL. */ void config(@Nullable Throwable throwable, Supplier messageSupplier); /** * Log the message from the provided {@code messageSupplier} at debug level. * *

Maps to {@link java.util.logging.Level#FINE} in JUL. */ void debug(Supplier messageSupplier); /** * Log the provided {@code Throwable} and message from the provided * {@code messageSupplier} at debug level. * *

Maps to {@link java.util.logging.Level#FINE} in JUL. */ void debug(@Nullable Throwable throwable, Supplier messageSupplier); /** * Log the message from the provided {@code messageSupplier} at trace level. * *

Maps to {@link java.util.logging.Level#FINER} in JUL. */ void trace(Supplier messageSupplier); /** * Log the provided {@code Throwable} and message from the provided * {@code messageSupplier} at trace level. * *

Maps to {@link java.util.logging.Level#FINER} in JUL. */ void trace(@Nullable Throwable throwable, Supplier messageSupplier); } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LoggerFactory.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.logging; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.LogRecord; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; /** * Factory for the {@link Logger} facade for JUL. * * @since 1.0 */ @API(status = INTERNAL, since = "1.0") public final class LoggerFactory { private LoggerFactory() { /* no-op */ } private static final Set listeners = ConcurrentHashMap.newKeySet(); /** * Get a {@link Logger} for the specified class. * * @param clazz the class for which to get the logger; never {@code null} * @return the logger */ @SuppressWarnings("ConstantValue") public static Logger getLogger(Class clazz) { // NOTE: we cannot use org.junit.platform.commons.util.Preconditions here // since that would introduce a package cycle. if (clazz == null) { throw new JUnitException("Class must not be null"); } return new DelegatingLogger(clazz.getName()); } /** * Add the supplied {@link LogRecordListener} to the set of registered * listeners. */ public static void addListener(LogRecordListener listener) { listeners.add(listener); } /** * Remove the supplied {@link LogRecordListener} from the set of registered * listeners. */ public static void removeListener(LogRecordListener listener) { listeners.remove(listener); } private static final class DelegatingLogger implements Logger { private static final String FQCN = DelegatingLogger.class.getName(); private final String name; private final java.util.logging.Logger julLogger; DelegatingLogger(String name) { this.name = name; this.julLogger = java.util.logging.Logger.getLogger(this.name); } @Override public void error(Supplier messageSupplier) { log(Level.SEVERE, null, messageSupplier); } @Override public void error(@Nullable Throwable throwable, Supplier messageSupplier) { log(Level.SEVERE, throwable, messageSupplier); } @Override public void warn(Supplier messageSupplier) { log(Level.WARNING, null, messageSupplier); } @Override public void warn(@Nullable Throwable throwable, Supplier messageSupplier) { log(Level.WARNING, throwable, messageSupplier); } @Override public void info(Supplier messageSupplier) { log(Level.INFO, null, messageSupplier); } @Override public void info(@Nullable Throwable throwable, Supplier messageSupplier) { log(Level.INFO, throwable, messageSupplier); } @Override public void config(Supplier messageSupplier) { log(Level.CONFIG, null, messageSupplier); } @Override public void config(@Nullable Throwable throwable, Supplier messageSupplier) { log(Level.CONFIG, throwable, messageSupplier); } @Override public void debug(Supplier messageSupplier) { log(Level.FINE, null, messageSupplier); } @Override public void debug(@Nullable Throwable throwable, Supplier messageSupplier) { log(Level.FINE, throwable, messageSupplier); } @Override public void trace(Supplier messageSupplier) { log(Level.FINER, null, messageSupplier); } @Override public void trace(@Nullable Throwable throwable, Supplier messageSupplier) { log(Level.FINER, throwable, messageSupplier); } private void log(Level level, @Nullable Throwable throwable, Supplier messageSupplier) { boolean loggable = this.julLogger.isLoggable(level); if (loggable || !listeners.isEmpty()) { LogRecord logRecord = createLogRecord(level, throwable, messageSupplier.get()); if (loggable) { this.julLogger.log(logRecord); } listeners.forEach(listener -> listener.logRecordSubmitted(logRecord)); } } private LogRecord createLogRecord(Level level, @Nullable Throwable throwable, String message) { String sourceClassName = null; String sourceMethodName = null; boolean found = false; for (StackTraceElement element : new Throwable().getStackTrace()) { String className = element.getClassName(); if (FQCN.equals(className)) { found = true; } else if (found) { sourceClassName = className; sourceMethodName = element.getMethodName(); break; } } LogRecord logRecord = new LogRecord(level, message); logRecord.setLoggerName(this.name); logRecord.setThrown(throwable); logRecord.setSourceClassName(sourceClassName); logRecord.setSourceMethodName(sourceMethodName); logRecord.setResourceBundleName(this.julLogger.getResourceBundleName()); logRecord.setResourceBundle(this.julLogger.getResourceBundle()); return logRecord; } } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/logging/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Internal logging package. * *

DISCLAIMER

* *

These classes are intended solely for usage within the JUnit framework * itself. Any usage by external parties is not supported. * Use at your own risk! */ @NullMarked package org.junit.platform.commons.logging; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Common APIs and support utilities for the JUnit Platform. * *

DISCLAIMER

* *

Any API annotated with {@code @API(status = INTERNAL)} is intended solely * for usage within the JUnit framework itself. Any usage of internal * APIs by external parties is not supported! */ @NullMarked package org.junit.platform.commons; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/support/AnnotationSupport.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.support; import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.MAINTAINED; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.List; import java.util.ListIterator; import java.util.Optional; import java.util.function.Predicate; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.annotation.Contract; import org.junit.platform.commons.util.AnnotationUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ReflectionUtils; /** * {@code AnnotationSupport} provides static utility methods for common tasks * regarding annotations — for example, checking if a class, method, or * field is annotated with a particular annotation; finding annotations on a * given class, method, or field; finding fields or methods annotated with * a particular annotation, etc. * *

{@link org.junit.platform.engine.TestEngine TestEngine} and extension * authors are encouraged to use these supported methods in order to align with * the behavior of the JUnit Platform. * * @since 1.0 * @see ClassSupport * @see ModifierSupport * @see ReflectionSupport * @see ResourceSupport */ @API(status = MAINTAINED, since = "1.0") public final class AnnotationSupport { private AnnotationSupport() { /* no-op */ } /** * Determine if an annotation of {@code annotationType} is either * present or meta-present on the supplied optional * {@code element}. * *

Note: This method does not find repeatable annotations. * To check for repeatable annotations, use {@link #findRepeatableAnnotations(Optional, Class)} * and verify that the returned list is not empty. * * @param element an {@link Optional} containing the element on which to * search for the annotation; may be {@code null} or empty * @param annotationType the annotation type to search for; never {@code null} * @return {@code true} if the annotation is present or meta-present * @since 1.3 * @see #isAnnotated(AnnotatedElement, Class) * @see #findAnnotation(Optional, Class) * @see #findRepeatableAnnotations(Optional, Class) */ @API(status = MAINTAINED, since = "1.3") @Contract("null, _ -> false") @SuppressWarnings("NullableOptional") public static boolean isAnnotated(@Nullable Optional element, Class annotationType) { return AnnotationUtils.isAnnotated(element, annotationType); } /** * Determine if an annotation of {@code annotationType} is either * present or meta-present on the supplied * {@code element}. * *

Note: This method does not find repeatable annotations. * To check for repeatable annotations, use {@link #findRepeatableAnnotations(AnnotatedElement, Class)} * and verify that the returned list is not empty. * * @param element the element on which to search for the annotation; may be * {@code null} * @param annotationType the annotation type to search for; never {@code null} * @return {@code true} if the annotation is present or meta-present * @see #isAnnotated(Optional, Class) * @see #findAnnotation(AnnotatedElement, Class) * @see #findRepeatableAnnotations(AnnotatedElement, Class) */ @Contract("null, _ -> false") public static boolean isAnnotated(@Nullable AnnotatedElement element, Class annotationType) { return AnnotationUtils.isAnnotated(element, annotationType); } /** * Find the first annotation of {@code annotationType} that is either * present or meta-present on the supplied optional * {@code element}. * * @param the annotation type * @param element an {@link Optional} containing the element on which to * search for the annotation; may be {@code null} or empty * @param annotationType the annotation type to search for; never {@code null} * @return an {@code Optional} containing the annotation; never {@code null} but * potentially empty * @since 1.1 * @see #findAnnotation(AnnotatedElement, Class) */ @API(status = MAINTAINED, since = "1.1") @SuppressWarnings("NullableOptional") public static Optional findAnnotation( @Nullable Optional element, Class annotationType) { return AnnotationUtils.findAnnotation(element, annotationType); } /** * Find the first annotation of {@code annotationType} that is either * directly present, meta-present, or indirectly * present on the supplied {@code element}. * *

If the element is a class and the annotation is neither directly * present nor meta-present on the class, this method will * additionally search on interfaces implemented by the class before * finding an annotation that is indirectly present on the class. * * @param the annotation type * @param element the element on which to search for the annotation; may be * {@code null} * @param annotationType the annotation type to search for; never {@code null} * @return an {@code Optional} containing the annotation; never {@code null} but * potentially empty */ public static Optional findAnnotation(@Nullable AnnotatedElement element, Class annotationType) { return AnnotationUtils.findAnnotation(element, annotationType); } /** * Find the first annotation of the specified type that is either * directly present, meta-present, or indirectly * present on the supplied class. * *

If the annotation is neither directly present nor meta-present * on the class, this method will additionally search on interfaces implemented * by the class before searching for an annotation that is indirectly present * on the class (i.e., within the class inheritance hierarchy). * *

If the annotation still has not been found, this method will optionally * search recursively through the enclosing class hierarchy if * {@link SearchOption#INCLUDE_ENCLOSING_CLASSES} is specified. * *

If {@link SearchOption#DEFAULT} is specified, this method has the same * semantics as {@link #findAnnotation(AnnotatedElement, Class)}. * * @param the annotation type * @param clazz the class on which to search for the annotation; may be {@code null} * @param annotationType the annotation type to search for; never {@code null} * @param searchOption the {@code SearchOption} to use; never {@code null} * @return an {@code Optional} containing the annotation; never {@code null} but * potentially empty * @since 1.8 * @see SearchOption * @see #findAnnotation(AnnotatedElement, Class) * @deprecated Use {@link #findAnnotation(AnnotatedElement, Class)} * (for {@code SearchOption.DEFAULT}) or * {@link #findAnnotation(Class, Class, List)} (for * {@code SearchOption.INCLUDE_ENCLOSING_CLASSES}) instead */ @Deprecated(since = "1.12") @API(status = DEPRECATED, since = "1.12") @SuppressWarnings("deprecation") public static Optional findAnnotation(@Nullable Class clazz, Class annotationType, SearchOption searchOption) { Preconditions.notNull(searchOption, "SearchOption must not be null"); return AnnotationUtils.findAnnotation(clazz, annotationType, searchOption == SearchOption.INCLUDE_ENCLOSING_CLASSES); } /** * Find the first annotation of the specified type that is either * directly present, meta-present, or indirectly * present on the supplied class. * *

If the annotation is neither directly present nor meta-present * on the class, this method will additionally search on interfaces implemented * by the class before searching for an annotation that is indirectly present * on the class (i.e., within the class inheritance hierarchy). * *

If the annotation still has not been found, this method will optionally * search recursively through the supplied enclosing instance types, starting * at the innermost enclosing class (the last one in the supplied list of * {@code enclosingInstanceTypes}). * * @implNote The classes supplied as {@code enclosingInstanceTypes} may * differ from the classes returned from invocations of * {@link Class#getEnclosingClass()} — for example, when a nested test * class is inherited from a superclass. * * @param the annotation type * @param clazz the class on which to search for the annotation; may be {@code null} * @param annotationType the annotation type to search for; never {@code null} * @param enclosingInstanceTypes the runtime types of the enclosing * instances for the class, ordered from outermost to innermost, * excluding {@code clazz}; never {@code null} * @return an {@code Optional} containing the annotation; never {@code null} but * potentially empty if {@code clazz} is not an inner class * @since 1.12 * @see #findAnnotation(AnnotatedElement, Class) */ @API(status = MAINTAINED, since = "1.13.3") public static Optional findAnnotation(@Nullable Class clazz, Class annotationType, List> enclosingInstanceTypes) { Preconditions.notNull(enclosingInstanceTypes, "enclosingInstanceTypes must not be null"); Optional annotation = findAnnotation(clazz, annotationType); if (annotation.isEmpty()) { ListIterator> iterator = enclosingInstanceTypes.listIterator(enclosingInstanceTypes.size()); while (iterator.hasPrevious()) { annotation = findAnnotation(iterator.previous(), annotationType); if (annotation.isPresent()) { break; } } } return annotation; } /** * Find all repeatable {@linkplain Annotation annotations} of the * supplied {@code annotationType} that are either present, * indirectly present, or meta-present on the supplied * optional {@code element}. * *

See {@link #findRepeatableAnnotations(AnnotatedElement, Class)} for * details of the algorithm used. * * @param the annotation type * @param element an {@link Optional} containing the element on which to * search for the annotation; may be {@code null} or empty * @param annotationType the repeatable annotation type to search for; never {@code null} * @return an immutable list of all such annotations found; never {@code null} * @since 1.5 * @see java.lang.annotation.Repeatable * @see java.lang.annotation.Inherited * @see #findRepeatableAnnotations(AnnotatedElement, Class) */ @API(status = MAINTAINED, since = "1.5") @SuppressWarnings("NullableOptional") public static List findRepeatableAnnotations( @Nullable Optional element, Class annotationType) { return AnnotationUtils.findRepeatableAnnotations(element, annotationType); } /** * Find all repeatable {@linkplain Annotation annotations} of the * supplied {@code annotationType} that are either present, * indirectly present, or meta-present on the supplied * {@link AnnotatedElement}. * *

This method extends the functionality of * {@link java.lang.reflect.AnnotatedElement#getAnnotationsByType(Class)} * with additional support for meta-annotations. * *

In addition, if the element is a class and the repeatable annotation * is {@link java.lang.annotation.Inherited @Inherited}, this method will * search on superclasses first in order to support top-down semantics. * The result is that this algorithm finds repeatable annotations that * would be shadowed and therefore not visible according to Java's * standard semantics for inherited, repeatable annotations, but most * developers will naturally assume that all repeatable annotations in JUnit * are discovered regardless of whether they are declared stand-alone, in a * container, or as a meta-annotation (e.g., multiple declarations of * {@code @ExtendWith} within a test class hierarchy). * *

If the element is a class and the repeatable annotation is not * discovered within the class hierarchy, this method will additionally * search on interfaces implemented by each class in the hierarchy. * *

If the supplied {@code element} is {@code null}, this method returns * an empty list. * *

The search algorithm will also find repeatable annotations used as * meta-annotations on other repeatable annotations. * * @param the annotation type * @param element the element to search on; may be {@code null} * @param annotationType the repeatable annotation type to search for; never {@code null} * @return an immutable list of all such annotations found; never {@code null} * @see java.lang.annotation.Repeatable * @see java.lang.annotation.Inherited */ public static List findRepeatableAnnotations(@Nullable AnnotatedElement element, Class annotationType) { return AnnotationUtils.findRepeatableAnnotations(element, annotationType); } /** * Find all {@code public} {@linkplain Field fields} of the supplied class * or interface that are declared to be of the specified {@code fieldType} * and are annotated or meta-annotated with the specified * {@code annotationType}. * *

Consult the Javadoc for {@link Class#getFields()} for details on * inheritance and ordering. * * @param clazz the class or interface in which to find the fields; never {@code null} * @param fieldType the declared type of fields to find; never {@code null} * @param annotationType the annotation type to search for; never {@code null} * @return the list of all such fields found; neither {@code null} nor mutable * @see Class#getFields() * @see Field#getType() * @see #findAnnotatedFields(Class, Class) * @see #findAnnotatedFields(Class, Class, Predicate, HierarchyTraversalMode) * @see ReflectionSupport#findFields(Class, Predicate, HierarchyTraversalMode) * @see ReflectionSupport#tryToReadFieldValue(Field, Object) */ public static List findPublicAnnotatedFields(Class clazz, Class fieldType, Class annotationType) { return AnnotationUtils.findPublicAnnotatedFields(clazz, fieldType, annotationType); } /** * Find all distinct {@linkplain Field fields} of the supplied class or * interface that are annotated or meta-annotated with the specified * {@code annotationType}, using top-down search semantics within the type * hierarchy. * *

Fields declared in the same class or interface will be ordered using * an algorithm that is deterministic but intentionally nonobvious. * *

The results will not contain fields that are hidden or * {@linkplain Field#isSynthetic() synthetic}. * * @param clazz the class or interface in which to find the fields; never {@code null} * @param annotationType the annotation type to search for; never {@code null} * @return the list of all such fields found; neither {@code null} nor mutable * @since 1.4 * @see Class#getDeclaredFields() * @see #findPublicAnnotatedFields(Class, Class, Class) * @see #findAnnotatedFields(Class, Class, Predicate, HierarchyTraversalMode) * @see ReflectionSupport#findFields(Class, Predicate, HierarchyTraversalMode) * @see ReflectionSupport#tryToReadFieldValue(Field, Object) */ @API(status = MAINTAINED, since = "1.4") public static List findAnnotatedFields(Class clazz, Class annotationType) { return findAnnotatedFields(clazz, annotationType, field -> true); } /** * Find all distinct {@linkplain Field fields} of the supplied class or * interface that are annotated or meta-annotated with the specified * {@code annotationType} and match the specified {@code predicate}, using * top-down search semantics within the type hierarchy. * *

Fields declared in the same class or interface will be ordered using * an algorithm that is deterministic but intentionally nonobvious. * *

The results will not contain fields that are hidden or * {@linkplain Field#isSynthetic() synthetic}. * * @param clazz the class or interface in which to find the fields; never {@code null} * @param annotationType the annotation type to search for; never {@code null} * @param predicate the field filter; never {@code null} * @return the list of all such fields found; neither {@code null} nor mutable * @since 1.10 * @see Class#getDeclaredFields() * @see #findPublicAnnotatedFields(Class, Class, Class) * @see #findAnnotatedFields(Class, Class, Predicate, HierarchyTraversalMode) * @see ReflectionSupport#findFields(Class, Predicate, HierarchyTraversalMode) * @see ReflectionSupport#tryToReadFieldValue(Field, Object) */ @API(status = MAINTAINED, since = "1.10") public static List findAnnotatedFields(Class clazz, Class annotationType, Predicate predicate) { return AnnotationUtils.findAnnotatedFields(clazz, annotationType, predicate); } /** * Find all distinct {@linkplain Field fields} of the supplied class or * interface that are annotated or meta-annotated with the specified * {@code annotationType} and match the specified {@code predicate}, using * the supplied hierarchy traversal mode. * *

Fields declared in the same class or interface will be ordered using * an algorithm that is deterministic but intentionally nonobvious. * *

The results will not contain fields that are hidden or * {@linkplain Field#isSynthetic() synthetic}. * * @param clazz the class or interface in which to find the fields; never {@code null} * @param annotationType the annotation type to search for; never {@code null} * @param predicate the field filter; never {@code null} * @param traversalMode the hierarchy traversal mode; never {@code null} * @return the list of all such fields found; neither {@code null} nor mutable * @since 1.4 * @see Class#getDeclaredFields() * @see #findAnnotatedFields(Class, Class) * @see ReflectionSupport#findFields(Class, Predicate, HierarchyTraversalMode) * @see ReflectionSupport#tryToReadFieldValue(Field, Object) */ @API(status = MAINTAINED, since = "1.4") public static List findAnnotatedFields(Class clazz, Class annotationType, Predicate predicate, HierarchyTraversalMode traversalMode) { Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); return AnnotationUtils.findAnnotatedFields(clazz, annotationType, predicate, ReflectionUtils.HierarchyTraversalMode.valueOf(traversalMode.name())); } /** * Find the values of all non-static {@linkplain Field fields} of the supplied * {@code instance} that are annotated or meta-annotated with the * specified {@code annotationType}, using top-down search semantics within * the type hierarchy. * *

Values from fields declared in the same class or interface will be * ordered using an algorithm that is deterministic but intentionally * nonobvious. * *

The results will not contain values from fields that are hidden * or {@linkplain Field#isSynthetic() synthetic}. * * @param instance the instance in which to find the fields; never {@code null} * @param annotationType the annotation type to search for; never {@code null} * @return the list of all such field values found; neither {@code null} nor mutable * @since 1.4 * @see #findAnnotatedFields(Class, Class) * @see #findAnnotatedFields(Class, Class, Predicate, HierarchyTraversalMode) * @see ReflectionSupport#findFields(Class, Predicate, HierarchyTraversalMode) * @see ReflectionSupport#tryToReadFieldValue(Field, Object) */ @API(status = MAINTAINED, since = "1.4") public static List<@Nullable Object> findAnnotatedFieldValues(Object instance, Class annotationType) { Preconditions.notNull(instance, "instance must not be null"); List fields = findAnnotatedFields(instance.getClass(), annotationType, ModifierSupport::isNotStatic, HierarchyTraversalMode.TOP_DOWN); @SuppressWarnings("unchecked") List<@Nullable Object> result = (List<@Nullable Object>) ReflectionUtils.readFieldValues(fields, instance); return result; } /** * Find the values of all static {@linkplain Field fields} of the supplied * class or interface that are annotated or meta-annotated with the * specified {@code annotationType}, using top-down search semantics within * the type hierarchy. * *

Values from fields declared in the same class or interface will be * ordered using an algorithm that is deterministic but intentionally * nonobvious. * *

The results will not contain values from fields that are hidden * or {@linkplain Field#isSynthetic() synthetic}. * * @param clazz the class or interface in which to find the fields; never {@code null} * @param annotationType the annotation type to search for; never {@code null} * @return the list of all such field values found; neither {@code null} nor mutable * @since 1.4 * @see #findAnnotatedFields(Class, Class) * @see #findAnnotatedFields(Class, Class, Predicate, HierarchyTraversalMode) * @see ReflectionSupport#findFields(Class, Predicate, HierarchyTraversalMode) * @see ReflectionSupport#tryToReadFieldValue(Field, Object) */ @API(status = MAINTAINED, since = "1.4") public static List<@Nullable Object> findAnnotatedFieldValues(Class clazz, Class annotationType) { List fields = findAnnotatedFields(clazz, annotationType, ModifierSupport::isStatic, HierarchyTraversalMode.TOP_DOWN); @SuppressWarnings("unchecked") List<@Nullable Object> result = (List<@Nullable Object>) ReflectionUtils.readFieldValues(fields, null); return result; } /** * Find the values of all non-static {@linkplain Field fields} of the supplied * {@code instance} that are declared to be of the specified {@code fieldType} * and are annotated or meta-annotated with the specified * {@code annotationType}, using top-down search semantics within the type * hierarchy. * *

Values from fields declared in the same class or interface will be * ordered using an algorithm that is deterministic but intentionally * nonobvious. * *

The results will not contain values from fields that are hidden * or {@linkplain Field#isSynthetic() synthetic}. * * @param instance the instance in which to find the fields; never {@code null} * @param annotationType the annotation type to search for; never {@code null} * @param fieldType the declared type of fields to find; never {@code null} * @return the list of all such field values found; neither {@code null} nor mutable * @since 1.4 * @see Field#getType() * @see #findAnnotatedFields(Class, Class) * @see #findAnnotatedFields(Class, Class, Predicate, HierarchyTraversalMode) * @see ReflectionSupport#findFields(Class, Predicate, HierarchyTraversalMode) * @see ReflectionSupport#tryToReadFieldValue(Field, Object) */ @SuppressWarnings("unchecked") @API(status = MAINTAINED, since = "1.4") public static List findAnnotatedFieldValues(Object instance, Class annotationType, Class fieldType) { Preconditions.notNull(instance, "instance must not be null"); Preconditions.notNull(fieldType, "fieldType must not be null"); Predicate predicate = // field -> ModifierSupport.isNotStatic(field) && fieldType.isAssignableFrom(field.getType()); List fields = findAnnotatedFields(instance.getClass(), annotationType, predicate, HierarchyTraversalMode.TOP_DOWN); return (List) ReflectionUtils.readFieldValues(fields, instance); } /** * Find the values of all static {@linkplain Field fields} of the supplied * class or interface that are declared to be of the specified * {@code fieldType} and are annotated or meta-annotated with the * specified {@code annotationType}, using top-down search semantics within * the type hierarchy. * *

Values from fields declared in the same class or interface will be * ordered using an algorithm that is deterministic but intentionally * nonobvious. * *

The results will not contain values from fields that are hidden * or {@linkplain Field#isSynthetic() synthetic}. * * @param clazz the class or interface in which to find the fields; never {@code null} * @param annotationType the annotation type to search for; never {@code null} * @param fieldType the declared type of fields to find; never {@code null} * @return the list of all such field values found; neither {@code null} nor mutable * @since 1.4 * @see Field#getType() * @see #findAnnotatedFields(Class, Class) * @see #findAnnotatedFields(Class, Class, Predicate, HierarchyTraversalMode) * @see ReflectionSupport#findFields(Class, Predicate, HierarchyTraversalMode) * @see ReflectionSupport#tryToReadFieldValue(Field, Object) */ @SuppressWarnings("unchecked") @API(status = MAINTAINED, since = "1.4") public static List findAnnotatedFieldValues(Class clazz, Class annotationType, Class fieldType) { Preconditions.notNull(fieldType, "fieldType must not be null"); Predicate predicate = // field -> ModifierSupport.isStatic(field) && fieldType.isAssignableFrom(field.getType()); List fields = findAnnotatedFields(clazz, annotationType, predicate, HierarchyTraversalMode.TOP_DOWN); return (List) ReflectionUtils.readFieldValues(fields, null); } /** * Find all distinct {@linkplain Method methods} of the supplied class or * interface that are annotated or meta-annotated with the specified * {@code annotationType}. * * @param clazz the class or interface in which to find the methods; never {@code null} * @param annotationType the annotation type to search for; never {@code null} * @param traversalMode the hierarchy traversal mode; never {@code null} * @return the list of all such methods found; neither {@code null} nor mutable */ public static List findAnnotatedMethods(Class clazz, Class annotationType, HierarchyTraversalMode traversalMode) { Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); return AnnotationUtils.findAnnotatedMethods(clazz, annotationType, ReflectionUtils.HierarchyTraversalMode.valueOf(traversalMode.name())); } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/support/ClassSupport.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.support; import static org.apiguardian.api.API.Status.MAINTAINED; import java.util.function.Function; import org.apiguardian.api.API; import org.junit.platform.commons.util.ClassUtils; /** * {@code ClassSupport} provides static utility methods for common tasks * regarding {@linkplain Class classes} — for example, generating a * comma-separated list of fully qualified class names for a set of supplied * classes. * *

{@link org.junit.platform.engine.TestEngine TestEngine} and extension * authors are encouraged to use these supported methods in order to align with * the behavior of the JUnit Platform. * * @since 1.1 * @see AnnotationSupport * @see ModifierSupport * @see ReflectionSupport * @see ResourceSupport */ @API(status = MAINTAINED, since = "1.1") public final class ClassSupport { private ClassSupport() { /* no-op */ } /** * Generate a comma-separated list of fully qualified class names for the * supplied classes. * * @param classes the classes whose names should be included in the * generated string * @return a comma-separated list of fully qualified class names, or an empty * string if the supplied class array is {@code null} or empty * @see #nullSafeToString(Function, Class...) */ public static String nullSafeToString(Class... classes) { return ClassUtils.nullSafeToString(classes); } /** * Generate a comma-separated list of mapped values for the supplied classes. * *

The values are generated by the supplied {@code mapper} * (e.g., {@code Class::getName}, {@code Class::getSimpleName}, etc.), unless * a class reference is {@code null} in which case it will be mapped to * {@code "null"}. * * @param mapper the mapper to use; never {@code null} * @param classes the classes to map * @return a comma-separated list of mapped values, or an empty string if * the supplied class array is {@code null} or empty * @see #nullSafeToString(Class...) */ public static String nullSafeToString(Function, ? extends String> mapper, Class... classes) { return ClassUtils.nullSafeToString(mapper, classes); } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/support/DefaultResource.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.support; import java.net.URI; import java.util.Objects; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; /** * Default implementation of {@link Resource}. * * @since 1.11 */ @SuppressWarnings("removal") record DefaultResource(String name, URI uri) implements Resource { public DefaultResource { Preconditions.notNull(name, "name must not be null"); Preconditions.notNull(uri, "uri must not be null"); } @Override public String getName() { return name; } @Override public URI getUri() { return uri; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj instanceof org.junit.platform.commons.io.Resource that) { return this.name.equals(that.getName()) // && this.uri.equals(that.getUri()); } return false; } @Override public int hashCode() { return Objects.hash(name, uri); } @Override public String toString() { return new ToStringBuilder(this) // .append("name", name) // .append("uri", uri) // .toString(); } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/support/HierarchyTraversalMode.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.support; import static org.apiguardian.api.API.Status.MAINTAINED; import org.apiguardian.api.API; /** * Modes in which a hierarchy can be traversed — for example, when * searching for methods or fields within a class hierarchy. * * @since 1.0 * @see #TOP_DOWN * @see #BOTTOM_UP */ @API(status = MAINTAINED, since = "1.0") public enum HierarchyTraversalMode { /** * Traverse the hierarchy using top-down semantics. */ TOP_DOWN, /** * Traverse the hierarchy using bottom-up semantics. */ BOTTOM_UP } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/support/ModifierSupport.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.support; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import java.lang.reflect.Member; import org.apiguardian.api.API; import org.junit.platform.commons.util.ReflectionUtils; /** * {@code ModifierSupport} provides static utility methods for working with * class and member {@linkplain java.lang.reflect.Modifier modifiers} — * for example, to determine if a class or member is declared as * {@code public}, {@code private}, {@code abstract}, {@code static}, etc. * *

{@link org.junit.platform.engine.TestEngine TestEngine} and extension * authors are encouraged to use these supported methods in order to align with * the behavior of the JUnit Platform. * * @since 1.4 * @see java.lang.reflect.Modifier * @see AnnotationSupport * @see ClassSupport * @see ReflectionSupport * @see ResourceSupport */ @API(status = MAINTAINED, since = "1.4") public final class ModifierSupport { private ModifierSupport() { /* no-op */ } /** * Determine if the supplied class is {@code public}. * * @param clazz the class to check; never {@code null} * @return {@code true} if the class is {@code public} * @see java.lang.reflect.Modifier#isPublic(int) */ public static boolean isPublic(Class clazz) { return ReflectionUtils.isPublic(clazz); } /** * Determine if the supplied member is {@code public}. * * @param member the member to check; never {@code null} * @return {@code true} if the member is {@code public} * @see java.lang.reflect.Modifier#isPublic(int) */ public static boolean isPublic(Member member) { return ReflectionUtils.isPublic(member); } /** * Determine if the supplied class is {@code private}. * * @param clazz the class to check; never {@code null} * @return {@code true} if the class is {@code private} * @see java.lang.reflect.Modifier#isPrivate(int) */ public static boolean isPrivate(Class clazz) { return ReflectionUtils.isPrivate(clazz); } /** * Determine if the supplied member is {@code private}. * * @param member the member to check; never {@code null} * @return {@code true} if the member is {@code private} * @see java.lang.reflect.Modifier#isPrivate(int) */ public static boolean isPrivate(Member member) { return ReflectionUtils.isPrivate(member); } /** * Determine if the supplied class is not {@code private}. * *

In other words this method will return {@code true} for classes * declared as {@code public}, {@code protected}, or * package private and {@code false} for classes declared as * {@code private}. * * @param clazz the class to check; never {@code null} * @return {@code true} if the class is not {@code private} * @see java.lang.reflect.Modifier#isPublic(int) * @see java.lang.reflect.Modifier#isProtected(int) * @see java.lang.reflect.Modifier#isPrivate(int) */ public static boolean isNotPrivate(Class clazz) { return ReflectionUtils.isNotPrivate(clazz); } /** * Determine if the supplied member is not {@code private}. * *

In other words this method will return {@code true} for members * declared as {@code public}, {@code protected}, or * package private and {@code false} for members declared as * {@code private}. * * @param member the member to check; never {@code null} * @return {@code true} if the member is not {@code private} * @see java.lang.reflect.Modifier#isPublic(int) * @see java.lang.reflect.Modifier#isProtected(int) * @see java.lang.reflect.Modifier#isPrivate(int) */ public static boolean isNotPrivate(Member member) { return ReflectionUtils.isNotPrivate(member); } /** * Determine if the supplied class is {@code abstract}. * * @param clazz the class to check; never {@code null} * @return {@code true} if the class is {@code abstract} * @see java.lang.reflect.Modifier#isAbstract(int) */ public static boolean isAbstract(Class clazz) { return ReflectionUtils.isAbstract(clazz); } /** * Determine if the supplied member is {@code abstract}. * * @param member the class to check; never {@code null} * @return {@code true} if the member is {@code abstract} * @see java.lang.reflect.Modifier#isAbstract(int) */ public static boolean isAbstract(Member member) { return ReflectionUtils.isAbstract(member); } /** * Determine if the supplied class is not {@code abstract}. * * @param clazz the class to check; never {@code null} * @return {@code true} if the class is not {@code abstract} * @since 1.13 * @see java.lang.reflect.Modifier#isAbstract(int) */ @API(status = EXPERIMENTAL, since = "6.0") public static boolean isNotAbstract(Class clazz) { return ReflectionUtils.isNotAbstract(clazz); } /** * Determine if the supplied member is not {@code abstract}. * * @param member the class to check; never {@code null} * @return {@code true} if the member is not {@code abstract} * @since 1.13 * @see java.lang.reflect.Modifier#isAbstract(int) */ @API(status = EXPERIMENTAL, since = "6.0") public static boolean isNotAbstract(Member member) { return ReflectionUtils.isNotAbstract(member); } /** * Determine if the supplied class is {@code static}. * * @param clazz the class to check; never {@code null} * @return {@code true} if the class is {@code static} * @see java.lang.reflect.Modifier#isStatic(int) */ public static boolean isStatic(Class clazz) { return ReflectionUtils.isStatic(clazz); } /** * Determine if the supplied member is {@code static}. * * @param member the member to check; never {@code null} * @return {@code true} if the member is {@code static} * @see java.lang.reflect.Modifier#isStatic(int) */ public static boolean isStatic(Member member) { return ReflectionUtils.isStatic(member); } /** * Determine if the supplied class is not {@code static}. * * @param clazz the class to check; never {@code null} * @return {@code true} if the class is not {@code static} * @see java.lang.reflect.Modifier#isStatic(int) */ public static boolean isNotStatic(Class clazz) { return ReflectionUtils.isNotStatic(clazz); } /** * Determine if the supplied member is not {@code static}. * * @param member the member to check; never {@code null} * @return {@code true} if the member is not {@code static} * @see java.lang.reflect.Modifier#isStatic(int) */ public static boolean isNotStatic(Member member) { return ReflectionUtils.isNotStatic(member); } /** * Determine if the supplied class is {@code final}. * * @param clazz the class to check; never {@code null} * @return {@code true} if the class is {@code final} * @since 1.5 * @see java.lang.reflect.Modifier#isFinal(int) */ @API(status = MAINTAINED, since = "1.5") public static boolean isFinal(Class clazz) { return ReflectionUtils.isFinal(clazz); } /** * Determine if the supplied class is not {@code final}. * * @param clazz the class to check; never {@code null} * @return {@code true} if the class is not {@code final} * @since 1.5 * @see java.lang.reflect.Modifier#isFinal(int) */ @API(status = MAINTAINED, since = "1.5") public static boolean isNotFinal(Class clazz) { return ReflectionUtils.isNotFinal(clazz); } /** * Determine if the supplied member is {@code final}. * * @param member the member to check; never {@code null} * @return {@code true} if the member is {@code final} * @since 1.5 * @see java.lang.reflect.Modifier#isFinal(int) */ @API(status = MAINTAINED, since = "1.5") public static boolean isFinal(Member member) { return ReflectionUtils.isFinal(member); } /** * Determine if the supplied member is not {@code final}. * * @param member the member to check; never {@code null} * @return {@code true} if the member is not {@code final} * @since 1.5 * @see java.lang.reflect.Modifier#isFinal(int) */ @API(status = MAINTAINED, since = "1.5") public static boolean isNotFinal(Member member) { return ReflectionUtils.isNotFinal(member); } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.support; import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.URI; import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.function.Try; import org.junit.platform.commons.io.ResourceFilter; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ReflectionUtils; /** * {@code ReflectionSupport} provides static utility methods for common * reflection tasks — for example, scanning for classes in the class-path * or module-path, loading classes, finding methods, invoking methods, etc. * *

{@link org.junit.platform.engine.TestEngine TestEngine} and extension * authors are encouraged to use these supported methods in order to align with * the behavior of the JUnit Platform. * * @since 1.0 * @see AnnotationSupport * @see ClassSupport * @see ModifierSupport * @see ResourceSupport */ @API(status = MAINTAINED, since = "1.0") public final class ReflectionSupport { private ReflectionSupport() { /* no-op */ } /** * Try to load a class by its primitive name or fully qualified * name, using the default {@link ClassLoader}. * *

Class names for arrays may be specified using either the JVM's internal * String representation (e.g., {@code [[I} for {@code int[][]}, * {@code [Lava.lang.String;} for {@code java.lang.String[]}, etc.) or * source code syntax (e.g., {@code int[][]}, {@code java.lang.String[]}, * etc.). * * @param name the name of the class to load; never {@code null} or blank * @return a successful {@code Try} containing the loaded class or a failed * {@code Try} containing the exception if no such class could be loaded; * never {@code null} * @since 1.4 * @see #tryToLoadClass(String, ClassLoader) * @see ResourceSupport#tryToGetResources(String) */ @API(status = MAINTAINED, since = "1.4") public static Try> tryToLoadClass(String name) { return ReflectionUtils.tryToLoadClass(name); } /** * Try to load a class by its primitive name or fully qualified * name, using the supplied {@link ClassLoader}. * *

See {@link ReflectionSupport#tryToLoadClass(String) tryToLoadClass(String)} * for details on support for class names for arrays. * * @param name the name of the class to load; never {@code null} or blank * @param classLoader the {@code ClassLoader} to use; never {@code null} * @return a successful {@code Try} containing the loaded class or a failed * {@code Try} containing the exception if no such class could be loaded; * never {@code null} * @since 1.10 * @see #tryToLoadClass(String) * @see ResourceSupport#tryToGetResources(String, ClassLoader) */ @API(status = MAINTAINED, since = "1.13.3") public static Try> tryToLoadClass(String name, ClassLoader classLoader) { return ReflectionUtils.tryToLoadClass(name, classLoader); } /** * Try to get the {@linkplain Resource resources} for the supplied classpath * resource name. * *

The name of a classpath resource must follow the semantics * for resource paths as defined in {@link ClassLoader#getResource(String)}. * *

If the supplied classpath resource name is prefixed with a slash * ({@code /}), the slash will be removed. * * @param classpathResourceName the name of the resource to load; never * {@code null} or blank * @return a successful {@code Try} containing the set of loaded resources * (potentially empty) or a failed {@code Try} containing the exception in * case a failure occurred while trying to list resources; never * {@code null} * @since 1.12 * @see #tryToGetResources(String, ClassLoader) * @deprecated Please use {@link ResourceSupport#tryToGetResources(String)} instead */ @API(status = DEPRECATED, since = "1.14") @Deprecated(since = "1.14", forRemoval = true) @SuppressWarnings("removal") public static Try> tryToGetResources(String classpathResourceName) { return ResourceSupport.tryToGetResources(classpathResourceName) // .andThenTry(ReflectionSupport::toSupportResourcesSet); } /** * Try to load the {@linkplain Resource resources} for the supplied classpath * resource name, using the supplied {@link ClassLoader}. * *

The name of a classpath resource must follow the semantics * for resource paths as defined in {@link ClassLoader#getResource(String)}. * *

If the supplied classpath resource name is prefixed with a slash * ({@code /}), the slash will be removed. * * @param classpathResourceName the name of the resource to load; never * {@code null} or blank * @param classLoader the {@code ClassLoader} to use; never {@code null} * @return a successful {@code Try} containing the set of loaded resources * (potentially empty) or a failed {@code Try} containing the exception in * case a failure occurred while trying to list resources; never * {@code null} * @since 1.12 * @see #tryToGetResources(String) * @deprecated Please use {@link ResourceSupport#tryToGetResources(String, ClassLoader)} instead */ @API(status = DEPRECATED, since = "1.14") @Deprecated(since = "1.14", forRemoval = true) @SuppressWarnings("removal") public static Try> tryToGetResources(String classpathResourceName, ClassLoader classLoader) { return ResourceSupport.tryToGetResources(classpathResourceName, classLoader) // .andThenTry(ReflectionSupport::toSupportResourcesSet); } /** * Find all {@linkplain Class classes} in the supplied classpath {@code root} * that match the specified {@code classFilter} and {@code classNameFilter} * predicates. * *

The classpath scanning algorithm searches recursively in subpackages * beginning with the root of the classpath. * * @param root the URI for the classpath root in which to scan; never * {@code null} * @param classFilter the class type filter; never {@code null} * @param classNameFilter the class name filter; never {@code null} * @return an immutable list of all such classes found; never {@code null} * but potentially empty * @see #findAllClassesInPackage(String, Predicate, Predicate) * @see #findAllClassesInModule(String, Predicate, Predicate) * @see ResourceSupport#findAllResourcesInClasspathRoot(URI, ResourceFilter) */ public static List> findAllClassesInClasspathRoot(URI root, Predicate> classFilter, Predicate classNameFilter) { return ReflectionUtils.findAllClassesInClasspathRoot(root, classFilter, classNameFilter); } /** * Find all {@linkplain Resource resources} in the supplied classpath {@code root} * that match the specified {@code resourceFilter} predicate. * *

The classpath scanning algorithm searches recursively in subpackages * beginning with the root of the classpath. * * @param root the URI for the classpath root in which to scan; never * {@code null} * @param resourceFilter the resource type filter; never {@code null} * @return an immutable list of all such resources found; never {@code null} * but potentially empty * @since 1.11 * @see #findAllResourcesInPackage(String, Predicate) * @see #findAllResourcesInModule(String, Predicate) * @deprecated Please use {@link ResourceSupport#findAllResourcesInClasspathRoot(URI, ResourceFilter)} instead */ @API(status = DEPRECATED, since = "1.14") @Deprecated(since = "1.14", forRemoval = true) @SuppressWarnings("removal") public static List findAllResourcesInClasspathRoot(URI root, Predicate resourceFilter) { return toSupportResourcesList( ResourceSupport.findAllResourcesInClasspathRoot(root, toResourceFilter(resourceFilter))); } /** * Find all {@linkplain Class classes} in the supplied classpath {@code root} * that match the specified {@code classFilter} and {@code classNameFilter} * predicates. * *

The classpath scanning algorithm searches recursively in subpackages * beginning with the root of the classpath. * * @param root the URI for the classpath root in which to scan; never * {@code null} * @param classFilter the class type filter; never {@code null} * @param classNameFilter the class name filter; never {@code null} * @return a stream of all such classes found; never {@code null} * but potentially empty * @since 1.10 * @see #streamAllClassesInPackage(String, Predicate, Predicate) * @see #streamAllClassesInModule(String, Predicate, Predicate) * @see ResourceSupport#streamAllResourcesInClasspathRoot(URI, ResourceFilter) */ @API(status = MAINTAINED, since = "1.10") public static Stream> streamAllClassesInClasspathRoot(URI root, Predicate> classFilter, Predicate classNameFilter) { return ReflectionUtils.streamAllClassesInClasspathRoot(root, classFilter, classNameFilter); } /** * Find all {@linkplain Resource resources} in the supplied classpath {@code root} * that match the specified {@code resourceFilter} predicate. * *

The classpath scanning algorithm searches recursively in subpackages * beginning with the root of the classpath. * * @param root the URI for the classpath root in which to scan; never * {@code null} * @param resourceFilter the resource type filter; never {@code null} * @return a stream of all such classes found; never {@code null} * but potentially empty * @since 1.11 * @see #streamAllResourcesInPackage(String, Predicate) * @see #streamAllResourcesInModule(String, Predicate) * @deprecated Please use {@link ResourceSupport#streamAllResourcesInClasspathRoot(URI, ResourceFilter)} instead */ @API(status = DEPRECATED, since = "1.14") @Deprecated(since = "1.14", forRemoval = true) @SuppressWarnings("removal") public static Stream streamAllResourcesInClasspathRoot(URI root, Predicate resourceFilter) { return toSupportResourcesStream( ResourceSupport.streamAllResourcesInClasspathRoot(root, toResourceFilter(resourceFilter))); } /** * Find all {@linkplain Class classes} in the supplied {@code basePackageName} * that match the specified {@code classFilter} and {@code classNameFilter} * predicates. * *

The classpath scanning algorithm searches recursively in subpackages * beginning within the supplied base package. * * @param basePackageName the name of the base package in which to start * scanning; must not be {@code null} and must be valid in terms of Java * syntax * @param classFilter the class type filter; never {@code null} * @param classNameFilter the class name filter; never {@code null} * @return an immutable list of all such classes found; never {@code null} * but potentially empty * @see #findAllClassesInClasspathRoot(URI, Predicate, Predicate) * @see #findAllClassesInModule(String, Predicate, Predicate) * @see ResourceSupport#findAllResourcesInPackage(String, ResourceFilter) */ public static List> findAllClassesInPackage(String basePackageName, Predicate> classFilter, Predicate classNameFilter) { return ReflectionUtils.findAllClassesInPackage(basePackageName, classFilter, classNameFilter); } /** * Find all {@linkplain Resource resources} in the supplied {@code basePackageName} * that match the specified {@code resourceFilter} predicate. * *

The classpath scanning algorithm searches recursively in subpackages * beginning within the supplied base package. The resulting list may include * identically named resources from different classpath roots. * * @param basePackageName the name of the base package in which to start * scanning; must not be {@code null} and must be valid in terms of Java * syntax * @param resourceFilter the resource type filter; never {@code null} * @return an immutable list of all such classes found; never {@code null} * but potentially empty * @since 1.11 * @see #findAllResourcesInClasspathRoot(URI, Predicate) * @see #findAllResourcesInModule(String, Predicate) * @deprecated Please use {@link ResourceSupport#findAllResourcesInPackage(String, ResourceFilter)} instead */ @API(status = DEPRECATED, since = "1.14") @Deprecated(since = "1.14", forRemoval = true) @SuppressWarnings("removal") public static List findAllResourcesInPackage(String basePackageName, Predicate resourceFilter) { return toSupportResourcesList( ResourceSupport.findAllResourcesInPackage(basePackageName, toResourceFilter(resourceFilter))); } /** * Find all {@linkplain Class classes} in the supplied {@code basePackageName} * that match the specified {@code classFilter} and {@code classNameFilter} * predicates. * *

The classpath scanning algorithm searches recursively in subpackages * beginning within the supplied base package. The resulting stream may * include identically named resources from different classpath roots. * * @param basePackageName the name of the base package in which to start * scanning; must not be {@code null} and must be valid in terms of Java * syntax * @param classFilter the class type filter; never {@code null} * @param classNameFilter the class name filter; never {@code null} * @return a stream of all such classes found; never {@code null} * but potentially empty * @since 1.10 * @see #streamAllClassesInClasspathRoot(URI, Predicate, Predicate) * @see #streamAllClassesInModule(String, Predicate, Predicate) * @see ResourceSupport#streamAllResourcesInPackage(String, ResourceFilter) */ @API(status = MAINTAINED, since = "1.10") public static Stream> streamAllClassesInPackage(String basePackageName, Predicate> classFilter, Predicate classNameFilter) { return ReflectionUtils.streamAllClassesInPackage(basePackageName, classFilter, classNameFilter); } /** * Find all {@linkplain Resource resources} in the supplied {@code basePackageName} * that match the specified {@code resourceFilter} predicate. * *

The classpath scanning algorithm searches recursively in subpackages * beginning within the supplied base package. The resulting stream may * include identically named resources from different classpath roots. * * @param basePackageName the name of the base package in which to start * scanning; must not be {@code null} and must be valid in terms of Java * syntax * @param resourceFilter the resource type filter; never {@code null} * @return a stream of all such resources found; never {@code null} * but potentially empty * @since 1.11 * @see #streamAllResourcesInClasspathRoot(URI, Predicate) * @see #streamAllResourcesInModule(String, Predicate) * @deprecated Please use {@link ResourceSupport#streamAllResourcesInPackage(String, ResourceFilter)} instead */ @API(status = DEPRECATED, since = "1.14") @Deprecated(since = "1.14", forRemoval = true) @SuppressWarnings("removal") public static Stream streamAllResourcesInPackage(String basePackageName, Predicate resourceFilter) { return toSupportResourcesStream( ResourceSupport.streamAllResourcesInPackage(basePackageName, toResourceFilter(resourceFilter))); } /** * Find all {@linkplain Class classes} in the supplied {@code moduleName} * that match the specified {@code classFilter} and {@code classNameFilter} * predicates. * *

The module-path scanning algorithm searches recursively in all * packages contained in the module. * * @param moduleName the name of the module to scan; never {@code null} or * empty * @param classFilter the class type filter; never {@code null} * @param classNameFilter the class name filter; never {@code null} * @return an immutable list of all such classes found; never {@code null} * but potentially empty * @since 1.1.1 * @see #findAllClassesInClasspathRoot(URI, Predicate, Predicate) * @see #findAllClassesInPackage(String, Predicate, Predicate) * @see ResourceSupport#findAllResourcesInModule(String, ResourceFilter) */ @API(status = MAINTAINED, since = "1.1.1") public static List> findAllClassesInModule(String moduleName, Predicate> classFilter, Predicate classNameFilter) { return ReflectionUtils.findAllClassesInModule(moduleName, classFilter, classNameFilter); } /** * Find all {@linkplain Class classes} in the supplied {@code module} * that match the specified {@code classFilter} and {@code classNameFilter} * predicates. * *

The module-path scanning algorithm searches recursively in all * packages contained in the module. * * @param module the module to scan; never {@code null} or unnamed * @param classFilter the class type filter; never {@code null} * @param classNameFilter the class name filter; never {@code null} * @return an immutable list of all such classes found; never {@code null} * but potentially empty * @since 6.1 * @see #findAllClassesInClasspathRoot(URI, Predicate, Predicate) * @see #findAllClassesInPackage(String, Predicate, Predicate) * @see ResourceSupport#findAllResourcesInModule(String, ResourceFilter) */ @API(status = EXPERIMENTAL, since = "6.1") public static List> findAllClassesInModule(Module module, Predicate> classFilter, Predicate classNameFilter) { return ReflectionUtils.findAllClassesInModule(module, classFilter, classNameFilter); } /** * Find all {@linkplain Resource resources} in the supplied {@code moduleName} * that match the specified {@code resourceFilter} predicate. * *

The module-path scanning algorithm searches recursively in all * packages contained in the module. * * @param moduleName the name of the module to scan; never {@code null} or * empty * @param resourceFilter the resource type filter; never {@code null} * @return an immutable list of all such resources found; never {@code null} * but potentially empty * @since 1.11 * @see #findAllResourcesInClasspathRoot(URI, Predicate) * @see #findAllResourcesInPackage(String, Predicate) * @deprecated Please use {@link ResourceSupport#findAllResourcesInModule(String, ResourceFilter)} instead */ @API(status = DEPRECATED, since = "1.14") @Deprecated(since = "1.14", forRemoval = true) @SuppressWarnings("removal") public static List findAllResourcesInModule(String moduleName, Predicate resourceFilter) { return toSupportResourcesList( ResourceSupport.findAllResourcesInModule(moduleName, toResourceFilter(resourceFilter))); } /** * Find all {@linkplain Class classes} in the supplied {@code moduleName} * that match the specified {@code classFilter} and {@code classNameFilter} * predicates. * *

The module-path scanning algorithm searches recursively in all * packages contained in the module. * * @param moduleName the name of the module to scan; never {@code null} or * empty * @param classFilter the class type filter; never {@code null} * @param classNameFilter the class name filter; never {@code null} * @return a stream of all such classes found; never {@code null} * but potentially empty * @since 1.10 * @see #streamAllClassesInClasspathRoot(URI, Predicate, Predicate) * @see #streamAllClassesInPackage(String, Predicate, Predicate) */ @API(status = MAINTAINED, since = "1.10") public static Stream> streamAllClassesInModule(String moduleName, Predicate> classFilter, Predicate classNameFilter) { return ReflectionUtils.streamAllClassesInModule(moduleName, classFilter, classNameFilter); } /** * Find all {@linkplain Resource resources} in the supplied {@code moduleName} * that match the specified {@code resourceFilter} predicate. * *

The module-path scanning algorithm searches recursively in all * packages contained in the module. * * @param moduleName the name of the module to scan; never {@code null} or * empty * @param resourceFilter the resource type filter; never {@code null} * @return a stream of all such resources found; never {@code null} * but potentially empty * @since 1.11 * @see #streamAllResourcesInClasspathRoot(URI, Predicate) * @see #streamAllResourcesInPackage(String, Predicate) * @deprecated Please use {@link ResourceSupport#streamAllResourcesInModule(String, ResourceFilter)} instead */ @API(status = DEPRECATED, since = "1.14") @Deprecated(since = "1.14", forRemoval = true) @SuppressWarnings("removal") public static Stream streamAllResourcesInModule(String moduleName, Predicate resourceFilter) { return toSupportResourcesStream( ResourceSupport.streamAllResourcesInModule(moduleName, toResourceFilter(resourceFilter))); } /** * Create a new instance of the specified {@link Class} by invoking * the constructor whose argument list matches the types of the supplied * arguments. * *

The constructor will be made accessible if necessary, and any checked * exception will be {@linkplain ExceptionUtils#throwAsUncheckedException masked} * as an unchecked exception. * * @param clazz the class to instantiate; never {@code null} * @param args the arguments to pass to the constructor, none of which may * be {@code null} * @return the new instance; never {@code null} * @see ExceptionUtils#throwAsUncheckedException(Throwable) */ public static T newInstance(Class clazz, Object... args) { return ReflectionUtils.newInstance(clazz, args); } /** * Invoke the supplied method, making it accessible if necessary and * {@linkplain ExceptionUtils#throwAsUncheckedException masking} any * checked exception as an unchecked exception. * * @param method the method to invoke; never {@code null} * @param target the object on which to invoke the method; may be * {@code null} if the method is {@code static} * @param args the arguments to pass to the method; never {@code null} * @return the value returned by the method invocation or {@code null} * if the return type is {@code void} * @see ExceptionUtils#throwAsUncheckedException(Throwable) */ public static @Nullable Object invokeMethod(Method method, @Nullable Object target, @Nullable Object... args) { return ReflectionUtils.invokeMethod(method, target, args); } /** * Find all distinct {@linkplain Field fields} of the supplied class or * interface that match the specified {@code predicate}. * *

Fields declared in the same class or interface will be ordered using * an algorithm that is deterministic but intentionally nonobvious. * *

The results will not contain fields that are * {@linkplain Field#isSynthetic() synthetic}. * * @param clazz the class or interface in which to find the fields; never {@code null} * @param predicate the field filter; never {@code null} * @param traversalMode the hierarchy traversal mode; never {@code null} * @return an immutable list of all such fields found; never {@code null} * but potentially empty * @since 1.4 */ @API(status = MAINTAINED, since = "1.4") public static List findFields(Class clazz, Predicate predicate, HierarchyTraversalMode traversalMode) { Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); return ReflectionUtils.findFields(clazz, predicate, ReflectionUtils.HierarchyTraversalMode.valueOf(traversalMode.name())); } /** * Find all distinct {@linkplain Field fields} of the supplied class or * interface that match the specified {@code predicate}. * *

Fields declared in the same class or interface will be ordered using * an algorithm that is deterministic but intentionally nonobvious. * *

The results will not contain fields that are * {@linkplain Field#isSynthetic() synthetic}. * * @param clazz the class or interface in which to find the fields; never {@code null} * @param predicate the field filter; never {@code null} * @param traversalMode the hierarchy traversal mode; never {@code null} * @return a stream of all such fields found; never {@code null} * but potentially empty * @since 1.10 */ @API(status = MAINTAINED, since = "1.10") public static Stream streamFields(Class clazz, Predicate predicate, HierarchyTraversalMode traversalMode) { Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); return ReflectionUtils.streamFields(clazz, predicate, ReflectionUtils.HierarchyTraversalMode.valueOf(traversalMode.name())); } /** * Try to read the value of a potentially inaccessible field. * *

If an exception occurs while reading the field, a failed {@link Try} * is returned that contains the corresponding exception. * * @param field the field to read; never {@code null} * @param instance the instance from which the value is to be read; may * be {@code null} for a static field * @since 1.4 */ @API(status = MAINTAINED, since = "1.4") public static Try<@Nullable Object> tryToReadFieldValue(Field field, @Nullable Object instance) { return ReflectionUtils.tryToReadFieldValue(field, instance); } /** * Find the first {@link Method} of the supplied class or interface that * meets the specified criteria, beginning with the specified class or * interface and traversing up the type hierarchy until such a method is * found or the type hierarchy is exhausted. * *

This method uses the {@link ClassLoader} of the supplied {@code clazz} * to load parameter types instead of using the default * {@code ClassLoader}, which allows parameter types to be resolved in different * {@code ClassLoader} arrangements. * *

The algorithm does not search for methods in {@link java.lang.Object}. * * @param clazz the class or interface in which to find the method; never {@code null} * @param methodName the name of the method to find; never {@code null} or empty * @param parameterTypeNames the fully qualified names of the types of parameters * accepted by the method, if any, provided as a comma-separated list * @return an {@code Optional} containing the method found; never {@code null} * but potentially empty if no such method could be found * @see #findMethod(Class, String, Class...) */ public static Optional findMethod(Class clazz, String methodName, @Nullable String parameterTypeNames) { return ReflectionUtils.findMethod(clazz, methodName, parameterTypeNames); } /** * Find the first {@link Method} of the supplied class or interface that * meets the specified criteria, beginning with the specified class or * interface and traversing up the type hierarchy until such a method is * found or the type hierarchy is exhausted. * *

The algorithm does not search for methods in {@link java.lang.Object}. * * @param clazz the class or interface in which to find the method; never {@code null} * @param methodName the name of the method to find; never {@code null} or empty * @param parameterTypes the types of parameters accepted by the method, if any; * never {@code null} * @return an {@code Optional} containing the method found; never {@code null} * but potentially empty if no such method could be found * @see #findMethod(Class, String, String) */ public static Optional findMethod(Class clazz, String methodName, Class... parameterTypes) { return ReflectionUtils.findMethod(clazz, methodName, parameterTypes); } /** * Find all distinct {@linkplain Method methods} of the supplied class or * interface that match the specified {@code predicate}. * *

The results will not contain methods that are overridden. * *

If you are looking for methods annotated with a certain annotation * type, consider using * {@link AnnotationSupport#findAnnotatedMethods(Class, Class, HierarchyTraversalMode)}. * * @param clazz the class or interface in which to find the methods; never {@code null} * @param predicate the method filter; never {@code null} * @param traversalMode the hierarchy traversal mode; never {@code null} * @return an immutable list of all such methods found; never {@code null} * but potentially empty */ public static List findMethods(Class clazz, Predicate predicate, HierarchyTraversalMode traversalMode) { Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); return ReflectionUtils.findMethods(clazz, predicate, ReflectionUtils.HierarchyTraversalMode.valueOf(traversalMode.name())); } /** * Find all distinct {@linkplain Method methods} of the supplied class or * interface that match the specified {@code predicate}. * *

The results will not contain methods that are overridden. * *

If you are looking for methods annotated with a certain annotation * type, consider using * {@link AnnotationSupport#findAnnotatedMethods(Class, Class, HierarchyTraversalMode)}. * * @param clazz the class or interface in which to find the methods; never {@code null} * @param predicate the method filter; never {@code null} * @param traversalMode the hierarchy traversal mode; never {@code null} * @return a stream of all such methods found; never {@code null} * but potentially empty * @since 1.10 */ @API(status = MAINTAINED, since = "1.10") public static Stream streamMethods(Class clazz, Predicate predicate, HierarchyTraversalMode traversalMode) { Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); return ReflectionUtils.streamMethods(clazz, predicate, ReflectionUtils.HierarchyTraversalMode.valueOf(traversalMode.name())); } /** * Find all nested classes within the supplied class, or inherited by the * supplied class, that conform to the supplied predicate. * *

This method does not search for nested classes * recursively. * *

Nested classes declared in the same enclosing class or interface will * be ordered using an algorithm that is deterministic but intentionally * nonobvious. * *

This method detects cycles in inner class hierarchies — * from the supplied class up to the outermost enclosing class — and * throws a {@link JUnitException} if such a cycle is detected. Cycles within * inner class hierarchies below the supplied class are not detected * by this method. * * @param clazz the class to be searched; never {@code null} * @param predicate the predicate against which the list of nested classes is * checked; never {@code null} * @return an immutable list of all such classes found; never {@code null} * but potentially empty * @throws JUnitException if a cycle is detected within an inner class hierarchy */ public static List> findNestedClasses(Class clazz, Predicate> predicate) throws JUnitException { return ReflectionUtils.findNestedClasses(clazz, predicate); } /** * Find all nested classes within the supplied class, or inherited by the * supplied class, that conform to the supplied predicate. * *

This method does not search for nested classes * recursively. * *

Nested classes declared in the same enclosing class or interface will * be ordered using an algorithm that is deterministic but intentionally * nonobvious. * *

This method detects cycles in inner class hierarchies — * from the supplied class up to the outermost enclosing class — and * throws a {@link JUnitException} if such a cycle is detected. Cycles within * inner class hierarchies below the supplied class are not detected * by this method. * * @param clazz the class to be searched; never {@code null} * @param predicate the predicate against which the list of nested classes is * checked; never {@code null} * @return a stream of all such classes found; never {@code null} * but potentially empty * @throws JUnitException if a cycle is detected within an inner class hierarchy * @since 1.10 */ @API(status = MAINTAINED, since = "1.10") public static Stream> streamNestedClasses(Class clazz, Predicate> predicate) throws JUnitException { return ReflectionUtils.streamNestedClasses(clazz, predicate); } /** * Make the supplied field accessible via reflection. * *

If you're looking for similar functionality for constructors or * methods, consider using {@link #newInstance(Class, Object...)} or * {@link #invokeMethod(Method, Object, Object...)}. * * @param field the field to make accessible; never {@code null} * @return the supplied field * @since 1.12 * @see Field#setAccessible(boolean) */ @API(status = MAINTAINED, since = "1.13.3") public static Field makeAccessible(Field field) { return ReflectionUtils.makeAccessible(Preconditions.notNull(field, "field must not be null")); } @SuppressWarnings("removal") private static ResourceFilter toResourceFilter(Predicate resourceFilter) { Preconditions.notNull(resourceFilter, "resourceFilter must not be null"); return ResourceFilter.of(r -> resourceFilter.test(Resource.of(r))); } @SuppressWarnings("removal") static List toSupportResourcesList(List resources) { return toSupportResourcesStream(resources.stream()).toList(); } @SuppressWarnings("removal") static Set toSupportResourcesSet(Set resources) { return toSupportResourcesStream(resources.stream()).collect(Collectors.toCollection(LinkedHashSet::new)); } @SuppressWarnings("removal") static Stream toSupportResourcesStream(Stream resources) { return resources.map(Resource::of); } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/support/Resource.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.support; import static org.apiguardian.api.API.Status.DEPRECATED; import java.net.URI; import java.util.function.Predicate; import org.apiguardian.api.API; import org.junit.platform.commons.util.Preconditions; /** * {@code Resource} represents a resource on the classpath. * *

WARNING: a {@code Resource} must provide correct * {@link Object#equals(Object) equals} and {@link Object#hashCode() hashCode} * implementations since a {@code Resource} may potentially be stored in a * collection or map. * * @since 1.11 * @see ReflectionSupport#findAllResourcesInClasspathRoot(URI, Predicate) * @see ReflectionSupport#findAllResourcesInPackage(String, Predicate) * @see ReflectionSupport#findAllResourcesInModule(String, Predicate) * @see ReflectionSupport#streamAllResourcesInClasspathRoot(URI, Predicate) * @see ReflectionSupport#streamAllResourcesInPackage(String, Predicate) * @see ReflectionSupport#streamAllResourcesInModule(String, Predicate) * @deprecated Please use {@link org.junit.platform.commons.io.Resource} instead. */ @SuppressWarnings("removal") @API(status = DEPRECATED, since = "1.14") @Deprecated(since = "1.14", forRemoval = true) public interface Resource extends org.junit.platform.commons.io.Resource { /** * Create a new {@link Resource} from the supplied * {@link org.junit.platform.commons.io.Resource}. * * @param resource the resource to copy attributes from; never {@code null} * @return a new {@code Resource} * @since 1.14 */ static Resource of(org.junit.platform.commons.io.Resource resource) { Preconditions.notNull(resource, "resource must not be null"); return new DefaultResource(resource.getName(), resource.getUri()); } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/support/ResourceSupport.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.support; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import java.net.URI; import java.util.List; import java.util.Set; import java.util.function.Predicate; import java.util.stream.Stream; import org.apiguardian.api.API; import org.junit.platform.commons.function.Try; import org.junit.platform.commons.io.Resource; import org.junit.platform.commons.io.ResourceFilter; import org.junit.platform.commons.util.ReflectionUtils; /** * {@code ResourceSupport} provides static utility methods for common tasks * dealing with resources; for example, scanning for resources on the class path * or module path. * *

{@link org.junit.platform.engine.TestEngine TestEngine} and extension * authors are encouraged to use these supported methods in order to align with * the behavior of the JUnit Platform. * * @since 1.14 * @see AnnotationSupport * @see ClassSupport * @see ModifierSupport * @see ReflectionSupport */ @API(status = MAINTAINED, since = "1.14") public class ResourceSupport { /** * Try to get the {@linkplain Resource resources} for the supplied classpath * resource name. * *

The name of a classpath resource must follow the semantics * for resource paths as defined in {@link ClassLoader#getResource(String)}. * *

If the supplied classpath resource name is prefixed with a slash * ({@code /}), the slash will be removed. * * @param classpathResourceName the name of the resource to load; never * {@code null} or blank * @return a successful {@code Try} containing the set of loaded resources * (potentially empty) or a failed {@code Try} containing the exception in * case a failure occurred while trying to list resources; never * {@code null} * @see #tryToGetResources(String, ClassLoader) * @see ReflectionSupport#tryToLoadClass(String) */ public static Try> tryToGetResources(String classpathResourceName) { return ReflectionUtils.tryToGetResources(classpathResourceName); } /** * Try to load the {@linkplain Resource resources} for the supplied classpath * resource name, using the supplied {@link ClassLoader}. * *

The name of a classpath resource must follow the semantics * for resource paths as defined in {@link ClassLoader#getResource(String)}. * *

If the supplied classpath resource name is prefixed with a slash * ({@code /}), the slash will be removed. * * @param classpathResourceName the name of the resource to load; never * {@code null} or blank * @param classLoader the {@code ClassLoader} to use; never {@code null} * @return a successful {@code Try} containing the set of loaded resources * (potentially empty) or a failed {@code Try} containing the exception in * case a failure occurred while trying to list resources; never * {@code null} * @see #tryToGetResources(String) * @see ReflectionSupport#tryToLoadClass(String, ClassLoader) */ public static Try> tryToGetResources(String classpathResourceName, ClassLoader classLoader) { return ReflectionUtils.tryToGetResources(classpathResourceName, classLoader); } /** * Find all {@linkplain Resource resources} in the supplied classpath {@code root} * that match the specified {@code resourceFilter}. * *

The classpath scanning algorithm searches recursively in subpackages * beginning with the root of the classpath. * * @param root the URI for the classpath root in which to scan; never * {@code null} * @param resourceFilter the resource type filter; never {@code null} * @return an immutable list of all such resources found; never {@code null} * but potentially empty * @see #findAllResourcesInPackage(String, ResourceFilter) * @see #findAllResourcesInModule(String, ResourceFilter) * @see ReflectionSupport#findAllClassesInClasspathRoot(URI, Predicate, Predicate) */ public static List findAllResourcesInClasspathRoot(URI root, ResourceFilter resourceFilter) { return ReflectionUtils.findAllResourcesInClasspathRoot(root, resourceFilter); } /** * Find all {@linkplain Resource resources} in the supplied classpath {@code root} * that match the specified {@code resourceFilter}. * *

The classpath scanning algorithm searches recursively in subpackages * beginning with the root of the classpath. * * @param root the URI for the classpath root in which to scan; never * {@code null} * @param resourceFilter the resource type filter; never {@code null} * @return a stream of all such classes found; never {@code null} * but potentially empty * @see #streamAllResourcesInPackage(String, ResourceFilter) * @see #streamAllResourcesInModule(String, ResourceFilter) * @see ReflectionSupport#streamAllClassesInClasspathRoot(URI, Predicate, Predicate) */ public static Stream streamAllResourcesInClasspathRoot(URI root, ResourceFilter resourceFilter) { return ReflectionUtils.streamAllResourcesInClasspathRoot(root, resourceFilter); } /** * Find all {@linkplain Resource resources} in the supplied {@code basePackageName} * that match the specified {@code resourceFilter}. * *

The classpath scanning algorithm searches recursively in subpackages * beginning within the supplied base package. The resulting list may include * identically named resources from different classpath roots. * * @param basePackageName the name of the base package in which to start * scanning; must not be {@code null} and must be valid in terms of Java * syntax * @param resourceFilter the resource type filter; never {@code null} * @return an immutable list of all such classes found; never {@code null} * but potentially empty * @see #findAllResourcesInClasspathRoot(URI, ResourceFilter) * @see #findAllResourcesInModule(String, ResourceFilter) * @see ReflectionSupport#findAllClassesInPackage(String, Predicate, Predicate) */ public static List findAllResourcesInPackage(String basePackageName, ResourceFilter resourceFilter) { return ReflectionUtils.findAllResourcesInPackage(basePackageName, resourceFilter); } /** * Find all {@linkplain Resource resources} in the supplied {@code basePackageName} * that match the specified {@code resourceFilter}. * *

The classpath scanning algorithm searches recursively in subpackages * beginning within the supplied base package. The resulting stream may * include identically named resources from different classpath roots. * * @param basePackageName the name of the base package in which to start * scanning; must not be {@code null} and must be valid in terms of Java * syntax * @param resourceFilter the resource type filter; never {@code null} * @return a stream of all such resources found; never {@code null} * but potentially empty * @see #streamAllResourcesInClasspathRoot(URI, ResourceFilter) * @see #streamAllResourcesInModule(String, ResourceFilter) * @see ReflectionSupport#streamAllClassesInPackage(String, Predicate, Predicate) */ public static Stream streamAllResourcesInPackage(String basePackageName, ResourceFilter resourceFilter) { return ReflectionUtils.streamAllResourcesInPackage(basePackageName, resourceFilter); } /** * Find all {@linkplain Resource resources} in the supplied {@code moduleName} * that match the specified {@code resourceFilter}. * *

The module-path scanning algorithm searches recursively in all * packages contained in the module. * * @param moduleName the name of the module to scan; never {@code null} or * empty * @param resourceFilter the resource type filter; never {@code null} * @return an immutable list of all such resources found; never {@code null} * but potentially empty * @see #findAllResourcesInClasspathRoot(URI, ResourceFilter) * @see #findAllResourcesInPackage(String, ResourceFilter) * @see ReflectionSupport#findAllClassesInModule(String, Predicate, Predicate) */ public static List findAllResourcesInModule(String moduleName, ResourceFilter resourceFilter) { return ReflectionUtils.findAllResourcesInModule(moduleName, resourceFilter); } /** * Find all {@linkplain Resource resources} in the supplied {@code module} * that match the specified {@code resourceFilter}. * *

The module-path scanning algorithm searches recursively in all * packages contained in the module. * * @param module the module to scan; never {@code null} or unnamed * @param resourceFilter the resource type filter; never {@code null} * @return an immutable list of all such resources found; never {@code null} * but potentially empty * @since 6.1 * @see #findAllResourcesInClasspathRoot(URI, ResourceFilter) * @see #findAllResourcesInPackage(String, ResourceFilter) * @see ReflectionSupport#findAllClassesInModule(String, Predicate, Predicate) */ @API(status = EXPERIMENTAL, since = "6.1") public static List findAllResourcesInModule(Module module, ResourceFilter resourceFilter) { return ReflectionUtils.findAllResourcesInModule(module, resourceFilter); } /** * Find all {@linkplain Resource resources} in the supplied {@code moduleName} * that match the specified {@code resourceFilter}. * *

The module-path scanning algorithm searches recursively in all * packages contained in the module. * * @param moduleName the name of the module to scan; never {@code null} or * empty * @param resourceFilter the resource type filter; never {@code null} * @return a stream of all such resources found; never {@code null} * but potentially empty * @see #streamAllResourcesInClasspathRoot(URI, ResourceFilter) * @see #streamAllResourcesInPackage(String, ResourceFilter) * @see ReflectionSupport#streamAllClassesInModule(String, Predicate, Predicate) */ public static Stream streamAllResourcesInModule(String moduleName, ResourceFilter resourceFilter) { return ReflectionUtils.streamAllResourcesInModule(moduleName, resourceFilter); } private ResourceSupport() { } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/support/SearchOption.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.support; import static org.apiguardian.api.API.Status.DEPRECATED; import org.apiguardian.api.API; /** * Search options for finding an annotation within a class hierarchy. * * @since 1.8 * @see #DEFAULT * @see #INCLUDE_ENCLOSING_CLASSES * @deprecated because there is only a single non-deprecated search option left */ @Deprecated(since = "1.12") @API(status = DEPRECATED, since = "1.12") public enum SearchOption { /** * Search the inheritance hierarchy (i.e., the current class, implemented * interfaces, and superclasses), but do not search on enclosing classes. * * @see Class#getSuperclass() * @see Class#getInterfaces() */ DEFAULT, /** * Search the inheritance hierarchy as with the {@link #DEFAULT} search option * but also search the {@linkplain Class#getEnclosingClass() enclosing class} * hierarchy for inner classes (i.e., a non-static member classes). * * @deprecated because it is preferable to inspect the runtime enclosing * types of a class rather than where they are declared. */ @Deprecated(since = "1.12") // @API(status = DEPRECATED, since = "1.12") INCLUDE_ENCLOSING_CLASSES } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionException.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.support.conversion; import static org.apiguardian.api.API.Status.MAINTAINED; import java.io.Serial; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; /** * {@code ConversionException} is an exception that can occur when an * object is converted to another object. * * @since 1.11 */ @API(status = MAINTAINED, since = "1.13.3") public class ConversionException extends JUnitException { @Serial private static final long serialVersionUID = 1L; public ConversionException(String message) { super(message); } public ConversionException(String message, @Nullable Throwable cause) { super(message, cause); } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionSupport.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.support.conversion; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.junit.platform.commons.util.ReflectionUtils.getWrapperType; import java.util.List; import java.util.Optional; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.ClassLoaderUtils; /** * {@code ConversionSupport} provides static utility methods for converting a * given object into an instance of a specified type. * * @since 1.11 */ @API(status = MAINTAINED, since = "1.13.3") public final class ConversionSupport { private static final List stringToObjectConverters = List.of( // new StringToBooleanConverter(), // new StringToCharacterConverter(), // new StringToNumberConverter(), // new StringToClassConverter(), // new StringToEnumConverter(), // new StringToJavaTimeConverter(), // new StringToCommonJavaTypesConverter(), // new FallbackStringToObjectConverter() // ); private ConversionSupport() { /* no-op */ } /** * Convert the supplied source {@code String} into an instance of the specified * target type. * *

If the target type is {@code String}, the source {@code String} will not * be modified. * *

Some forms of conversion require a {@link ClassLoader}. If none is * provided, the {@linkplain ClassLoaderUtils#getDefaultClassLoader() default * ClassLoader} will be used. * *

This method is able to convert strings into primitive types and their * corresponding wrapper types ({@link Boolean}, {@link Character}, {@link Byte}, * {@link Short}, {@link Integer}, {@link Long}, {@link Float}, and * {@link Double}), enum constants, date and time types from the * {@code java.time} package, as well as common Java types such as {@link Class}, * {@link java.io.File}, {@link java.nio.file.Path}, {@link java.nio.charset.Charset}, * {@link java.math.BigDecimal}, {@link java.math.BigInteger}, * {@link java.util.Currency}, {@link java.util.Locale}, {@link java.util.UUID}, * {@link java.net.URI}, and {@link java.net.URL}. * *

If the target type is not covered by any of the above, a convention-based * conversion strategy will be used to convert the source {@code String} into the * given target type by invoking a static factory method or factory constructor * defined in the target type. The search algorithm used in this strategy is * outlined below. * *

Search Algorithm

* *
    *
  1. Search for a single, non-private static factory method in the target * type that converts from a {@link String} to the target type. Use the * factory method if present.
  2. *
  3. Search for a single, non-private constructor in the target type that * accepts a {@link String}. Use the constructor if present.
  4. *
  5. Search for a single, non-private static factory method in the target * type that converts from a {@link CharSequence} to the target type. Use the * factory method if present.
  6. *
  7. Search for a single, non-private constructor in the target type that * accepts a {@link CharSequence}. Use the constructor if present.
  8. *
* *

If multiple suitable factory methods or constructors are discovered they * will be ignored. If neither a single factory method nor a single constructor * is found, the convention-based conversion strategy will not apply. * * @param source the source {@code String} to convert; may be {@code null} * but only if the target type is a reference type * @param targetType the target type the source should be converted into; * never {@code null} * @param classLoader the {@code ClassLoader} to use; may be {@code null} to * use the default {@code ClassLoader} * @param the type of the target * @return the converted object; may be {@code null} but only if the target * type is a reference type * * @since 1.11 */ @SuppressWarnings("unchecked") public static @Nullable T convert(@Nullable String source, Class targetType, @Nullable ClassLoader classLoader) { if (source == null) { if (targetType.isPrimitive()) { throw new ConversionException( "Cannot convert null to primitive value of type " + targetType.getTypeName()); } return null; } if (String.class.equals(targetType)) { return (T) source; } Class targetTypeToUse = toWrapperType(targetType); Optional converter = stringToObjectConverters.stream().filter( candidate -> candidate.canConvertTo(targetTypeToUse)).findFirst(); if (converter.isPresent()) { try { ClassLoader classLoaderToUse = classLoader != null ? classLoader : ClassLoaderUtils.getDefaultClassLoader(); return (T) converter.get().convert(source, targetTypeToUse, classLoaderToUse); } catch (Exception ex) { if (ex instanceof ConversionException conversionException) { // simply rethrow it throw conversionException; } // else throw new ConversionException( "Failed to convert String \"%s\" to type %s".formatted(source, targetType.getTypeName()), ex); } } throw new ConversionException( "No built-in converter for source type java.lang.String and target type " + targetType.getTypeName()); } private static Class toWrapperType(Class targetType) { Class wrapperType = getWrapperType(targetType); return wrapperType != null ? wrapperType : targetType; } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/FallbackStringToObjectConverter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.support.conversion; import static org.junit.platform.commons.support.HierarchyTraversalMode.BOTTOM_UP; import static org.junit.platform.commons.support.ModifierSupport.isNotPrivate; import static org.junit.platform.commons.support.ModifierSupport.isNotStatic; import static org.junit.platform.commons.support.ReflectionSupport.findMethods; import static org.junit.platform.commons.support.ReflectionSupport.invokeMethod; import static org.junit.platform.commons.support.conversion.FallbackStringToObjectConverter.DeprecationStatus.EXCLUDE_DEPRECATED; import static org.junit.platform.commons.support.conversion.FallbackStringToObjectConverter.DeprecationStatus.INCLUDE_DEPRECATED; import static org.junit.platform.commons.util.ReflectionUtils.findConstructors; import static org.junit.platform.commons.util.ReflectionUtils.newInstance; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Method; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import java.util.function.Predicate; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; /** * {@code FallbackStringToObjectConverter} is a {@link StringToObjectConverter} * that provides a fallback conversion strategy for converting from a * {@link String} or {@link CharSequence} to a given target type by invoking a * static factory method or factory constructor defined in the target type. * *

Search Algorithm

* *
    *
  1. Search for a single, non-private static factory method in the target type * that converts from a {@link String} to the target type. Use the factory method * if present.
  2. *
  3. Search for a single, non-private constructor in the target type that accepts * a {@link String}. Use the constructor if present.
  4. *
  5. Search for a single, non-private static factory method in the target type * that converts from a {@link CharSequence} to the target type. Use the factory * method if present.
  6. *
  7. Search for a single, non-private constructor in the target type that accepts * a {@link CharSequence}. Use the constructor if present.
  8. *
  9. Search for a single, non-private static factory method in the target type * that converts from a {@link String} to the target type, excluding candidates * annotated with {@link Deprecated @Deprecated}. Use the factory method if * present.
  10. *
  11. Search for a single, non-private static factory method in the target type * that converts from a {@link CharSequence} to the target type, excluding * candidates annotated with {@link Deprecated @Deprecated}. Use the factory * method if present.
  12. *
* *

If a suitable factory method or constructor is not found, this converter * acts as a no-op. * * @since 1.11 * @see ConversionSupport */ class FallbackStringToObjectConverter implements StringToObjectConverter { /** * Implementation of the NULL Object Pattern. */ private static final Function NULL_EXECUTABLE = source -> source; /** * Cache for factory methods and factory constructors. * *

Searches that do not find a factory method or constructor are tracked * by the presence of a {@link #NULL_EXECUTABLE} object stored in the map. * This prevents the framework from repeatedly searching for things which * are already known not to exist. */ private static final ConcurrentHashMap, Function> factoryExecutableCache // = new ConcurrentHashMap<>(64); @Override public boolean canConvertTo(Class targetType) { return findFactoryExecutable(targetType) != NULL_EXECUTABLE; } @Override public @Nullable Object convert(String source, Class targetType) throws Exception { Function executable = findFactoryExecutable(targetType); Preconditions.condition(executable != NULL_EXECUTABLE, "Illegal state: convert() must not be called if canConvert() returned false"); return executable.apply(source); } private static Function findFactoryExecutable(Class targetType) { return factoryExecutableCache.computeIfAbsent(targetType, type -> { // First, search for exact String argument matches. var factory = findFactoryMethodExecutable(type, String.class, INCLUDE_DEPRECATED); if (factory != null) { return factory; } factory = findFactoryConstructorExecutable(type, String.class); if (factory != null) { return factory; } // Second, fall back to CharSequence argument matches. factory = findFactoryMethodExecutable(type, CharSequence.class, INCLUDE_DEPRECATED); if (factory != null) { return factory; } factory = findFactoryConstructorExecutable(type, CharSequence.class); if (factory != null) { return factory; } // Third, try factory methods again, but exclude deprecated methods factory = findFactoryMethodExecutable(type, String.class, EXCLUDE_DEPRECATED); if (factory != null) { return factory; } factory = findFactoryMethodExecutable(type, CharSequence.class, EXCLUDE_DEPRECATED); if (factory != null) { return factory; } // Else, nothing found. return NULL_EXECUTABLE; }); } private static @Nullable Function findFactoryMethodExecutable(Class targetType, Class parameterType, DeprecationStatus deprecationStatus) { Method factoryMethod = findFactoryMethod(targetType, parameterType, deprecationStatus); if (factoryMethod != null) { return source -> invokeMethod(factoryMethod, null, source); } return null; } private static @Nullable Function findFactoryConstructorExecutable(Class targetType, Class parameterType) { Constructor constructor = findFactoryConstructor(targetType, parameterType); if (constructor != null) { return source -> newInstance(constructor, source); } return null; } private static @Nullable Method findFactoryMethod(Class targetType, Class parameterType, DeprecationStatus deprecationStatus) { var isFactoryMethod = new IsFactoryMethod(targetType, parameterType, deprecationStatus); List factoryMethods = findMethods(targetType, isFactoryMethod, BOTTOM_UP); if (factoryMethods.size() == 1) { return factoryMethods.get(0); } return null; } private static @Nullable Constructor findFactoryConstructor(Class targetType, Class parameterType) { List> constructors = findConstructors(targetType, new IsFactoryConstructor(targetType, parameterType)); if (constructors.size() == 1) { return constructors.get(0); } return null; } enum DeprecationStatus { INCLUDE_DEPRECATED, EXCLUDE_DEPRECATED } /** * {@link Predicate} that determines if the {@link Method} supplied to * {@link #test(Method)} is a non-private static factory method for the * supplied {@link #targetType} and {@link #parameterType}. */ record IsFactoryMethod(Class targetType, Class parameterType, DeprecationStatus deprecationStatus) implements Predicate { @Override public boolean test(Method method) { // Please do not collapse the following into a single statement. if (!method.getReturnType().equals(this.targetType)) { return false; } if (isNotStatic(method)) { return false; } if (deprecationStatus == DeprecationStatus.EXCLUDE_DEPRECATED && method.isAnnotationPresent(Deprecated.class)) { return false; } return isFactoryCandidate(method, this.parameterType); } } /** * {@link Predicate} that determines if the {@link Constructor} supplied to * {@link #test(Constructor)} is a non-private factory constructor for the * supplied {@link #targetType} and {@link #parameterType}. */ record IsFactoryConstructor(Class targetType, Class parameterType) implements Predicate> { @Override public boolean test(Constructor constructor) { // Please do not collapse the following into a single statement. if (!constructor.getDeclaringClass().equals(this.targetType)) { return false; } return isFactoryCandidate(constructor, this.parameterType); } } /** * Determine if the supplied {@link Executable} is not private and accepts a * single argument of the supplied parameter type. */ private static boolean isFactoryCandidate(Executable executable, Class parameterType) { return isNotPrivate(executable) // && (executable.getParameterCount() == 1) // && (executable.getParameterTypes()[0] == parameterType); } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToBooleanConverter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.support.conversion; import org.junit.platform.commons.util.Preconditions; class StringToBooleanConverter implements StringToObjectConverter { @Override public boolean canConvertTo(Class targetType) { return targetType == Boolean.class; } @Override public Boolean convert(String source, Class targetType) { boolean isTrue = "true".equalsIgnoreCase(source); Preconditions.condition(isTrue || "false".equalsIgnoreCase(source), () -> "String must be 'true' or 'false' (ignoring case): " + source); return isTrue; } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToCharacterConverter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.support.conversion; import org.junit.platform.commons.util.Preconditions; class StringToCharacterConverter implements StringToObjectConverter { @Override public boolean canConvertTo(Class targetType) { return targetType == Character.class; } @Override public Character convert(String source, Class targetType) { Preconditions.condition(source.length() == 1, () -> "String must have length of 1: " + source); return source.charAt(0); } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToClassConverter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.support.conversion; import org.junit.platform.commons.support.ReflectionSupport; class StringToClassConverter implements StringToObjectConverter { @Override public boolean canConvertTo(Class targetType) { return targetType == Class.class; } @Override public Object convert(String source, Class targetType) throws Exception { throw new UnsupportedOperationException("Invoke convert(String, Class, ClassLoader) instead"); } @Override public Class convert(String className, Class targetType, ClassLoader classLoader) throws Exception { // @formatter:off return ReflectionSupport.tryToLoadClass(className, classLoader) .getNonNullOrThrow(cause -> new ConversionException( "Failed to convert String \"" + className + "\" to type java.lang.Class", cause)); // @formatter:on } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToCommonJavaTypesConverter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.support.conversion; import java.io.File; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.nio.charset.Charset; import java.nio.file.Path; import java.util.Currency; import java.util.Locale; import java.util.Map; import java.util.UUID; import java.util.function.Function; import org.junit.platform.commons.util.Preconditions; class StringToCommonJavaTypesConverter implements StringToObjectConverter { private static final Map, Function> CONVERTERS = Map.of( // // java.io and java.nio File.class, File::new, // Charset.class, Charset::forName, // Path.class, Path::of, // java.net URI.class, URI::create, // URL.class, StringToCommonJavaTypesConverter::toURL, // java.util Currency.class, Currency::getInstance, // Locale.class, Locale::forLanguageTag, // UUID.class, UUID::fromString // ); @Override public boolean canConvertTo(Class targetType) { return CONVERTERS.containsKey(targetType); } @Override public Object convert(String source, Class targetType) throws Exception { Function converter = Preconditions.notNull(CONVERTERS.get(targetType), () -> "No registered converter for %s".formatted(targetType.getName())); return converter.apply(source); } private static URL toURL(String url) { try { return URI.create(url).toURL(); } catch (MalformedURLException ex) { throw new ConversionException("Failed to convert String \"" + url + "\" to type java.net.URL", ex); } } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToEnumConverter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.support.conversion; class StringToEnumConverter implements StringToObjectConverter { @Override public boolean canConvertTo(Class targetType) { return targetType.isEnum(); } @Override @SuppressWarnings({ "unchecked", "rawtypes" }) public Enum convert(String source, Class targetType) throws Exception { return Enum.valueOf(targetType, source); } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToJavaTimeConverter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.support.conversion; import static java.util.Map.entry; import java.time.Duration; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.MonthDay; import java.time.OffsetDateTime; import java.time.OffsetTime; import java.time.Period; import java.time.Year; import java.time.YearMonth; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.Map; import java.util.function.Function; import org.junit.platform.commons.util.Preconditions; class StringToJavaTimeConverter implements StringToObjectConverter { private static final Map, Function> CONVERTERS = Map.ofEntries( // entry(Duration.class, Duration::parse), // entry(Instant.class, Instant::parse), // entry(LocalDate.class, LocalDate::parse), // entry(LocalDateTime.class, LocalDateTime::parse), // entry(LocalTime.class, LocalTime::parse), // entry(MonthDay.class, MonthDay::parse), // entry(OffsetDateTime.class, OffsetDateTime::parse), // entry(OffsetTime.class, OffsetTime::parse), // entry(Period.class, Period::parse), // entry(Year.class, Year::parse), // entry(YearMonth.class, YearMonth::parse), // entry(ZonedDateTime.class, ZonedDateTime::parse), // entry(ZoneId.class, ZoneId::of), // entry(ZoneOffset.class, ZoneOffset::of) // ); @Override public boolean canConvertTo(Class targetType) { return CONVERTERS.containsKey(targetType); } @Override public Object convert(String source, Class targetType) throws Exception { Function converter = Preconditions.notNull(CONVERTERS.get(targetType), () -> "No registered converter for %s".formatted(targetType.getName())); return converter.apply(source); } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToNumberConverter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.support.conversion; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Map; import java.util.function.Function; import org.junit.platform.commons.util.Preconditions; class StringToNumberConverter implements StringToObjectConverter { private static final Map, Function> CONVERTERS = Map.of( // Byte.class, Byte::decode, // Short.class, Short::decode, // Integer.class, Integer::decode, // Long.class, Long::decode, // Float.class, Float::valueOf, // Double.class, Double::valueOf, // // Technically, BigInteger and BigDecimal constructors are covered by // FallbackStringToObjectConverter, but we have explicit conversion // configured for them anyway. BigInteger.class, BigInteger::new, // BigDecimal.class, BigDecimal::new // ); @Override public boolean canConvertTo(Class targetType) { return CONVERTERS.containsKey(targetType); } @Override public Number convert(String source, Class targetType) { Function converter = Preconditions.notNull(CONVERTERS.get(targetType), () -> "No registered converter for %s".formatted(targetType.getName())); return converter.apply(source.replace("_", "")); } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToObjectConverter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.support.conversion; import org.jspecify.annotations.Nullable; /** * Internal API for converting arguments of type {@link String} to a specified * target type. */ interface StringToObjectConverter { /** * Determine if this converter can convert from a {@link String} to the * supplied target type (which is guaranteed to be a wrapper type for * primitives — for example, {@link Integer} instead of {@code int}). */ boolean canConvertTo(Class targetType); /** * Convert the supplied {@link String} to the supplied target type (which is * guaranteed to be a wrapper type for primitives — for example, * {@link Integer} instead of {@code int}). * *

This method will only be invoked if {@link #canConvertTo(Class)} * returns {@code true} for the same target type. */ @Nullable Object convert(String source, Class targetType) throws Exception; /** * Convert the supplied {@link String} to the supplied target type (which is * guaranteed to be a wrapper type for primitives — for example, * {@link Integer} instead of {@code int}). * *

This method will only be invoked if {@link #canConvertTo(Class)} * returns {@code true} for the same target type. * *

The default implementation simply delegates to {@link #convert(String, Class)}. * Can be overridden by concrete implementations of this interface that need * access to the supplied {@link ClassLoader}. */ default @Nullable Object convert(String source, Class targetType, ClassLoader classLoader) throws Exception { return convert(source, targetType); } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Conversion APIs provided by the JUnit Platform. */ @NullMarked package org.junit.platform.commons.support.conversion; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/support/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Common support APIs provided by the JUnit Platform. * *

The purpose of this package is to provide {@code TestEngine} and * {@code Extension} authors convenient access to a subset of internal utility * methods to assist with their implementation. This prevents re-inventing the * wheel and ensures that common tasks are handled in third-party engines and * extensions with the same semantics as within the JUnit Platform itself. */ @NullMarked package org.junit.platform.commons.support; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/ClassFilter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.support.scanning; import static org.apiguardian.api.API.Status.MAINTAINED; import java.util.function.Predicate; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.annotation.Contract; /** * Class-related predicate used by reflection utilities. * * @since 1.1 */ @API(status = MAINTAINED, since = "1.13.3") public class ClassFilter { /** * Create a {@link ClassFilter} instance that accepts all names but filters classes. * * @param classPredicate the class type predicate; never {@code null} * @return an instance of {@code ClassFilter}; never {@code null} */ public static ClassFilter of(Predicate> classPredicate) { return of(name -> true, classPredicate); } /** * Create a {@link ClassFilter} instance that filters by names and classes. * * @param namePredicate the class name predicate; never {@code null} * @param classPredicate the class type predicate; never {@code null} * @return an instance of {@code ClassFilter}; never {@code null} */ public static ClassFilter of(Predicate namePredicate, Predicate> classPredicate) { return new ClassFilter(namePredicate, classPredicate); } private final Predicate namePredicate; private final Predicate> classPredicate; private ClassFilter(Predicate namePredicate, Predicate> classPredicate) { this.namePredicate = checkNotNull(namePredicate, "name predicate"); this.classPredicate = checkNotNull(classPredicate, "class predicate"); } // Cannot use Preconditions due to package cycle @Contract("null, _ -> fail; !null, _ -> param1") private static T checkNotNull(@Nullable T input, String title) { if (input == null) { throw new PreconditionViolationException(title + " must not be null"); } return input; } /** * Test the given name using the stored name predicate. * * @param name the name to test; never {@code null} * @return {@code true} if the input name matches the predicate, otherwise * {@code false} */ public boolean match(String name) { return namePredicate.test(name); } /** * Test the given class using the stored class predicate. * * @param type the type to test; never {@code null} * @return {@code true} if the input type matches the predicate, otherwise * {@code false} */ public boolean match(Class type) { return classPredicate.test(type); } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/ClasspathScanner.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.support.scanning; import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.MAINTAINED; import java.net.URI; import java.util.List; import java.util.function.Predicate; import org.apiguardian.api.API; import org.junit.platform.commons.io.Resource; import org.junit.platform.commons.io.ResourceFilter; /** * {@code ClasspathScanner} allows to scan the classpath for classes and * resources. * *

An implementation of this interface can be registered via the * {@link java.util.ServiceLoader ServiceLoader} mechanism. * * @since 1.12 */ @API(status = MAINTAINED, since = "1.13.3") public interface ClasspathScanner { /** * Find all {@linkplain Class classes} in the supplied classpath {@code root} * that match the specified {@code classFilter} filter. * *

The classpath scanning algorithm searches recursively in subpackages * beginning with the root of the classpath. * * @param basePackageName the name of the base package in which to start * scanning; must not be {@code null} and must be valid in terms of Java * syntax * @param classFilter the class type filter; never {@code null} * @return a list of all such classes found; never {@code null} * but potentially empty */ List> scanForClassesInPackage(String basePackageName, ClassFilter classFilter); /** * Find all {@linkplain Class classes} in the supplied classpath {@code root} * that match the specified {@code classFilter} filter. * *

The classpath scanning algorithm searches recursively in subpackages * beginning with the root of the classpath. * * @param root the URI for the classpath root in which to scan; never * {@code null} * @param classFilter the class type filter; never {@code null} * @return a list of all such classes found; never {@code null} * but potentially empty */ List> scanForClassesInClasspathRoot(URI root, ClassFilter classFilter); /** * Find all {@linkplain org.junit.platform.commons.support.Resource resources} in the supplied classpath {@code root} * that match the specified {@code resourceFilter} predicate. * *

The classpath scanning algorithm searches recursively in subpackages * beginning with the root of the classpath. * * @param basePackageName the name of the base package in which to start * scanning; must not be {@code null} and must be valid in terms of Java * syntax * @param resourceFilter the resource type filter; never {@code null} * @return a list of all such resources found; never {@code null} * but potentially empty * @deprecated Please implement * {@link #scanForResourcesInPackage(String, ResourceFilter)} instead */ @API(status = DEPRECATED, since = "1.14") @Deprecated(since = "1.14", forRemoval = true) @SuppressWarnings({ "removal", "unused" }) default List scanForResourcesInPackage(String basePackageName, Predicate resourceFilter) { throw new UnsupportedOperationException("Implement scanForResourcesInPackage(String, ResourceFilter) instead"); } /** * Find all {@linkplain org.junit.platform.commons.support.Resource resources} in the supplied classpath {@code root} * that match the specified {@code resourceFilter} predicate. * *

The classpath scanning algorithm searches recursively in subpackages * beginning with the root of the classpath. * * @param root the URI for the classpath root in which to scan; never * {@code null} * @param resourceFilter the resource type filter; never {@code null} * @return a list of all such resources found; never {@code null} * but potentially empty * @deprecated Please implement * {@link #scanForResourcesInClasspathRoot(URI, ResourceFilter)} instead */ @API(status = DEPRECATED, since = "1.14") @Deprecated(since = "1.14", forRemoval = true) @SuppressWarnings("removal") default List scanForResourcesInClasspathRoot(URI root, Predicate resourceFilter) { throw new UnsupportedOperationException( "Implement scanForResourcesInClasspathRoot(URI, ResourceFilter) instead"); } /** * Find all {@linkplain Resource resources} in the supplied classpath {@code root} * that match the specified {@code resourceFilter} predicate. * *

The classpath scanning algorithm searches recursively in subpackages * beginning with the root of the classpath. * * @param basePackageName the name of the base package in which to start * scanning; must not be {@code null} and must be valid in terms of Java * syntax * @param resourceFilter the resource type filter; never {@code null} * @return a list of all such resources found; never {@code null} * but potentially empty * @since 1.14 */ @API(status = MAINTAINED, since = "1.14") @SuppressWarnings("removal") default List scanForResourcesInPackage(String basePackageName, ResourceFilter resourceFilter) { return scanForResourcesInPackage(basePackageName, resourceFilter::match); } /** * Find all {@linkplain Resource resources} in the supplied classpath {@code root} * that match the specified {@code resourceFilter} predicate. * *

The classpath scanning algorithm searches recursively in subpackages * beginning with the root of the classpath. * * @param root the URI for the classpath root in which to scan; never * {@code null} * @param resourceFilter the resource type filter; never {@code null} * @return a list of all such resources found; never {@code null} * but potentially empty * @since 1.14 */ @API(status = MAINTAINED, since = "1.14") @SuppressWarnings("removal") default List scanForResourcesInClasspathRoot(URI root, ResourceFilter resourceFilter) { return scanForResourcesInClasspathRoot(root, resourceFilter::match); } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Classpath scanning APIs provided by the JUnit Platform. */ @NullMarked package org.junit.platform.commons.support.scanning; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/util/AnnotationUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static java.util.Objects.requireNonNull; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.commons.util.ReflectionUtils.isInnerClass; import java.lang.annotation.Annotation; import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.annotation.Contract; import org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode; /** * Collection of utilities for working with {@linkplain Annotation annotations}. * *

DISCLAIMER

* *

These utilities are intended solely for usage within the JUnit framework * itself. Any usage by external parties is not supported. * Use at your own risk! * *

Some utilities are published via the maintained {@code AnnotationSupport} * class. * * @since 1.0 * @see Annotation * @see AnnotatedElement * @see org.junit.platform.commons.support.AnnotationSupport */ @API(status = INTERNAL, since = "1.0") public final class AnnotationUtils { private AnnotationUtils() { /* no-op */ } private static final ConcurrentHashMap, Boolean> repeatableAnnotationContainerCache = // new ConcurrentHashMap<>(16); /** * Determine if an annotation of {@code annotationType} is either * present or meta-present on the supplied optional * {@code element}. * * @see #findAnnotation(Optional, Class) * @see org.junit.platform.commons.support.AnnotationSupport#isAnnotated(Optional, Class) */ @SuppressWarnings("NullableOptional") @Contract("null, _ -> false") public static boolean isAnnotated(@Nullable Optional element, Class annotationType) { return findAnnotation(element, annotationType).isPresent(); } /** * @since 1.8 * @see #findAnnotation(Parameter, int, Class) */ public static boolean isAnnotated(Parameter parameter, int index, Class annotationType) { return findAnnotation(parameter, index, annotationType).isPresent(); } /** * Determine if an annotation of {@code annotationType} is either * present or meta-present on the supplied * {@code element}. * * @param element the element on which to search for the annotation; may be * {@code null} * @param annotationType the annotation type to search for; never {@code null} * @return {@code true} if the annotation is present or meta-present * @see #findAnnotation(AnnotatedElement, Class) * @see org.junit.platform.commons.support.AnnotationSupport#isAnnotated(AnnotatedElement, Class) */ @Contract("null, _ -> false") public static boolean isAnnotated(@Nullable AnnotatedElement element, Class annotationType) { return findAnnotation(element, annotationType).isPresent(); } /** * @see org.junit.platform.commons.support.AnnotationSupport#findAnnotation(Optional, Class) */ @SuppressWarnings({ "OptionalAssignedToNull", "NullableOptional" }) public static Optional findAnnotation( @Nullable Optional element, Class annotationType) { if (element == null || element.isEmpty()) { return Optional.empty(); } return findAnnotation(element.get(), annotationType); } /** * @since 1.8 * @see #findAnnotation(AnnotatedElement, Class) */ public static Optional findAnnotation(Parameter parameter, int index, Class annotationType) { return findAnnotation(getEffectiveAnnotatedParameter(parameter, index), annotationType); } /** * @see org.junit.platform.commons.support.AnnotationSupport#findAnnotation(AnnotatedElement, Class) */ public static Optional findAnnotation(@Nullable AnnotatedElement element, Class annotationType) { Preconditions.notNull(annotationType, "annotationType must not be null"); boolean inherited = annotationType.isAnnotationPresent(Inherited.class); return findAnnotation(element, annotationType, inherited, new HashSet<>()); } private static Optional findAnnotation(@Nullable AnnotatedElement element, Class annotationType, boolean inherited, Set visited) { Preconditions.notNull(annotationType, "annotationType must not be null"); if (element == null) { return Optional.empty(); } // Directly present? A annotation = element.getDeclaredAnnotation(annotationType); if (annotation != null) { return Optional.of(annotation); } // Meta-present on directly present annotations? Optional directMetaAnnotation = findMetaAnnotation(annotationType, element.getDeclaredAnnotations(), inherited, visited); if (directMetaAnnotation.isPresent()) { return directMetaAnnotation; } if (element instanceof Class clazz) { // Search on interfaces for (Class ifc : clazz.getInterfaces()) { if (ifc != Annotation.class) { Optional annotationOnInterface = findAnnotation(ifc, annotationType, inherited, visited); if (annotationOnInterface.isPresent()) { return annotationOnInterface; } } } // Indirectly present? // Search in class hierarchy if (inherited) { Class superclass = clazz.getSuperclass(); if (superclass != null && superclass != Object.class) { Optional annotationOnSuperclass = findAnnotation(superclass, annotationType, inherited, visited); if (annotationOnSuperclass.isPresent()) { return annotationOnSuperclass; } } } } // Meta-present on indirectly present annotations? return findMetaAnnotation(annotationType, element.getAnnotations(), inherited, visited); } private static Optional findMetaAnnotation(Class annotationType, Annotation[] candidates, boolean inherited, Set visited) { for (Annotation candidateAnnotation : candidates) { Class candidateAnnotationType = candidateAnnotation.annotationType(); if (!isInJavaLangAnnotationPackage(candidateAnnotationType) && visited.add(candidateAnnotation)) { Optional metaAnnotation = findAnnotation(candidateAnnotationType, annotationType, inherited, visited); if (metaAnnotation.isPresent()) { return metaAnnotation; } } } return Optional.empty(); } /** * Find the first annotation of the specified type that is either * directly present, meta-present, or indirectly * present on the supplied class, optionally searching recursively * through the enclosing class hierarchy if not found on the supplied class. * *

The enclosing class hierarchy will only be searched above an inner * class (i.e., a non-static member class). * * @param the annotation type * @param clazz the class on which to search for the annotation; may be {@code null} * @param annotationType the annotation type to search for; never {@code null} * @param searchEnclosingClasses whether the enclosing class hierarchy should * be searched * @return an {@code Optional} containing the annotation; never {@code null} but * potentially empty * @since 1.8 * @see #findAnnotation(AnnotatedElement, Class) */ public static Optional findAnnotation(@Nullable Class clazz, Class annotationType, boolean searchEnclosingClasses) { Preconditions.notNull(annotationType, "annotationType must not be null"); if (!searchEnclosingClasses) { return findAnnotation(clazz, annotationType); } Class candidate = clazz; while (candidate != null) { Optional annotation = findAnnotation(candidate, annotationType); if (annotation.isPresent()) { return annotation; } candidate = (isInnerClass(candidate) ? candidate.getEnclosingClass() : null); } return Optional.empty(); } /** * @since 1.5 * @see org.junit.platform.commons.support.AnnotationSupport#findRepeatableAnnotations(Optional, Class) */ @SuppressWarnings({ "OptionalAssignedToNull", "NullableOptional" }) public static List findRepeatableAnnotations( @Nullable Optional element, Class annotationType) { if (element == null || element.isEmpty()) { return Collections.emptyList(); } return findRepeatableAnnotations(element.get(), annotationType); } /** * @since 1.8 * @see #findRepeatableAnnotations(AnnotatedElement, Class) */ public static List findRepeatableAnnotations(Parameter parameter, int index, Class annotationType) { return findRepeatableAnnotations(getEffectiveAnnotatedParameter(parameter, index), annotationType); } /** * @see org.junit.platform.commons.support.AnnotationSupport#findRepeatableAnnotations(AnnotatedElement, Class) */ public static List findRepeatableAnnotations(@Nullable AnnotatedElement element, Class annotationType) { Preconditions.notNull(annotationType, "annotationType must not be null"); Repeatable repeatable = annotationType.getAnnotation(Repeatable.class); Preconditions.notNull(repeatable, () -> annotationType.getName() + " must be @Repeatable"); Class containerType = repeatable.value(); boolean inherited = containerType.isAnnotationPresent(Inherited.class); // Short circuit the search algorithm. if (element == null) { return Collections.emptyList(); } // We use a LinkedHashSet because the search algorithm may discover // duplicates, but we need to maintain the original order. Set found = new LinkedHashSet<>(16); findRepeatableAnnotations(element, annotationType, containerType, inherited, found, new HashSet<>(16)); // unmodifiable since returned from public, non-internal method(s) return List.copyOf(found); } private static void findRepeatableAnnotations(AnnotatedElement element, Class annotationType, Class containerType, boolean inherited, Set found, Set visited) { if (element instanceof Class clazz) { // Recurse first in order to support top-down semantics for inherited, repeatable annotations. if (inherited) { Class superclass = clazz.getSuperclass(); if (superclass != null && superclass != Object.class) { findRepeatableAnnotations(superclass, annotationType, containerType, inherited, found, visited); } } // Search on interfaces for (Class ifc : clazz.getInterfaces()) { if (ifc != Annotation.class) { findRepeatableAnnotations(ifc, annotationType, containerType, inherited, found, visited); } } } // Find annotations that are directly present or meta-present on directly present annotations. findRepeatableAnnotations(element.getDeclaredAnnotations(), annotationType, containerType, inherited, found, visited); // Find annotations that are indirectly present or meta-present on indirectly present annotations. findRepeatableAnnotations(element.getAnnotations(), annotationType, containerType, inherited, found, visited); } @SuppressWarnings({ "unchecked", "GetClassOnAnnotation" }) private static void findRepeatableAnnotations(Annotation[] candidates, Class annotationType, Class containerType, boolean inherited, Set found, Set visited) { for (Annotation candidate : candidates) { Class candidateAnnotationType = candidate.annotationType(); if (!isInJavaLangAnnotationPackage(candidateAnnotationType) && visited.add(candidate)) { // Exact match? if (candidateAnnotationType.equals(annotationType)) { found.add(annotationType.cast(candidate)); } // Container? else if (candidateAnnotationType.equals(containerType)) { // Note: it's not a legitimate containing annotation type if it doesn't declare // a 'value' attribute that returns an array of the contained annotation type. // See https://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.6.3 Method method = ReflectionUtils.tryToGetMethod(containerType, "value").getOrThrow( cause -> new JUnitException( "Container annotation type '%s' must declare a 'value' attribute of type %s[].".formatted( containerType, annotationType), cause)); Annotation[] containedAnnotations = (Annotation[]) ReflectionUtils.invokeMethod(method, candidate); Collections.addAll(found, (A[]) requireNonNull(containedAnnotations)); } // Nested container annotation? else if (isRepeatableAnnotationContainer(candidateAnnotationType)) { Method method = ReflectionUtils.tryToGetMethod(candidateAnnotationType, "value").toOptional().get(); Annotation[] containedAnnotations = (Annotation[]) ReflectionUtils.invokeMethod(method, candidate); for (Annotation containedAnnotation : requireNonNull(containedAnnotations)) { findRepeatableAnnotations(containedAnnotation.getClass(), annotationType, containerType, inherited, found, visited); } } // Otherwise search recursively through the meta-annotation hierarchy... else { findRepeatableAnnotations(candidateAnnotationType, annotationType, containerType, inherited, found, visited); } } } } /** * Determine if the supplied annotation type is a container for a repeatable * annotation. * * @since 1.5 */ private static boolean isRepeatableAnnotationContainer(Class candidateContainerType) { return repeatableAnnotationContainerCache.computeIfAbsent(candidateContainerType, candidate -> { // @formatter:off Repeatable repeatable = Arrays.stream(candidate.getMethods()) .filter(attribute -> "value".equals(attribute.getName()) && attribute.getReturnType().isArray()) .findFirst() .map(attribute -> attribute.getReturnType().getComponentType().getAnnotation(Repeatable.class)) .orElse(null); // @formatter:on return repeatable != null && candidate.equals(repeatable.value()); }); } /** * Due to a bug in {@code javac} on JDK versions prior to JDK 9, looking up * annotations directly on a {@link Parameter} will fail for inner class * constructors. * *

Bug in {@code javac} on JDK versions prior to JDK 9

* *

The parameter annotations array in the compiled byte code for the user's * class excludes an entry for the implicit enclosing instance * parameter for an inner class constructor. * *

Workaround

* *

This method provides a workaround for this off-by-one error by helping * JUnit maintainers and extension authors to access annotations on the preceding * {@link Parameter} object (i.e., {@code index - 1}). If the supplied * {@code index} is zero in such situations this method will return {@code null} * since the parameter for the implicit enclosing instance will never * be annotated. * *

WARNING

* *

The {@code AnnotatedElement} returned by this method should never be cast and * treated as a {@code Parameter} since the metadata (e.g., {@link Parameter#getName()}, * {@link Parameter#getType()}, etc.) will not match those for the declared parameter * at the given index in an inner class constructor for code compiled with JDK 8. * * @return the supplied {@code Parameter}, or the effective {@code Parameter} * if the aforementioned bug is detected, or {@code null} if the bug is detected and * the supplied {@code index} is {@code 0} * @since 1.8 */ private static @Nullable AnnotatedElement getEffectiveAnnotatedParameter(Parameter parameter, int index) { Preconditions.notNull(parameter, "Parameter must not be null"); Executable executable = parameter.getDeclaringExecutable(); if (executable instanceof Constructor && isInnerClass(executable.getDeclaringClass()) && executable.getParameterAnnotations().length == executable.getParameterCount() - 1) { if (index == 0) { return null; } return executable.getParameters()[index - 1]; } return parameter; } /** * @see org.junit.platform.commons.support.AnnotationSupport#findPublicAnnotatedFields(Class, Class, Class) */ public static List findPublicAnnotatedFields(Class clazz, Class fieldType, Class annotationType) { Preconditions.notNull(clazz, "Class must not be null"); Preconditions.notNull(fieldType, "fieldType must not be null"); Preconditions.notNull(annotationType, "annotationType must not be null"); // @formatter:off return Arrays.stream(clazz.getFields()) .filter(field -> fieldType.isAssignableFrom(field.getType()) && isAnnotated(field, annotationType)) .toList(); // @formatter:on } /** * @see org.junit.platform.commons.support.AnnotationSupport#findAnnotatedFields(Class, Class, Predicate) * @see #findAnnotatedFields(Class, Class, Predicate, HierarchyTraversalMode) */ public static List findAnnotatedFields(Class clazz, Class annotationType, Predicate predicate) { return findAnnotatedFields(clazz, annotationType, predicate, HierarchyTraversalMode.TOP_DOWN); } /** * Find all {@linkplain Field fields} of the supplied class or interface * that are annotated or meta-annotated with the specified * {@code annotationType} and match the specified {@code predicate}. * * @param clazz the class or interface in which to find the fields; never {@code null} * @param annotationType the annotation type to search for; never {@code null} * @param predicate the field filter; never {@code null} * @param traversalMode the hierarchy traversal mode; never {@code null} * @return the list of all such fields found; neither {@code null} nor mutable */ public static List findAnnotatedFields(Class clazz, Class annotationType, Predicate predicate, HierarchyTraversalMode traversalMode) { Preconditions.notNull(clazz, "Class must not be null"); Preconditions.notNull(annotationType, "annotationType must not be null"); Preconditions.notNull(predicate, "Predicate must not be null"); Predicate annotated = field -> isAnnotated(field, annotationType); return ReflectionUtils.findFields(clazz, annotated.and(predicate), traversalMode); } /** * @see org.junit.platform.commons.support.AnnotationSupport#findAnnotatedMethods(Class, Class, org.junit.platform.commons.support.HierarchyTraversalMode) */ public static List findAnnotatedMethods(Class clazz, Class annotationType, HierarchyTraversalMode traversalMode) { Preconditions.notNull(clazz, "Class must not be null"); Preconditions.notNull(annotationType, "annotationType must not be null"); return ReflectionUtils.findMethods(clazz, method -> isAnnotated(method, annotationType), traversalMode); } private static boolean isInJavaLangAnnotationPackage(Class annotationType) { return (annotationType != null && annotationType.getName().startsWith("java.lang.annotation")); } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassLoaderUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static org.apiguardian.api.API.Status.INTERNAL; import java.net.URL; import java.security.CodeSource; import java.util.Optional; import org.apiguardian.api.API; /** * Collection of utilities for working with {@linkplain ClassLoader} and associated tasks. * *

DISCLAIMER

* *

These utilities are intended solely for usage within the JUnit framework * itself. Any usage by external parties is not supported. * Use at your own risk! * * @since 1.0 */ @API(status = INTERNAL, since = "1.0") public final class ClassLoaderUtils { private ClassLoaderUtils() { /* no-op */ } /** * Get the {@link ClassLoader} for the supplied {@link Class}, falling back * to the {@link #getDefaultClassLoader() default class loader} if the class * loader for the supplied class is {@code null}. * @param clazz the class for which to retrieve the class loader; never {@code null} * @since 1.10 */ public static ClassLoader getClassLoader(Class clazz) { Preconditions.notNull(clazz, "Class must not be null"); ClassLoader classLoader = clazz.getClassLoader(); return (classLoader != null) ? classLoader : getDefaultClassLoader(); } public static ClassLoader getDefaultClassLoader() { try { ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); if (contextClassLoader != null) { return contextClassLoader; } } catch (Throwable t) { UnrecoverableExceptions.rethrowIfUnrecoverable(t); /* otherwise ignore */ } return ClassLoader.getSystemClassLoader(); } /** * Get the location from which the supplied object's class was loaded. * * @param object the object for whose class the location should be retrieved * @return an {@code Optional} containing the URL of the class' location; never * {@code null} but potentially empty */ public static Optional getLocation(Object object) { Preconditions.notNull(object, "object must not be null"); // determine class loader ClassLoader loader = object.getClass().getClassLoader(); if (loader == null) { loader = ClassLoader.getSystemClassLoader(); while (loader != null && loader.getParent() != null) { loader = loader.getParent(); } } // try finding resource by name if (loader != null) { String name = object.getClass().getName(); name = name.replace(".", "/") + ".class"; try { return Optional.ofNullable(loader.getResource(name)); } catch (Throwable t) { UnrecoverableExceptions.rethrowIfUnrecoverable(t); /* otherwise ignore */ } } // try protection domain try { CodeSource codeSource = object.getClass().getProtectionDomain().getCodeSource(); if (codeSource != null) { return Optional.ofNullable(codeSource.getLocation()); } } catch (SecurityException ignore) { /* ignore */ } return Optional.empty(); } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassNamePatternFilterUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.function.Function; import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; /** * Collection of utilities for creating filters based on class names. * *

DISCLAIMER

* *

These utilities are intended solely for usage within the JUnit framework * itself. Any usage by external parties is not supported. * Use at your own risk! * * @since 1.7 */ @API(status = INTERNAL, since = "1.7") public class ClassNamePatternFilterUtils { private ClassNamePatternFilterUtils() { /* no-op */ } public static final String ALL_PATTERN = "*"; public static final String BLANK = ""; /** * Create a {@link Predicate} that can be used to exclude (i.e., filter out) * objects of type {@code T} whose fully qualified class names match any of * the supplied patterns. * * @param patterns a comma-separated list of patterns */ public static Predicate excludeMatchingClasses(@Nullable String patterns) { return matchingClasses(patterns, object -> object.getClass().getName(), FilterType.EXCLUDE); } /** * Create a {@link Predicate} that can be used to exclude (i.e., filter out) * fully qualified class names matching any of the supplied patterns. * * @param patterns a comma-separated list of patterns */ public static Predicate excludeMatchingClassNames(@Nullable String patterns) { return matchingClasses(patterns, Function.identity(), FilterType.EXCLUDE); } /** * Create a {@link Predicate} that can be used to include (i.e., filter in) * objects of type {@code T} whose fully qualified class names match any of * the supplied patterns. * * @param patterns a comma-separated list of patterns */ public static Predicate includeMatchingClasses(@Nullable String patterns) { return matchingClasses(patterns, object -> object.getClass().getName(), FilterType.INCLUDE); } /** * Create a {@link Predicate} that can be used to include (i.e., filter in) * fully qualified class names matching any of the supplied patterns. * * @param patterns a comma-separated list of patterns */ public static Predicate includeMatchingClassNames(@Nullable String patterns) { return matchingClasses(patterns, Function.identity(), FilterType.INCLUDE); } private enum FilterType { INCLUDE, EXCLUDE } private static Predicate matchingClasses(@Nullable String patterns, Function classNameProvider, FilterType type) { // @formatter:off return Optional.ofNullable(patterns) .filter(StringUtils::isNotBlank) .map(String::strip) .map(trimmedPatterns -> createPredicateFromPatterns(trimmedPatterns, classNameProvider, type)) .orElse(type == FilterType.EXCLUDE ? __ -> true : __ -> false); // @formatter:on } private static Predicate createPredicateFromPatterns(String patterns, Function classNameProvider, FilterType type) { if (ALL_PATTERN.equals(patterns)) { return type == FilterType.INCLUDE ? __ -> true : __ -> false; } List patternList = convertToRegularExpressions(patterns); return object -> { boolean isMatchingAnyPattern = patternList.stream().anyMatch( pattern -> pattern.matcher(classNameProvider.apply(object)).matches()); return (type == FilterType.INCLUDE) == isMatchingAnyPattern; }; } private static List convertToRegularExpressions(String patterns) { // @formatter:off return Arrays.stream(patterns.split(",")) .filter(StringUtils::isNotBlank) .map(String::strip) .map(ClassNamePatternFilterUtils::replaceRegExElements) .map(Pattern::compile) .toList(); // @formatter:on } private static String replaceRegExElements(String pattern) { return Matcher.quoteReplacement(pattern) // Match "." against "." and "$" since users may declare a "." instead of a // "$" as the separator between classes and nested classes. .replace(".", "[.$]") // Convert our "*" wildcard into a proper RegEx pattern. .replace("*", ".+"); } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static java.util.Arrays.stream; import static java.util.stream.Collectors.joining; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.function.Function; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; /** * Collection of utilities for working with {@link Class classes}. * *

DISCLAIMER

* *

These utilities are intended solely for usage within the JUnit framework * itself. Any usage by external parties is not supported. * Use at your own risk! * * @since 1.0 */ @API(status = INTERNAL, since = "1.0") public final class ClassUtils { private ClassUtils() { /* no-op */ } /** * Get the fully qualified name of the supplied class. * *

This is a null-safe variant of {@link Class#getName()}. * * @param clazz the class whose name should be retrieved, potentially * {@code null} * @return the fully qualified class name or {@code "null"} if the supplied * class reference is {@code null} * @since 1.3 * @see #nullSafeToString(Class...) * @see StringUtils#nullSafeToString(Object) */ public static String nullSafeToString(@Nullable Class clazz) { return clazz == null ? "null" : clazz.getName(); } /** * Generate a comma-separated list of fully qualified class names for the * supplied classes. * * @param classes the classes whose names should be included in the * generated string * @return a comma-separated list of fully qualified class names, or an empty * string if the supplied class array is {@code null} or empty * @see #nullSafeToString(Function, Class...) * @see StringUtils#nullSafeToString(Object) */ public static String nullSafeToString(@Nullable Class @Nullable... classes) { return nullSafeToString(Class::getName, classes); } /** * Generate a comma-separated list of mapped values for the supplied classes. * *

The values are generated by the supplied {@code mapper} * (e.g., {@code Class::getName}, {@code Class::getSimpleName}, etc.), unless * a class reference is {@code null} in which case it will be mapped to * {@code "null"}. * * @param mapper the mapper to use; never {@code null} * @param classes the classes to map * @return a comma-separated list of mapped values, or an empty string if * the supplied class array is {@code null} or empty * @see #nullSafeToString(Class...) * @see StringUtils#nullSafeToString(Object) */ public static String nullSafeToString(Function, ? extends String> mapper, @Nullable Class @Nullable... classes) { Preconditions.notNull(mapper, "Mapping function must not be null"); if (classes == null || classes.length == 0) { return ""; } return stream(classes) // .map(clazz -> clazz == null ? "null" : mapper.apply(clazz)) // .collect(joining(", ")); } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathFileVisitor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static java.nio.file.FileVisitResult.CONTINUE; import java.io.IOException; import java.nio.file.FileVisitResult; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.function.BiConsumer; import java.util.function.Predicate; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; /** * @since 1.11 */ class ClasspathFileVisitor extends SimpleFileVisitor { private static final Logger logger = LoggerFactory.getLogger(ClasspathFileVisitor.class); private final Path basePath; private final BiConsumer consumer; private final Predicate filter; ClasspathFileVisitor(Path basePath, Predicate filter, BiConsumer consumer) { this.basePath = basePath; this.filter = filter; this.consumer = consumer; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) { if (filter.test(file)) { consumer.accept(basePath, file); } return CONTINUE; } @Override public FileVisitResult visitFileFailed(Path file, IOException ex) { logger.warn(ex, () -> "I/O error visiting file: " + file); return CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, @Nullable IOException ex) { if (ex != null) { logger.warn(ex, () -> "I/O error visiting directory: " + dir); } return CONTINUE; } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathScannerLoader.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import java.util.List; import java.util.ServiceLoader; import java.util.ServiceLoader.Provider; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.support.scanning.ClasspathScanner; /** * @since 1.12 */ class ClasspathScannerLoader { static ClasspathScanner getInstance() { ServiceLoader serviceLoader = ServiceLoader.load(ClasspathScanner.class, ClassLoaderUtils.getDefaultClassLoader()); List> classpathScanners = serviceLoader.stream().toList(); if (classpathScanners.size() == 1) { return classpathScanners.get(0).get(); } if (classpathScanners.size() > 1) { throw new JUnitException( "There should not be more than one ClasspathScanner implementation present on the classpath but there were %d: %s".formatted( classpathScanners.size(), classpathScanners.stream().map(Provider::type).map(Class::getName).toList())); } return new DefaultClasspathScanner(ClassLoaderUtils::getDefaultClassLoader, ReflectionUtils::tryToLoadClass); } private ClasspathScannerLoader() { } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/util/CloseablePath.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static java.util.Collections.emptyMap; import java.io.Closeable; import java.io.IOException; import java.io.UncheckedIOException; import java.net.URI; import java.net.URISyntaxException; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Path; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import org.jspecify.annotations.Nullable; /** * @since 1.0 */ final class CloseablePath implements Closeable { private static final String FILE_URI_SCHEME = "file"; static final String JAR_URI_SCHEME = "jar"; private static final String JAR_FILE_EXTENSION = ".jar"; private static final String JAR_URI_SEPARATOR = "!/"; private static final Closeable NULL_CLOSEABLE = () -> { }; private static final ConcurrentMap MANAGED_FILE_SYSTEMS = new ConcurrentHashMap<>(); private final AtomicBoolean closed = new AtomicBoolean(); private final Path path; private final Closeable delegate; static CloseablePath create(URI uri) throws URISyntaxException { return create(uri, it -> FileSystems.newFileSystem(it, emptyMap())); } static CloseablePath create(URI uri, FileSystemProvider fileSystemProvider) throws URISyntaxException { if (JAR_URI_SCHEME.equals(uri.getScheme())) { // Parsing: jar:!/[], see java.net.JarURLConnection String uriString = uri.toString(); int lastJarUriSeparator = uriString.lastIndexOf(JAR_URI_SEPARATOR); String jarUri = uriString.substring(0, lastJarUriSeparator); String jarEntry = uriString.substring(lastJarUriSeparator + 1); return createForJarFileSystem(new URI(jarUri), fileSystem -> fileSystem.getPath(jarEntry), fileSystemProvider); } if (FILE_URI_SCHEME.equals(uri.getScheme()) && uri.getPath().endsWith(JAR_FILE_EXTENSION)) { return createForJarFileSystem(new URI(JAR_URI_SCHEME + ':' + uri), fileSystem -> fileSystem.getRootDirectories().iterator().next(), fileSystemProvider); } return new CloseablePath(Path.of(uri), NULL_CLOSEABLE); } private static CloseablePath createForJarFileSystem(URI jarUri, Function pathProvider, FileSystemProvider fileSystemProvider) { ManagedFileSystem managedFileSystem = MANAGED_FILE_SYSTEMS.compute(jarUri, (__, oldValue) -> oldValue == null ? new ManagedFileSystem(jarUri, fileSystemProvider) : oldValue.retain()); Path path = pathProvider.apply(managedFileSystem.fileSystem); return new CloseablePath(path, () -> MANAGED_FILE_SYSTEMS.compute(jarUri, (__, ___) -> managedFileSystem.release())); } private CloseablePath(Path path, Closeable delegate) { this.path = path; this.delegate = delegate; } public Path getPath() { return path; } @Override public void close() throws IOException { if (closed.compareAndSet(false, true)) { delegate.close(); } } private static class ManagedFileSystem { private final AtomicInteger referenceCount = new AtomicInteger(1); private final FileSystem fileSystem; private final URI jarUri; ManagedFileSystem(URI jarUri, FileSystemProvider fileSystemProvider) { this.jarUri = jarUri; try { fileSystem = fileSystemProvider.newFileSystem(jarUri); } catch (IOException e) { throw new UncheckedIOException("Failed to create file system for " + jarUri, e); } } private ManagedFileSystem retain() { referenceCount.incrementAndGet(); return this; } private @Nullable ManagedFileSystem release() { if (referenceCount.decrementAndGet() == 0) { close(); return null; } return this; } private void close() { try { fileSystem.close(); } catch (IOException e) { throw new UncheckedIOException("Failed to close file system for " + jarUri, e); } } } interface FileSystemProvider { FileSystem newFileSystem(URI uri) throws IOException; } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/util/CollectionUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static java.util.Spliterator.ORDERED; import static java.util.Spliterators.spliteratorUnknownSize; import static java.util.stream.StreamSupport.stream; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.commons.util.ReflectionUtils.invokeMethod; import java.lang.reflect.Array; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Optional; import java.util.function.Consumer; import java.util.stream.DoubleStream; import java.util.stream.IntStream; import java.util.stream.LongStream; import java.util.stream.Stream; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; /** * Collection of utilities for working with {@link Collection Collections}. * *

DISCLAIMER

* *

These utilities are intended solely for usage within the JUnit framework * itself. Any usage by external parties is not supported. * Use at your own risk! * * @since 1.0 */ @API(status = INTERNAL, since = "1.0") public final class CollectionUtils { private CollectionUtils() { /* no-op */ } /** * Get the only element of a collection of size 1. * * @param collection the collection to get the element from * @return the only element of the collection * @throws PreconditionViolationException if the collection is {@code null} * or does not contain exactly one element */ public static T getOnlyElement(Collection collection) { Preconditions.notNull(collection, "collection must not be null"); Preconditions.condition(collection.size() == 1, () -> "collection must contain exactly one element: " + collection); return firstElement(collection); } /** * Get the first element of the supplied collection unless it's empty. * * @param collection the collection to get the element from * @return the first element of the collection; empty if the collection is empty * @throws PreconditionViolationException if the collection is {@code null} * @since 1.11 */ @API(status = INTERNAL, since = "1.11") public static Optional getFirstElement(Collection collection) { Preconditions.notNull(collection, "collection must not be null"); return collection.isEmpty() // ? Optional.empty() // : Optional.ofNullable(firstElement(collection)); } private static T firstElement(Collection collection) { return collection instanceof List list // ? list.get(0) // : collection.iterator().next(); } /** * Determine if an instance of the supplied type can be converted into a * {@code Stream}. * *

If this method returns {@code true}, {@link #toStream(Object)} can * successfully convert an object of the specified type into a stream. See * {@link #toStream(Object)} for supported types. * * @param type the type to check; may be {@code null} * @return {@code true} if an instance of the type can be converted into a stream * @since 1.9.1 * @see #toStream(Object) */ @API(status = INTERNAL, since = "1.9.1") public static boolean isConvertibleToStream(@Nullable Class type) { if (type == null || type == void.class) { return false; } return (Stream.class.isAssignableFrom(type)// || DoubleStream.class.isAssignableFrom(type)// || IntStream.class.isAssignableFrom(type)// || LongStream.class.isAssignableFrom(type)// || Iterable.class.isAssignableFrom(type)// || Iterator.class.isAssignableFrom(type)// || Object[].class.isAssignableFrom(type)// || (type.isArray() && type.getComponentType().isPrimitive())// || findIteratorMethod(type).isPresent()); } /** * Convert an object of one of the following supported types into a {@code Stream}. * *

    *
  • {@link Stream}
  • *
  • {@link DoubleStream}
  • *
  • {@link IntStream}
  • *
  • {@link LongStream}
  • *
  • {@link Collection}
  • *
  • {@link Iterable}
  • *
  • {@link Iterator}
  • *
  • {@link Object} array
  • *
  • primitive array
  • *
  • any type that provides an * {@link java.util.Iterator Iterator}-returning {@code iterator()} method * (such as, for example, a {@code kotlin.sequences.Sequence})
  • *
* * @param object the object to convert into a stream; never {@code null} * @return the resulting stream * @throws PreconditionViolationException if the supplied object is {@code null} * or not one of the supported types * @see #isConvertibleToStream(Class) */ public static Stream toStream(Object object) { Preconditions.notNull(object, "Object must not be null"); if (object instanceof Stream stream) { return stream; } if (object instanceof DoubleStream stream) { return stream.boxed(); } if (object instanceof IntStream stream) { return stream.boxed(); } if (object instanceof LongStream stream) { return stream.boxed(); } if (object instanceof Collection collection) { return collection.stream(); } if (object instanceof Iterable iterable) { return stream(iterable.spliterator(), false); } if (object instanceof Iterator iterator) { return stream(spliteratorUnknownSize(iterator, ORDERED), false); } if (object instanceof Object[] array) { return Arrays.stream(array); } if (object instanceof double[] array) { return DoubleStream.of(array).boxed(); } if (object instanceof int[] array) { return IntStream.of(array).boxed(); } if (object instanceof long[] array) { return LongStream.of(array).boxed(); } if (object.getClass().isArray() && object.getClass().getComponentType().isPrimitive()) { return IntStream.range(0, Array.getLength(object)).mapToObj(i -> Array.get(object, i)); } return tryConvertToStreamByReflection(object); } private static Stream tryConvertToStreamByReflection(Object object) { return findIteratorMethod(object.getClass()) // .map(method -> (Iterator) invokeMethod(method, object)) // .map(iterator -> spliteratorUnknownSize(iterator, ORDERED)) // .map(spliterator -> stream(spliterator, false)) // .orElseThrow(() -> new PreconditionViolationException( "Cannot convert instance of %s into a Stream: %s".formatted(object.getClass().getName(), object))); } private static Optional findIteratorMethod(Class type) { return ReflectionUtils.findMethod(type, "iterator") // .filter(method -> method.getReturnType() == Iterator.class); } /** * Call the supplied action on each element of the supplied {@link List} from last to first element. */ @API(status = INTERNAL, since = "1.9.2") public static void forEachInReverseOrder(List list, Consumer action) { if (list.isEmpty()) { return; } if (list.size() == 1) { action.accept(list.get(0)); return; } for (ListIterator iterator = list.listIterator(list.size()); iterator.hasPrevious();) { action.accept(iterator.previous()); } } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/util/DefaultClasspathScanner.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static java.util.stream.Collectors.joining; import static org.junit.platform.commons.util.SearchPathUtils.PACKAGE_SEPARATOR_CHAR; import static org.junit.platform.commons.util.SearchPathUtils.PACKAGE_SEPARATOR_STRING; import static org.junit.platform.commons.util.SearchPathUtils.determineSimpleClassName; import static org.junit.platform.commons.util.StringUtils.isNotBlank; import java.io.IOException; import java.net.URI; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Stream; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.function.Try; import org.junit.platform.commons.io.Resource; import org.junit.platform.commons.io.ResourceFilter; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.support.scanning.ClassFilter; import org.junit.platform.commons.support.scanning.ClasspathScanner; /** *

DISCLAIMER

* *

These utilities are intended solely for usage within the JUnit framework * itself. Any usage by external parties is not supported. * Use at your own risk! * * @since 1.0 */ class DefaultClasspathScanner implements ClasspathScanner { private static final Logger logger = LoggerFactory.getLogger(DefaultClasspathScanner.class); private static final char CLASSPATH_RESOURCE_PATH_SEPARATOR = '/'; private static final String CLASSPATH_RESOURCE_PATH_SEPARATOR_STRING = String.valueOf( CLASSPATH_RESOURCE_PATH_SEPARATOR); /** * Malformed class name InternalError like reported in #401. */ private static final String MALFORMED_CLASS_NAME_ERROR_MESSAGE = "Malformed class name"; private final Supplier classLoaderSupplier; private final BiFunction>> loadClass; DefaultClasspathScanner(Supplier classLoaderSupplier, BiFunction>> loadClass) { this.classLoaderSupplier = classLoaderSupplier; this.loadClass = loadClass; } @Override public List> scanForClassesInPackage(String basePackageName, ClassFilter classFilter) { Preconditions.condition( PackageUtils.DEFAULT_PACKAGE_NAME.equals(basePackageName) || isNotBlank(basePackageName), "basePackageName must not be null or blank"); Preconditions.notNull(classFilter, "classFilter must not be null"); basePackageName = basePackageName.strip(); List roots = getRootUrisForPackageNameOnClassPathAndModulePath(basePackageName); return findClassesForUris(roots, basePackageName, classFilter); } @Override public List> scanForClassesInClasspathRoot(URI root, ClassFilter classFilter) { Preconditions.notNull(root, "root must not be null"); Preconditions.notNull(classFilter, "classFilter must not be null"); return findClassesForUri(root, PackageUtils.DEFAULT_PACKAGE_NAME, classFilter); } @Override public List scanForResourcesInPackage(String basePackageName, ResourceFilter resourceFilter) { Preconditions.condition( PackageUtils.DEFAULT_PACKAGE_NAME.equals(basePackageName) || isNotBlank(basePackageName), "basePackageName must not be null or blank"); Preconditions.notNull(resourceFilter, "resourceFilter must not be null"); basePackageName = basePackageName.strip(); List roots = getRootUrisForPackageNameOnClassPathAndModulePath(basePackageName); return findResourcesForUris(roots, basePackageName, resourceFilter); } @Override public List scanForResourcesInClasspathRoot(URI root, ResourceFilter resourceFilter) { Preconditions.notNull(root, "root must not be null"); Preconditions.notNull(resourceFilter, "resourceFilter must not be null"); return findResourcesForUri(root, PackageUtils.DEFAULT_PACKAGE_NAME, resourceFilter); } /** * Recursively scan for classes in all the supplied source directories. */ private List> findClassesForUris(List baseUris, String basePackageName, ClassFilter classFilter) { // @formatter:off return baseUris.stream() .map(baseUri -> findClassesForUri(baseUri, basePackageName, classFilter)) .flatMap(Collection::stream) .distinct() .toList(); // @formatter:on } private List> findClassesForUri(URI baseUri, String basePackageName, ClassFilter classFilter) { List> classes = new ArrayList<>(); // @formatter:off walkFilesForUri(baseUri, SearchPathUtils::isClassOrSourceFile, (baseDir, file) -> processClassFileSafely(baseDir, basePackageName, classFilter, file, classes::add)); // @formatter:on return classes; } /** * Recursively scan for resources in all the supplied source directories. */ private List findResourcesForUris(List baseUris, String basePackageName, ResourceFilter resourceFilter) { // @formatter:off return baseUris.stream() .map(baseUri -> findResourcesForUri(baseUri, basePackageName, resourceFilter)) .flatMap(Collection::stream) .distinct() .toList(); // @formatter:on } private List findResourcesForUri(URI baseUri, String basePackageName, ResourceFilter resourceFilter) { List resources = new ArrayList<>(); // @formatter:off walkFilesForUri(baseUri, SearchPathUtils::isResourceFile, (baseDir, file) -> processResourceFileSafely(baseDir, basePackageName, resourceFilter, file, resources::add)); // @formatter:on return resources; } private static void walkFilesForUri(URI baseUri, Predicate filter, BiConsumer consumer) { try (CloseablePath closeablePath = CloseablePath.create(baseUri)) { Path baseDir = closeablePath.getPath(); Preconditions.condition(Files.exists(baseDir), () -> "baseDir must exist: " + baseDir); try { Files.walkFileTree(baseDir, new ClasspathFileVisitor(baseDir, filter, consumer)); } catch (IOException ex) { logger.warn(ex, () -> "I/O error scanning files in " + baseDir); } } catch (PreconditionViolationException ex) { throw ex; } catch (Exception ex) { logger.warn(ex, () -> "Error scanning files for URI " + baseUri); } } private void processClassFileSafely(Path baseDir, String basePackageName, ClassFilter classFilter, Path file, Consumer> classConsumer) { try { String fullyQualifiedClassName = determineFullyQualifiedClassName(baseDir, basePackageName, file); if (classFilter.match(fullyQualifiedClassName)) { try { // @formatter:off loadClass.apply(fullyQualifiedClassName, getClassLoader()) .toOptional() .filter(classFilter::match) .ifPresent(classConsumer); // @formatter:on } catch (InternalError internalError) { handleInternalError(file, fullyQualifiedClassName, internalError); } } } catch (Throwable throwable) { handleThrowable(file, throwable); } } private void processResourceFileSafely(Path baseDir, String basePackageName, ResourceFilter resourceFilter, Path resourceFile, Consumer resourceConsumer) { try { String fullyQualifiedResourceName = determineFullyQualifiedResourceName(baseDir, basePackageName, resourceFile); Resource resource = Resource.of(fullyQualifiedResourceName, resourceFile.toUri()); if (resourceFilter.match(resource)) { resourceConsumer.accept(resource); } // @formatter:on } catch (Throwable throwable) { handleThrowable(resourceFile, throwable); } } private String determineFullyQualifiedClassName(Path baseDir, String basePackageName, Path file) { // @formatter:off return Stream.of( basePackageName, determineSubpackageName(baseDir, file), determineSimpleClassName(file) ) .filter(value -> !value.isEmpty()) // Handle default package appropriately. .collect(joining(PACKAGE_SEPARATOR_STRING)); // @formatter:on } /** * The fully qualified resource name is a {@code /}-separated path. * *

The path is relative to the classpath root in which the resource is * located. * * @return the resource name; never {@code null} */ private String determineFullyQualifiedResourceName(Path baseDir, String basePackageName, Path resourceFile) { // @formatter:off return Stream.of( packagePath(basePackageName), packagePath(determineSubpackageName(baseDir, resourceFile)), determineSimpleResourceName(resourceFile) ) .filter(value -> !value.isEmpty()) // Handle default package appropriately. .collect(joining(CLASSPATH_RESOURCE_PATH_SEPARATOR_STRING)); // @formatter:on } private String determineSimpleResourceName(Path resourceFile) { return resourceFile.getFileName().toString(); } private String determineSubpackageName(Path baseDir, Path file) { Path relativePath = baseDir.relativize(file.getParent()); String pathSeparator = baseDir.getFileSystem().getSeparator(); return relativePath.toString().replace(pathSeparator, PACKAGE_SEPARATOR_STRING); } private void handleInternalError(Path classFile, String fullyQualifiedClassName, InternalError ex) { if (MALFORMED_CLASS_NAME_ERROR_MESSAGE.equals(ex.getMessage())) { logMalformedClassName(classFile, fullyQualifiedClassName, ex); } else { logGenericFileProcessingException(classFile, ex); } } private void handleThrowable(Path classFile, Throwable throwable) { UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); logGenericFileProcessingException(classFile, throwable); } private void logMalformedClassName(Path classFile, String fullyQualifiedClassName, InternalError ex) { try { logger.debug(ex, () -> "The java.lang.Class loaded from path [%s] has a malformed class name [%s].".formatted( classFile.toAbsolutePath(), fullyQualifiedClassName)); } catch (Throwable t) { UnrecoverableExceptions.rethrowIfUnrecoverable(t); ex.addSuppressed(t); logGenericFileProcessingException(classFile, ex); } } private void logGenericFileProcessingException(Path classpathFile, Throwable throwable) { logger.debug(throwable, () -> "Failed to load [%s] during classpath scanning.".formatted(classpathFile.toAbsolutePath())); } private ClassLoader getClassLoader() { return this.classLoaderSupplier.get(); } private List getRootUrisForPackageNameOnClassPathAndModulePath(String basePackageName) { Set uriSet = new LinkedHashSet<>(getRootUrisForPackage(basePackageName)); if (!basePackageName.isEmpty() && !basePackageName.endsWith(PACKAGE_SEPARATOR_STRING)) { getRootUrisForPackage(basePackageName + PACKAGE_SEPARATOR_STRING).stream() // .map(DefaultClasspathScanner::removeTrailingClasspathResourcePathSeparator) // .forEach(uriSet::add); } return new ArrayList<>(uriSet); } private static URI removeTrailingClasspathResourcePathSeparator(URI uri) { String string = uri.toString(); if (string.endsWith(CLASSPATH_RESOURCE_PATH_SEPARATOR_STRING)) { return URI.create(string.substring(0, string.length() - 1)); } return uri; } private static String packagePath(String packageName) { if (packageName.isEmpty()) { return ""; } return packageName.replace(PACKAGE_SEPARATOR_CHAR, CLASSPATH_RESOURCE_PATH_SEPARATOR); } private List getRootUrisForPackage(String basePackageName) { List uris = new ArrayList<>(); try { Enumeration resources = getClassLoader().getResources(packagePath(basePackageName)); while (resources.hasMoreElements()) { URL resource = resources.nextElement(); uris.add(resource.toURI()); } } catch (Exception ex) { logger.warn(ex, () -> "Error reading URIs from class loader for base package " + basePackageName); } return uris; } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/util/ExceptionUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static org.apiguardian.api.API.Status.INTERNAL; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Deque; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.function.Predicate; import org.apiguardian.api.API; import org.junit.platform.commons.annotation.Contract; /** * Collection of utilities for working with exceptions. * *

DISCLAIMER

* *

These utilities are intended solely for usage within the JUnit framework * itself. Any usage by external parties is not supported. * Use at your own risk! * * @since 1.0 */ @API(status = INTERNAL, since = "1.0") public final class ExceptionUtils { private static final String JUNIT_START_PACKAGE_PREFIX = "org.junit.start."; private static final String JUNIT_PLATFORM_LAUNCHER_PACKAGE_PREFIX = "org.junit.platform.launcher."; private static final Predicate STACK_TRACE_ELEMENT_FILTER = ClassNamePatternFilterUtils // .excludeMatchingClassNames("org.junit.*,jdk.internal.reflect.*,sun.reflect.*"); private ExceptionUtils() { /* no-op */ } /** * Throw the supplied {@link Throwable}, masked as an * unchecked exception. * *

The supplied {@code Throwable} will not be wrapped. Rather, it * will be thrown as is using an exploit of the Java language * that relies on a combination of generics and type erasure to trick * the Java compiler into believing that the thrown exception is an * unchecked exception even if it is a checked exception. * *

Warning

* *

This method should be used sparingly. * * @param t the {@code Throwable} to throw as an unchecked exception; * never {@code null} * @return this method always throws an exception and therefore never * returns anything; the return type is merely present to allow this * method to be supplied as the operand in a {@code throw} statement */ @Contract("_ -> fail") public static RuntimeException throwAsUncheckedException(Throwable t) { Preconditions.notNull(t, "Throwable must not be null"); // The following line will never actually return an exception but rather // throw t masked as a RuntimeException. return ExceptionUtils.throwAs(t); } @Contract("_ -> fail") @SuppressWarnings({ "unchecked", "TypeParameterUnusedInFormals" }) private static T throwAs(Throwable t) throws T { throw (T) t; } /** * Read the stacktrace of the supplied {@link Throwable} into a String. */ public static String readStackTrace(Throwable throwable) { Preconditions.notNull(throwable, "Throwable must not be null"); StringWriter stringWriter = new StringWriter(); try (PrintWriter printWriter = new PrintWriter(stringWriter)) { throwable.printStackTrace(printWriter); } return stringWriter.toString(); } /** * Prune the stack trace of the supplied {@link Throwable}. * *

Prune all {@linkplain StackTraceElement stack trace elements} up to one * of the supplied {@code classNames}. All subsequent elements in the stack * trace will be retained. * *

If the {@code classNames} do not match any of the stacktrace elements * then the {@code org.junit}, {@code jdk.internal.reflect}, and * {@code sun.reflect} packages are pruned. * *

Additionally: *

    *
  • all elements prior to and including the first JUnit Platform Launcher call will be removed. *
  • all elements prior to and including {@code org.junit.start} are kept. *
* * @param throwable the {@code Throwable} whose stack trace should be pruned; * never {@code null} * @param classNames the class names that should stop the pruning if encountered; * never {@code null} * * @since 1.10 */ @API(status = INTERNAL, since = "1.10") public static void pruneStackTrace(Throwable throwable, List classNames) { Preconditions.notNull(throwable, "Throwable must not be null"); Preconditions.notNull(classNames, "List of class names must not be null"); List stackTrace = Arrays.asList(throwable.getStackTrace()); List prunedStackTrace = new ArrayList<>(); List junitStartStackTrace = new ArrayList<>(0); Collections.reverse(stackTrace); for (int i = 0; i < stackTrace.size(); i++) { StackTraceElement element = stackTrace.get(i); String className = element.getClassName(); if (classNames.contains(className) && !includesJunitStart(stackTrace, i + 1)) { // We found the test // everything before that is not informative. prunedStackTrace.clear(); // Include all elements called by the test prunedStackTrace.addAll(stackTrace.subList(i, stackTrace.size())); break; } else if (className.startsWith(JUNIT_START_PACKAGE_PREFIX)) { junitStartStackTrace.addAll(prunedStackTrace); prunedStackTrace.clear(); junitStartStackTrace.add(element); } else if (className.startsWith(JUNIT_PLATFORM_LAUNCHER_PACKAGE_PREFIX)) { prunedStackTrace.clear(); } else if (STACK_TRACE_ELEMENT_FILTER.test(className)) { prunedStackTrace.add(element); } } if (!junitStartStackTrace.isEmpty()) { junitStartStackTrace.addAll(prunedStackTrace); prunedStackTrace = junitStartStackTrace; } Collections.reverse(prunedStackTrace); throwable.setStackTrace(prunedStackTrace.toArray(new StackTraceElement[0])); } private static boolean includesJunitStart(List stackTrace, int fromIndex) { return stackTrace.stream() // .skip(fromIndex) // .map(StackTraceElement::getClassName) // .anyMatch(className -> className.startsWith(JUNIT_START_PACKAGE_PREFIX)); } /** * Find all causes and suppressed exceptions in the stack trace of the * supplied {@link Throwable}. * * @param rootThrowable the {@code Throwable} to explore; never {@code null} * @return an immutable list of all throwables found, including the supplied * one; never {@code null} * * @since 1.10 */ @API(status = INTERNAL, since = "1.10") public static List findNestedThrowables(Throwable rootThrowable) { Preconditions.notNull(rootThrowable, "Throwable must not be null"); Set visited = new LinkedHashSet<>(); Deque toVisit = new ArrayDeque<>(); toVisit.add(rootThrowable); while (!toVisit.isEmpty()) { Throwable current = toVisit.remove(); boolean isFirstVisit = visited.add(current); if (isFirstVisit) { Throwable cause = current.getCause(); if (cause != null) { toVisit.add(cause); } Collections.addAll(toVisit, current.getSuppressed()); } } return List.copyOf(visited); } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/util/FunctionUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.function.Function; import java.util.function.Predicate; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; /** * Collection of utilities for working with {@link Function Functions}, * {@link Predicate Predicates}, etc. * *

DISCLAIMER

* *

These utilities are intended solely for usage within the JUnit framework * itself. Any usage by external parties is not supported. * Use at your own risk! * * @since 1.0 */ @API(status = INTERNAL, since = "1.0") public final class FunctionUtils { private FunctionUtils() { /* no-op */ } /** * Return a predicate that first applies the specified function and then * tests the specified predicate against the result of the function. * * @param function the function to apply * @param predicate the predicate to test against the result of the function */ public static Predicate where(Function function, Predicate predicate) { Preconditions.notNull(function, "function must not be null"); Preconditions.notNull(predicate, "predicate must not be null"); return input -> predicate.test(function.apply(input)); } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/util/KotlinFunctionUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static kotlin.jvm.JvmClassMappingKt.getJavaClass; import static kotlin.reflect.full.KCallables.callSuspendBy; import static kotlin.reflect.jvm.KCallablesJvm.isAccessible; import static kotlin.reflect.jvm.KCallablesJvm.setAccessible; import static kotlin.reflect.jvm.KTypesJvm.getJvmErasure; import static kotlin.reflect.jvm.ReflectJvmMapping.getJavaType; import static kotlinx.coroutines.BuildersKt.runBlocking; import static org.junit.platform.commons.util.ExceptionUtils.throwAsUncheckedException; import static org.junit.platform.commons.util.ReflectionUtils.EMPTY_CLASS_ARRAY; import static org.junit.platform.commons.util.ReflectionUtils.getUnderlyingCause; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.lang.reflect.Type; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import kotlin.Unit; import kotlin.coroutines.EmptyCoroutineContext; import kotlin.reflect.KFunction; import kotlin.reflect.KParameter; import kotlin.reflect.jvm.ReflectJvmMapping; class KotlinFunctionUtils { static Class getReturnType(Method method) { var returnType = getJavaClass(getJvmErasure(getKotlinFunction(method).getReturnType())); if (Unit.class.equals(returnType)) { return void.class; } return returnType; } static Type getGenericReturnType(Method method) { return getJavaType(getKotlinFunction(method).getReturnType()); } static Parameter[] getParameters(Method method) { var parameterCount = method.getParameterCount(); if (parameterCount == 1) { return new Parameter[0]; } return Arrays.copyOf(method.getParameters(), parameterCount - 1); } static Class[] getParameterTypes(Method method) { var parameterCount = method.getParameterCount(); if (parameterCount == 1) { return EMPTY_CLASS_ARRAY; } return Arrays.stream(method.getParameterTypes()).limit(parameterCount - 1).toArray(Class[]::new); } static @Nullable Object invokeKotlinFunction(Method method, @Nullable Object target, @Nullable Object[] args) { try { return invokeKotlinFunction(getKotlinFunction(method), target, args); } catch (InterruptedException e) { throw throwAsUncheckedException(e); } } private static T invokeKotlinFunction(KFunction function, @Nullable Object target, @Nullable Object[] args) throws InterruptedException { if (!isAccessible(function)) { setAccessible(function, true); } return function.callBy(toArgumentMap(target, args, function)); } static @Nullable Object invokeKotlinSuspendingFunction(Method method, @Nullable Object target, @Nullable Object[] args) { try { return invokeKotlinSuspendingFunction(getKotlinFunction(method), target, args); } catch (InterruptedException e) { throw throwAsUncheckedException(e); } } private static T invokeKotlinSuspendingFunction(KFunction function, @Nullable Object target, @Nullable Object[] args) throws InterruptedException { if (!isAccessible(function)) { setAccessible(function, true); } return runBlocking(EmptyCoroutineContext.INSTANCE, (__, continuation) -> { try { return callSuspendBy(function, toArgumentMap(target, args, function), continuation); } catch (Exception e) { throw throwAsUncheckedException(getUnderlyingCause(e)); } }); } private static Map toArgumentMap(@Nullable Object target, @Nullable Object[] args, KFunction function) { Map arguments = new HashMap<>(args.length + 1); int index = 0; for (KParameter parameter : function.getParameters()) { switch (parameter.getKind()) { case INSTANCE -> arguments.put(parameter, target); case VALUE, EXTENSION_RECEIVER -> { arguments.put(parameter, args[index]); index++; } default -> throw new JUnitException("Unsupported parameter kind: " + parameter.getKind()); } } return arguments; } private static KFunction getKotlinFunction(Method method) { return Preconditions.notNull(ReflectJvmMapping.getKotlinFunction(method), () -> "Failed to get Kotlin function for method: " + method); } private KotlinFunctionUtils() { } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/util/KotlinReflectionUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.commons.util.ReflectionUtils.EMPTY_CLASS_ARRAY; import static org.junit.platform.commons.util.ReflectionUtils.findMethod; import static org.junit.platform.commons.util.ReflectionUtils.isStatic; import static org.junit.platform.commons.util.ReflectionUtils.tryToLoadClass; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.lang.reflect.Type; import java.util.Arrays; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.function.Try; /** * Internal Kotlin-specific reflection utilities * * @since 1.13.3 */ @API(status = INTERNAL, since = "1.13.3") public class KotlinReflectionUtils { private static final String DEFAULT_IMPLS_CLASS_NAME = "DefaultImpls"; private static final @Nullable Class kotlinMetadata; private static final @Nullable Class jvmInline; private static final @Nullable Class kotlinCoroutineContinuation; private static final boolean kotlinReflectPresent; private static final boolean kotlinxCoroutinesPresent; static { var metadata = tryToLoadKotlinMetadataClass(); kotlinMetadata = metadata.toOptional().orElse(null); jvmInline = tryToLoadJvmInlineClass().toOptional().orElse(null); kotlinCoroutineContinuation = metadata // .andThen(__ -> tryToLoadClass("kotlin.coroutines.Continuation")) // .toOptional() // .orElse(null); kotlinReflectPresent = metadata.andThen(__ -> tryToLoadClass("kotlin.reflect.jvm.ReflectJvmMapping")) // .toOptional() // .isPresent(); kotlinxCoroutinesPresent = metadata.andThen(__ -> tryToLoadClass("kotlinx.coroutines.BuildersKt")) // .toOptional() // .isPresent(); } @SuppressWarnings("unchecked") private static Try> tryToLoadKotlinMetadataClass() { return tryToLoadClass("kotlin.Metadata") // .andThenTry(it -> (Class) it); } @SuppressWarnings("unchecked") private static Try> tryToLoadJvmInlineClass() { return tryToLoadClass("kotlin.jvm.JvmInline") // .andThenTry(it -> (Class) it); } /** * @since 6.0 */ @API(status = INTERNAL, since = "6.0") public static boolean isKotlinSuspendingFunction(Method method) { if (!method.isSynthetic() && kotlinCoroutineContinuation != null && isKotlinType(method.getDeclaringClass())) { int parameterCount = method.getParameterCount(); return parameterCount > 0 // && method.getParameterTypes()[parameterCount - 1] == kotlinCoroutineContinuation; } return false; } /** * Determines whether the supplied class is a {@code DefaultImpls} class * generated by the Kotlin compiler. * *

See * Kotlin documentation * for details. */ public static boolean isKotlinInterfaceDefaultImplsClass(Class clazz) { if (!isKotlinType(clazz) || !DEFAULT_IMPLS_CLASS_NAME.equals(clazz.getSimpleName()) || !isStatic(clazz)) { return false; } Class enclosingClass = clazz.getEnclosingClass(); if (enclosingClass != null && enclosingClass.isInterface()) { return Arrays.stream(clazz.getDeclaredMethods()) // .anyMatch(method -> isCompilerGeneratedDefaultMethod(method, enclosingClass)); } return false; } private static boolean isCompilerGeneratedDefaultMethod(Method method, Class enclosingClass) { if (isStatic(method) && method.getParameterCount() > 0) { var parameterTypes = method.getParameterTypes(); if (parameterTypes[0] == enclosingClass) { var originalParameterTypes = copyWithoutFirst(parameterTypes); return findMethod(enclosingClass, method.getName(), originalParameterTypes).isPresent(); } } return false; } private static Class[] copyWithoutFirst(Class[] values) { if (values.length == 1) { return EMPTY_CLASS_ARRAY; } var result = new Class[values.length - 1]; System.arraycopy(values, 1, result, 0, result.length); return result; } @API(status = INTERNAL, since = "6.1") public static boolean isKotlinType(Class clazz) { return kotlinMetadata != null // && clazz.getDeclaredAnnotation(kotlinMetadata) != null; } @API(status = INTERNAL, since = "6.1") public static boolean isKotlinReflectPresent() { return kotlinReflectPresent; } public static Class getKotlinSuspendingFunctionReturnType(Method method) { requireKotlinReflect(method); return KotlinFunctionUtils.getReturnType(method); } public static Type getKotlinSuspendingFunctionGenericReturnType(Method method) { requireKotlinReflect(method); return KotlinFunctionUtils.getGenericReturnType(method); } public static Parameter[] getKotlinSuspendingFunctionParameters(Method method) { requireKotlinReflect(method); return KotlinFunctionUtils.getParameters(method); } public static Class[] getKotlinSuspendingFunctionParameterTypes(Method method) { requireKotlinReflect(method); return KotlinFunctionUtils.getParameterTypes(method); } public static @Nullable Object invokeKotlinSuspendingFunction(Method method, @Nullable Object target, @Nullable Object[] args) { requireKotlinReflect(method); requireKotlinxCoroutines(method); return KotlinFunctionUtils.invokeKotlinSuspendingFunction(method, target, args); } @API(status = INTERNAL, since = "6.1") public static boolean isInstanceOfInlineType(@Nullable Object value) { return jvmInline != null && value != null && value.getClass().getDeclaredAnnotation(jvmInline) != null; } @API(status = INTERNAL, since = "6.1") public static @Nullable Object invokeKotlinFunction(Method method, @Nullable Object target, @Nullable Object... args) { requireKotlinReflect(method); return KotlinFunctionUtils.invokeKotlinFunction(method, target, args); } private static void requireKotlinReflect(Method method) { requireDependency(method, kotlinReflectPresent, "org.jetbrains.kotlin:kotlin-reflect"); } private static void requireKotlinxCoroutines(Method method) { requireDependency(method, kotlinxCoroutinesPresent, "org.jetbrains.kotlinx:kotlinx-coroutines-core"); } private static void requireDependency(Method method, boolean condition, String dependencyNotation) { Preconditions.condition(condition, () -> ("Kotlin suspending function [%s] requires %s to be on the classpath or module path. " + "Please add a corresponding dependency.").formatted(method.toGenericString(), dependencyNotation)); } private KotlinReflectionUtils() { } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/util/LruCache.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static org.apiguardian.api.API.Status.INTERNAL; import java.io.Serial; import java.util.LinkedHashMap; import java.util.Map; import org.apiguardian.api.API; /** * A simple LRU cache with a maximum size. * *

This class is not thread-safe. * * @param the type of keys maintained by this cache * @param the type of values maintained by this cache * @since 1.6 */ @API(status = INTERNAL, since = "1.6") public class LruCache extends LinkedHashMap { @Serial private static final long serialVersionUID = 1L; private final int maxSize; /** * Create a new LRU cache that maintains at most the supplied number of * entries. * *

For optimal use of the internal data structures, you should pick a * number that's one below a power of two since this is based on a * {@link java.util.HashMap} and the eldest entry will be evicted after * adding the entry that increases the {@linkplain #size() size} to be above * {@code maxSize}. */ public LruCache(int maxSize) { super(maxSize + 1, 1, true); this.maxSize = maxSize; } @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() > maxSize; } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/util/ModuleUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static java.util.Objects.requireNonNull; import static java.util.function.Predicate.isEqual; import static java.util.stream.Collectors.toCollection; import static java.util.stream.Collectors.toSet; import static org.apiguardian.api.API.Status.INTERNAL; import java.io.IOException; import java.lang.module.Configuration; import java.lang.module.ModuleFinder; import java.lang.module.ModuleReader; import java.lang.module.ModuleReference; import java.lang.module.ResolvedModule; import java.net.URI; import java.net.URISyntaxException; import java.nio.file.Path; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.function.Predicate; import java.util.stream.Stream; import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.io.Resource; import org.junit.platform.commons.io.ResourceFilter; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.support.scanning.ClassFilter; /** * Collection of utilities for working with {@code java.lang.Module} * and friends. * *

DISCLAIMER

* *

These utilities are intended solely for usage within the JUnit framework * itself. Any usage by external parties is not supported. * Use at your own risk! * * @since 1.1 */ @API(status = INTERNAL, since = "1.1") public class ModuleUtils { private static final Logger logger = LoggerFactory.getLogger(ModuleUtils.class); /** * Find all non-system boot modules names. * * @return a set of all such module names; never {@code null} but * potentially empty */ public static Set findAllNonSystemBootModuleNames() { // @formatter:off Set systemModules = ModuleFinder.ofSystem().findAll().stream() .map(reference -> reference.descriptor().name()) .collect(toSet()); return streamResolvedModules(name -> !systemModules.contains(name)) .map(ResolvedModule::name) .collect(toCollection(LinkedHashSet::new)); // @formatter:on } public static Optional getModuleName(Class type) { Preconditions.notNull(type, "Class type must not be null"); return Optional.ofNullable(type.getModule().getName()); } public static Optional getModuleVersion(Class type) { Preconditions.notNull(type, "Class type must not be null"); Module module = type.getModule(); return module.isNamed() ? module.getDescriptor().rawVersion() : Optional.empty(); } /** * Find all {@linkplain Class classes} for the given module name. * * @param moduleName the name of the module to scan; never {@code null} or * empty * @param filter the class filter to apply; never {@code null} * @return an immutable list of all such classes found; never {@code null} * but potentially empty */ public static List> findAllClassesInModule(String moduleName, ClassFilter filter) { Preconditions.notBlank(moduleName, "Module name must not be null or empty"); Preconditions.notNull(filter, "Class filter must not be null"); logger.debug(() -> "Looking for classes in module: " + moduleName); // @formatter:off Set moduleReferences = streamResolvedModules(isEqual(moduleName)) .map(ResolvedModule::reference) .collect(toSet()); // @formatter:on return scan(moduleReferences, filter, ModuleUtils.class.getClassLoader()); } /** * Find all {@linkplain Class classes} for the given module. * * @param module the module to scan; never {@code null} or unnamed * @param filter the class filter to apply; never {@code null} * @return an immutable list of all such classes found; never {@code null} * but potentially empty * @since 6.1 */ @API(status = INTERNAL, since = "6.1") public static List> findAllClassesInModule(Module module, ClassFilter filter) { Preconditions.notNull(module, "Module must not be null"); Preconditions.condition(module.isNamed(), "Module must not be unnamed"); Preconditions.notNull(filter, "Class filter must not be null"); String name = module.getName(); logger.debug(() -> "Looking for classes in module: " + name); var reference = module.getLayer().configuration().findModule(name).orElseThrow().reference(); return scan(Set.of(reference), filter, module.getClassLoader()); } /** * Find all {@linkplain Resource resources} for the given module name. * * @param moduleName the name of the module to scan; never {@code null} or * empty * @param filter the class filter to apply; never {@code null} * @return an immutable list of all such resources found; never {@code null} * but potentially empty * @since 1.11 */ @API(status = INTERNAL, since = "1.11") public static List findAllResourcesInModule(String moduleName, ResourceFilter filter) { Preconditions.notBlank(moduleName, "Module name must not be null or empty"); Preconditions.notNull(filter, "Resource filter must not be null"); logger.debug(() -> "Looking for resources in module: " + moduleName); // @formatter:off Set moduleReferences = streamResolvedModules(isEqual(moduleName)) .map(ResolvedModule::reference) .collect(toSet()); // @formatter:on return scan(moduleReferences, filter, ModuleUtils.class.getClassLoader()); } /** * Find all {@linkplain Resource resources} for the given module. * * @param module the module to scan; never {@code null} or empty * @param filter the class filter to apply; never {@code null} * @return an immutable list of all such resources found; never {@code null} * but potentially empty * @since 6.1 */ @API(status = INTERNAL, since = "6.1") public static List findAllResourcesInModule(Module module, ResourceFilter filter) { Preconditions.notNull(module, "Module must not be null"); Preconditions.condition(module.isNamed(), "Module must not be unnamed"); Preconditions.notNull(filter, "Resource filter must not be null"); String name = module.getName(); logger.debug(() -> "Looking for resources in module: " + name); var reference = module.getLayer().configuration().findModule(name).orElseThrow().reference(); return scan(Set.of(reference), filter, module.getClassLoader()); } /** * Stream resolved modules from current (or boot) module layer. */ private static Stream streamResolvedModules(Predicate moduleNamePredicate) { Module module = ModuleUtils.class.getModule(); ModuleLayer layer = module.getLayer(); if (layer == null) { logger.config(() -> ModuleUtils.class + " is a member of " + module + " - using boot layer returned by ModuleLayer.boot() as fall-back."); layer = ModuleLayer.boot(); } return streamResolvedModules(moduleNamePredicate, layer); } /** * Stream resolved modules from the supplied layer. */ private static Stream streamResolvedModules(Predicate moduleNamePredicate, ModuleLayer layer) { logger.debug(() -> "Streaming modules for layer @" + System.identityHashCode(layer) + ": " + layer); Configuration configuration = layer.configuration(); logger.debug(() -> "Module layer configuration: " + configuration); Stream stream = configuration.modules().stream(); return stream.filter(module -> moduleNamePredicate.test(module.name())); } /** * Scan for classes using the supplied set of module references, class * filter, and loader. */ private static List> scan(Set references, ClassFilter filter, ClassLoader loader) { logger.debug(() -> "Scanning " + references.size() + " module references: " + references); ModuleReferenceClassScanner scanner = new ModuleReferenceClassScanner(filter, loader); List> classes = new ArrayList<>(); for (ModuleReference reference : references) { classes.addAll(scanner.scan(reference)); } logger.debug(() -> "Found " + classes.size() + " classes: " + classes); return List.copyOf(classes); } /** * Scan for resources using the supplied set of module references, class * filter, and loader. */ private static List scan(Set references, ResourceFilter filter, ClassLoader loader) { logger.debug(() -> "Scanning " + references.size() + " module references: " + references); ModuleReferenceResourceScanner scanner = new ModuleReferenceResourceScanner(filter, loader); List resources = new ArrayList<>(); for (ModuleReference reference : references) { resources.addAll(scanner.scan(reference)); } logger.debug(() -> "Found " + resources.size() + " resources: " + resources); return List.copyOf(resources); } /** * {@link ModuleReference} class scanner. * * @since 1.1 */ static class ModuleReferenceClassScanner { private final ClassFilter classFilter; private final ClassLoader classLoader; ModuleReferenceClassScanner(ClassFilter classFilter, ClassLoader classLoader) { this.classFilter = classFilter; this.classLoader = classLoader; } /** * Scan module reference for classes that potentially contain testable methods. */ List> scan(ModuleReference reference) { try (ModuleReader reader = reference.open()) { try (Stream names = reader.list()) { // @formatter:off return names.filter(name -> !name.endsWith("/")) // remove directories .map(Path::of) .filter(SearchPathUtils::isClassOrSourceFile) .map(SearchPathUtils::determineFullyQualifiedClassName) .filter(classFilter::match) .> map(this::loadClassUnchecked) .filter(classFilter::match) .toList(); // @formatter:on } } catch (IOException e) { throw new JUnitException("Failed to read contents of " + reference + ".", e); } } /** * Load class by its binary name. * * @see ClassLoader#loadClass(String) */ private Class loadClassUnchecked(String binaryName) { try { return classLoader.loadClass(binaryName); } catch (ClassNotFoundException e) { throw new JUnitException("Failed to load class with name '" + binaryName + "'.", e); } } } /** * {@link ModuleReference} resource class scanner. * * @since 1.11 */ static class ModuleReferenceResourceScanner { private final ResourceFilter resourceFilter; private final ClassLoader classLoader; ModuleReferenceResourceScanner(ResourceFilter resourceFilter, ClassLoader classLoader) { this.resourceFilter = resourceFilter; this.classLoader = classLoader; } /** * Scan module reference for resources that potentially contain testable resources. */ List scan(ModuleReference reference) { try (ModuleReader reader = reference.open()) { try (Stream names = reader.list()) { // @formatter:off return names.filter(name -> !name.endsWith(".class")) .map(this::loadResourceUnchecked) .filter(resourceFilter::match) .toList(); // @formatter:on } } catch (IOException e) { throw new JUnitException("Failed to read contents of " + reference + ".", e); } } private Resource loadResourceUnchecked(String binaryName) { try { URI uri = requireNonNull(classLoader.getResource(binaryName)).toURI(); return Resource.of(binaryName, uri); } catch (NullPointerException | URISyntaxException e) { throw new JUnitException("Failed to load resource with name '" + binaryName + "'.", e); } } } private ModuleUtils() { } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/util/PackageUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static org.apiguardian.api.API.Status.INTERNAL; import java.io.File; import java.net.URL; import java.util.Optional; import java.util.function.Function; import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.jar.Manifest; import org.apiguardian.api.API; /** * Collection of utilities for working with {@linkplain Package packages}. * *

DISCLAIMER

* *

These utilities are intended solely for usage within the JUnit framework * itself. Any usage by external parties is not supported. * Use at your own risk! * * @since 1.0 */ @API(status = INTERNAL, since = "1.0") public final class PackageUtils { private PackageUtils() { /* no-op */ } public static final String DEFAULT_PACKAGE_NAME = ""; /** * Get the package attribute for the supplied {@code type} using the * supplied {@code function}. * *

This method only returns a non-empty {@link Optional} value holder * if the class loader for the supplied type created a {@link Package} * object and the supplied function does not return {@code null} when * applied. * * @param type the type to get the package attribute for * @param function a function that computes the package attribute value * (e.g., {@code Package::getImplementationTitle}); never {@code null} * @return an {@code Optional} containing the attribute value; never * {@code null} but potentially empty * @throws org.junit.platform.commons.PreconditionViolationException if the * supplied type or function is {@code null} * @see Class#getPackage() * @see Package#getImplementationTitle() * @see Package#getImplementationVersion() */ public static Optional getAttribute(Class type, Function function) { Preconditions.notNull(type, "type must not be null"); Preconditions.notNull(function, "function must not be null"); return Optional.ofNullable(type.getPackage()).map(function); } /** * Get the value of the specified attribute name, specified as a string, * or an empty {@link Optional} if the attribute was not found. The attribute * name is case-insensitive. * *

This method also returns an empty {@link Optional} value holder * if any exception is caught while loading the manifest file via the * JAR file of the specified type. * * @param type the type to get the attribute for * @param name the attribute name as a string * @return an {@code Optional} containing the attribute value; never * {@code null} but potentially empty * @throws org.junit.platform.commons.PreconditionViolationException if the * supplied type is {@code null} or the specified name is blank * @see Manifest#getMainAttributes() */ public static Optional getAttribute(Class type, String name) { Preconditions.notNull(type, "type must not be null"); Preconditions.notBlank(name, "name must not be blank"); try { URL jarUrl = type.getProtectionDomain().getCodeSource().getLocation(); try (JarFile jarFile = new JarFile(new File(jarUrl.toURI()))) { Attributes mainAttributes = jarFile.getManifest().getMainAttributes(); return Optional.ofNullable(mainAttributes.getValue(name)); } } catch (Exception ex) { return Optional.empty(); } } /** * Get the module or implementation version for the supplied {@code type}. * *

The former is only available if the type is part of a versioned module * on the module path; the latter only if the type is part of a JAR file with * a manifest that contains an {@code Implementation-Version} attribute. * * @since 1.11 */ @API(status = INTERNAL, since = "1.11") public static Optional getModuleOrImplementationVersion(Class type) { Optional moduleVersion = ModuleUtils.getModuleVersion(type); if (moduleVersion.isPresent()) { return moduleVersion; } return getAttribute(type, Package::getImplementationVersion); } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/util/Preconditions.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.Arrays; import java.util.Collection; import java.util.function.Supplier; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.annotation.Contract; /** * Collection of utilities for asserting preconditions for method and * constructor arguments. * *

Each method in this class throws a {@link PreconditionViolationException} * if the precondition is violated. * *

DISCLAIMER

* *

These utilities are intended solely for usage within the JUnit framework * itself. Any usage by external parties is not supported. * Use at your own risk! * * @since 1.0 */ @API(status = INTERNAL, since = "1.0") public final class Preconditions { private Preconditions() { /* no-op */ } /** * Assert that the supplied {@link Object} is not {@code null}. * * @param object the object to check * @param message precondition violation message * @return the supplied object as a convenience * @throws PreconditionViolationException if the supplied object is {@code null} * @see #notNull(Object, Supplier) */ @Contract("null, _ -> fail; !null, _ -> param1") public static T notNull(@Nullable T object, String message) throws PreconditionViolationException { condition(object != null, message); return object; } /** * Assert that the supplied {@link Object} is not {@code null}. * * @param object the object to check * @param messageSupplier precondition violation message supplier * @return the supplied object as a convenience * @throws PreconditionViolationException if the supplied object is {@code null} * @see #condition(boolean, Supplier) */ @Contract("null, _ -> fail; !null, _ -> param1") public static T notNull(@Nullable T object, Supplier messageSupplier) throws PreconditionViolationException { condition(object != null, messageSupplier); return object; } /** * Assert that the supplied array is neither {@code null} nor empty. * * @param array the array to check * @param message precondition violation message * @return the supplied array as a convenience * @throws PreconditionViolationException if the supplied array is * {@code null} or empty * @since 1.9 * @see #condition(boolean, String) */ @Contract("null, _ -> fail") @API(status = INTERNAL, since = "1.11") public static int[] notEmpty(int @Nullable [] array, String message) throws PreconditionViolationException { condition(array != null && array.length > 0, message); return array; } /** * Assert that the supplied array is neither {@code null} nor empty. * *

WARNING: this method does NOT check if the supplied * array contains any {@code null} elements. * * @param array the array to check * @param message precondition violation message * @return the supplied array as a convenience * @throws PreconditionViolationException if the supplied array is * {@code null} or empty * @see #containsNoNullElements(Object[], String) * @see #condition(boolean, String) */ @Contract("null, _ -> fail") public static T[] notEmpty(T @Nullable [] array, String message) throws PreconditionViolationException { condition(array != null && array.length > 0, message); return array; } /** * Assert that the supplied array is neither {@code null} nor empty. * *

WARNING: this method does NOT check if the supplied * array contains any {@code null} elements. * * @param array the array to check * @param messageSupplier precondition violation message supplier * @return the supplied array as a convenience * @throws PreconditionViolationException if the supplied array is * {@code null} or empty * @see #containsNoNullElements(Object[], String) * @see #condition(boolean, String) */ @Contract("null, _ -> fail") public static T[] notEmpty(T @Nullable [] array, Supplier messageSupplier) throws PreconditionViolationException { condition(array != null && array.length > 0, messageSupplier); return array; } /** * Assert that the supplied {@link Collection} is neither {@code null} nor empty. * *

WARNING: this method does NOT check if the supplied * collection contains any {@code null} elements. * * @param collection the collection to check * @param message precondition violation message * @return the supplied collection as a convenience * @throws PreconditionViolationException if the supplied collection is {@code null} or empty * @see #containsNoNullElements(Collection, String) * @see #condition(boolean, String) */ @Contract("null, _ -> fail") public static > T notEmpty(@Nullable T collection, String message) throws PreconditionViolationException { condition(collection != null && !collection.isEmpty(), message); return collection; } /** * Assert that the supplied {@link Collection} is neither {@code null} nor empty. * *

WARNING: this method does NOT check if the supplied * collection contains any {@code null} elements. * * @param collection the collection to check * @param messageSupplier precondition violation message supplier * @return the supplied collection as a convenience * @throws PreconditionViolationException if the supplied collection is {@code null} or empty * @see #containsNoNullElements(Collection, String) * @see #condition(boolean, String) */ @Contract("null, _ -> fail") public static > T notEmpty(@Nullable T collection, Supplier messageSupplier) throws PreconditionViolationException { condition(collection != null && !collection.isEmpty(), messageSupplier); return collection; } /** * Assert that the supplied array contains no {@code null} elements. * *

WARNING: this method does NOT check if the supplied * array is {@code null} or empty. * * @param array the array to check * @param message precondition violation message * @return the supplied array as a convenience * @throws PreconditionViolationException if the supplied array contains * any {@code null} elements * @see #notNull(Object, String) */ @Contract("null, _ -> null") public static T @Nullable [] containsNoNullElements(T @Nullable [] array, String message) throws PreconditionViolationException { if (array != null) { Arrays.stream(array).forEach(object -> notNull(object, message)); } return array; } /** * Assert that the supplied array contains no {@code null} elements. * *

WARNING: this method does NOT check if the supplied * array is {@code null} or empty. * * @param array the array to check * @param messageSupplier precondition violation message supplier * @return the supplied array as a convenience * @throws PreconditionViolationException if the supplied array contains * any {@code null} elements * @see #notNull(Object, String) */ @Contract("null, _ -> null") public static T @Nullable [] containsNoNullElements(T @Nullable [] array, Supplier messageSupplier) throws PreconditionViolationException { if (array != null) { Arrays.stream(array).forEach(object -> notNull(object, messageSupplier)); } return array; } /** * Assert that the supplied collection contains no {@code null} elements. * *

WARNING: this method does NOT check if the supplied * collection is {@code null} or empty. * * @param collection the collection to check * @param message precondition violation message * @return the supplied collection as a convenience * @throws PreconditionViolationException if the supplied collection contains * any {@code null} elements * @see #notNull(Object, String) */ @Contract("null, _ -> null") public static > @Nullable T containsNoNullElements(@Nullable T collection, String message) throws PreconditionViolationException { if (collection != null) { collection.forEach(object -> notNull(object, message)); } return collection; } /** * Assert that the supplied collection contains no {@code null} elements. * *

WARNING: this method does NOT check if the supplied * collection is {@code null} or empty. * * @param collection the collection to check * @param messageSupplier precondition violation message supplier * @return the supplied collection as a convenience * @throws PreconditionViolationException if the supplied collection contains * any {@code null} elements * @see #notNull(Object, String) */ @Contract("null, _ -> null") public static > @Nullable T containsNoNullElements(@Nullable T collection, Supplier messageSupplier) throws PreconditionViolationException { if (collection != null) { collection.forEach(object -> notNull(object, messageSupplier)); } return collection; } /** * Assert that the supplied collection contains no blank elements. * *

WARNING: this method does NOT check if the supplied * collection is {@code null} or empty. * * @param collection the collection to check * @param message precondition violation message * @return the supplied collection as a convenience * @throws PreconditionViolationException if the supplied collection contains * any blank elements * @since 6.1 * @see #notBlank(String, String) */ @API(status = INTERNAL, since = "6.1") @Contract("null, _ -> null") public static > @Nullable T containsNoBlankElements(@Nullable T collection, String message) throws PreconditionViolationException { if (collection != null) { collection.forEach(object -> notBlank(object, message)); } return collection; } /** * Assert that the supplied collection contains no blank elements. * *

WARNING: this method does NOT check if the supplied * collection is {@code null} or empty. * * @param collection the collection to check * @param messageSupplier precondition violation message supplier * @return the supplied collection as a convenience * @throws PreconditionViolationException if the supplied collection contains * any blank elements * @since 6.1 * @see #notBlank(String, String) */ @API(status = INTERNAL, since = "6.1") @Contract("null, _ -> null") public static > @Nullable T containsNoBlankElements(@Nullable T collection, Supplier messageSupplier) throws PreconditionViolationException { if (collection != null) { collection.forEach(object -> notBlank(object, messageSupplier)); } return collection; } /** * Assert that the supplied {@link String} is not blank. * *

A {@code String} is blank if it is {@code null} or consists * only of whitespace characters. * * @param str the string to check * @param message precondition violation message * @return the supplied string as a convenience * @throws PreconditionViolationException if the supplied string is blank * @see #notBlank(String, Supplier) */ @Contract("null, _ -> fail") public static String notBlank(@Nullable String str, String message) throws PreconditionViolationException { condition(StringUtils.isNotBlank(str), message); return str; } /** * Assert that the supplied {@link String} is not blank. * *

A {@code String} is blank if it is {@code null} or consists * only of whitespace characters. * * @param str the string to check * @param messageSupplier precondition violation message supplier * @return the supplied string as a convenience * @throws PreconditionViolationException if the supplied string is blank * @see StringUtils#isNotBlank(String) * @see #condition(boolean, Supplier) */ @Contract("null, _ -> fail") public static String notBlank(@Nullable String str, Supplier messageSupplier) throws PreconditionViolationException { condition(StringUtils.isNotBlank(str), messageSupplier); return str; } /** * Assert that the supplied {@code predicate} is {@code true}. * * @param predicate the predicate to check * @param message precondition violation message * @throws PreconditionViolationException if the predicate is {@code false} * @see #condition(boolean, Supplier) */ @Contract("false, _ -> fail") public static void condition(boolean predicate, String message) throws PreconditionViolationException { if (!predicate) { throw new PreconditionViolationException(message); } } /** * Assert that the supplied {@code predicate} is {@code true}. * * @param predicate the predicate to check * @param messageSupplier precondition violation message supplier * @throws PreconditionViolationException if the predicate is {@code false} */ @Contract("false, _ -> fail") public static void condition(boolean predicate, Supplier messageSupplier) throws PreconditionViolationException { if (!predicate) { throw new PreconditionViolationException(messageSupplier.get()); } } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static java.util.Collections.synchronizedMap; import static java.util.stream.Collectors.toCollection; import static java.util.stream.Collectors.toSet; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP; import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.TOP_DOWN; import java.io.File; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Field; import java.lang.reflect.GenericArrayType; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Stream; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.function.Try; import org.junit.platform.commons.io.Resource; import org.junit.platform.commons.io.ResourceFilter; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.support.scanning.ClassFilter; import org.junit.platform.commons.support.scanning.ClasspathScanner; /** * Collection of utilities for working with the Java reflection APIs. * *

DISCLAIMER

* *

These utilities are intended solely for usage within the JUnit framework * itself. Any usage by external parties is not supported. * Use at your own risk! * *

Some utilities are published via the maintained {@code ReflectionSupport} * class. * * @since 1.0 * @see org.junit.platform.commons.support.ReflectionSupport */ @API(status = INTERNAL, since = "1.0") public final class ReflectionUtils { private static final Logger logger = LoggerFactory.getLogger(ReflectionUtils.class); private ReflectionUtils() { /* no-op */ } /** * Modes in which a hierarchy can be traversed — for example, when * searching for methods or fields within a class hierarchy. */ public enum HierarchyTraversalMode { /** * Traverse the hierarchy using top-down semantics. */ TOP_DOWN, /** * Traverse the hierarchy using bottom-up semantics. */ BOTTOM_UP } // Pattern: "java.lang.String[]", "int[]", "int[][][][]", etc. // ?> => non-capturing atomic group // ++ => possessive quantifier private static final Pattern SOURCE_CODE_SYNTAX_ARRAY_PATTERN = Pattern.compile("^([^\\[\\]]+)((?>\\[\\])++)$"); static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; private static final ClasspathScanner classpathScanner = ClasspathScannerLoader.getInstance(); /** * Cache for equivalent methods on an interface implemented by the declaring class. * @since 1.11 * @see #getInterfaceMethodIfPossible(Method, Class) */ private static final Map interfaceMethodCache = synchronizedMap(new LruCache<>(255)); /** * Set of fully qualified class names for which no cycles have been detected * in inner class hierarchies. *

This serves as a cache to avoid repeated cycle detection for classes * that have already been checked. * @since 1.6 * @see #detectInnerClassCycle(Class, CycleErrorHandling) */ private static final Set noCyclesDetectedCache = ConcurrentHashMap.newKeySet(); /** * Internal cache of common class names mapped to their types. */ private static final Map> classNameToTypeMap; /** * Internal cache of primitive types mapped to their wrapper types. */ private static final Map, Class> primitiveToWrapperMap; static { // @formatter:off List> commonTypes = Arrays.asList( boolean.class, byte.class, char.class, short.class, int.class, long.class, float.class, double.class, void.class, boolean[].class, byte[].class, char[].class, short[].class, int[].class, long[].class, float[].class, double[].class, boolean[][].class, byte[][].class, char[][].class, short[][].class, int[][].class, long[][].class, float[][].class, double[][].class, Boolean.class, Byte.class, Character.class, Short.class, Integer.class, Long.class, Float.class, Double.class, Void.class, String.class, Boolean[].class, Byte[].class, Character[].class, Short[].class, Integer[].class, Long[].class, Float[].class, Double[].class, String[].class, Boolean[][].class, Byte[][].class, Character[][].class, Short[][].class, Integer[][].class, Long[][].class, Float[][].class, Double[][].class, String[][].class ); // @formatter:on Map> classNamesToTypes = new HashMap<>(64); commonTypes.forEach(type -> { classNamesToTypes.put(type.getName(), type); classNamesToTypes.put(type.getCanonicalName(), type); }); classNameToTypeMap = Collections.unmodifiableMap(classNamesToTypes); primitiveToWrapperMap = createPrimitivesToWrapperMap(); } @SuppressWarnings("IdentityHashMapUsage") private static Map, Class> createPrimitivesToWrapperMap() { Map, Class> primitivesToWrappers = new IdentityHashMap<>(8); primitivesToWrappers.put(boolean.class, Boolean.class); primitivesToWrappers.put(byte.class, Byte.class); primitivesToWrappers.put(char.class, Character.class); primitivesToWrappers.put(short.class, Short.class); primitivesToWrappers.put(int.class, Integer.class); primitivesToWrappers.put(long.class, Long.class); primitivesToWrappers.put(float.class, Float.class); primitivesToWrappers.put(double.class, Double.class); return Collections.unmodifiableMap(primitivesToWrappers); } public static boolean isPublic(Class clazz) { Preconditions.notNull(clazz, "Class must not be null"); return Modifier.isPublic(clazz.getModifiers()); } public static boolean isPublic(Member member) { Preconditions.notNull(member, "Member must not be null"); return Modifier.isPublic(member.getModifiers()); } public static boolean isPrivate(Class clazz) { Preconditions.notNull(clazz, "Class must not be null"); return Modifier.isPrivate(clazz.getModifiers()); } public static boolean isPrivate(Member member) { Preconditions.notNull(member, "Member must not be null"); return Modifier.isPrivate(member.getModifiers()); } @API(status = INTERNAL, since = "1.4") public static boolean isNotPrivate(Class clazz) { return !isPrivate(clazz); } @API(status = INTERNAL, since = "1.1") public static boolean isNotPrivate(Member member) { return !isPrivate(member); } public static boolean isAbstract(Class clazz) { Preconditions.notNull(clazz, "Class must not be null"); return Modifier.isAbstract(clazz.getModifiers()); } @API(status = INTERNAL, since = "1.13") public static boolean isNotAbstract(Class clazz) { return !isAbstract(clazz); } public static boolean isAbstract(Member member) { Preconditions.notNull(member, "Member must not be null"); return Modifier.isAbstract(member.getModifiers()); } @API(status = INTERNAL, since = "1.13") public static boolean isNotAbstract(Member member) { return !isAbstract(member); } public static boolean isStatic(Class clazz) { Preconditions.notNull(clazz, "Class must not be null"); return Modifier.isStatic(clazz.getModifiers()); } @API(status = INTERNAL, since = "1.4") public static boolean isNotStatic(Class clazz) { return !isStatic(clazz); } public static boolean isStatic(Member member) { Preconditions.notNull(member, "Member must not be null"); return Modifier.isStatic(member.getModifiers()); } @API(status = INTERNAL, since = "1.1") public static boolean isNotStatic(Member member) { return !isStatic(member); } /** * @since 1.5 */ @API(status = INTERNAL, since = "1.5") public static boolean isFinal(Class clazz) { Preconditions.notNull(clazz, "Class must not be null"); return Modifier.isFinal(clazz.getModifiers()); } /** * @since 1.5 */ @API(status = INTERNAL, since = "1.5") public static boolean isNotFinal(Class clazz) { return !isFinal(clazz); } /** * @since 1.5 */ @API(status = INTERNAL, since = "1.5") public static boolean isFinal(Member member) { Preconditions.notNull(member, "Member must not be null"); return Modifier.isFinal(member.getModifiers()); } /** * @since 1.5 */ @API(status = INTERNAL, since = "1.5") public static boolean isNotFinal(Member member) { return !isFinal(member); } /** * Determine if the supplied class is an inner class (i.e., a * non-static member class). * *

Technically speaking (i.e., according to the Java Language * Specification), "an inner class may be a non-static member class, a * local class, or an anonymous class." However, this method does not * return {@code true} for a local or anonymous class. * * @param clazz the class to check; never {@code null} * @return {@code true} if the class is an inner class */ public static boolean isInnerClass(Class clazz) { Preconditions.notNull(clazz, "Class must not be null"); return !isStatic(clazz) && clazz.isMemberClass(); } /** * {@return whether the supplied {@code object} is an instance of a record class} * @since 1.12 */ @API(status = INTERNAL, since = "1.12") public static boolean isRecordObject(@Nullable Object object) { return object != null && isRecordClass(object.getClass()); } /** * {@return whether the supplied {@code clazz} is a record class} * @since 1.12 */ @API(status = INTERNAL, since = "1.12") public static boolean isRecordClass(Class clazz) { return clazz.isRecord(); } /** * Determine if the return type of the supplied method is primitive {@code void}. * * @param method the method to test; never {@code null} * @return {@code true} if the method's return type is {@code void} */ public static boolean returnsPrimitiveVoid(Method method) { return method.getReturnType() == void.class; } /** * Determine if the supplied object is an array. * * @param obj the object to test; potentially {@code null} * @return {@code true} if the object is an array */ public static boolean isArray(@Nullable Object obj) { return (obj != null && obj.getClass().isArray()); } /** * Determine if the supplied object is a multidimensional array. * * @param obj the object to test; potentially {@code null} * @return {@code true} if the object is a multidimensional array * @since 1.3.2 */ @API(status = INTERNAL, since = "1.3.2") public static boolean isMultidimensionalArray(@Nullable Object obj) { return (obj != null && obj.getClass().isArray() && obj.getClass().getComponentType().isArray()); } /** * Determine if an object of the supplied source type can be assigned to the * supplied target type for the purpose of reflective method invocations. * *

In contrast to {@link Class#isAssignableFrom(Class)}, this method * returns {@code true} if the target type represents a primitive type whose * wrapper matches the supplied source type. In addition, this method also supports * * widening conversions for primitive target types. * * @param sourceType the non-primitive target type; never {@code null} * @param targetType the target type; never {@code null} * @return {@code true} if an object of the source type is assignment compatible * with the target type * @since 1.8 * @see Class#isInstance(Object) * @see Class#isAssignableFrom(Class) * @see #isAssignableTo(Object, Class) */ public static boolean isAssignableTo(Class sourceType, Class targetType) { Preconditions.notNull(sourceType, "source type must not be null"); Preconditions.condition(!sourceType.isPrimitive(), "source type must not be a primitive type"); Preconditions.notNull(targetType, "target type must not be null"); if (targetType.isAssignableFrom(sourceType)) { return true; } if (targetType.isPrimitive()) { return sourceType == primitiveToWrapperMap.get(targetType) || isWideningConversion(sourceType, targetType); } return false; } /** * Determine if the supplied object can be assigned to the supplied target * type for the purpose of reflective method invocations. * *

In contrast to {@link Class#isInstance(Object)}, this method returns * {@code true} if the target type represents a primitive type whose wrapper * matches the supplied object's type. In addition, this method also supports * * widening conversions for primitive types and their corresponding * wrapper types. * *

If the supplied object is {@code null} and the supplied type does not * represent a primitive type, this method returns {@code true}. * * @param obj the object to test for assignment compatibility; potentially {@code null} * @param targetType the type to check against; never {@code null} * @return {@code true} if the object is assignment compatible * @see Class#isInstance(Object) * @see Class#isAssignableFrom(Class) * @see #isAssignableTo(Class, Class) */ public static boolean isAssignableTo(@Nullable Object obj, Class targetType) { Preconditions.notNull(targetType, "target type must not be null"); if (obj == null) { return !targetType.isPrimitive(); } if (targetType.isInstance(obj)) { return true; } if (targetType.isPrimitive()) { Class sourceType = obj.getClass(); return sourceType == primitiveToWrapperMap.get(targetType) || isWideningConversion(sourceType, targetType); } return false; } /** * Determine if Java supports a widening primitive conversion from the * supplied source type to the supplied primitive target type. */ static boolean isWideningConversion(Class sourceType, Class targetType) { Preconditions.condition(targetType.isPrimitive(), "targetType must be primitive"); boolean isPrimitive = sourceType.isPrimitive(); boolean isWrapper = primitiveToWrapperMap.containsValue(sourceType); // Neither a primitive nor a wrapper? if (!isPrimitive && !isWrapper) { return false; } if (isPrimitive) { sourceType = primitiveToWrapperMap.get(sourceType); } // @formatter:off if (sourceType == Byte.class) { return targetType == short.class || targetType == int.class || targetType == long.class || targetType == float.class || targetType == double.class; } if (sourceType == Short.class || sourceType == Character.class) { return targetType == int.class || targetType == long.class || targetType == float.class || targetType == double.class; } if (sourceType == Integer.class) { return targetType == long.class || targetType == float.class || targetType == double.class; } if (sourceType == Long.class) { return targetType == float.class || targetType == double.class; } if (sourceType == Float.class) { return targetType == double.class; } // @formatter:on return false; } /** * Get the wrapper type for the supplied primitive type. * * @param type the primitive type for which to retrieve the wrapper type * @return the corresponding wrapper type or {@code null} if the * supplied type is not a primitive type */ public static @Nullable Class getWrapperType(Class type) { return primitiveToWrapperMap.get(type); } /** * @see org.junit.platform.commons.support.ReflectionSupport#newInstance(Class, Object...) * @see #newInstance(Constructor, Object...) */ public static T newInstance(Class clazz, Object... args) { Preconditions.notNull(clazz, "Class must not be null"); Preconditions.notNull(args, "Argument array must not be null"); Preconditions.containsNoNullElements(args, "Individual arguments must not be null"); try { Class[] parameterTypes = Arrays.stream(args).map(Object::getClass).toArray(Class[]::new); return newInstance(clazz.getDeclaredConstructor(parameterTypes), args); } catch (Throwable t) { throw ExceptionUtils.throwAsUncheckedException(getUnderlyingCause(t)); } } /** * Create a new instance of type {@code T} by invoking the supplied constructor * with the supplied arguments. * *

The constructor will be made accessible if necessary, and any checked * exception will be {@linkplain ExceptionUtils#throwAsUncheckedException masked} * as an unchecked exception. * * @param constructor the constructor to invoke; never {@code null} * @param args the arguments to pass to the constructor * @return the new instance; never {@code null} * @see #newInstance(Class, Object...) * @see ExceptionUtils#throwAsUncheckedException(Throwable) */ public static T newInstance(Constructor constructor, @Nullable Object... args) { Preconditions.notNull(constructor, "Constructor must not be null"); try { return makeAccessible(constructor).newInstance(args); } catch (Throwable t) { throw ExceptionUtils.throwAsUncheckedException(getUnderlyingCause(t)); } } /** * Try to read the value of a potentially inaccessible or nonexistent field. * *

If the field does not exist or an exception occurs while reading it, a * failed {@link Try} is returned that contains the corresponding exception. * * @param clazz the class where the field is declared; never {@code null} * @param fieldName the name of the field; never {@code null} or empty * @param instance the instance from where the value is to be read; may * be {@code null} for a static field * @since 1.4 * @see #tryToReadFieldValue(Field) * @see #tryToReadFieldValue(Field, Object) */ @API(status = INTERNAL, since = "1.4") public static Try tryToReadFieldValue(Class clazz, String fieldName, @Nullable T instance) { Preconditions.notNull(clazz, "Class must not be null"); Preconditions.notBlank(fieldName, "Field name must not be null or blank"); // @formatter:off return Try.call(() -> clazz.getDeclaredField(fieldName)) .andThen(field -> tryToReadFieldValue(field, instance)); // @formatter:on } /** * Try to read the value of a potentially inaccessible static field. * *

If an exception occurs while reading the field, a failed {@link Try} * is returned that contains the corresponding exception. * * @param field the field to read; never {@code null} * @since 1.4 * @see #tryToReadFieldValue(Field, Object) * @see #tryToReadFieldValue(Class, String, Object) */ @API(status = INTERNAL, since = "1.4") public static Try<@Nullable Object> tryToReadFieldValue(Field field) { return tryToReadFieldValue(field, null); } /** * @since 1.4 * @see org.junit.platform.commons.support.ReflectionSupport#tryToReadFieldValue(Field, Object) * @see #tryToReadFieldValue(Class, String, Object) */ @API(status = INTERNAL, since = "1.4") public static Try<@Nullable Object> tryToReadFieldValue(Field field, @Nullable Object instance) { Preconditions.notNull(field, "Field must not be null"); Preconditions.condition((instance != null || isStatic(field)), () -> "Cannot read non-static field [%s] on a null instance.".formatted(field)); return Try.<@Nullable Object> call(() -> makeAccessible(field).get(instance)); } /** * Read the values of the supplied fields, making each field accessible if * necessary and {@linkplain ExceptionUtils#throwAsUncheckedException masking} * any checked exception as an unchecked exception. * * @param fields the list of fields to read; never {@code null} * @param instance the instance from which the values are to be read; may * be {@code null} for static fields * @return an immutable list of the values of the specified fields; never * {@code null} but may be empty or contain {@code null} entries */ public static List readFieldValues(List fields, @Nullable Object instance) { return readFieldValues(fields, instance, field -> true); } /** * Read the values of the supplied fields, making each field accessible if * necessary, {@linkplain ExceptionUtils#throwAsUncheckedException masking} * any checked exception as an unchecked exception, and filtering out fields * that do not pass the supplied {@code predicate}. * * @param fields the list of fields to read; never {@code null} * @param instance the instance from which the values are to be read; may * be {@code null} for static fields * @param predicate the field filter; never {@code null} * @return an immutable list of the values of the specified fields; never * {@code null} but may be empty or contain {@code null} entries */ public static List readFieldValues(List fields, @Nullable Object instance, Predicate predicate) { Preconditions.notNull(fields, "fields list must not be null"); Preconditions.notNull(predicate, "Predicate must not be null"); // @formatter:off return fields.stream() .filter(predicate) .map(field -> tryToReadFieldValue(field, instance).getOrThrow(ExceptionUtils::throwAsUncheckedException)) .toList(); // @formatter:on } /** * @see org.junit.platform.commons.support.ReflectionSupport#invokeMethod(Method, Object, Object...) */ public static @Nullable Object invokeMethod(Method method, @Nullable Object target, @Nullable Object... args) { Preconditions.notNull(method, "Method must not be null"); Preconditions.condition((target != null || isStatic(method)), () -> "Cannot invoke non-static method [%s] on a null target.".formatted(method.toGenericString())); try { return makeAccessible(method).invoke(target, args); } catch (Throwable t) { throw ExceptionUtils.throwAsUncheckedException(getUnderlyingCause(t)); } } /** * @since 1.4 * @see org.junit.platform.commons.support.ReflectionSupport#tryToLoadClass(String) */ @API(status = INTERNAL, since = "1.4") public static Try> tryToLoadClass(String name) { return tryToLoadClass(name, ClassLoaderUtils.getDefaultClassLoader()); } /** * Load a class by its primitive name or fully qualified name, * using the supplied {@link ClassLoader}. * *

See {@link org.junit.platform.commons.support.ReflectionSupport#tryToLoadClass(String)} * for details on support for class names for arrays. * * @param name the name of the class to load; never {@code null} or blank * @param classLoader the {@code ClassLoader} to use; never {@code null} * @throws JUnitException if the class could not be loaded * @since 1.11 * @see #tryToLoadClass(String, ClassLoader) */ @API(status = INTERNAL, since = "1.11") public static Class loadRequiredClass(String name, ClassLoader classLoader) throws JUnitException { return tryToLoadClass(name, classLoader).getNonNullOrThrow( cause -> new JUnitException("Could not load class [%s]".formatted(name), cause)); } /** * Try to load a class by its primitive name or fully qualified * name, using the supplied {@link ClassLoader}. * *

See {@link org.junit.platform.commons.support.ReflectionSupport#tryToLoadClass(String)} * for details on support for class names for arrays. * * @param name the name of the class to load; never {@code null} or blank * @param classLoader the {@code ClassLoader} to use; never {@code null} * @since 1.4 * @see #tryToLoadClass(String) */ @API(status = INTERNAL, since = "1.4") public static Try> tryToLoadClass(String name, ClassLoader classLoader) { Preconditions.notBlank(name, "Class name must not be null or blank"); Preconditions.notNull(classLoader, "ClassLoader must not be null"); String strippedName = name.strip(); if (classNameToTypeMap.containsKey(strippedName)) { return Try.success(classNameToTypeMap.get(strippedName)); } return Try.call(() -> { // Arrays such as "java.lang.String[]", "int[]", "int[][][][]", etc. Matcher matcher = SOURCE_CODE_SYNTAX_ARRAY_PATTERN.matcher(strippedName); if (matcher.matches()) { String componentTypeName = matcher.group(1); String bracketPairs = matcher.group(2); // Calculate dimensions by counting bracket pairs. int dimensions = bracketPairs.length() / 2; return loadArrayType(classLoader, componentTypeName, dimensions); } // Fallback to standard VM class loading return Class.forName(strippedName, false, classLoader); }); } /** * @see org.junit.platform.commons.support.ResourceSupport#tryToGetResources(String) */ @API(status = INTERNAL, since = "1.12") public static Try> tryToGetResources(String classpathResourceName) { return tryToGetResources(classpathResourceName, ClassLoaderUtils.getDefaultClassLoader()); } /** * @see org.junit.platform.commons.support.ResourceSupport#tryToGetResources(String, ClassLoader) */ @API(status = INTERNAL, since = "1.12") public static Try> tryToGetResources(String classpathResourceName, ClassLoader classLoader) { Preconditions.notBlank(classpathResourceName, "Resource name must not be null or blank"); Preconditions.notNull(classLoader, "Class loader must not be null"); boolean startsWithSlash = classpathResourceName.startsWith("/"); String canonicalClasspathResourceName = (startsWithSlash ? classpathResourceName.substring(1) : classpathResourceName); return Try.call(() -> { List resources = Collections.list(classLoader.getResources(canonicalClasspathResourceName)); return resources.stream().map(url -> { try { return Resource.of(canonicalClasspathResourceName, url.toURI()); } catch (URISyntaxException e) { throw ExceptionUtils.throwAsUncheckedException(e); } }).collect(toCollection(LinkedHashSet::new)); }); } private static Class loadArrayType(ClassLoader classLoader, String componentTypeName, int dimensions) throws ClassNotFoundException { Class componentType = classNameToTypeMap.containsKey(componentTypeName) ? classNameToTypeMap.get(componentTypeName) : Class.forName(componentTypeName, false, classLoader); return Array.newInstance(componentType, new int[dimensions]).getClass(); } /** * Build the fully qualified method name for the method described by the * supplied class and method. * *

Note that the class is not necessarily the class in which the method is * declared. * * @param clazz the class from which the method should be referenced; never {@code null} * @param method the method; never {@code null} * @return fully qualified method name; never {@code null} * @since 1.4 * @see #getFullyQualifiedMethodName(Class, String, Class...) */ public static String getFullyQualifiedMethodName(Class clazz, Method method) { Preconditions.notNull(method, "Method must not be null"); return getFullyQualifiedMethodName(clazz, method.getName(), method.getParameterTypes()); } /** * Build the fully qualified method name for the method described by the * supplied class, method name, and parameter types. * *

Note that the class is not necessarily the class in which the method is * declared. * * @param clazz the class from which the method should be referenced; never {@code null} * @param methodName the name of the method; never {@code null} or blank * @param parameterTypes the parameter types of the method; may be {@code null} or empty * @return fully qualified method name; never {@code null} */ public static String getFullyQualifiedMethodName(Class clazz, String methodName, Class @Nullable... parameterTypes) { Preconditions.notNull(clazz, "Class must not be null"); return getFullyQualifiedMethodName(clazz.getName(), methodName, ClassUtils.nullSafeToString(parameterTypes)); } /** * Build the fully qualified method name for the method described by the * supplied class name, method name, and parameter types. * *

Note that the class is not necessarily the class in which the method is * declared. * * @param className the name of the class from which the method should be referenced; * never {@code null} * @param methodName the name of the method; never {@code null} or blank * @param parameterTypeNames the parameter type names of the method; may be * empty but not {@code null} * @return fully qualified method name; never {@code null} * @since 1.11 */ @API(status = INTERNAL, since = "1.11") public static String getFullyQualifiedMethodName(String className, String methodName, String parameterTypeNames) { Preconditions.notBlank(className, "Class name must not be null or blank"); Preconditions.notBlank(methodName, "Method name must not be null or blank"); Preconditions.notNull(parameterTypeNames, "Parameter type names must not be null"); return "%s#%s(%s)".formatted(className, methodName, parameterTypeNames); } /** * Parse the supplied fully qualified method name into a 3-element * {@code String[]} with the following content. * *

    *
  • index {@code 0}: the fully qualified class name
  • *
  • index {@code 1}: the name of the method
  • *
  • index {@code 2}: a comma-separated list of parameter types, or a * blank string if the method does not declare any formal parameters
  • *
* * @param fullyQualifiedMethodName a fully qualified method name, * never {@code null} or blank * @return a 3-element array of strings containing the parsed values */ public static String[] parseFullyQualifiedMethodName(String fullyQualifiedMethodName) { Preconditions.notBlank(fullyQualifiedMethodName, "fullyQualifiedMethodName must not be null or blank"); int indexOfFirstHashtag = fullyQualifiedMethodName.indexOf('#'); boolean validSyntax = (indexOfFirstHashtag > 0) && (indexOfFirstHashtag < fullyQualifiedMethodName.length() - 1); Preconditions.condition(validSyntax, () -> "[" + fullyQualifiedMethodName + "] is not a valid fully qualified method name: " + "it must start with a fully qualified class name followed by a '#' " + "and then the method name, optionally followed by a parameter list enclosed in parentheses."); String className = fullyQualifiedMethodName.substring(0, indexOfFirstHashtag); String methodPart = fullyQualifiedMethodName.substring(indexOfFirstHashtag + 1); String methodName = methodPart; String methodParameters = ""; if (methodPart.endsWith("()")) { methodName = methodPart.substring(0, methodPart.length() - 2); } else if (methodPart.endsWith(")")) { int indexOfLastOpeningParenthesis = methodPart.lastIndexOf('('); if ((indexOfLastOpeningParenthesis > 0) && (indexOfLastOpeningParenthesis < methodPart.length() - 1)) { methodName = methodPart.substring(0, indexOfLastOpeningParenthesis); methodParameters = methodPart.substring(indexOfLastOpeningParenthesis + 1, methodPart.length() - 1); } } return new String[] { className, methodName, methodParameters }; } /** * Parse the supplied fully qualified field name into a 2-element * {@code String[]} with the following content. * *
    *
  • index {@code 0}: the fully qualified class name
  • *
  • index {@code 1}: the name of the field
  • *
* * @param fullyQualifiedFieldName a fully qualified field name, * never {@code null} or blank * @return a 2-element array of strings containing the parsed values * @since 1.11 */ @API(status = INTERNAL, since = "1.11") public static String[] parseFullyQualifiedFieldName(String fullyQualifiedFieldName) { Preconditions.notBlank(fullyQualifiedFieldName, "fullyQualifiedFieldName must not be null or blank"); int indexOfHashtag = fullyQualifiedFieldName.indexOf('#'); boolean validSyntax = (indexOfHashtag > 0) && (indexOfHashtag < fullyQualifiedFieldName.length() - 1); Preconditions.condition(validSyntax, () -> "[" + fullyQualifiedFieldName + "] is not a valid fully qualified field name: " + "it must start with a fully qualified class name followed by a '#' " + "and then the field name."); return fullyQualifiedFieldName.split("#"); } public static Set getAllClasspathRootDirectories() { // This is quite a hack, since sometimes the classpath is quite different String fullClassPath = System.getProperty("java.class.path"); // @formatter:off return Arrays.stream(fullClassPath.split(File.pathSeparator)) .map(Path::of) .filter(Files::isDirectory) .collect(toSet()); // @formatter:on } /** * @see org.junit.platform.commons.support.ReflectionSupport#findAllClassesInClasspathRoot(URI, Predicate, Predicate) */ public static List> findAllClassesInClasspathRoot(URI root, Predicate> classFilter, Predicate classNameFilter) { // unmodifiable since returned by public, non-internal method(s) return findAllClassesInClasspathRoot(root, ClassFilter.of(classNameFilter, classFilter)); } /** * @since 1.10 * @see org.junit.platform.commons.support.ReflectionSupport#streamAllClassesInClasspathRoot(URI, Predicate, Predicate) */ public static Stream> streamAllClassesInClasspathRoot(URI root, Predicate> classFilter, Predicate classNameFilter) { return streamAllClassesInClasspathRoot(root, ClassFilter.of(classNameFilter, classFilter)); } /** * @since 1.1 */ public static List> findAllClassesInClasspathRoot(URI root, ClassFilter classFilter) { return List.copyOf(classpathScanner.scanForClassesInClasspathRoot(root, classFilter)); } /** * @since 1.11 */ public static List findAllResourcesInClasspathRoot(URI root, ResourceFilter resourceFilter) { return List.copyOf(classpathScanner.scanForResourcesInClasspathRoot(root, resourceFilter)); } /** * @since 1.10 */ public static Stream> streamAllClassesInClasspathRoot(URI root, ClassFilter classFilter) { return findAllClassesInClasspathRoot(root, classFilter).stream(); } /** * @since 1.11 */ public static Stream streamAllResourcesInClasspathRoot(URI root, ResourceFilter resourceFilter) { return findAllResourcesInClasspathRoot(root, resourceFilter).stream(); } /** * @see org.junit.platform.commons.support.ReflectionSupport#findAllClassesInPackage(String, Predicate, Predicate) */ public static List> findAllClassesInPackage(String basePackageName, Predicate> classFilter, Predicate classNameFilter) { // unmodifiable since returned by public, non-internal method(s) return findAllClassesInPackage(basePackageName, ClassFilter.of(classNameFilter, classFilter)); } /** * since 1.10 * @see org.junit.platform.commons.support.ReflectionSupport#streamAllClassesInPackage(String, Predicate, Predicate) */ public static Stream> streamAllClassesInPackage(String basePackageName, Predicate> classFilter, Predicate classNameFilter) { return streamAllClassesInPackage(basePackageName, ClassFilter.of(classNameFilter, classFilter)); } /** * @since 1.1 */ public static List> findAllClassesInPackage(String basePackageName, ClassFilter classFilter) { return List.copyOf(classpathScanner.scanForClassesInPackage(basePackageName, classFilter)); } /** * @since 1.11 */ public static List findAllResourcesInPackage(String basePackageName, ResourceFilter resourceFilter) { return List.copyOf(classpathScanner.scanForResourcesInPackage(basePackageName, resourceFilter)); } /** * @since 1.10 */ public static Stream> streamAllClassesInPackage(String basePackageName, ClassFilter classFilter) { return findAllClassesInPackage(basePackageName, classFilter).stream(); } /** * @since 1.11 */ public static Stream streamAllResourcesInPackage(String basePackageName, ResourceFilter resourceFilter) { return findAllResourcesInPackage(basePackageName, resourceFilter).stream(); } /** * @since 1.1.1 * @see org.junit.platform.commons.support.ReflectionSupport#findAllClassesInModule(String, Predicate, Predicate) */ public static List> findAllClassesInModule(String moduleName, Predicate> classFilter, Predicate classNameFilter) { // unmodifiable since returned by public, non-internal method(s) return findAllClassesInModule(moduleName, ClassFilter.of(classNameFilter, classFilter)); } /** * @since 6.1 * @see org.junit.platform.commons.support.ReflectionSupport#findAllClassesInModule(Module, Predicate, Predicate) */ public static List> findAllClassesInModule(Module module, Predicate> classFilter, Predicate classNameFilter) { // unmodifiable since returned by public, non-internal method(s) return findAllClassesInModule(module, ClassFilter.of(classNameFilter, classFilter)); } /** * @since 1.10 * @see org.junit.platform.commons.support.ReflectionSupport#streamAllClassesInModule(String, Predicate, Predicate) */ public static Stream> streamAllClassesInModule(String moduleName, Predicate> classFilter, Predicate classNameFilter) { return streamAllClassesInModule(moduleName, ClassFilter.of(classNameFilter, classFilter)); } /** * @since 1.1.1 */ public static List> findAllClassesInModule(String moduleName, ClassFilter classFilter) { return List.copyOf(ModuleUtils.findAllClassesInModule(moduleName, classFilter)); } /** * @since 6.1 */ public static List> findAllClassesInModule(Module module, ClassFilter classFilter) { return List.copyOf(ModuleUtils.findAllClassesInModule(module, classFilter)); } /** * @since 1.11 */ public static List findAllResourcesInModule(String moduleName, ResourceFilter resourceFilter) { return List.copyOf(ModuleUtils.findAllResourcesInModule(moduleName, resourceFilter)); } /** * @since 6.1 */ public static List findAllResourcesInModule(Module module, ResourceFilter resourceFilter) { return List.copyOf(ModuleUtils.findAllResourcesInModule(module, resourceFilter)); } /** * @since 1.10 */ public static Stream> streamAllClassesInModule(String moduleName, ClassFilter classFilter) { return findAllClassesInModule(moduleName, classFilter).stream(); } /** * @since 1.11 */ public static Stream streamAllResourcesInModule(String moduleName, ResourceFilter resourceFilter) { return findAllResourcesInModule(moduleName, resourceFilter).stream(); } /** * @see org.junit.platform.commons.support.ReflectionSupport#findNestedClasses(Class, Predicate) */ public static List> findNestedClasses(Class clazz, Predicate> predicate) { return findNestedClasses(clazz, predicate, CycleErrorHandling.THROW_EXCEPTION); } /** * @since 1.13.2 */ @API(status = INTERNAL, since = "1.13.2") public static List> findNestedClasses(Class clazz, Predicate> predicate, CycleErrorHandling errorHandling) { Preconditions.notNull(clazz, "Class must not be null"); Preconditions.notNull(predicate, "Predicate must not be null"); Set> candidates = new LinkedHashSet<>(); visitAllNestedClasses(clazz, predicate, candidates::add, errorHandling); return List.copyOf(candidates); } /** * Determine if a nested class within the supplied class, or inherited by the * supplied class, that conforms to the supplied predicate is present. * *

This method does not search for nested classes * recursively. * *

This method detects cycles in inner class hierarchies — * from the supplied class up to the outermost enclosing class — and * throws a {@link JUnitException} if such a cycle is detected. Cycles within * inner class hierarchies below the supplied class are not detected * by this method. * * @param clazz the class to be searched; never {@code null} * @param predicate the predicate against which the list of nested classes is * checked; never {@code null} * @return {@code true} if such a nested class is present * @throws JUnitException if a cycle is detected within an inner class hierarchy * @since 1.13.2 */ @API(status = INTERNAL, since = "1.13.2") public static boolean isNestedClassPresent(Class clazz, Predicate> predicate, CycleErrorHandling errorHandling) { Preconditions.notNull(clazz, "Class must not be null"); Preconditions.notNull(predicate, "Predicate must not be null"); Preconditions.notNull(errorHandling, "CycleErrorHandling must not be null"); AtomicBoolean foundNestedClass = new AtomicBoolean(false); visitAllNestedClasses(clazz, predicate, __ -> foundNestedClass.setPlain(true), errorHandling); return foundNestedClass.getPlain(); } /** * @since 1.10 * @see org.junit.platform.commons.support.ReflectionSupport#streamNestedClasses(Class, Predicate) */ @API(status = INTERNAL, since = "1.10") public static Stream> streamNestedClasses(Class clazz, Predicate> predicate) { return findNestedClasses(clazz, predicate).stream(); } /** * @since 1.13.2 */ @API(status = INTERNAL, since = "1.13.2") public static Stream> streamNestedClasses(Class clazz, Predicate> predicate, CycleErrorHandling errorHandling) { return findNestedClasses(clazz, predicate, errorHandling).stream(); } /** * Visit all nested classes without support for short-circuiting * in order to ensure all of them are checked for class cycles. */ private static void visitAllNestedClasses(Class clazz, Predicate> predicate, Consumer> consumer, CycleErrorHandling errorHandling) { if (!isSearchable(clazz)) { return; } if (isInnerClass(clazz) && predicate.test(clazz)) { if (detectInnerClassCycle(clazz, errorHandling)) { return; } } try { // Candidates in current class for (Class nestedClass : toSortedMutableList(clazz.getDeclaredClasses())) { if (predicate.test(nestedClass)) { consumer.accept(nestedClass); if (detectInnerClassCycle(nestedClass, errorHandling)) { return; } } } } catch (NoClassDefFoundError error) { logger.debug(error, () -> "Failed to retrieve declared classes for " + clazz.getName()); } // Search class hierarchy visitAllNestedClasses(clazz.getSuperclass(), predicate, consumer, errorHandling); // Search interface hierarchy for (Class ifc : clazz.getInterfaces()) { visitAllNestedClasses(ifc, predicate, consumer, errorHandling); } } /** * Detect a cycle in the inner class hierarchy in which the supplied class * resides — from the supplied class up to the outermost enclosing class * — and throw a {@link JUnitException} if a cycle is detected. *

This method does not detect cycles within inner class * hierarchies below the supplied class. *

If the supplied class is not an inner class and does not have a * searchable superclass, this method is effectively a no-op. * * @since 1.6 * @see #isInnerClass(Class) * @see #isSearchable(Class) */ private static boolean detectInnerClassCycle(Class clazz, CycleErrorHandling errorHandling) { Preconditions.notNull(clazz, "Class must not be null"); String className = clazz.getName(); if (noCyclesDetectedCache.contains(className)) { return false; } Class superclass = clazz.getSuperclass(); if (isInnerClass(clazz) && isSearchable(superclass)) { for (Class enclosing = clazz.getEnclosingClass(); enclosing != null; enclosing = enclosing.getEnclosingClass()) { if (superclass.equals(enclosing)) { errorHandling.handle(clazz, enclosing); return true; } } } noCyclesDetectedCache.add(className); return false; } /** * Get the sole declared, non-synthetic {@link Constructor} for the supplied class. * *

Throws a {@link org.junit.platform.commons.PreconditionViolationException} * if the supplied class declares more than one non-synthetic constructor. * * @param clazz the class to get the constructor for * @return the sole declared constructor; never {@code null} * @see Class#getDeclaredConstructors() * @see Class#isSynthetic() */ @SuppressWarnings("unchecked") public static Constructor getDeclaredConstructor(Class clazz) { Preconditions.notNull(clazz, "Class must not be null"); try { Constructor[] constructors = Arrays.stream(clazz.getDeclaredConstructors())// .filter(ctor -> !ctor.isSynthetic())// .toArray(Constructor[]::new); Preconditions.condition(constructors.length == 1, () -> "Class [%s] must declare a single constructor".formatted(clazz.getName())); return (Constructor) constructors[0]; } catch (Throwable t) { throw ExceptionUtils.throwAsUncheckedException(getUnderlyingCause(t)); } } /** * Find all constructors in the supplied class that match the supplied predicate. * *

Note that this method may return {@linkplain Class#isSynthetic() synthetic} * constructors. If you wish to ignore synthetic constructors, you may filter * them out with the supplied {@code predicate} or filter them out of the list * returned by this method. * * @param clazz the class in which to search for constructors; never {@code null} * @param predicate the predicate to use to test for a match; never {@code null} * @return an immutable list of all such constructors found; never {@code null} * but potentially empty * @see Class#getDeclaredConstructors() * @see Class#isSynthetic() */ public static List> findConstructors(Class clazz, Predicate> predicate) { Preconditions.notNull(clazz, "Class must not be null"); Preconditions.notNull(predicate, "Predicate must not be null"); try { // @formatter:off return Arrays.stream(clazz.getDeclaredConstructors()) .filter(predicate) .toList(); // @formatter:on } catch (Throwable t) { throw ExceptionUtils.throwAsUncheckedException(getUnderlyingCause(t)); } } /** * @see org.junit.platform.commons.support.ReflectionSupport#findFields(Class, Predicate, org.junit.platform.commons.support.HierarchyTraversalMode) */ public static List findFields(Class clazz, Predicate predicate, HierarchyTraversalMode traversalMode) { return streamFields(clazz, predicate, traversalMode).toList(); } /** * @since 1.10 * @see org.junit.platform.commons.support.ReflectionSupport#streamFields(Class, Predicate, org.junit.platform.commons.support.HierarchyTraversalMode) */ @API(status = INTERNAL, since = "1.10") public static Stream streamFields(Class clazz, Predicate predicate, HierarchyTraversalMode traversalMode) { Preconditions.notNull(clazz, "Class must not be null"); Preconditions.notNull(predicate, "Predicate must not be null"); Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); // @formatter:off return findAllFieldsInHierarchy(clazz, traversalMode).stream() .filter(predicate) .distinct(); // @formatter:on } private static List findAllFieldsInHierarchy(Class clazz, HierarchyTraversalMode traversalMode) { Preconditions.notNull(clazz, "Class must not be null"); Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); // @formatter:off Field[] localFields = getDeclaredFields(clazz).stream() .filter(field -> !field.isSynthetic()) .toArray(Field[]::new); // @formatter:on List superclassFields = getSuperclassFields(clazz, traversalMode); List interfaceFields = getInterfaceFields(clazz, traversalMode); List fields = new ArrayList<>(superclassFields.size() + interfaceFields.size() + localFields.length); if (traversalMode == TOP_DOWN) { fields.addAll(superclassFields); fields.addAll(interfaceFields); } Collections.addAll(fields, localFields); if (traversalMode == BOTTOM_UP) { fields.addAll(interfaceFields); fields.addAll(superclassFields); } return fields; } /** * Determine if a {@link Method} matching the supplied {@link Predicate} * is present within the type hierarchy of the specified class, beginning * with the specified class or interface and traversing up the type * hierarchy until such a method is found or the type hierarchy is exhausted. * * @param clazz the class or interface in which to find the method; never * {@code null} * @param predicate the predicate to use to test for a match; never * {@code null} * @return {@code true} if such a method is present * @see #findMethod(Class, String, String) * @see #findMethod(Class, String, Class...) */ public static boolean isMethodPresent(Class clazz, Predicate predicate) { Preconditions.notNull(clazz, "Class must not be null"); Preconditions.notNull(predicate, "Predicate must not be null"); return findMethod(clazz, predicate).isPresent(); } /** * Try to get the {@link Method} in the specified class with the specified * name and parameter types. * *

This method delegates to {@link Class#getMethod(String, Class...)} but * catches any exception thrown. * * @param clazz the class in which to search for the method; never {@code null} * @param methodName the name of the method to get; never {@code null} or blank * @param parameterTypes the parameter types of the method; may be {@code null} * or empty * @return a successful {@link Try} containing the method or a failed * {@link Try} containing the {@link NoSuchMethodException} thrown by * {@code Class#getMethod()}; never {@code null} * @since 1.4 */ @API(status = INTERNAL, since = "1.4") public static Try tryToGetMethod(Class clazz, String methodName, Class @Nullable... parameterTypes) { Preconditions.notNull(clazz, "Class must not be null"); Preconditions.notBlank(methodName, "Method name must not be null or blank"); return Try.call(() -> clazz.getMethod(methodName, parameterTypes)); } /** * Determine a corresponding interface method for the given method handle, if possible. *

This is particularly useful for arriving at a public exported type on the Java * Module System which can be reflectively invoked without an illegal access warning. * @param method the method to be invoked, potentially from an implementation class; * never {@code null} * @param targetClass the target class to check for declared interfaces; * potentially {@code null} * @return the corresponding interface method, or the original method if none found * @since 1.11 */ @API(status = INTERNAL, since = "1.11") @SuppressWarnings("ReferenceEquality") public static Method getInterfaceMethodIfPossible(Method method, @Nullable Class targetClass) { if (!isPublic(method) || method.getDeclaringClass().isInterface()) { return method; } // Try cached version of method in its declaring class Method result = interfaceMethodCache.computeIfAbsent(method, m -> findInterfaceMethodIfPossible(m, m.getParameterTypes(), m.getDeclaringClass(), Object.class)); if (result == method && targetClass != null) { // No interface method found yet -> try given target class (possibly a subclass of the // declaring class, late-binding a base class method to a subclass-declared interface: // see e.g. HashMap.HashIterator.hasNext) result = findInterfaceMethodIfPossible(method, method.getParameterTypes(), targetClass, method.getDeclaringClass()); } return result; } private static Method findInterfaceMethodIfPossible(Method method, Class[] parameterTypes, Class startClass, Class endClass) { Class current = startClass; while (current != null && current != endClass) { for (Class ifc : current.getInterfaces()) { try { return ifc.getMethod(method.getName(), parameterTypes); } catch (NoSuchMethodException ex) { // ignore } } current = current.getSuperclass(); } return method; } /** * @see org.junit.platform.commons.support.ReflectionSupport#findMethod(Class, String, String) */ public static Optional findMethod(Class clazz, String methodName, @Nullable String parameterTypeNames) { Preconditions.notNull(clazz, "Class must not be null"); Preconditions.notBlank(methodName, "Method name must not be null or blank"); return findMethod(clazz, methodName, resolveParameterTypes(clazz, methodName, parameterTypeNames)); } /** * @since 1.10 */ @API(status = INTERNAL, since = "1.10") public static Class[] resolveParameterTypes(Class clazz, String methodName, @Nullable String parameterTypeNames) { if (parameterTypeNames == null || StringUtils.isBlank(parameterTypeNames)) { return EMPTY_CLASS_ARRAY; } // @formatter:off return Arrays.stream(parameterTypeNames.split(",")) .map(String::strip) .map(typeName -> loadRequiredParameterType(clazz, methodName, typeName)) .toArray(Class[]::new); // @formatter:on } private static Class loadRequiredParameterType(Class clazz, String methodName, String typeName) { ClassLoader classLoader = ClassLoaderUtils.getClassLoader(clazz); // @formatter:off return tryToLoadClass(typeName, classLoader) .getNonNullOrThrow(cause -> new JUnitException( "Failed to load parameter type [%s] for method [%s] in class [%s].".formatted( typeName, methodName, clazz.getName()), cause)); // @formatter:on } /** * @see org.junit.platform.commons.support.ReflectionSupport#findMethod(Class, String, Class...) */ public static Optional findMethod(Class clazz, String methodName, Class... parameterTypes) { Preconditions.notNull(clazz, "Class must not be null"); Preconditions.notBlank(methodName, "Method name must not be null or blank"); Preconditions.notNull(parameterTypes, "Parameter types array must not be null"); Preconditions.containsNoNullElements(parameterTypes, "Individual parameter types must not be null"); return findMethod(clazz, method -> hasCompatibleSignature(method, methodName, parameterTypes)); } private static Optional findMethod(Class clazz, Predicate predicate) { Preconditions.notNull(clazz, "Class must not be null"); Preconditions.notNull(predicate, "Predicate must not be null"); for (Class current = clazz; isSearchable(current); current = current.getSuperclass()) { // Search for match in current type List methods = current.isInterface() ? getMethods(current) : getDeclaredMethods(current, BOTTOM_UP); for (Method method : methods) { if (predicate.test(method)) { return Optional.of(method); } } // Search for match in interfaces implemented by current type for (Class ifc : current.getInterfaces()) { Optional optional = findMethod(ifc, predicate); if (optional.isPresent()) { return optional; } } } return Optional.empty(); } /** * Find the first {@link Method} of the supplied class or interface that * meets the specified criteria, beginning with the specified class or * interface and traversing up the type hierarchy until such a method is * found or the type hierarchy is exhausted. * *

Use this method as an alternative to * {@link #findMethod(Class, String, Class...)} for use cases in which the * method is required to be present. * * @param clazz the class or interface in which to find the method; * never {@code null} * @param methodName the name of the method to find; never {@code null} * or empty * @param parameterTypes the types of parameters accepted by the method, * if any; never {@code null} * @return the {@code Method} found; never {@code null} * @throws JUnitException if no method is found * * @since 1.7 * @see #findMethod(Class, String, Class...) */ @API(status = INTERNAL, since = "1.7") public static Method getRequiredMethod(Class clazz, String methodName, Class... parameterTypes) { return ReflectionUtils.findMethod(clazz, methodName, parameterTypes).orElseThrow(() -> new JUnitException( "Could not find method [%s] in class [%s]".formatted(methodName, clazz.getName()))); } /** * Find all {@linkplain Method methods} of the supplied class or interface * that match the specified {@code predicate}, using top-down search semantics * within the type hierarchy. * *

The results will not contain instance methods that are overridden. * * @param clazz the class or interface in which to find the methods; never {@code null} * @param predicate the method filter; never {@code null} * @return an immutable list of all such methods found; never {@code null} * @see HierarchyTraversalMode#TOP_DOWN * @see #findMethods(Class, Predicate, HierarchyTraversalMode) */ public static List findMethods(Class clazz, Predicate predicate) { return findMethods(clazz, predicate, TOP_DOWN); } /** * @see org.junit.platform.commons.support.ReflectionSupport#findMethods(Class, Predicate, org.junit.platform.commons.support.HierarchyTraversalMode) */ public static List findMethods(Class clazz, Predicate predicate, HierarchyTraversalMode traversalMode) { return streamMethods(clazz, predicate, traversalMode).toList(); } /** * @since 1.10 * @see org.junit.platform.commons.support.ReflectionSupport#streamMethods(Class, Predicate, org.junit.platform.commons.support.HierarchyTraversalMode) */ @API(status = INTERNAL, since = "1.10") public static Stream streamMethods(Class clazz, Predicate predicate, HierarchyTraversalMode traversalMode) { Preconditions.notNull(clazz, "Class must not be null"); Preconditions.notNull(predicate, "Predicate must not be null"); Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); // @formatter:off return findAllMethodsInHierarchy(clazz, traversalMode).stream() .filter(predicate) .distinct(); // @formatter:on } /** * Find all non-synthetic methods in the superclass and interface hierarchy, * excluding Object. */ private static List findAllMethodsInHierarchy(Class clazz, HierarchyTraversalMode traversalMode) { Preconditions.notNull(clazz, "Class must not be null"); Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); // @formatter:off Method[] localMethods = getDeclaredMethods(clazz, traversalMode).stream() .filter(method -> !method.isSynthetic()) .toArray(Method[]::new); Method[] superclassMethods = getSuperclassMethods(clazz, traversalMode).stream() .filter(method -> isNotOverriddenByLocalMethods(method, localMethods)) .toArray(Method[]::new); Method[] interfaceMethods = getInterfaceMethods(clazz, traversalMode).stream() .filter(method -> isNotOverriddenByLocalMethods(method, localMethods)) .toArray(Method[]::new); // @formatter:on List methods = new ArrayList<>( superclassMethods.length + interfaceMethods.length + localMethods.length); if (traversalMode == TOP_DOWN) { Collections.addAll(methods, superclassMethods); Collections.addAll(methods, interfaceMethods); } Collections.addAll(methods, localMethods); if (traversalMode == BOTTOM_UP) { Collections.addAll(methods, interfaceMethods); Collections.addAll(methods, superclassMethods); } return methods; } /** * Custom alternative to {@link Class#getDeclaredFields()} that sorts the * fields and converts them to a mutable list. */ private static List getDeclaredFields(Class clazz) { return toSortedMutableList(clazz.getDeclaredFields()); } /** * Custom alternative to {@link Class#getMethods()} that sorts the methods * and converts them to a mutable list. */ private static List getMethods(Class clazz) { return toSortedMutableList(clazz.getMethods()); } /** * Custom alternative to {@link Class#getDeclaredMethods()} that sorts the * methods and converts them to a mutable list. * *

In addition, the list returned by this method includes interface * default methods which are either prepended or appended to the list of * declared methods depending on the supplied traversal mode. */ private static List getDeclaredMethods(Class clazz, HierarchyTraversalMode traversalMode) { // Note: getDefaultMethods() already sorts the methods, List defaultMethods = getDefaultMethods(clazz); List declaredMethods = toSortedMutableList(clazz.getDeclaredMethods()); // Take the traversal mode into account in order to retain the inherited // nature of interface default methods. if (traversalMode == BOTTOM_UP) { declaredMethods.addAll(defaultMethods); return declaredMethods; } else { defaultMethods.addAll(declaredMethods); return defaultMethods; } } /** * Get a sorted, mutable list of all default methods present in interfaces * implemented by the supplied class which are also visible within * the supplied class. * * @see Method Visibility * in the Java Language Specification */ private static List getDefaultMethods(Class clazz) { // @formatter:off // Visible default methods are interface default methods that have not // been overridden. List visibleDefaultMethods = Arrays.stream(clazz.getMethods()) .filter(Method::isDefault) .collect(toCollection(ArrayList::new)); if (visibleDefaultMethods.isEmpty()) { return visibleDefaultMethods; } return Arrays.stream(clazz.getInterfaces()) .map(ReflectionUtils::getMethods) .flatMap(List::stream) .filter(visibleDefaultMethods::contains) .collect(toCollection(ArrayList::new)); // @formatter:on } private static List toSortedMutableList(Field[] fields) { return toSortedMutableList(fields, ReflectionUtils::defaultFieldSorter); } private static List toSortedMutableList(Method[] methods) { return toSortedMutableList(methods, ReflectionUtils::defaultMethodSorter); } private static List> toSortedMutableList(Class[] classes) { return toSortedMutableList(classes, ReflectionUtils::defaultClassSorter); } private static List toSortedMutableList(T[] items, Comparator comparator) { List result = new ArrayList<>(items.length); Collections.addAll(result, items); result.sort(comparator); return result; } /** * Field comparator inspired by JUnit 4's {@code org.junit.internal.MethodSorter} * implementation. */ private static int defaultFieldSorter(Field field1, Field field2) { return Integer.compare(field1.getName().hashCode(), field2.getName().hashCode()); } /** * Method comparator based upon JUnit 4's {@code org.junit.internal.MethodSorter} * implementation. */ private static int defaultMethodSorter(Method method1, Method method2) { String name1 = method1.getName(); String name2 = method2.getName(); int comparison = Integer.compare(name1.hashCode(), name2.hashCode()); if (comparison == 0) { comparison = name1.compareTo(name2); if (comparison == 0) { comparison = method1.toString().compareTo(method2.toString()); } } return comparison; } /** * Class comparator to achieve deterministic but nonobvious order. */ private static int defaultClassSorter(Class class1, Class class2) { String name1 = class1.getName(); String name2 = class2.getName(); int comparison = Integer.compare(name1.hashCode(), name2.hashCode()); if (comparison == 0) { comparison = name1.compareTo(name2); } return comparison; } private static List getInterfaceMethods(Class clazz, HierarchyTraversalMode traversalMode) { List allInterfaceMethods = new ArrayList<>(); for (Class ifc : clazz.getInterfaces()) { // @formatter:off Method[] localInterfaceMethods = getMethods(ifc).stream() .filter(m -> !isAbstract(m)) .toArray(Method[]::new); Method[] superinterfaceMethods = getInterfaceMethods(ifc, traversalMode).stream() .filter(method -> isNotOverriddenByLocalMethods(method, localInterfaceMethods)) .toArray(Method[]::new); // @formatter:on if (traversalMode == TOP_DOWN) { Collections.addAll(allInterfaceMethods, superinterfaceMethods); } Collections.addAll(allInterfaceMethods, localInterfaceMethods); if (traversalMode == BOTTOM_UP) { Collections.addAll(allInterfaceMethods, superinterfaceMethods); } } return allInterfaceMethods; } private static List getInterfaceFields(Class clazz, HierarchyTraversalMode traversalMode) { List allInterfaceFields = new ArrayList<>(); for (Class ifc : clazz.getInterfaces()) { Field[] localInterfaceFields = ifc.getFields(); Arrays.sort(localInterfaceFields, ReflectionUtils::defaultFieldSorter); List superinterfaceFields = getInterfaceFields(ifc, traversalMode); if (traversalMode == TOP_DOWN) { allInterfaceFields.addAll(superinterfaceFields); } Collections.addAll(allInterfaceFields, localInterfaceFields); if (traversalMode == BOTTOM_UP) { allInterfaceFields.addAll(superinterfaceFields); } } return allInterfaceFields; } private static List getSuperclassFields(Class clazz, HierarchyTraversalMode traversalMode) { Class superclass = clazz.getSuperclass(); if (!isSearchable(superclass)) { return Collections.emptyList(); } return findAllFieldsInHierarchy(superclass, traversalMode); } private static List getSuperclassMethods(Class clazz, HierarchyTraversalMode traversalMode) { Class superclass = clazz.getSuperclass(); if (!isSearchable(superclass)) { return Collections.emptyList(); } return findAllMethodsInHierarchy(superclass, traversalMode); } private static boolean isNotOverriddenByLocalMethods(Method method, Method[] localMethods) { for (Method local : localMethods) { if (isMethodOverriddenBy(method, local)) { return false; } } return true; } private static boolean isMethodOverriddenBy(Method upper, Method lower) { // A static method cannot override anything. if (Modifier.isStatic(lower.getModifiers())) { return false; } // Cannot override a private, static, or final method. int modifiers = upper.getModifiers(); if (Modifier.isPrivate(modifiers) || Modifier.isStatic(modifiers) || Modifier.isFinal(modifiers)) { return false; } // Cannot override a package-private method in another package. if (isPackagePrivate(upper) && !isDeclaredInSamePackage(upper, lower)) { return false; } return hasCompatibleSignature(upper, lower.getName(), lower.getParameterTypes()); } /** * @since 5.14.1 */ @API(status = INTERNAL, since = "5.14.1") public static boolean isPackagePrivate(Member member) { int modifiers = member.getModifiers(); return !(Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers) || Modifier.isPrivate(modifiers)); } private static boolean isDeclaredInSamePackage(Method m1, Method m2) { return isDeclaredInSamePackage(m1.getDeclaringClass(), m2.getDeclaringClass()); } /** * @since 5.14.1 */ @API(status = INTERNAL, since = "5.14.1") public static boolean isDeclaredInSamePackage(Class c1, Class c2) { return c1.getPackageName().equals(c2.getPackageName()); } /** * Determine if the supplied candidate method (typically a method higher in * the type hierarchy) has a signature that is compatible with a method that * has the supplied name and parameter types, taking method sub-signatures * and generics into account. */ private static boolean hasCompatibleSignature(Method candidate, String methodName, Class[] parameterTypes) { if (!methodName.equals(candidate.getName())) { return false; } if (parameterTypes.length != candidate.getParameterCount()) { return false; } Class[] candidateParameterTypes = candidate.getParameterTypes(); // trivial case: parameter types exactly match if (Arrays.equals(parameterTypes, candidateParameterTypes)) { return true; } // param count is equal, but types do not match exactly: check for method sub-signatures // https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.2 for (int i = 0; i < parameterTypes.length; i++) { Class lowerType = parameterTypes[i]; Class upperType = candidateParameterTypes[i]; if (!upperType.isAssignableFrom(lowerType)) { return false; } } // lower is sub-signature of upper: check for generics in upper method return isGeneric(candidate); } static boolean isGeneric(Method method) { return isGeneric(method.getGenericReturnType()) || Arrays.stream(method.getGenericParameterTypes()).anyMatch(ReflectionUtils::isGeneric); } private static boolean isGeneric(Type type) { return type instanceof TypeVariable || type instanceof GenericArrayType; } /** * @since 1.11 */ @API(status = INTERNAL, since = "1.11") @SuppressWarnings("deprecation") // "AccessibleObject.isAccessible()" is deprecated in Java 9 public static T makeAccessible(T executable) { if ((!isPublic(executable) || !isPublic(executable.getDeclaringClass())) && !executable.isAccessible()) { executable.setAccessible(true); } return executable; } /** * @since 1.12 */ @API(status = INTERNAL, since = "1.12") @SuppressWarnings("deprecation") // "AccessibleObject.isAccessible()" is deprecated in Java 9 public static Field makeAccessible(Field field) { if ((!isPublic(field) || !isPublic(field.getDeclaringClass()) || isFinal(field)) && !field.isAccessible()) { field.setAccessible(true); } return field; } /** * Return all classes and interfaces that can be used as assignment types * for instances of the specified {@link Class}, including itself. * * @param clazz the {@code Class} to look up * @see Class#isAssignableFrom */ public static Set> getAllAssignmentCompatibleClasses(Class clazz) { Preconditions.notNull(clazz, "Class must not be null"); Set> result = new LinkedHashSet<>(); getAllAssignmentCompatibleClasses(clazz, result); return result; } private static void getAllAssignmentCompatibleClasses(Class clazz, Set> result) { for (Class current = clazz; current != null; current = current.getSuperclass()) { result.add(current); for (Class interfaceClass : current.getInterfaces()) { if (!result.contains(interfaceClass)) { getAllAssignmentCompatibleClasses(interfaceClass, result); } } } } /** * Determine if the supplied class is searchable: is non-null and is * not equal to the class reference for {@code java.lang.Object}. * *

This method is often used to determine if a superclass should be * searched but may be applicable for other use cases as well. * @since 1.6 */ private static boolean isSearchable(@Nullable Class clazz) { return (clazz != null && clazz != Object.class); } /** * Get the underlying cause of the supplied {@link Throwable}. * *

If the supplied {@code Throwable} is an instance of * {@link InvocationTargetException}, this method will be invoked * recursively with the underlying * {@linkplain InvocationTargetException#getTargetException() target * exception}; otherwise, this method returns the supplied {@code Throwable}. */ static Throwable getUnderlyingCause(Throwable t) { if (t instanceof InvocationTargetException ite) { return getUnderlyingCause(ite.getTargetException()); } return t; } /** * @since 1.13.2 */ @API(status = INTERNAL, since = "1.13.2") public enum CycleErrorHandling { THROW_EXCEPTION { @Override void handle(Class clazz, Class enclosing) { throw new JUnitException("Detected cycle in inner class hierarchy between %s and %s".formatted( clazz.getName(), enclosing.getName())); } }, ABORT_VISIT { @Override void handle(Class clazz, Class enclosing) { // ignore } }; abstract void handle(Class clazz, Class enclosing); } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/util/ResourceUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static org.apiguardian.api.API.Status.INTERNAL; import java.net.URI; import org.apiguardian.api.API; /** * Collection of static utility methods for working with resources. * * @since 1.3 (originally in org.junit.platform.engine.support.descriptor) */ @API(status = INTERNAL, since = "1.12") public final class ResourceUtils { private ResourceUtils() { /* no-op */ } /** * Strip the {@link URI#getQuery() query} component from the supplied * {@link URI}. * * @param uri the {@code URI} from which to strip the query component * @return a new {@code URI} with the query component removed, or the * original {@code URI} unmodified if it does not have a query component * * @since 1.3 */ public static URI stripQueryComponent(URI uri) { Preconditions.notNull(uri, "URI must not be null"); if (StringUtils.isBlank(uri.getQuery())) { return uri; } String uriAsString = uri.toString(); return URI.create(uriAsString.substring(0, uriAsString.indexOf('?'))); } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/util/RuntimeUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.List; import java.util.Optional; import org.apiguardian.api.API; /** * Collection of utilities for working with {@link Runtime}, * {@link java.lang.management.RuntimeMXBean}, etc. * *

DISCLAIMER

* *

These utilities are intended solely for usage within the JUnit framework * itself. Any usage by external parties is not supported. * Use at your own risk! * * @since 1.6 */ @API(status = INTERNAL, since = "1.6") public final class RuntimeUtils { private RuntimeUtils() { /* no-op */ } /** * Try to determine whether the VM was started in debug mode or not. */ public static boolean isDebugMode() { return getInputArguments() // .map(args -> args.stream().anyMatch( arg -> arg.startsWith("-agentlib:jdwp") || arg.startsWith("-Xrunjdwp"))) // .orElse(false); } /** * Try to get the input arguments the VM was started with. */ static Optional> getInputArguments() { Optional> managementFactoryClass = ReflectionUtils.tryToLoadClass( "java.lang.management.ManagementFactory").toOptional(); if (managementFactoryClass.isEmpty()) { return Optional.empty(); } // Can't use "java.lang.management.ManagementFactory.getRuntimeMXBean().getInputArguments()" // directly as module "java.management" might not be available and/or the current platform // doesn't support the Java Management Extensions (JMX) API (like Android?). // See https://github.com/junit-team/junit4/pull/1187 try { Object bean = managementFactoryClass.get().getMethod("getRuntimeMXBean").invoke(null); Class mx = ReflectionUtils.tryToLoadClass("java.lang.management.RuntimeMXBean").getNonNull(); @SuppressWarnings("unchecked") List args = (List) mx.getMethod("getInputArguments").invoke(bean); return Optional.of(args); } catch (Exception e) { return Optional.empty(); } } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/util/SearchPathUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static java.util.stream.Collectors.joining; import java.nio.file.Path; import java.util.stream.IntStream; import org.junit.platform.commons.JUnitException; /** * @since 1.11 */ class SearchPathUtils { static final char PACKAGE_SEPARATOR_CHAR = '.'; static final String PACKAGE_SEPARATOR_STRING = String.valueOf(PACKAGE_SEPARATOR_CHAR); private static final char FILE_NAME_EXTENSION_SEPARATOR_CHAR = '.'; private static final String CLASS_FILE_SUFFIX = ".class"; private static final String SOURCE_FILE_SUFFIX = ".java"; private static final String PACKAGE_INFO_FILE_NAME = "package-info"; private static final String MODULE_INFO_FILE_NAME = "module-info"; // System property defined since Java 12: https://bugs.java/bugdatabase/JDK-8210877 private static final boolean SOURCE_MODE = System.getProperty("jdk.launcher.sourcefile") != null; static boolean isResourceFile(Path file) { return !isClassFile(file); } static boolean isClassOrSourceFile(Path file) { var fileName = file.getFileName().toString(); return isClassOrSourceFile(fileName) && !isModuleInfoOrPackageInfo(fileName); } private static boolean isModuleInfoOrPackageInfo(String fileName) { var fileNameWithoutExtension = removeExtension(fileName); return PACKAGE_INFO_FILE_NAME.equals(fileNameWithoutExtension) // || MODULE_INFO_FILE_NAME.equals(fileNameWithoutExtension); } static String determineFullyQualifiedClassName(Path path) { var simpleClassName = determineSimpleClassName(path); var parent = path.getParent(); return parent == null ? simpleClassName : joinPathNamesWithPackageSeparator(parent.resolve(simpleClassName)); } private static String joinPathNamesWithPackageSeparator(Path path) { return IntStream.range(0, path.getNameCount()) // .mapToObj(i -> path.getName(i).toString()) // .collect(joining(PACKAGE_SEPARATOR_STRING)); } static String determineSimpleClassName(Path file) { return removeExtension(file.getFileName().toString()); } private static String removeExtension(String fileName) { int lastDot = fileName.lastIndexOf(FILE_NAME_EXTENSION_SEPARATOR_CHAR); if (lastDot < 0) { throw new JUnitException("Expected file name with file extension, but got: " + fileName); } return fileName.substring(0, lastDot); } private static boolean isClassOrSourceFile(String name) { return name.endsWith(CLASS_FILE_SUFFIX) || (SOURCE_MODE && name.endsWith(SOURCE_FILE_SUFFIX)); } private static boolean isClassFile(Path file) { return file.getFileName().toString().endsWith(CLASS_FILE_SUFFIX); } private SearchPathUtils() { } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/util/ServiceLoaderUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.ServiceLoader; import java.util.function.Predicate; import java.util.stream.Stream; import org.apiguardian.api.API; /** * Collection of utilities for working with {@link ServiceLoader}. * *

DISCLAIMER

* *

These utilities are intended solely for usage within the JUnit framework * itself. Any usage by external parties is not supported. * Use at your own risk! * * @since 1.11 */ @API(status = INTERNAL, since = "1.11") public class ServiceLoaderUtils { private ServiceLoaderUtils() { /* no-op */ } /** * Filters the supplied service loader using the supplied predicate. * * @param the type of the service * @param serviceLoader the service loader to be filtered * @param providerPredicate the predicate to filter the loaded services * @return a stream of loaded services that match the predicate */ public static Stream filter(ServiceLoader serviceLoader, Predicate> providerPredicate) { Preconditions.notNull(serviceLoader, "serviceLoader must not be null"); Preconditions.notNull(providerPredicate, "providerPredicate must not be null"); // @formatter:off return serviceLoader .stream() .filter(provider -> providerPredicate.test(provider.type())) .map(ServiceLoader.Provider::get); // @formatter:on } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/util/StringUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static java.util.regex.Pattern.UNICODE_CHARACTER_CLASS; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.Arrays; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; import java.util.regex.Pattern; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.annotation.Contract; /** * Collection of utilities for working with {@link String Strings}, * {@link CharSequence CharSequences}, etc. * *

DISCLAIMER

* *

These utilities are intended solely for usage within the JUnit framework * itself. Any usage by external parties is not supported. * Use at your own risk! * * @since 1.0 */ @API(status = INTERNAL, since = "1.0") public final class StringUtils { private static final Pattern ISO_CONTROL_PATTERN = compileIsoControlPattern(); private static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s"); /** * Guard against "IllegalArgumentException: Unsupported flags: 256" errors. * @see #1800 */ static Pattern compileIsoControlPattern() { // https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/regex/Pattern.html#posix try { // All of the characters that Unicode refers to as 'control characters' return Pattern.compile("\\p{Cntrl}", UNICODE_CHARACTER_CLASS); } catch (IllegalArgumentException e) { // Fall-back to ASCII control characters only: [\x00-\x1F\x7F] return Pattern.compile("\\p{Cntrl}"); } } private StringUtils() { /* no-op */ } /** * Determine if the supplied {@link String} is blank (i.e., * {@code null} or consisting only of whitespace characters). * * @param str the string to check; may be {@code null} * @return {@code true} if the string is blank * @see String#isBlank() * @see #isNotBlank(String) */ @Contract("null -> true") public static boolean isBlank(@Nullable String str) { return (str == null || str.isBlank()); } /** * Determine if the supplied {@link String} is not {@linkplain #isBlank * blank}. * * @param str the string to check; may be {@code null} * @return {@code true} if the string is not blank * @see #isBlank(String) */ @Contract("null -> false") public static boolean isNotBlank(@Nullable String str) { return !isBlank(str); } /** * Determine if the supplied {@link String} contains any whitespace characters. * * @param str the string to check; may be {@code null} * @return {@code true} if the string contains whitespace * @see #containsIsoControlCharacter(String) * @see Character#isWhitespace(int) */ @Contract("null -> false") public static boolean containsWhitespace(@Nullable String str) { return str != null && str.codePoints().anyMatch(Character::isWhitespace); } /** * Determine if the supplied {@link String} does not contain any whitespace * characters. * * @param str the string to check; may be {@code null} * @return {@code true} if the string does not contain whitespace * @see #containsWhitespace(String) * @see #containsIsoControlCharacter(String) * @see Character#isWhitespace(int) */ @Contract("null -> true") public static boolean doesNotContainWhitespace(@Nullable String str) { return !containsWhitespace(str); } /** * Determine if the supplied {@link String} contains any ISO control characters. * * @param str the string to check; may be {@code null} * @return {@code true} if the string contains an ISO control character * @see #containsWhitespace(String) * @see Character#isISOControl(int) */ @Contract("null -> false") public static boolean containsIsoControlCharacter(@Nullable String str) { return str != null && str.codePoints().anyMatch(Character::isISOControl); } /** * Determine if the supplied {@link String} does not contain any ISO control * characters. * * @param str the string to check; may be {@code null} * @return {@code true} if the string does not contain an ISO control character * @see #containsIsoControlCharacter(String) * @see #containsWhitespace(String) * @see Character#isISOControl(int) */ @Contract("null -> true") public static boolean doesNotContainIsoControlCharacter(@Nullable String str) { return !containsIsoControlCharacter(str); } /** * Convert the supplied {@code Object} to a {@code String} using the * following algorithm. * *

    *
  • If the supplied object is {@code null}, this method returns {@code "null"}.
  • *
  • If the supplied object is a primitive array, the appropriate * {@code Arrays#toString(...)} variant will be used to convert it to a String.
  • *
  • If the supplied object is an object array, {@code Arrays#deepToString(Object[])} * will be used to convert it to a String.
  • *
  • Otherwise, {@code toString()} will be invoked on the object. If the * result is non-null, that result will be returned. If the result is * {@code null}, {@code "null"} will be returned.
  • *
  • If any of the above results in an exception, this method delegates to * {@link #defaultToString(Object)}
  • *
* * @param obj the object to convert to a String; may be {@code null} * @return a String representation of the supplied object; never {@code null} * @see Arrays#deepToString(Object[]) * @see ClassUtils#nullSafeToString(Class...) */ public static String nullSafeToString(@Nullable Object obj) { if (obj == null) { return "null"; } try { if (obj.getClass().isArray()) { if (obj.getClass().getComponentType().isPrimitive()) { if (obj instanceof boolean[] booleans) { return Arrays.toString(booleans); } if (obj instanceof char[] chars) { return Arrays.toString(chars); } if (obj instanceof short[] shorts) { return Arrays.toString(shorts); } if (obj instanceof byte[] bytes) { return Arrays.toString(bytes); } if (obj instanceof int[] ints) { return Arrays.toString(ints); } if (obj instanceof long[] longs) { return Arrays.toString(longs); } if (obj instanceof float[] floats) { return Arrays.toString(floats); } if (obj instanceof double[] doubles) { return Arrays.toString(doubles); } } return Arrays.deepToString((Object[]) obj); } // else String result = obj.toString(); return result != null ? result : "null"; } catch (Throwable throwable) { UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); return defaultToString(obj); } } /** * Convert the supplied {@code Object} to a default {@code String} * representation using the following algorithm. * *
    *
  • If the supplied object is {@code null}, this method returns {@code "null"}.
  • *
  • Otherwise, the String returned by this method will be generated analogous * to the default implementation of {@link Object#toString()} by using the supplied * object's class name and hash code as follows: * {@code obj.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(obj))}
  • *
* * @param obj the object to convert to a String; may be {@code null} * @return the default String representation of the supplied object; never {@code null} * @see #nullSafeToString(Object) * @see ClassUtils#nullSafeToString(Class...) */ public static String defaultToString(@Nullable Object obj) { if (obj == null) { return "null"; } return obj.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(obj)); } /** * Replace all ISO control characters in the supplied {@link String}. * * @param str the string in which to perform the replacement; may be {@code null} * @param replacement the replacement string; never {@code null} * @return the supplied string with all control characters replaced, or * {@code null} if the supplied string was {@code null} * @since 1.4 */ @API(status = INTERNAL, since = "1.4") @Contract("null, _ -> null; !null, _ -> !null") public static @Nullable String replaceIsoControlCharacters(@Nullable String str, String replacement) { Preconditions.notNull(replacement, "replacement must not be null"); return str == null ? null : ISO_CONTROL_PATTERN.matcher(str).replaceAll(replacement); } /** * Replace all whitespace characters in the supplied {@link String}. * * @param str the string in which to perform the replacement; may be {@code null} * @param replacement the replacement string; never {@code null} * @return the supplied string with all whitespace characters replaced, or * {@code null} if the supplied string was {@code null} * @since 1.4 */ @API(status = INTERNAL, since = "1.4") @Contract("null, _ -> null; !null, _ -> !null") public static @Nullable String replaceWhitespaceCharacters(@Nullable String str, String replacement) { Preconditions.notNull(replacement, "replacement must not be null"); return str == null ? null : WHITESPACE_PATTERN.matcher(str).replaceAll(replacement); } /** * Split the supplied {@link String} into up to two parts using the supplied * separator character. * * @param separator the separator character * @param value the value to split; never {@code null} * @since 1.11 */ @API(status = INTERNAL, since = "1.11") public static TwoPartSplitResult splitIntoTwo(char separator, String value) { Preconditions.notNull(value, "value must not be null"); return splitIntoTwo(value, value.indexOf(separator), 1); } /** * Split the supplied {@link String} into up to two parts using the supplied * separator string. * * @param separator the separator string; never {@code null} * @param value the value to split; never {@code null} * @since 1.11 */ @API(status = INTERNAL, since = "1.11") public static TwoPartSplitResult splitIntoTwo(String separator, String value) { Preconditions.notNull(separator, "separator must not be null"); Preconditions.notNull(value, "value must not be null"); return splitIntoTwo(value, value.indexOf(separator), separator.length()); } private static TwoPartSplitResult splitIntoTwo(String value, int index, int length) { if (index == -1) { return new OnePart(value); } return new TwoParts(value.substring(0, index), value.substring(index + length)); } /** * The result of splitting a string into up to two parts. * * @since 1.11 * @see StringUtils#splitIntoTwo(char, String) * @see StringUtils#splitIntoTwo(String, String) */ @API(status = INTERNAL, since = "1.11") public sealed interface TwoPartSplitResult { /** * Map the result of splitting a string into two parts or throw an exception. * * @param onePartExceptionCreator the exception creator to use if the string * was split into a single part * @param twoPartsMapper the mapper to use if the string was split into two parts */ default T mapTwo(Supplier onePartExceptionCreator, BiFunction twoPartsMapper) { Function onePartMapper = __ -> { throw onePartExceptionCreator.get(); }; return map(onePartMapper, twoPartsMapper); } /** * Map the result of splitting a string into up to two parts. * * @param onePartMapper the mapper to use if the string was split into a single part * @param twoPartsMapper the mapper to use if the string was split into two parts */ T map(Function onePartMapper, BiFunction twoPartsMapper); } private record OnePart(String value) implements TwoPartSplitResult { @Override public T map(Function onePartMapper, BiFunction twoPartsMapper) { return onePartMapper.apply(this.value); } } private record TwoParts(String first, String second) implements TwoPartSplitResult { @Override public T map(Function onePartMapper, BiFunction twoPartsMapper) { return twoPartsMapper.apply(this.first, this.second); } } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/util/ToStringBuilder.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static java.lang.String.join; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.ArrayList; import java.util.List; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; /** * Simple builder for generating strings in custom implementations of * {@link Object#toString toString()}. * *

DISCLAIMER

* *

These utilities are intended solely for usage within the JUnit framework * itself. Any usage by external parties is not supported. * Use at your own risk! * * @since 1.0 */ @API(status = INTERNAL, since = "1.0") public class ToStringBuilder { private final String typeName; private final List values = new ArrayList<>(); public ToStringBuilder(Object obj) { this(Preconditions.notNull(obj, "Object must not be null").getClass().getSimpleName()); } public ToStringBuilder(Class type) { this(Preconditions.notNull(type, "Class must not be null").getSimpleName()); } @API(status = INTERNAL, since = "1.7") public ToStringBuilder(String typeName) { this.typeName = Preconditions.notNull(typeName, "Type name must not be null"); } public ToStringBuilder append(String name, @Nullable Object value) { Preconditions.notBlank(name, "Name must not be null or blank"); this.values.add(name + " = " + toString(value)); return this; } private String toString(@Nullable Object obj) { return (obj instanceof CharSequence) ? ("'" + obj + "'") : StringUtils.nullSafeToString(obj); } @Override public String toString() { return this.typeName + " [" + join(", ", this.values) + "]"; } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/util/UnrecoverableExceptions.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static org.apiguardian.api.API.Status.INTERNAL; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; /** * Internal utilities for working with unrecoverable exceptions. * *

Unrecoverable exceptions are those that should always terminate * test plan execution immediately. * *

Currently Unrecoverable Exceptions

*
    *
  • {@link OutOfMemoryError}
  • *
* *

DISCLAIMER

* *

These utilities are intended solely for usage within the JUnit framework * itself. Any usage by external parties is not supported. * Use at your own risk! * * @since 1.7 */ @API(status = INTERNAL, since = "1.7") public final class UnrecoverableExceptions { private UnrecoverableExceptions() { /* no-op */ } /** * Rethrow the supplied {@link Throwable exception} if it is * unrecoverable. * *

If the supplied {@code exception} is not unrecoverable, this * method does nothing. */ public static void rethrowIfUnrecoverable(@Nullable Throwable exception) { if (exception instanceof OutOfMemoryError) { throw ExceptionUtils.throwAsUncheckedException(exception); } } } ================================================ FILE: junit-platform-commons/src/main/java/org/junit/platform/commons/util/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Internal common utilities for JUnit. * *

DISCLAIMER

* *

These utilities are intended solely for usage within the JUnit framework * itself. Any usage by external parties is not supported. * Use at your own risk! */ @NullMarked package org.junit.platform.commons.util; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-commons/src/test/README.md ================================================ For compatibility with the Eclipse IDE, the test for this module are in the `platform-tests` project. ================================================ FILE: junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/ConcurrencyTestingUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.test; import static java.util.concurrent.TimeUnit.SECONDS; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.jspecify.annotations.Nullable; public class ConcurrencyTestingUtils { public static void executeConcurrently(int threads, Runnable action) throws Exception { ConcurrencyTestingUtils.<@Nullable Object> executeConcurrently(threads, () -> { action.run(); return null; }); } @SuppressWarnings("Finally") public static List executeConcurrently(int threads, Callable action) throws Exception { ExecutorService executorService = Executors.newFixedThreadPool(threads); try { CountDownLatch latch = new CountDownLatch(threads); List> futures = new ArrayList<>(); for (int i = 0; i < threads; i++) { futures.add(CompletableFuture.supplyAsync(() -> { try { latch.countDown(); latch.await(); return action.call(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new CompletionException(e); } catch (Exception e) { throw new CompletionException("Action failed", e); } }, executorService)); } List list = new ArrayList<>(); for (CompletableFuture future : futures) { list.add(future.get(5, SECONDS)); } return list; } finally { executorService.shutdownNow(); var terminated = executorService.awaitTermination(5, SECONDS); if (!terminated) { //noinspection ThrowFromFinallyBlock throw new AssertionError("ExecutorService did not cleanly shut down"); } } } private ConcurrencyTestingUtils() { } } ================================================ FILE: junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/IdeUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.test; /** * Collection of test utilities for IDEs. */ public class IdeUtils { /** * Determine if the current code is running in the Eclipse IDE. *

Copied from {@code org.springframework.core.testfixture.ide.IdeUtils}. */ public static boolean runningInEclipse() { return StackWalker.getInstance().walk( stream -> stream.anyMatch(stackFrame -> stackFrame.getClassName().startsWith("org.eclipse.jdt"))); } private IdeUtils() { } } ================================================ FILE: junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/PreconditionAssertions.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.test; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import org.assertj.core.api.ThrowableAssert.ThrowingCallable; import org.assertj.core.api.ThrowableAssertAlternative; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.Preconditions; /** * Collection of assertions for working with {@link Preconditions}. * * @since 1.14 */ public final class PreconditionAssertions { private PreconditionAssertions() { /* no-op */ } public static void assertPreconditionViolationNotNullFor(String name, ThrowingCallable throwingCallable) { assertPreconditionViolationFor(throwingCallable).withMessage("%s must not be null", name); } public static void assertPreconditionViolationNotBlankFor(String name, ThrowingCallable throwingCallable) { assertPreconditionViolationFor(throwingCallable).withMessageContaining("%s must not be blank", name); } public static void assertPreconditionViolationNotEmptyFor(String name, ThrowingCallable throwingCallable) { assertPreconditionViolationFor(throwingCallable).withMessage("%s must not be empty", name); } public static void assertPreconditionViolationNotNullOrBlankFor(String name, ThrowingCallable throwingCallable) { assertPreconditionViolationFor(throwingCallable).withMessage("%s must not be null or blank", name); } public static void assertPreconditionViolationNotNullOrEmptyFor(String name, ThrowingCallable throwingCallable) { assertPreconditionViolationFor(throwingCallable).withMessage("%s must not be null or empty", name); } public static void assertPreconditionViolationContainsNoNullElementsFor(String name, ThrowingCallable throwingCallable) { assertPreconditionViolationFor(throwingCallable).withMessage("%s must not contain null elements", name); } public static ThrowableAssertAlternative assertPreconditionViolationFor( ThrowingCallable throwingCallable) { return assertThatExceptionOfType(PreconditionViolationException.class).isThrownBy(throwingCallable); } } ================================================ FILE: junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/TestClassLoader.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.test; import java.lang.StackWalker.Option; import java.net.URL; import java.net.URLClassLoader; import java.security.CodeSource; import java.util.Arrays; import java.util.List; import java.util.function.Predicate; import org.junit.platform.commons.util.ClassLoaderUtils; /** * Test {@link ClassLoader} which accepts a class name {@link Predicate} to * filter classes that should be loaded by this {@code ClassLoader} instead of * the {@linkplain ClassLoaderUtils#getDefaultClassLoader() default ClassLoader}. * *

This class loader is only suitable for specific testing scenarios, where * you need to load particular classes from a different class loader. * * @since 1.10 */ public class TestClassLoader extends URLClassLoader { private static final StackWalker stackWalker = StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE); static { ClassLoader.registerAsParallelCapable(); } /** * Create a {@link TestClassLoader} that filters the provided classes. * * @see #forClasses(List) * @see #forClassNamePrefix(String) */ public static TestClassLoader forClasses(Class... classes) { Predicate classNameFilter = name -> Arrays.stream(classes).map(Class::getName).anyMatch(name::equals); return new TestClassLoader(getCodeSourceUrl(stackWalker.getCallerClass()), classNameFilter); } /** * Create a {@link TestClassLoader} that filters the provided classes. * * @see #forClasses(Class...) * @see #forClassNamePrefix(String) */ public static TestClassLoader forClasses(List> classes) { Predicate classNameFilter = name -> classes.stream().map(Class::getName).anyMatch(name::equals); return new TestClassLoader(getCodeSourceUrl(stackWalker.getCallerClass()), classNameFilter); } /** * Create a {@link TestClassLoader} that filters classes whose fully * qualified names start with the provided prefix. * * @see #forClasses(Class...) * @see #forClasses(List) */ public static TestClassLoader forClassNamePrefix(String prefix) { return new TestClassLoader(getCodeSourceUrl(stackWalker.getCallerClass()), name -> name.startsWith(prefix)); } private final Predicate classNameFilter; private TestClassLoader(URL codeSourceUrl, Predicate classNameFilter) { super(new URL[] { codeSourceUrl }, ClassLoaderUtils.getDefaultClassLoader()); this.classNameFilter = classNameFilter; } @Override public Class loadClass(String name) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { Class clazz = findLoadedClass(name); if (clazz != null) { return clazz; } return this.classNameFilter.test(name) ? findClass(name) : super.loadClass(name); } } /** * Get the {@link CodeSource} {@link URL} of the supplied class. */ private static URL getCodeSourceUrl(Class clazz) { return clazz.getProtectionDomain().getCodeSource().getLocation(); } } ================================================ FILE: junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/package-info.java ================================================ @NullMarked package org.junit.platform.commons.test; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-console/LICENSE-picocli.md ================================================ Apache License ============== _Version 2.0, January 2004_ _<>_ ### Terms and Conditions for use, reproduction, and distribution #### 1. Definitions “License” shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. “Licensor” shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. “Legal Entity” shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, “control” means **(i)** the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the outstanding shares, or **(iii)** beneficial ownership of such entity. “You” (or “Your”) shall mean an individual or Legal Entity exercising permissions granted by this License. “Source” form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. “Object” form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. “Work” shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). “Derivative Works” shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. “Contribution” shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, “submitted” means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as “Not a Contribution.” “Contributor” shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. #### 2. Grant of Copyright License Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. #### 3. Grant of Patent License Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. #### 4. Redistribution You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: * **(a)** You must give any other recipients of the Work or Derivative Works a copy of this License; and * **(b)** You must cause any modified files to carry prominent notices stating that You changed the files; and * **(c)** You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and * **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. #### 5. Submission of Contributions Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. #### 6. Trademarks This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. #### 7. Disclaimer of Warranty Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. #### 8. Limitation of Liability In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. #### 9. Accepting Warranty or Additional Liability While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. _END OF TERMS AND CONDITIONS_ ### APPENDIX: How to apply the Apache License to your work To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets `[]` replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same “printed page” as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: junit-platform-console/junit-platform-console.gradle.kts ================================================ import junitbuild.extensions.javaModuleName import junitbuild.java.UpdateJarAction import net.ltgt.gradle.errorprone.errorprone import net.ltgt.gradle.nullaway.nullaway plugins { id("junitbuild.java-library-conventions") id("junitbuild.shadow-conventions") } description = "JUnit Platform Console" dependencies { api(platform(projects.junitBom)) api(projects.junitPlatformReporting) compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) shadowed(libs.picocli) osgiVerification(projects.junitJupiterEngine) osgiVerification(projects.junitPlatformLauncher) osgiVerification(libs.openTestReporting.tooling.spi) } tasks { compileJava { options.compilerArgs.addAll(listOf( "-Xlint:-module", // due to qualified exports "--add-modules", "info.picocli", "--add-reads", "${javaModuleName}=info.picocli" )) options.errorprone.nullaway { excludedFieldAnnotations.addAll( "picocli.CommandLine.ArgGroup", "picocli.CommandLine.Mixin", "picocli.CommandLine.Spec", ) } } javadoc { (options as StandardJavadocDocletOptions).apply { addStringOption("-add-modules", "info.picocli") addStringOption("-add-reads", "${javaModuleName}=info.picocli") } } shadowJar { exclude("META-INF/**/module-info.class") relocate("picocli", "org.junit.platform.console.shadow.picocli") from(projectDir) { include("LICENSE-picocli.md") into("META-INF") } doLast(objects.newInstance(UpdateJarAction::class).apply { javaLauncher = project.javaToolchains.launcherFor(java.toolchain) args.addAll( "--file", archiveFile.get().asFile.absolutePath, "--main-class", "org.junit.platform.console.ConsoleLauncher", ) }) bundle { // Ignore warning for package that is only exported as "INTERNAL" bnd(""" -fixupmessages.picocli.export: "Export org.junit.platform.console.options";is:=ignore """) } } codeCoverageClassesJar { exclude("org/junit/platform/console/options/ConsoleUtils.class") } jar { manifest { attributes("Main-Class" to "org.junit.platform.console.ConsoleLauncher") } } } ================================================ FILE: junit-platform-console/src/main/java/module-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Support for launching the JUnit Platform from the console. * * @since 1.0 * @provides java.util.spi.ToolProvider */ module org.junit.platform.console { requires static org.apiguardian.api; requires static transitive org.jspecify; requires org.junit.platform.commons; requires org.junit.platform.engine; requires org.junit.platform.launcher; requires org.junit.platform.reporting; exports org.junit.platform.console.output to org.junit.start; provides java.util.spi.ToolProvider with org.junit.platform.console.ConsoleLauncherToolProvider; } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/ConsoleLauncher.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.MAINTAINED; import java.io.PrintWriter; import org.apiguardian.api.API; import org.junit.platform.console.command.CommandFacade; import org.junit.platform.console.command.CommandResult; import org.junit.platform.console.command.ConsoleTestExecutor; import org.junit.platform.console.command.CustomClassLoaderCloseStrategy; /** * The {@code ConsoleLauncher} is a stand-alone application for launching the * JUnit Platform from the console. * * @since 1.0 */ @API(status = MAINTAINED, since = "1.0") public class ConsoleLauncher { public static void main(String... args) { CommandFacade facade = newCommandFacade(CustomClassLoaderCloseStrategy.KEEP_OPEN); CommandResult result = facade.run(args); System.exit(result.getExitCode()); } @API(status = INTERNAL, since = "1.0") public static CommandResult run(PrintWriter out, PrintWriter err, String... args) { CommandFacade facade = newCommandFacade(CustomClassLoaderCloseStrategy.CLOSE_AFTER_CALLING_LAUNCHER); return facade.run(args, out, err); } private static CommandFacade newCommandFacade(CustomClassLoaderCloseStrategy classLoaderCleanupStrategy) { return new CommandFacade((discoveryOptions, outputOptions) -> new ConsoleTestExecutor(discoveryOptions, outputOptions, classLoaderCleanupStrategy)); } private ConsoleLauncher() { } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/ConsoleLauncherToolProvider.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console; import static org.apiguardian.api.API.Status.STABLE; import java.io.PrintWriter; import java.util.spi.ToolProvider; import org.apiguardian.api.API; /** * Run the JUnit Platform Console Launcher as a service. * * @since 1.6 */ @API(status = STABLE, since = "1.10") public class ConsoleLauncherToolProvider implements ToolProvider { @Override public String name() { return "junit"; } @Override public int run(PrintWriter out, PrintWriter err, String... args) { return ConsoleLauncher.run(out, err, args).getExitCode(); } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/command/BaseCommand.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.command; import java.io.PrintWriter; import java.util.concurrent.Callable; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.console.options.AnsiColorOptionMixin; import picocli.CommandLine; import picocli.CommandLine.Mixin; import picocli.CommandLine.Model.CommandSpec; import picocli.CommandLine.Option; import picocli.CommandLine.ParameterException; import picocli.CommandLine.Spec; abstract class BaseCommand implements Callable { @Spec CommandSpec commandSpec; @Mixin AnsiColorOptionMixin ansiColorOption; @Option(names = "--disable-banner", description = "Disable print out of the welcome message.") private boolean disableBanner; @SuppressWarnings("unused") @Option(names = { "-h", "--help" }, usageHelp = true, description = "Display help information.") private boolean helpRequested; @SuppressWarnings("unused") @Option(names = "--version", versionHelp = true, description = "Display version information.") private boolean versionRequested; void execute(String... args) { toCommandLine().execute(args); } void parseArgs(String... args) { toCommandLine().parseArgs(args); } private CommandLine toCommandLine() { return BaseCommand.initialize(new CommandLine(this)); } static CommandLine initialize(CommandLine commandLine) { CommandLine.IParameterExceptionHandler defaultParameterExceptionHandler = commandLine.getParameterExceptionHandler(); return commandLine // .setParameterExceptionHandler((ex, args) -> { defaultParameterExceptionHandler.handleParseException(ex, args); return ExitCode.ANY_ERROR; }) // .setExecutionExceptionHandler((ex, cmd, __) -> { commandLine.getErr().println(cmd.getColorScheme().richStackTraceString(ex)); commandLine.getErr().println(); commandLine.getErr().flush(); cmd.usage(commandLine.getOut()); return ExitCode.ANY_ERROR; }) // .setCaseInsensitiveEnumValuesAllowed(true) // .setAtFileCommentChar(null); } @Override public final T call() { PrintWriter out = getOut(); if (!disableBanner) { displayBanner(out); } try { return execute(out); } catch (PreconditionViolationException e) { throw new ParameterException(commandSpec.commandLine(), e.getMessage(), e.getCause()); } } private PrintWriter getOut() { return commandSpec.commandLine().getOut(); } private void displayBanner(PrintWriter out) { out.println(); CommandLine.Help.ColorScheme colorScheme = getColorScheme(); if (colorScheme.ansi().enabled()) { out.print("💚 "); } out.println(colorScheme.string( "@|italic Thanks for using JUnit!|@ Support its development at @|underline https://junit.org/sponsoring|@")); out.println(); out.flush(); } protected final CommandLine.Help.ColorScheme getColorScheme() { return commandSpec.commandLine().getColorScheme(); } protected abstract T execute(PrintWriter out); } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/command/CommandFacade.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.command; import static org.apiguardian.api.API.Status.INTERNAL; import java.io.PrintWriter; import java.util.Optional; import org.apiguardian.api.API; /** * Internal facade to run a CLI command that exists to hide implementation * details such as the used library. * * @since 1.10 */ @API(status = INTERNAL, since = "1.10") public class CommandFacade { private final ConsoleTestExecutor.Factory consoleTestExecutorFactory; public CommandFacade(ConsoleTestExecutor.Factory consoleTestExecutorFactory) { this.consoleTestExecutorFactory = consoleTestExecutorFactory; } public CommandResult run(String[] args) { return run(args, Optional.empty()); } public CommandResult run(String[] args, PrintWriter out, PrintWriter err) { try { return run(args, Optional.of(new OutputStreamConfig(out, err))); } finally { out.flush(); err.flush(); } } private CommandResult run(String[] args, Optional outputStreamConfig) { Optional version = ManifestVersionProvider.getImplementationVersion(); System.setProperty("junit.docs.version", version.map(it -> it.endsWith("-SNAPSHOT") ? "snapshot" : it).orElse("current")); return new MainCommand(consoleTestExecutorFactory).run(args, outputStreamConfig); } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/command/CommandResult.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.command; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.Optional; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; /** * @since 1.10 */ @API(status = INTERNAL, since = "1.10") public class CommandResult { public static CommandResult success() { return create(ExitCode.SUCCESS, null); } public static CommandResult create(int exitCode, @Nullable T value) { return new CommandResult<>(exitCode, value); } private final int exitCode; private final @Nullable T value; private CommandResult(int exitCode, @Nullable T value) { this.exitCode = exitCode; this.value = value; } public int getExitCode() { return this.exitCode; } public Optional getValue() { return Optional.ofNullable(this.value); } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/command/ConsoleTestExecutor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.command; import static java.util.Objects.requireNonNullElseGet; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.console.command.DiscoveryRequestCreator.toDiscoveryRequestBuilder; import static org.junit.platform.launcher.LauncherConstants.OUTPUT_DIR_PROPERTY_NAME; import java.io.PrintStream; import java.io.PrintWriter; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Path; import java.util.EnumSet; import java.util.List; import java.util.Optional; import java.util.function.Supplier; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.ClassLoaderUtils; import org.junit.platform.console.options.Details; import org.junit.platform.console.options.TestConsoleOutputOptions; import org.junit.platform.console.options.TestDiscoveryOptions; import org.junit.platform.console.output.ColorPalette; import org.junit.platform.console.output.DetailsPrintingListener; import org.junit.platform.console.output.FlatPrintingListener; import org.junit.platform.console.output.TestFeedPrintingListener; import org.junit.platform.console.output.Theme; import org.junit.platform.console.output.TreePrintingListener; import org.junit.platform.console.output.VerboseTreePrintingListener; import org.junit.platform.engine.CancellationToken; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestPlan; import org.junit.platform.launcher.core.LauncherFactory; import org.junit.platform.launcher.listeners.SummaryGeneratingListener; import org.junit.platform.launcher.listeners.TestExecutionSummary; import org.junit.platform.reporting.legacy.xml.LegacyXmlReportGeneratingListener; /** * @since 1.0 */ @API(status = INTERNAL, since = "1.0") public class ConsoleTestExecutor { private final TestDiscoveryOptions discoveryOptions; private final TestConsoleOutputOptions outputOptions; private final Supplier launcherSupplier; private final CustomClassLoaderCloseStrategy classLoaderCloseStrategy; public ConsoleTestExecutor(TestDiscoveryOptions discoveryOptions, TestConsoleOutputOptions outputOptions) { this(discoveryOptions, outputOptions, CustomClassLoaderCloseStrategy.CLOSE_AFTER_CALLING_LAUNCHER); } public ConsoleTestExecutor(TestDiscoveryOptions discoveryOptions, TestConsoleOutputOptions outputOptions, CustomClassLoaderCloseStrategy classLoaderCloseStrategy) { this(discoveryOptions, outputOptions, classLoaderCloseStrategy, LauncherFactory::create); } // for tests only ConsoleTestExecutor(TestDiscoveryOptions discoveryOptions, TestConsoleOutputOptions outputOptions, Supplier launcherSupplier) { this(discoveryOptions, outputOptions, CustomClassLoaderCloseStrategy.CLOSE_AFTER_CALLING_LAUNCHER, launcherSupplier); } private ConsoleTestExecutor(TestDiscoveryOptions discoveryOptions, TestConsoleOutputOptions outputOptions, CustomClassLoaderCloseStrategy classLoaderCloseStrategy, Supplier launcherSupplier) { this.discoveryOptions = discoveryOptions; this.outputOptions = outputOptions; this.launcherSupplier = launcherSupplier; this.classLoaderCloseStrategy = classLoaderCloseStrategy; } public void discover(PrintWriter out) { createCustomContextClassLoaderExecutor().invoke(() -> { discoverTests(out); return null; }); } public TestExecutionSummary execute(PrintWriter out, Optional reportsDir, boolean failFast) { return createCustomContextClassLoaderExecutor() // .invoke(() -> executeTests(out, reportsDir, failFast)); } private CustomContextClassLoaderExecutor createCustomContextClassLoaderExecutor() { return new CustomContextClassLoaderExecutor(createCustomClassLoader(), classLoaderCloseStrategy); } private void discoverTests(PrintWriter out) { Launcher launcher = launcherSupplier.get(); Optional commandLineTestPrinter = createDetailsPrintingListener(out); LauncherDiscoveryRequest discoveryRequest = toDiscoveryRequestBuilder(discoveryOptions).build(); TestPlan testPlan = launcher.discover(discoveryRequest); commandLineTestPrinter.ifPresent(printer -> printer.listTests(testPlan)); if (outputOptions.getDetails() != Details.NONE) { printFoundTestsSummary(out, testPlan); } } private static void printFoundTestsSummary(PrintWriter out, TestPlan testPlan) { SummaryGeneratingListener summaryListener = new SummaryGeneratingListener(); summaryListener.testPlanExecutionStarted(testPlan); TestExecutionSummary summary = summaryListener.getSummary(); out.printf("%n[%10d containers found ]%n[%10d tests found ]%n%n", summary.getContainersFoundCount(), summary.getTestsFoundCount()); out.flush(); } private TestExecutionSummary executeTests(PrintWriter out, Optional reportsDir, boolean failFast) { Launcher launcher = launcherSupplier.get(); CancellationToken cancellationToken = failFast ? CancellationToken.create() : null; SummaryGeneratingListener summaryListener = registerListeners(out, reportsDir, launcher, cancellationToken); PrintStream originalOut = System.out; PrintStream originalErr = System.err; try (StandardStreamsHandler standardStreamsHandler = new StandardStreamsHandler()) { standardStreamsHandler.redirectStandardStreams(outputOptions.getStdoutPath(), outputOptions.getStderrPath()); launchTests(launcher, reportsDir, cancellationToken); } finally { System.setOut(originalOut); System.setErr(originalErr); } TestExecutionSummary summary = summaryListener.getSummary(); if (summary.getTotalFailureCount() > 0 || outputOptions.getDetails() != Details.NONE) { printSummary(summary, out); } if (cancellationToken != null && cancellationToken.isCancellationRequested()) { out.println("Test execution was cancelled due to --fail-fast mode."); out.println(); } return summary; } private void launchTests(Launcher launcher, Optional reportsDir, @Nullable CancellationToken cancellationToken) { var discoveryRequestBuilder = toDiscoveryRequestBuilder(discoveryOptions); reportsDir.ifPresent(dir -> discoveryRequestBuilder.configurationParameter(OUTPUT_DIR_PROPERTY_NAME, dir.toAbsolutePath().toString())); var executionRequest = discoveryRequestBuilder.forExecution() // .cancellationToken(requireNonNullElseGet(cancellationToken, CancellationToken::disabled)) // .build(); launcher.execute(executionRequest); } private Optional createCustomClassLoader() { List additionalClasspathEntries = discoveryOptions.getExistingAdditionalClasspathEntries(); if (!additionalClasspathEntries.isEmpty()) { URL[] urls = additionalClasspathEntries.stream().map(this::toURL).toArray(URL[]::new); ClassLoader parentClassLoader = ClassLoaderUtils.getDefaultClassLoader(); ClassLoader customClassLoader = URLClassLoader.newInstance(urls, parentClassLoader); return Optional.of(customClassLoader); } return Optional.empty(); } private URL toURL(Path path) { try { return path.toUri().toURL(); } catch (Exception ex) { throw new JUnitException("Invalid classpath entry: " + path, ex); } } private SummaryGeneratingListener registerListeners(PrintWriter out, Optional reportsDir, Launcher launcher, @Nullable CancellationToken cancellationToken) { // always register summary generating listener SummaryGeneratingListener summaryListener = new SummaryGeneratingListener(); launcher.registerTestExecutionListeners(summaryListener); // optionally, register test plan execution details printing listener createDetailsPrintingListener(out).ifPresent(launcher::registerTestExecutionListeners); // optionally, register XML reports writing listener createXmlWritingListener(out, reportsDir).ifPresent(launcher::registerTestExecutionListeners); createFailFastListener(cancellationToken).ifPresent(launcher::registerTestExecutionListeners); return summaryListener; } private Optional createDetailsPrintingListener(PrintWriter out) { ColorPalette colorPalette = getColorPalette(); Theme theme = outputOptions.getTheme(); return switch (outputOptions.getDetails()) { case SUMMARY -> // summary listener is always created and registered Optional.empty(); case FLAT -> Optional.of(new FlatPrintingListener(out, colorPalette)); case TREE -> Optional.of(new TreePrintingListener(out, colorPalette, theme)); case VERBOSE -> Optional.of(new VerboseTreePrintingListener(out, colorPalette, 16, theme)); case TESTFEED -> Optional.of(new TestFeedPrintingListener(out, colorPalette)); case NONE -> Optional.empty(); }; } private ColorPalette getColorPalette() { if (outputOptions.isAnsiColorOutputDisabled()) { return ColorPalette.NONE; } if (outputOptions.getColorPalettePath() != null) { return new ColorPalette(outputOptions.getColorPalettePath()); } if (outputOptions.isSingleColorPalette()) { return ColorPalette.SINGLE_COLOR; } return ColorPalette.DEFAULT; } private Optional createXmlWritingListener(PrintWriter out, Optional reportsDir) { return reportsDir.map(it -> new LegacyXmlReportGeneratingListener(it, out)); } private Optional createFailFastListener(@Nullable CancellationToken cancellationToken) { return Optional.ofNullable(cancellationToken).map(FailFastListener::new); } private void printSummary(TestExecutionSummary summary, PrintWriter out) { // Otherwise the failures have already been printed in detail if (EnumSet.of(Details.NONE, Details.SUMMARY, Details.TREE).contains(outputOptions.getDetails())) { summary.printFailuresTo(out); } summary.printTo(out); } @FunctionalInterface public interface Factory { ConsoleTestExecutor create(TestDiscoveryOptions discoveryOptions, TestConsoleOutputOptions outputOptions); } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/command/CustomClassLoaderCloseStrategy.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.command; import static org.apiguardian.api.API.Status.INTERNAL; import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; /** * Defines the strategy for closing custom class loaders created for test * discovery and execution. */ @API(status = INTERNAL, since = "1.13") public enum CustomClassLoaderCloseStrategy { /** * Close the custom class loader after calling the * {@link org.junit.platform.launcher.Launcher} for test discovery or * execution. */ CLOSE_AFTER_CALLING_LAUNCHER { @Override public void handle(ClassLoader customClassLoader) { if (customClassLoader instanceof @SuppressWarnings("resource") AutoCloseable closeable) { try { closeable.close(); } catch (Exception ex) { throw new JUnitException("Failed to close custom class loader", ex); } } } }, /** * Rely on the JVM to release resources held by the custom class loader when * it terminates. * *

This mode is only safe to use when calling {@link System#exit(int)} * afterward. */ KEEP_OPEN { @Override public void handle(ClassLoader customClassLoader) { // do nothing } }; /** * Handle the class loader according to the strategy. */ public abstract void handle(ClassLoader classLoader); } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/command/CustomContextClassLoaderExecutor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.command; import java.util.Optional; import java.util.function.Supplier; /** * @since 1.0 */ class CustomContextClassLoaderExecutor { private final Optional customClassLoader; private final CustomClassLoaderCloseStrategy closeStrategy; CustomContextClassLoaderExecutor(Optional customClassLoader) { this(customClassLoader, CustomClassLoaderCloseStrategy.CLOSE_AFTER_CALLING_LAUNCHER); } CustomContextClassLoaderExecutor(Optional customClassLoader, CustomClassLoaderCloseStrategy closeStrategy) { this.customClassLoader = customClassLoader; this.closeStrategy = closeStrategy; } T invoke(Supplier supplier) { if (customClassLoader.isPresent()) { // Only get/set context class loader when necessary to prevent problems with // security managers return replaceThreadContextClassLoaderAndInvoke(customClassLoader.get(), supplier); } return supplier.get(); } private T replaceThreadContextClassLoaderAndInvoke(ClassLoader customClassLoader, Supplier supplier) { ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(customClassLoader); return supplier.get(); } finally { Thread.currentThread().setContextClassLoader(originalClassLoader); closeStrategy.handle(customClassLoader); } } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/command/DiscoverTestsCommand.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.command; import java.io.PrintWriter; import org.junit.platform.console.options.TestConsoleOutputOptions; import org.junit.platform.console.options.TestConsoleOutputOptionsMixin; import org.junit.platform.console.options.TestDiscoveryOptions; import org.junit.platform.console.options.TestDiscoveryOptionsMixin; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; @Command(// name = "discover", // description = "Discover tests" // ) class DiscoverTestsCommand extends BaseCommand { private final ConsoleTestExecutor.Factory consoleTestExecutorFactory; @Mixin TestDiscoveryOptionsMixin discoveryOptions; @Mixin TestConsoleOutputOptionsMixin testOutputOptions; DiscoverTestsCommand(ConsoleTestExecutor.Factory consoleTestExecutorFactory) { this.consoleTestExecutorFactory = consoleTestExecutorFactory; } @Override protected Void execute(PrintWriter out) { TestDiscoveryOptions discoveryOptions = this.discoveryOptions.toTestDiscoveryOptions(); TestConsoleOutputOptions testOutputOptions = this.testOutputOptions.toTestConsoleOutputOptions(); testOutputOptions.setAnsiColorOutputDisabled(this.ansiColorOption.isDisableAnsiColors()); this.consoleTestExecutorFactory.create(discoveryOptions, testOutputOptions).discover(out); return null; } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/command/DiscoveryRequestCreator.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.command; import static org.junit.platform.engine.discovery.ClassNameFilter.excludeClassNamePatterns; import static org.junit.platform.engine.discovery.ClassNameFilter.includeClassNamePatterns; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathRoots; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectModules; import static org.junit.platform.engine.discovery.PackageNameFilter.excludePackageNames; import static org.junit.platform.engine.discovery.PackageNameFilter.includePackageNames; import static org.junit.platform.launcher.EngineFilter.excludeEngines; import static org.junit.platform.launcher.EngineFilter.includeEngines; import static org.junit.platform.launcher.MethodFilter.excludeMethodNamePatterns; import static org.junit.platform.launcher.MethodFilter.includeMethodNamePatterns; import static org.junit.platform.launcher.TagFilter.excludeTags; import static org.junit.platform.launcher.TagFilter.includeTags; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.regex.Pattern; import java.util.stream.Stream; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.ModuleUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.console.options.TestDiscoveryOptions; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.discovery.ClassNameFilter; import org.junit.platform.engine.discovery.ClassSelector; import org.junit.platform.engine.discovery.ClasspathRootSelector; import org.junit.platform.engine.discovery.IterationSelector; import org.junit.platform.engine.discovery.MethodSelector; import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; /** * @since 1.0 */ class DiscoveryRequestCreator { private static final Logger logger = LoggerFactory.getLogger(DiscoveryRequestCreator.class); static LauncherDiscoveryRequestBuilder toDiscoveryRequestBuilder(TestDiscoveryOptions options) { LauncherDiscoveryRequestBuilder requestBuilder = request(); List selectors = createDiscoverySelectors(options); requestBuilder.selectors(selectors); addFilters(requestBuilder, options, selectors); requestBuilder.configurationParameters(options.getConfigurationParameters()); requestBuilder.configurationParametersResources( options.getConfigurationParametersResources().toArray(new String[0])); return requestBuilder; } private static List createDiscoverySelectors(TestDiscoveryOptions options) { List explicitSelectors = options.getExplicitSelectors(); if (options.isScanClasspath()) { Preconditions.condition(explicitSelectors.isEmpty(), "Scanning the classpath and using explicit selectors at the same time is not supported"); return createClasspathRootSelectors(options); } if (options.isScanModulepath()) { Preconditions.condition(explicitSelectors.isEmpty(), "Scanning the module-path and using explicit selectors at the same time is not supported"); return selectModules(ModuleUtils.findAllNonSystemBootModuleNames()); } return Preconditions.notEmpty(explicitSelectors, "Please specify an explicit selector option or use --scan-class-path or --scan-modules"); } private static List createClasspathRootSelectors(TestDiscoveryOptions options) { Set classpathRoots = validateAndLogInvalidRoots(determineClasspathRoots(options)); return selectClasspathRoots(classpathRoots); } private static Set determineClasspathRoots(TestDiscoveryOptions options) { var selectedClasspathEntries = Preconditions.notNull(options.getSelectedClasspathEntries(), () -> "No classpath entries selected"); if (selectedClasspathEntries.isEmpty()) { Set rootDirs = new LinkedHashSet<>(ReflectionUtils.getAllClasspathRootDirectories()); rootDirs.addAll(options.getAdditionalClasspathEntries()); return rootDirs; } return new LinkedHashSet<>(selectedClasspathEntries); } private static Set validateAndLogInvalidRoots(Set roots) { LinkedHashSet valid = new LinkedHashSet<>(); HashSet seen = new HashSet<>(); for (Path root : roots) { if (!seen.add(root)) { continue; } if (Files.exists(root)) { valid.add(root); } else { logger.warn(() -> "Ignoring nonexistent classpath root: %s".formatted(root)); } } return valid; } private static void addFilters(LauncherDiscoveryRequestBuilder requestBuilder, TestDiscoveryOptions options, List selectors) { requestBuilder.filters(includedClassNamePatterns(options, selectors)); if (!options.getExcludedClassNamePatterns().isEmpty()) { requestBuilder.filters( excludeClassNamePatterns(options.getExcludedClassNamePatterns().toArray(new String[0]))); } if (!options.getIncludedPackages().isEmpty()) { requestBuilder.filters(includePackageNames(options.getIncludedPackages())); } if (!options.getExcludedPackages().isEmpty()) { requestBuilder.filters(excludePackageNames(options.getExcludedPackages())); } if (!options.getIncludedMethodNamePatterns().isEmpty()) { requestBuilder.filters(includeMethodNamePatterns(options.getIncludedMethodNamePatterns())); } if (!options.getExcludedMethodNamePatterns().isEmpty()) { requestBuilder.filters(excludeMethodNamePatterns(options.getExcludedMethodNamePatterns())); } if (!options.getIncludedTagExpressions().isEmpty()) { requestBuilder.filters(includeTags(options.getIncludedTagExpressions())); } if (!options.getExcludedTagExpressions().isEmpty()) { requestBuilder.filters(excludeTags(options.getExcludedTagExpressions())); } if (!options.getIncludedEngines().isEmpty()) { requestBuilder.filters(includeEngines(options.getIncludedEngines())); } if (!options.getExcludedEngines().isEmpty()) { requestBuilder.filters(excludeEngines(options.getExcludedEngines())); } } private static ClassNameFilter includedClassNamePatterns(TestDiscoveryOptions options, List selectors) { Stream patternStreams = Stream.concat( // options.getIncludedClassNamePatterns().stream(), // selectors.stream() // .map(selector -> selector instanceof IterationSelector iterationSelector ? iterationSelector.getParentSelector() : selector) // .map(selector -> { if (selector instanceof ClassSelector classSelector) { return classSelector.getClassName(); } if (selector instanceof MethodSelector methodSelector) { return methodSelector.getClassName(); } return null; }) // .filter(Objects::nonNull) // .map(Pattern::quote)); return includeClassNamePatterns(patternStreams.toArray(String[]::new)); } private DiscoveryRequestCreator() { } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/command/ExecuteTestsCommand.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.command; import static org.junit.platform.console.command.ExitCode.NO_TESTS_FOUND; import static org.junit.platform.console.command.ExitCode.SUCCESS; import static org.junit.platform.console.command.ExitCode.TEST_FAILED; import java.io.PrintWriter; import java.nio.file.Path; import java.util.Optional; import org.jspecify.annotations.Nullable; import org.junit.platform.console.options.TestConsoleOutputOptions; import org.junit.platform.console.options.TestConsoleOutputOptionsMixin; import org.junit.platform.console.options.TestDiscoveryOptions; import org.junit.platform.console.options.TestDiscoveryOptionsMixin; import org.junit.platform.launcher.listeners.TestExecutionSummary; import picocli.CommandLine; import picocli.CommandLine.ArgGroup; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; import picocli.CommandLine.Option; @Command(// name = "execute", // description = "Execute tests" // ) class ExecuteTestsCommand extends BaseCommand implements CommandLine.IExitCodeGenerator { private final ConsoleTestExecutor.Factory consoleTestExecutorFactory; @Mixin TestDiscoveryOptionsMixin discoveryOptions; @Mixin TestConsoleOutputOptionsMixin testOutputOptions; @ArgGroup(validate = false, order = 6, heading = "%n@|bold REPORTING|@%n%n") ReportingOptions reportingOptions; ExecuteTestsCommand(ConsoleTestExecutor.Factory consoleTestExecutorFactory) { this.consoleTestExecutorFactory = consoleTestExecutorFactory; } @Override protected TestExecutionSummary execute(PrintWriter out) { return consoleTestExecutorFactory.create(toTestDiscoveryOptions(), toTestConsoleOutputOptions()) // .execute(out, getReportsDir(), isFailFast()); } Optional getReportsDir() { return getReportingOptions().flatMap(ReportingOptions::getReportsDir); } boolean isFailFast() { return getReportingOptions().map(options -> options.failFast).orElse(false); } private Optional getReportingOptions() { return Optional.ofNullable(reportingOptions); } TestDiscoveryOptions toTestDiscoveryOptions() { return this.discoveryOptions == null // ? new TestDiscoveryOptions() // : this.discoveryOptions.toTestDiscoveryOptions(); } TestConsoleOutputOptions toTestConsoleOutputOptions() { TestConsoleOutputOptions testOutputOptions = this.testOutputOptions.toTestConsoleOutputOptions(); testOutputOptions.setAnsiColorOutputDisabled(this.ansiColorOption.isDisableAnsiColors()); return testOutputOptions; } @Override public int getExitCode() { TestExecutionSummary executionResult = commandSpec.commandLine().getExecutionResult(); boolean failIfNoTests = getReportingOptions().map(it -> it.failIfNoTests).orElse(false); if (failIfNoTests && executionResult.getTestsFoundCount() == 0) { return NO_TESTS_FOUND; } return executionResult.getTotalFailureCount() == 0 ? SUCCESS : TEST_FAILED; } static class ReportingOptions { @Option(names = "--fail-if-no-tests", description = "Fail and return exit status code 2 if no tests are found.") private boolean failIfNoTests; /** * @since 6.0 */ @Option(names = "--fail-fast", description = "Stops test execution after the first failed test.") private boolean failFast; @Option(names = "--reports-dir", paramLabel = "DIR", description = "Enable report output into a specified local directory (will be created if it does not exist).") private @Nullable Path reportsDir; Optional getReportsDir() { return Optional.ofNullable(reportsDir); } } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/command/ExitCode.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.command; /** * Well-known exit codes of the {@code junit} tool. * * @since 6.0.1 */ final class ExitCode { /** * Exit code indicating a successful tool run. */ public static final int SUCCESS = 0; /** * Exit code indicating an unsuccessful run. */ public static final int ANY_ERROR = -1; /** * Exit code indicating test failure(s). */ public static final int TEST_FAILED = 1; /** * Exit code indicating no tests found. */ public static final int NO_TESTS_FOUND = 2; /** * Exit code indicating invalid user input. */ public static final int INVALID_INPUT = 3; private ExitCode() { } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/command/FailFastListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.command; import static org.junit.platform.engine.TestExecutionResult.Status.FAILED; import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; /** * @since 6.0 */ class FailFastListener implements TestExecutionListener { private final CancellationToken cancellationToken; FailFastListener(CancellationToken cancellationToken) { this.cancellationToken = cancellationToken; } @Override public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { if (testExecutionResult.getStatus() == FAILED) { cancellationToken.cancel(); } } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/command/ListTestEnginesCommand.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.command; import java.io.PrintWriter; import java.util.Comparator; import java.util.StringJoiner; import java.util.stream.StreamSupport; import org.junit.platform.engine.TestEngine; import org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry; import picocli.CommandLine.Command; @Command(// name = "engines", // description = "List available test engines" // ) class ListTestEnginesCommand extends BaseCommand { @Override protected Void execute(PrintWriter out) { displayEngines(out); return null; } void displayEngines(PrintWriter out) { ServiceLoaderTestEngineRegistry registry = new ServiceLoaderTestEngineRegistry(); Iterable engines = registry.loadTestEngines(); StreamSupport.stream(engines.spliterator(), false) // .sorted(Comparator.comparing(TestEngine::getId)) // .forEach(engine -> displayEngine(out, engine)); out.flush(); } private void displayEngine(PrintWriter out, TestEngine engine) { StringJoiner details = new StringJoiner(":", " (", ")"); engine.getGroupId().ifPresent(details::add); engine.getArtifactId().ifPresent(details::add); engine.getVersion().ifPresent(details::add); out.println(getColorScheme().string("@|bold %s|@%s".formatted(engine.getId(), details))); } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/command/MainCommand.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.command; import static java.util.Objects.requireNonNull; import java.util.Optional; import org.jspecify.annotations.Nullable; import org.junit.platform.console.options.AnsiColorOptionMixin; import picocli.CommandLine; import picocli.CommandLine.Command; import picocli.CommandLine.IExitCodeGenerator; import picocli.CommandLine.Mixin; import picocli.CommandLine.Model.CommandSpec; import picocli.CommandLine.Option; import picocli.CommandLine.ParameterException; import picocli.CommandLine.Spec; @Command(// name = "junit", // abbreviateSynopsis = true, // synopsisSubcommandLabel = "COMMAND", // sortOptions = false, // usageHelpWidth = 95, // showAtFileInUsageHelp = true, // usageHelpAutoWidth = true, // description = "Launches the JUnit Platform for test discovery and execution.", // footerHeading = "%n", // footer = "For more information, please refer to the JUnit User Guide at%n" // + "@|underline https://docs.junit.org/${junit.docs.version}/|@", // scope = CommandLine.ScopeType.INHERIT, // exitCodeOnInvalidInput = ExitCode.INVALID_INPUT, // exitCodeOnExecutionException = ExitCode.ANY_ERROR, // versionProvider = ManifestVersionProvider.class // ) class MainCommand implements Runnable, IExitCodeGenerator { private final ConsoleTestExecutor.Factory consoleTestExecutorFactory; @Option(names = { "-h", "--help" }, help = true, description = "Display help information.") private boolean helpRequested; @Option(names = "--version", versionHelp = true, description = "Display version information.") private boolean versionRequested; @Mixin AnsiColorOptionMixin ansiColorOption; @Spec CommandSpec commandSpec; @Nullable CommandResult commandResult; MainCommand(ConsoleTestExecutor.Factory consoleTestExecutorFactory) { this.consoleTestExecutorFactory = consoleTestExecutorFactory; } @Override public void run() { if (helpRequested) { commandSpec.commandLine().usage(commandSpec.commandLine().getOut()); commandResult = CommandResult.success(); } else if (versionRequested) { commandSpec.commandLine().printVersionHelp(commandSpec.commandLine().getOut()); commandResult = CommandResult.success(); } else { throw new ParameterException(commandSpec.commandLine(), "Missing required subcommand"); } } @Override public int getExitCode() { return requireNonNull(commandResult).getExitCode(); } CommandResult run(String[] args, @SuppressWarnings("OptionalUsedAsFieldOrParameterType") Optional outputStreamConfig) { CommandLine commandLine = new CommandLine(this) // .addSubcommand(new DiscoverTestsCommand(consoleTestExecutorFactory)) // .addSubcommand(new ExecuteTestsCommand(consoleTestExecutorFactory)) // .addSubcommand(new ListTestEnginesCommand()); return runCommand(commandLine, args, outputStreamConfig); } private static CommandResult runCommand(CommandLine commandLine, String[] args, @SuppressWarnings("OptionalUsedAsFieldOrParameterType") Optional outputStreamConfig) { BaseCommand.initialize(commandLine); outputStreamConfig.ifPresent(it -> it.applyTo(commandLine)); int exitCode = commandLine.execute(args); return CommandResult.create(exitCode, getLikelyExecutedCommand(commandLine).getExecutionResult()); } /** * Get the most likely executed subcommand, if any, or the main command otherwise. * @see Executing Commands with Subcommands */ private static CommandLine getLikelyExecutedCommand(final CommandLine commandLine) { return Optional.ofNullable(commandLine.getParseResult().subcommand()) // .map(parseResult -> parseResult.commandSpec().commandLine()) // .orElse(commandLine); } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/command/ManifestVersionProvider.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.command; import java.util.Optional; import org.junit.platform.commons.util.PackageUtils; import picocli.CommandLine; class ManifestVersionProvider implements CommandLine.IVersionProvider { public static Optional getImplementationVersion() { return PackageUtils.getModuleOrImplementationVersion(ManifestVersionProvider.class); } @Override public String[] getVersion() { return new String[] { // "@|bold JUnit Platform Console Launcher %s|@".formatted(getImplementationVersion().orElse("")), // "JVM: ${java.version} (${java.vendor} ${java.vm.name} ${java.vm.version})", // "OS: ${os.name} ${os.version} ${os.arch}" // }; } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/command/OutputStreamConfig.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.command; import java.io.PrintWriter; import picocli.CommandLine; class OutputStreamConfig { private final PrintWriter out; private final PrintWriter err; OutputStreamConfig(PrintWriter out, PrintWriter err) { this.out = out; this.err = err; } void applyTo(CommandLine commandLine) { commandLine.setOut(this.out).setErr(this.err); } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/command/StandardStreamsHandler.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.command; import java.io.IOException; import java.io.PrintStream; import java.nio.file.Files; import java.nio.file.Path; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; class StandardStreamsHandler implements AutoCloseable { private @Nullable PrintStream stdout; private @Nullable PrintStream stderr; StandardStreamsHandler() { } /** * Redirect standard output (stdout) and standard error (stderr) to the specified * file paths. * *

If the paths are the same, both streams are redirected to the same file. * *

The default charset is used for writing to the files. * * @param stdoutPath the file path for standard output, or {@code null} to * indicate no redirection * @param stderrPath the file path for standard error, or {@code null} to * indicate no redirection */ public void redirectStandardStreams(@Nullable Path stdoutPath, @Nullable Path stderrPath) { if (isSameFile(stdoutPath, stderrPath)) { try { PrintStream commonStream = new PrintStream(Files.newOutputStream(stdoutPath), true); this.stdout = commonStream; this.stderr = commonStream; } catch (IOException ex) { throw new JUnitException("Error redirecting stdout and stderr to file: " + stdoutPath, ex); } } else { if (stdoutPath != null) { try { this.stdout = new PrintStream(Files.newOutputStream(stdoutPath), true); } catch (IOException ex) { throw new JUnitException("Error redirecting stdout to file: " + stdoutPath, ex); } } if (stderrPath != null) { try { this.stderr = new PrintStream(Files.newOutputStream(stderrPath), true); } catch (IOException ex) { throw new JUnitException("Error redirecting stderr to file: " + stderrPath, ex); } } } if (this.stdout != null) { System.setOut(this.stdout); } if (this.stderr != null) { System.setErr(this.stderr); } } @Override public void close() { try { if (this.stdout != null) { this.stdout.close(); } } finally { if (this.stderr != null) { this.stderr.close(); } } } private static boolean isSameFile(@Nullable Path path1, @Nullable Path path2) { if (path1 == null || path2 == null) { return false; } return path1.normalize().toAbsolutePath().equals(path2.normalize().toAbsolutePath()); } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/command/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Commands of JUnit's console launcher. */ @NullMarked package org.junit.platform.console.command; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/options/AnsiColorOptionMixin.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.options; import static org.apiguardian.api.API.Status.INTERNAL; import static picocli.CommandLine.Help.defaultColorScheme; import static picocli.CommandLine.Spec.Target.MIXEE; import org.apiguardian.api.API; import picocli.CommandLine.Help.Ansi; import picocli.CommandLine.Model.CommandSpec; import picocli.CommandLine.Option; import picocli.CommandLine.Spec; @API(status = INTERNAL, since = "1.14") public class AnsiColorOptionMixin { @Spec(MIXEE) CommandSpec commandSpec; // https://no-color.org // ANSI is disabled when environment variable NO_COLOR is defined (regardless of its value). private boolean disableAnsiColors = System.getenv("NO_COLOR") != null; public boolean isDisableAnsiColors() { return this.disableAnsiColors; } @Option(names = "--disable-ansi-colors", description = "Disable ANSI colors in output (not supported by all terminals).") public void setDisableAnsiColors(boolean disableAnsiColors) { if (disableAnsiColors) { this.commandSpec.commandLine().setColorScheme(defaultColorScheme(Ansi.OFF)); } this.disableAnsiColors = disableAnsiColors; } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/options/ClasspathEntriesConverter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.options; import java.io.File; import java.nio.file.Path; import java.util.List; import java.util.stream.Stream; import picocli.CommandLine; class ClasspathEntriesConverter implements CommandLine.ITypeConverter> { @Override public List convert(String value) { return Stream.of(value.split(File.pathSeparator)).map(Path::of).toList(); } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/options/ConsoleUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.options; import static org.apiguardian.api.API.Status.INTERNAL; import java.io.Console; import java.nio.charset.Charset; import org.apiguardian.api.API; /** * Collection of utilities for working with {@code java.io.Console} * and friends. * *

DISCLAIMER

* *

These utilities are intended solely for usage within the JUnit framework * itself. Any usage by external parties is not supported. * Use at your own risk! * * @since 1.9 */ @API(status = INTERNAL, since = "1.9") public class ConsoleUtils { /** * {@return the charset of the console} */ @SuppressWarnings("SystemConsoleNull") public static Charset charset() { Console console = System.console(); return console != null ? console.charset() : Charset.defaultCharset(); } private ConsoleUtils() { } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/options/Details.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.options; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.Locale; import org.apiguardian.api.API; /** * @since 1.0 */ @API(status = INTERNAL, since = "1.0") public enum Details { /** * No test plan execution details are printed. */ NONE, /** * Print summary table of counts only. */ SUMMARY, /** * Test plan execution details are rendered in a flat, line-by-line mode. */ FLAT, /** * Test plan execution details are rendered as a simple tree. */ TREE, /** * Combines {@link #TREE} and {@link #FLAT} modes. */ VERBOSE, /** * Test plan execution events are rendered as they occur in a concise format. * * @since 1.10 */ TESTFEED; /** * Return lower case {@link #name()} for easier usage in help text for * available options. */ @Override public String toString() { return name().toLowerCase(Locale.ROOT); } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/options/SelectorConverter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.options; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathResource; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectDirectory; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectFile; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectModule; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUri; import java.net.URI; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.ResourceUtils; import org.junit.platform.engine.DiscoverySelectorIdentifier; import org.junit.platform.engine.discovery.ClassSelector; import org.junit.platform.engine.discovery.ClasspathResourceSelector; import org.junit.platform.engine.discovery.DirectorySelector; import org.junit.platform.engine.discovery.DiscoverySelectors; import org.junit.platform.engine.discovery.FilePosition; import org.junit.platform.engine.discovery.FileSelector; import org.junit.platform.engine.discovery.IterationSelector; import org.junit.platform.engine.discovery.MethodSelector; import org.junit.platform.engine.discovery.ModuleSelector; import org.junit.platform.engine.discovery.PackageSelector; import org.junit.platform.engine.discovery.UniqueIdSelector; import org.junit.platform.engine.discovery.UriSelector; import picocli.CommandLine.ITypeConverter; class SelectorConverter { static class Module implements ITypeConverter { @Override public ModuleSelector convert(String value) { return selectModule(value); } } static class Uri implements ITypeConverter { @Override public UriSelector convert(String value) { return selectUri(value); } } static class File implements ITypeConverter { @Override public FileSelector convert(String value) { URI uri = URI.create(value); String path = ResourceUtils.stripQueryComponent(uri).getPath(); FilePosition filePosition = FilePosition.fromQuery(uri.getQuery()).orElse(null); return selectFile(path, filePosition); } } static class Directory implements ITypeConverter { @Override public DirectorySelector convert(String value) { return selectDirectory(value); } } @SuppressWarnings("JavaLangClash") static class Package implements ITypeConverter { @Override public PackageSelector convert(String value) { return selectPackage(value); } } @SuppressWarnings("JavaLangClash") static class Class implements ITypeConverter { @Override public ClassSelector convert(String value) { return selectClass(value); } } static class Method implements ITypeConverter { @Override public MethodSelector convert(String value) { return selectMethod(value); } } static class ClasspathResource implements ITypeConverter { @Override public ClasspathResourceSelector convert(String value) { URI uri = URI.create(value); String path = ResourceUtils.stripQueryComponent(uri).getPath(); FilePosition filePosition = FilePosition.fromQuery(uri.getQuery()).orElse(null); return selectClasspathResource(path, filePosition); } } static class Iteration implements ITypeConverter { @Override public IterationSelector convert(String value) { DiscoverySelectorIdentifier identifier = DiscoverySelectorIdentifier.create( IterationSelector.IdentifierParser.PREFIX, value); return (IterationSelector) DiscoverySelectors.parse(identifier) // .orElseThrow(() -> new PreconditionViolationException("Invalid format: Failed to parse selector")); } } static class UniqueId implements ITypeConverter { @Override public UniqueIdSelector convert(String value) { return selectUniqueId(value); } } static class Identifier implements ITypeConverter { @Override public DiscoverySelectorIdentifier convert(String value) { return DiscoverySelectorIdentifier.parse(value); } } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/options/TestConsoleOutputOptions.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.options; import static org.apiguardian.api.API.Status.INTERNAL; import java.nio.file.Path; import java.util.Locale; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.console.output.Theme; /** * @since 1.10 */ @API(status = INTERNAL, since = "1.10") public class TestConsoleOutputOptions { static final String DEFAULT_DETAILS_NAME = "tree"; public static final Details DEFAULT_DETAILS = Details.valueOf(DEFAULT_DETAILS_NAME.toUpperCase(Locale.ROOT)); static final Theme DEFAULT_THEME = Theme.valueOf(ConsoleUtils.charset()); private boolean ansiColorOutputDisabled; private @Nullable Path colorPalettePath; private boolean isSingleColorPalette; private Details details = DEFAULT_DETAILS; private Theme theme = DEFAULT_THEME; private @Nullable Path stdoutPath; private @Nullable Path stderrPath; public boolean isAnsiColorOutputDisabled() { return this.ansiColorOutputDisabled; } public void setAnsiColorOutputDisabled(boolean ansiColorOutputDisabled) { this.ansiColorOutputDisabled = ansiColorOutputDisabled; } public @Nullable Path getColorPalettePath() { return colorPalettePath; } public void setColorPalettePath(@Nullable Path colorPalettePath) { this.colorPalettePath = colorPalettePath; } public boolean isSingleColorPalette() { return isSingleColorPalette; } public void setSingleColorPalette(boolean singleColorPalette) { this.isSingleColorPalette = singleColorPalette; } public Details getDetails() { return this.details; } public void setDetails(Details details) { this.details = details; } public Theme getTheme() { return this.theme; } public void setTheme(Theme theme) { this.theme = theme; } @API(status = INTERNAL, since = "1.13") public @Nullable Path getStdoutPath() { return this.stdoutPath; } @API(status = INTERNAL, since = "1.13") public void setStdoutPath(@Nullable Path stdoutPath) { this.stdoutPath = stdoutPath; } @API(status = INTERNAL, since = "1.13") public @Nullable Path getStderrPath() { return this.stderrPath; } @API(status = INTERNAL, since = "1.13") public void setStderrPath(@Nullable Path stderrPath) { this.stderrPath = stderrPath; } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/options/TestConsoleOutputOptionsMixin.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.options; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.console.options.TestConsoleOutputOptions.DEFAULT_DETAILS; import static org.junit.platform.console.options.TestConsoleOutputOptions.DEFAULT_DETAILS_NAME; import static org.junit.platform.console.options.TestConsoleOutputOptions.DEFAULT_THEME; import java.nio.file.Path; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.console.output.Theme; import picocli.CommandLine.ArgGroup; import picocli.CommandLine.Option; @API(status = INTERNAL, since = "1.14") public class TestConsoleOutputOptionsMixin { @ArgGroup(validate = false, order = 5, heading = "%n@|bold CONSOLE OUTPUT|@%n%n") ConsoleOutputOptions consoleOutputOptions = new ConsoleOutputOptions(); public static class ConsoleOutputOptions { @Option(names = "--color-palette", paramLabel = "FILE", description = "Specify a path to a properties file to customize ANSI style of output (not supported by all terminals).") private @Nullable Path colorPalette; @Option(names = "--single-color", description = "Style test output using only text attributes, no color (not supported by all terminals).") private boolean singleColorPalette; @Option(names = "--details", paramLabel = "MODE", defaultValue = DEFAULT_DETAILS_NAME, description = "Select an output details mode for when tests are executed. " // + "Use one of: ${COMPLETION-CANDIDATES}. If 'none' is selected, " // + "then only the summary and test failures are shown. Default: ${DEFAULT-VALUE}.") private Details details = DEFAULT_DETAILS; @Option(names = "--details-theme", paramLabel = "THEME", description = "Select an output details tree theme for when tests are executed. " + "Use one of: ${COMPLETION-CANDIDATES}. Default is detected based on default character encoding.") private Theme theme = DEFAULT_THEME; @Option(names = "--redirect-stdout", paramLabel = "FILE", description = "Redirect test output to stdout to a file.") private @Nullable Path stdout; @Option(names = "--redirect-stderr", paramLabel = "FILE", description = "Redirect test output to stderr to a file.") private @Nullable Path stderr; private void applyTo(TestConsoleOutputOptions result) { result.setColorPalettePath(colorPalette); result.setSingleColorPalette(singleColorPalette); result.setDetails(details); result.setTheme(theme); result.setStdoutPath(stdout); result.setStderrPath(stderr); } } public TestConsoleOutputOptions toTestConsoleOutputOptions() { TestConsoleOutputOptions result = new TestConsoleOutputOptions(); if (this.consoleOutputOptions != null) { this.consoleOutputOptions.applyTo(result); } return result; } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/options/TestDiscoveryOptions.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.options; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.engine.discovery.ClassNameFilter.STANDARD_INCLUDE_PATTERN; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.DiscoverySelectorIdentifier; import org.junit.platform.engine.discovery.ClassSelector; import org.junit.platform.engine.discovery.ClasspathResourceSelector; import org.junit.platform.engine.discovery.DirectorySelector; import org.junit.platform.engine.discovery.DiscoverySelectors; import org.junit.platform.engine.discovery.FileSelector; import org.junit.platform.engine.discovery.IterationSelector; import org.junit.platform.engine.discovery.MethodSelector; import org.junit.platform.engine.discovery.ModuleSelector; import org.junit.platform.engine.discovery.PackageSelector; import org.junit.platform.engine.discovery.UniqueIdSelector; import org.junit.platform.engine.discovery.UriSelector; /** * @since 1.10 */ @API(status = INTERNAL, since = "1.10") public class TestDiscoveryOptions { private boolean scanClasspath; private List additionalClasspathEntries = emptyList(); private @Nullable List selectedClasspathEntries = emptyList(); private boolean scanModulepath; private List selectedModules = emptyList(); private List selectedUris = emptyList(); private List selectedFiles = emptyList(); private List selectedDirectories = emptyList(); private List selectedPackages = emptyList(); private List selectedClasses = emptyList(); private List selectedMethods = emptyList(); private List selectedClasspathResources = emptyList(); private List selectedIterations = emptyList(); private List selectedUniqueIds = emptyList(); private List selectorIdentifiers = emptyList(); private List includedClassNamePatterns = List.of(STANDARD_INCLUDE_PATTERN); private List excludedClassNamePatterns = emptyList(); private List includedPackages = emptyList(); private List excludedPackages = emptyList(); private List includedMethodNamePatterns = emptyList(); private List excludedMethodNamePatterns = emptyList(); private List includedEngines = emptyList(); private List excludedEngines = emptyList(); private List includedTagExpressions = emptyList(); private List excludedTagExpressions = emptyList(); private List configurationParametersResources = emptyList(); private Map configurationParameters = emptyMap(); public boolean isScanModulepath() { return this.scanModulepath; } public void setScanModulepath(boolean scanModulepath) { this.scanModulepath = scanModulepath; } public boolean isScanClasspath() { return this.scanClasspath; } public void setScanClasspath(boolean scanClasspath) { this.scanClasspath = scanClasspath; } public List getExistingAdditionalClasspathEntries() { return this.additionalClasspathEntries.stream().filter(Files::exists).toList(); } public List getAdditionalClasspathEntries() { return this.additionalClasspathEntries; } public void setAdditionalClasspathEntries(List additionalClasspathEntries) { this.additionalClasspathEntries = additionalClasspathEntries; } public @Nullable List getSelectedClasspathEntries() { return this.selectedClasspathEntries; } public void setSelectedClasspathEntries(@Nullable List selectedClasspathEntries) { this.selectedClasspathEntries = selectedClasspathEntries; } public List getSelectedUris() { return selectedUris; } public void setSelectedUris(List selectedUris) { this.selectedUris = selectedUris; } public List getSelectedFiles() { return selectedFiles; } public void setSelectedFiles(List selectedFiles) { this.selectedFiles = selectedFiles; } public List getSelectedDirectories() { return selectedDirectories; } public void setSelectedDirectories(List selectedDirectories) { this.selectedDirectories = selectedDirectories; } public List getSelectedModules() { return selectedModules; } public void setSelectedModules(List selectedModules) { this.selectedModules = selectedModules; } public List getSelectedPackages() { return selectedPackages; } public void setSelectedPackages(List selectedPackages) { this.selectedPackages = selectedPackages; } public List getSelectedClasses() { return selectedClasses; } public void setSelectedClasses(List selectedClasses) { this.selectedClasses = selectedClasses; } public List getSelectedMethods() { return selectedMethods; } public void setSelectedMethods(List selectedMethods) { this.selectedMethods = selectedMethods; } public List getSelectedClasspathResources() { return selectedClasspathResources; } public void setSelectedClasspathResources(List selectedClasspathResources) { this.selectedClasspathResources = selectedClasspathResources; } public List getSelectedIterations() { return selectedIterations; } public void setSelectedIterations(List selectedIterations) { this.selectedIterations = selectedIterations; } public List getSelectedUniqueIds() { return selectedUniqueIds; } public void setSelectedUniqueId(List selectedUniqueIds) { this.selectedUniqueIds = selectedUniqueIds; } public List getSelectorIdentifiers() { return selectorIdentifiers; } public void setSelectorIdentifiers(List selectorIdentifiers) { this.selectorIdentifiers = selectorIdentifiers; } public List getExplicitSelectors() { List selectors = new ArrayList<>(); selectors.addAll(getSelectedUniqueIds()); selectors.addAll(getSelectedUris()); selectors.addAll(getSelectedFiles()); selectors.addAll(getSelectedDirectories()); selectors.addAll(getSelectedModules()); selectors.addAll(getSelectedPackages()); selectors.addAll(getSelectedClasses()); selectors.addAll(getSelectedMethods()); selectors.addAll(getSelectedClasspathResources()); selectors.addAll(getSelectedIterations()); DiscoverySelectors.parseAll(getSelectorIdentifiers()).forEach(selectors::add); return selectors; } public List getIncludedClassNamePatterns() { return this.includedClassNamePatterns; } public void setIncludedClassNamePatterns(List includedClassNamePatterns) { this.includedClassNamePatterns = includedClassNamePatterns; } public List getExcludedClassNamePatterns() { return this.excludedClassNamePatterns; } public void setExcludedClassNamePatterns(List excludedClassNamePatterns) { this.excludedClassNamePatterns = excludedClassNamePatterns; } public List getIncludedPackages() { return this.includedPackages; } public void setIncludedPackages(List includedPackages) { this.includedPackages = includedPackages; } public List getExcludedPackages() { return this.excludedPackages; } public void setExcludedPackages(List excludedPackages) { this.excludedPackages = excludedPackages; } public List getIncludedMethodNamePatterns() { return includedMethodNamePatterns; } public void setIncludedMethodNamePatterns(List includedMethodNamePatterns) { this.includedMethodNamePatterns = includedMethodNamePatterns; } public List getExcludedMethodNamePatterns() { return excludedMethodNamePatterns; } public void setExcludedMethodNamePatterns(List excludedMethodNamePatterns) { this.excludedMethodNamePatterns = excludedMethodNamePatterns; } public List getIncludedEngines() { return this.includedEngines; } public void setIncludedEngines(List includedEngines) { this.includedEngines = includedEngines; } public List getExcludedEngines() { return this.excludedEngines; } public void setExcludedEngines(List excludedEngines) { this.excludedEngines = excludedEngines; } public List getIncludedTagExpressions() { return this.includedTagExpressions; } public void setIncludedTagExpressions(List includedTags) { this.includedTagExpressions = includedTags; } public List getExcludedTagExpressions() { return this.excludedTagExpressions; } public void setExcludedTagExpressions(List excludedTags) { this.excludedTagExpressions = excludedTags; } public Map getConfigurationParameters() { return this.configurationParameters; } public void setConfigurationParameters(Map configurationParameters) { this.configurationParameters = configurationParameters; } public List getConfigurationParametersResources() { return this.configurationParametersResources; } public TestDiscoveryOptions setConfigurationParametersResources(List configurationParametersResources) { this.configurationParametersResources = configurationParametersResources; return this; } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/options/TestDiscoveryOptionsMixin.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.options; import static org.apiguardian.api.API.Status.INTERNAL; import java.nio.file.Path; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.engine.DiscoverySelectorIdentifier; import org.junit.platform.engine.discovery.ClassNameFilter; import org.junit.platform.engine.discovery.ClassSelector; import org.junit.platform.engine.discovery.ClasspathResourceSelector; import org.junit.platform.engine.discovery.DirectorySelector; import org.junit.platform.engine.discovery.FileSelector; import org.junit.platform.engine.discovery.IterationSelector; import org.junit.platform.engine.discovery.MethodSelector; import org.junit.platform.engine.discovery.ModuleSelector; import org.junit.platform.engine.discovery.PackageSelector; import org.junit.platform.engine.discovery.UniqueIdSelector; import org.junit.platform.engine.discovery.UriSelector; import picocli.CommandLine; import picocli.CommandLine.ArgGroup; import picocli.CommandLine.Option; @API(status = INTERNAL, since = "1.14") public class TestDiscoveryOptionsMixin { private static final String CP_OPTION = "cp"; @ArgGroup(validate = false, order = 2, heading = "%n@|bold SELECTORS|@%n%n") SelectorOptions selectorOptions; @ArgGroup(validate = false, order = 3, heading = "%n For more information on selectors including syntax examples, see" + "%n @|underline https://docs.junit.org/${junit.docs.version}/running-tests/discovery-selectors.html|@" + "%n%n@|bold FILTERS|@%n%n") FilterOptions filterOptions; @ArgGroup(validate = false, order = 4, heading = "%n@|bold RUNTIME CONFIGURATION|@%n%n") RuntimeConfigurationOptions runtimeConfigurationOptions; public static class SelectorOptions { @Option(names = { "--scan-classpath", "--scan-class-path" }, converter = ClasspathEntriesConverter.class, paramLabel = "PATH", arity = "0..1", description = "Scan all directories on the classpath or explicit classpath roots. " // + "Without arguments, only directories on the system classpath as well as additional classpath " // + "entries supplied via -" + CP_OPTION + " (directories and JAR files) are scanned. " // + "Explicit classpath roots that are not on the classpath will be silently ignored. " // + "This option can be repeated.") private @Nullable List selectedClasspathEntries; @Option(names = "--scan-modules", description = "Scan all resolved modules for test discovery.") private boolean scanModulepath; @Option(names = { "-u", "--select-uri" }, paramLabel = "URI", arity = "1..*", converter = SelectorConverter.Uri.class, description = "Select a URI for test discovery. This option can be repeated.") private List selectedUris = new ArrayList<>(); @Option(names = { "-f", "--select-file" }, paramLabel = "FILE", arity = "1..*", converter = SelectorConverter.File.class, // description = "Select a file for test discovery. " + "The line and column numbers can be provided as URI query parameters (e.g. foo.txt?line=12&column=34). " + "This option can be repeated.") private List selectedFiles = new ArrayList<>(); @Option(names = { "-d", "--select-directory" }, paramLabel = "DIR", arity = "1..*", converter = SelectorConverter.Directory.class, description = "Select a directory for test discovery. This option can be repeated.") private List selectedDirectories = new ArrayList<>(); @Option(names = { "-o", "--select-module" }, paramLabel = "NAME", arity = "1..*", converter = SelectorConverter.Module.class, description = "Select single module for test discovery. This option can be repeated.") private List selectedModules = new ArrayList<>(); @Option(names = { "-p", "--select-package" }, paramLabel = "PKG", arity = "1..*", converter = SelectorConverter.Package.class, description = "Select a package for test discovery. This option can be repeated.") private List selectedPackages = new ArrayList<>(); @Option(names = { "-c", "--select-class" }, paramLabel = "CLASS", arity = "1..*", converter = SelectorConverter.Class.class, description = "Select a class for test discovery. This option can be repeated.") private List selectedClasses = new ArrayList<>(); @Option(names = { "-m", "--select-method" }, paramLabel = "NAME", arity = "1..*", converter = SelectorConverter.Method.class, description = "Select a method for test discovery. This option can be repeated.") private List selectedMethods = new ArrayList<>(); @Option(names = { "-r", "--select-resource" }, paramLabel = "RESOURCE", arity = "1..*", converter = SelectorConverter.ClasspathResource.class, description = "Select a classpath resource for test discovery. This option can be repeated.") private List selectedClasspathResources = new ArrayList<>(); @Option(names = { "-i", "--select-iteration" }, paramLabel = "PREFIX:VALUE[INDEX(..INDEX)?(,INDEX(..INDEX)?)*]", arity = "1..*", converter = SelectorConverter.Iteration.class, // description = "Select iterations for test discovery via a prefixed identifier and a list of indexes or index ranges " + "(e.g. method:com.acme.Foo#m()[1..2] selects the first and second iteration of the m() method in the com.acme.Foo class). " + "This option can be repeated.") private List selectedIterations = new ArrayList<>(); @Option(names = { "--select-unique-id", "--uid" }, paramLabel = "UNIQUE-ID", arity = "1..*", converter = SelectorConverter.UniqueId.class, // description = "Select a unique id for test discovery. This option can be repeated.") private List selectedUniqueIds = new ArrayList<>(); @Option(names = "--select", paramLabel = "PREFIX:VALUE", arity = "1..*", converter = SelectorConverter.Identifier.class, // description = "Select via a prefixed identifier (e.g. method:com.acme.Foo#m selects the m() method in the com.acme.Foo class). " + "This option can be repeated.") private List selectorIdentifiers = new ArrayList<>(); SelectorOptions() { } private void applyTo(TestDiscoveryOptions result) { result.setScanClasspath(this.selectedClasspathEntries != null); // flag was specified result.setScanModulepath(this.scanModulepath); result.setSelectedModules(this.selectedModules); result.setSelectedClasspathEntries(this.selectedClasspathEntries); result.setSelectedUris(this.selectedUris); result.setSelectedFiles(this.selectedFiles); result.setSelectedDirectories(this.selectedDirectories); result.setSelectedPackages(this.selectedPackages); result.setSelectedClasses(this.selectedClasses); result.setSelectedMethods(this.selectedMethods); result.setSelectedClasspathResources(this.selectedClasspathResources); result.setSelectedIterations(this.selectedIterations); result.setSelectedUniqueId(this.selectedUniqueIds); result.setSelectorIdentifiers(this.selectorIdentifiers); } } public static class FilterOptions { @Option(names = { "-n", "--include-classname" }, paramLabel = "PATTERN", defaultValue = ClassNameFilter.STANDARD_INCLUDE_PATTERN, arity = "1", description = "Provide a regular expression to include only classes whose fully qualified names match. " // + "To avoid loading classes unnecessarily, the default pattern only includes class " // + "names that begin with \"Test\" or end with \"Test\" or \"Tests\". " // + "When this option is repeated, all patterns will be combined using OR semantics. " // + "Default: ${DEFAULT-VALUE}") private List includeClassNamePatterns = new ArrayList<>(); @Option(names = { "-N", "--exclude-classname" }, paramLabel = "PATTERN", arity = "1", description = "Provide a regular expression to exclude those classes whose fully qualified names match. " // + "When this option is repeated, all patterns will be combined using OR semantics.") private List excludeClassNamePatterns = new ArrayList<>(); @Option(names = "--include-package", paramLabel = "PKG", arity = "1", description = "?Provide a package to be included in the test run. This option can be repeated.") private List includePackages = new ArrayList<>(); @Option(names = "--exclude-package", paramLabel = "PKG", arity = "1", description = "Provide a package to be excluded from the test run. This option can be repeated.") private List excludePackages = new ArrayList<>(); @Option(names = "--include-methodname", paramLabel = "PATTERN", arity = "1", description = "Provide a regular expression to include only methods whose fully qualified names without parameters match. " // + "When this option is repeated, all patterns will be combined using OR semantics.") private List includeMethodNamePatterns = new ArrayList<>(); @Option(names = "--exclude-methodname", paramLabel = "PATTERN", arity = "1", description = "Provide a regular expression to exclude those methods whose fully qualified names without parameters match. " // + "When this option is repeated, all patterns will be combined using OR semantics.") private List excludeMethodNamePatterns = new ArrayList<>(); @Option(names = { "-t", "--include-tag" }, paramLabel = "TAG", arity = "1", description = "Provide a tag or tag expression to include only tests whose tags match. " + // "When this option is repeated, all patterns will be combined using OR semantics.") private List includedTags = new ArrayList<>(); @Option(names = { "-T", "--exclude-tag" }, paramLabel = "TAG", arity = "1", description = "Provide a tag or tag expression to exclude those tests whose tags match. " + // "When this option is repeated, all patterns will be combined using OR semantics.") private List excludedTags = new ArrayList<>(); @Option(names = { "-e", "--include-engine" }, paramLabel = "ID", arity = "1", description = "Provide the ID of an engine to be included in the test run. This option can be repeated.") private List includedEngines = new ArrayList<>(); @Option(names = { "-E", "--exclude-engine" }, paramLabel = "ID", arity = "1", description = "Provide the ID of an engine to be excluded from the test run. This option can be repeated.") private List excludedEngines = new ArrayList<>(); private void applyTo(TestDiscoveryOptions result) { result.setIncludedClassNamePatterns(this.includeClassNamePatterns); result.setExcludedClassNamePatterns(this.excludeClassNamePatterns); result.setIncludedPackages(this.includePackages); result.setExcludedPackages(this.excludePackages); result.setIncludedMethodNamePatterns(new ArrayList<>(this.includeMethodNamePatterns)); result.setExcludedMethodNamePatterns(new ArrayList<>(this.excludeMethodNamePatterns)); result.setIncludedTagExpressions(this.includedTags); result.setExcludedTagExpressions(this.excludedTags); result.setIncludedEngines(this.includedEngines); result.setExcludedEngines(this.excludedEngines); } } public static class RuntimeConfigurationOptions { @Option(names = { "-" + CP_OPTION, "--classpath", "--class-path" }, converter = ClasspathEntriesConverter.class, paramLabel = "PATH", arity = "1", description = "Provide additional classpath entries " + "-- for example, for adding engines and their dependencies. This option can be repeated.") private List additionalClasspathEntries = new ArrayList<>(); // Implementation note: the @Option annotation is on a setter method to allow validation. private Map configurationParameters = new LinkedHashMap<>(); @Option(names = "--config-resource", paramLabel = "PATH", arity = "1", description = "Set configuration parameters for test discovery and execution via a classpath resource. This option can be repeated.") private List configurationParametersResources = new ArrayList<>(); @CommandLine.Spec private CommandLine.Model.CommandSpec spec; /** * Adds the specified key-value pair (or pairs) to the configuration parameters. * A {@code ParameterException} is thrown if the same key is specified multiple times * on the command line. * * @param map the key-value pairs to add * @throws CommandLine.ParameterException if the map already contains this key * @see #1308 */ @Option(names = "--config", paramLabel = "KEY=VALUE", arity = "1", description = "Set a configuration parameter for test discovery and execution. This option can be repeated.") public void setConfigurationParameters(Map map) { for (String key : map.keySet()) { String newValue = map.get(key); validateUnique(key, newValue); configurationParameters.put(key, newValue); } } private void validateUnique(String key, String newValue) { String existing = configurationParameters.get(key); if (existing != null && !existing.equals(newValue)) { throw new CommandLine.ParameterException(spec.commandLine(), "Duplicate key '%s' for values '%s' and '%s'.".formatted(key, existing, newValue)); } } private void applyTo(TestDiscoveryOptions result) { result.setAdditionalClasspathEntries(additionalClasspathEntries); result.setConfigurationParametersResources(configurationParametersResources); result.setConfigurationParameters(configurationParameters); } } public TestDiscoveryOptions toTestDiscoveryOptions() { TestDiscoveryOptions result = new TestDiscoveryOptions(); if (this.selectorOptions != null) { this.selectorOptions.applyTo(result); } if (this.filterOptions != null) { this.filterOptions.applyTo(result); } if (this.runtimeConfigurationOptions != null) { this.runtimeConfigurationOptions.applyTo(result); } return result; } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/options/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Command-line option utility classes of JUnit's console launcher. */ @NullMarked package org.junit.platform.console.options; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/output/ColorPalette.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.output; import static org.apiguardian.api.API.Status.INTERNAL; import java.io.FileReader; import java.io.IOException; import java.io.Reader; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.Arrays; import java.util.EnumMap; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.function.Function; import java.util.stream.Collectors; import org.apiguardian.api.API; /** * @since 1.9 */ @API(status = INTERNAL, since = "1.14") public class ColorPalette { public static final ColorPalette SINGLE_COLOR = new ColorPalette(singleColorPalette(), false); public static final ColorPalette DEFAULT = new ColorPalette(defaultPalette(), false); public static final ColorPalette NONE = new ColorPalette(new EnumMap<>(Style.class), true); private final Map colorsToAnsiSequences; private final boolean disableAnsiColors; // https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters private static Map defaultPalette() { Map colorsToAnsiSequences = new EnumMap<>(Style.class); colorsToAnsiSequences.put(Style.NONE, "0"); colorsToAnsiSequences.put(Style.SUCCESSFUL, "32"); colorsToAnsiSequences.put(Style.ABORTED, "33"); colorsToAnsiSequences.put(Style.FAILED, "31"); colorsToAnsiSequences.put(Style.SKIPPED, "35"); colorsToAnsiSequences.put(Style.CONTAINER, "36"); colorsToAnsiSequences.put(Style.TEST, "34"); colorsToAnsiSequences.put(Style.DYNAMIC, "35"); colorsToAnsiSequences.put(Style.REPORTED, "37"); return colorsToAnsiSequences; } private static Map singleColorPalette() { Map colorsToAnsiSequences = new EnumMap<>(Style.class); colorsToAnsiSequences.put(Style.NONE, "0"); colorsToAnsiSequences.put(Style.SUCCESSFUL, "1"); colorsToAnsiSequences.put(Style.ABORTED, "4"); colorsToAnsiSequences.put(Style.FAILED, "7"); colorsToAnsiSequences.put(Style.SKIPPED, "9"); colorsToAnsiSequences.put(Style.CONTAINER, "1"); colorsToAnsiSequences.put(Style.TEST, "0"); colorsToAnsiSequences.put(Style.DYNAMIC, "0"); colorsToAnsiSequences.put(Style.REPORTED, "2"); return colorsToAnsiSequences; } ColorPalette(Map overrides) { this(defaultPalette(), false); if (overrides.containsKey(Style.NONE)) { throw new IllegalArgumentException("Cannot override the standard style 'NONE'"); } this.colorsToAnsiSequences.putAll(overrides); } ColorPalette(Properties properties) { this(toOverrideMap(properties)); } ColorPalette(Reader reader) { this(getProperties(reader)); } public ColorPalette(Path path) { this(getProperties(path)); } private ColorPalette(Map colorsToAnsiSequences, boolean disableAnsiColors) { this.colorsToAnsiSequences = colorsToAnsiSequences; this.disableAnsiColors = disableAnsiColors; } private static Map toOverrideMap(Properties properties) { Map upperCaseProperties = properties.entrySet().stream().collect(Collectors.toMap( entry -> ((String) entry.getKey()).toUpperCase(Locale.ROOT), entry -> (String) entry.getValue())); return Arrays.stream(Style.values()).filter(style -> upperCaseProperties.containsKey(style.name())).collect( Collectors.toMap(Function.identity(), style -> upperCaseProperties.get(style.name()))); } private static Properties getProperties(Reader reader) { Properties properties = new Properties(); try { properties.load(reader); } catch (IOException e) { throw new IllegalArgumentException("Could not read color palette properties", e); } return properties; } private static Properties getProperties(Path path) { try (FileReader fileReader = new FileReader(path.toFile(), StandardCharsets.UTF_8)) { return getProperties(fileReader); } catch (IOException e) { throw new IllegalArgumentException("Could not open color palette properties file", e); } } public String paint(Style style, String text) { return this.disableAnsiColors || style == Style.NONE ? text : getAnsiFormatter(style) + text + getAnsiFormatter(Style.NONE); } private String getAnsiFormatter(Style style) { return "\u001B[%sm".formatted(this.colorsToAnsiSequences.get(style)); } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/output/DetailsPrintingListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.output; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.regex.Pattern; import org.apiguardian.api.API; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestPlan; /** * @since 1.0 */ @API(status = INTERNAL, since = "1.14") public interface DetailsPrintingListener extends TestExecutionListener { Pattern LINE_START_PATTERN = Pattern.compile("(?m)^"); void listTests(TestPlan testPlan); static String indented(String message, String indentation) { return LINE_START_PATTERN.matcher(message).replaceAll(indentation).strip(); } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/output/FlatPrintingListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.output; import static org.apiguardian.api.API.Status.INTERNAL; import java.io.PrintWriter; import org.apiguardian.api.API; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; /** * @since 1.0 */ @API(status = INTERNAL, since = "1.14") public class FlatPrintingListener implements DetailsPrintingListener { static final String INDENTATION = " "; private final PrintWriter out; private final ColorPalette colorPalette; public FlatPrintingListener(PrintWriter out, ColorPalette colorPalette) { this.out = out; this.colorPalette = colorPalette; } @Override public void testPlanExecutionStarted(TestPlan testPlan) { this.out.printf("Test execution started. Number of static tests: %d%n", testPlan.countTestIdentifiers(TestIdentifier::isTest)); } @Override public void testPlanExecutionFinished(TestPlan testPlan) { this.out.println("Test execution finished."); } @Override public void dynamicTestRegistered(TestIdentifier testIdentifier) { printlnTestDescriptor(Style.DYNAMIC, "Registered:", testIdentifier); } @Override public void executionSkipped(TestIdentifier testIdentifier, String reason) { printlnTestDescriptor(Style.SKIPPED, "Skipped:", testIdentifier); printlnMessage(Style.SKIPPED, "Reason", reason); } @Override public void executionStarted(TestIdentifier testIdentifier) { printlnTestDescriptor(Style.valueOf(testIdentifier), "Started:", testIdentifier); } @Override public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { Style style = Style.valueOf(testExecutionResult); printlnTestDescriptor(style, "Finished:", testIdentifier); testExecutionResult.getThrowable().ifPresent(t -> printlnException(style, t)); } @Override public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry entry) { printlnTestDescriptor(Style.REPORTED, "Reported:", testIdentifier); printlnMessage(Style.REPORTED, "Reported values", entry.toString()); } @Override public void fileEntryPublished(TestIdentifier testIdentifier, FileEntry file) { printlnTestDescriptor(Style.REPORTED, "Reported:", testIdentifier); printlnMessage(Style.REPORTED, "Reported file", file.toString()); } private void printlnTestDescriptor(Style style, String message, TestIdentifier testIdentifier) { println(style, "%-10s %s (%s)", message, testIdentifier.getDisplayName(), testIdentifier.getUniqueId()); } private void printlnException(Style style, Throwable throwable) { printlnMessage(style, "Exception", ExceptionUtils.readStackTrace(throwable)); } private void printlnMessage(Style style, String message, String detail) { println(style, INDENTATION + "=> " + message + ": %s", indented(detail)); } private void println(Style style, String format, Object... args) { this.out.println(colorPalette.paint(style, format.formatted(args))); } /** * Indent the given message if it is a multi-line string. * *

{@link #INDENTATION} is used to prefix the start of each new line * except the first one. * * @param message the message to indent * @return indented message */ private static String indented(String message) { return DetailsPrintingListener.indented(message, INDENTATION); } @Override public void listTests(TestPlan testPlan) { testPlan.accept(new TestPlan.Visitor() { @Override public void visit(TestIdentifier testIdentifier) { println(Style.valueOf(testIdentifier), "%s (%s)", testIdentifier.getDisplayName(), testIdentifier.getUniqueId()); } }); } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/output/Style.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.output; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.launcher.TestIdentifier; /** * @since 1.9 */ enum Style { NONE, SUCCESSFUL, ABORTED, FAILED, SKIPPED, CONTAINER, TEST, DYNAMIC, REPORTED; static Style valueOf(TestExecutionResult result) { return switch (result.getStatus()) { case SUCCESSFUL -> Style.SUCCESSFUL; case ABORTED -> Style.ABORTED; case FAILED -> Style.FAILED; }; } static Style valueOf(TestIdentifier testIdentifier) { return testIdentifier.isContainer() ? CONTAINER : TEST; } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/output/TestFeedPrintingListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.output; import static java.util.Objects.requireNonNull; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.engine.TestExecutionResult.Status.SUCCESSFUL; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.UniqueId; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; @API(status = INTERNAL, since = "1.14") public class TestFeedPrintingListener implements DetailsPrintingListener { private static final String INDENTATION = "\t"; private static final String STATUS_SEPARATOR = " :: "; private final PrintWriter out; private final ColorPalette colorPalette; private @Nullable TestPlan testPlan; public TestFeedPrintingListener(PrintWriter out, ColorPalette colorPalette) { this.out = out; this.colorPalette = colorPalette; } @Override public void testPlanExecutionStarted(TestPlan testPlan) { this.testPlan = testPlan; } @Override public void testPlanExecutionFinished(TestPlan testPlan) { this.testPlan = null; } @Override public void executionSkipped(TestIdentifier testIdentifier, String reason) { if (shouldPrint(testIdentifier)) { String msg = formatTestIdentifier(testIdentifier); String indentedReason = indented("Reason: %s".formatted(reason)); println(Style.SKIPPED, ("%s" + STATUS_SEPARATOR + "SKIPPED%n" + INDENTATION + "%s").formatted(msg, indentedReason)); } } @Override public void executionStarted(TestIdentifier testIdentifier) { if (shouldPrint(testIdentifier)) { String msg = formatTestIdentifier(testIdentifier); println(Style.NONE, ("%s" + STATUS_SEPARATOR + "STARTED").formatted(msg)); } } @Override public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { TestExecutionResult.Status status = testExecutionResult.getStatus(); if (testExecutionResult.getThrowable().isPresent()) { Style style = Style.valueOf(testExecutionResult); String msg = formatTestIdentifier(testIdentifier); Throwable throwable = testExecutionResult.getThrowable().get(); String stacktrace = indented(ExceptionUtils.readStackTrace(throwable)); println(style, ("%s" + STATUS_SEPARATOR + "%s%n" + INDENTATION + "%s").formatted(msg, status, stacktrace)); } else if (shouldPrint(testIdentifier) || testExecutionResult.getStatus() != SUCCESSFUL) { Style style = Style.valueOf(testExecutionResult); String msg = formatTestIdentifier(testIdentifier); println(style, ("%s" + STATUS_SEPARATOR + "%s").formatted(msg, status)); } } private String formatTestIdentifier(TestIdentifier testIdentifier) { return String.join(" > ", collectDisplayNames(testIdentifier.getUniqueIdObject())); } private void println(Style style, String message) { this.out.println(colorPalette.paint(style, message)); } private List collectDisplayNames(UniqueId uniqueId) { int size = uniqueId.getSegments().size(); List displayNames = new ArrayList<>(size); for (int i = 0; i < size; i++) { displayNames.add(0, requireNonNull(testPlan).getTestIdentifier(uniqueId).getDisplayName()); if (i < size - 1) { uniqueId = uniqueId.removeLastSegment(); } } return displayNames; } private static String indented(String message) { return DetailsPrintingListener.indented(message, INDENTATION); } @Override public void listTests(TestPlan testPlan) { this.testPlan = testPlan; try { testPlan.accept(new TestPlan.Visitor() { @Override public void visit(TestIdentifier testIdentifier) { if (shouldPrint(testIdentifier)) { println(Style.NONE, formatTestIdentifier(testIdentifier)); } } }); } finally { this.testPlan = null; } } private static boolean shouldPrint(TestIdentifier testIdentifier) { return testIdentifier.isTest(); } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/output/Theme.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.output; import static org.apiguardian.api.API.Status.INTERNAL; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Locale; import org.apiguardian.api.API; import org.junit.platform.engine.TestExecutionResult; /** * @since 1.0 */ @API(status = INTERNAL, since = "1.0") public enum Theme { /** * ASCII 7-bit characters form the tree branch. * *

Example test plan execution tree: *

	 * +-- engine alpha
	 * | '-- container BEGIN
	 * |   +-- test 00 [OK]
	 * |   '-- test 01 [OK]
	 * '-- engine omega
	 *   +-- container END
	 *   | +-- test 10 [OK]
	 *   | '-- test 11 [A] aborted
	 *   '-- container FINAL
	 *     +-- skipped [S] because
	 *     '-- failing [X] BäMM
	 * 
*/ ASCII(".", "| ", "+--", "'--", "[OK]", "[A]", "[X]", "[S]"), /** * Unicode (extended ASCII) characters are used to display the test execution tree. * *

Example test plan execution tree: *

	 * ├─ engine alpha ✔
	 * │  └─ container BEGIN ✔
	 * │     ├─ test 00 ✔
	 * │     └─ test 01 ✔
	 * └─ engine omega ✔
	 *    ├─ container END ✔
	 *    │  ├─ test 10 ✔
	 *    │  └─ test 11 ■ aborted
	 *    └─ container FINAL ✔
	 *       ├─ skipped ↷ because
	 *       └─ failing ✘ BäMM
	 * 
*/ UNICODE("╷", "│ ", "├─", "└─", "✔", "■", "✘", "↷"); public static Theme valueOf(Charset charset) { if (StandardCharsets.UTF_8.equals(charset)) { return UNICODE; } return ASCII; } private final String[] tiles; private final String blank; Theme(String... tiles) { this.tiles = tiles; this.blank = new String(new char[vertical().length()]).replace('\0', ' '); } public final String root() { return tiles[0]; } public final String vertical() { return tiles[1]; } public final String blank() { return blank; } public final String entry() { return tiles[2]; } public final String end() { return tiles[3]; } public final String successful() { return tiles[4]; } public final String aborted() { return tiles[5]; } public final String failed() { return tiles[6]; } public final String skipped() { return tiles[7]; } public final String status(TestExecutionResult result) { return switch (result.getStatus()) { case SUCCESSFUL -> successful(); case ABORTED -> aborted(); case FAILED -> failed(); }; } /** * Return lower case {@link #name()} for easier usage in help text for * available options. */ @Override public final String toString() { return name().toLowerCase(Locale.ROOT); } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/output/TreeNode.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.output; import java.util.Optional; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.StringUtils; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.launcher.TestIdentifier; /** * @since 1.0 */ class TreeNode { private final String caption; private final long creation; long duration; private @Nullable String reason; private @Nullable TestIdentifier identifier; private @Nullable TestExecutionResult result; final Queue reports = new ConcurrentLinkedQueue<>(); final Queue files = new ConcurrentLinkedQueue<>(); final Queue children = new ConcurrentLinkedQueue<>(); boolean visible; TreeNode(String caption) { this.caption = caption; this.creation = System.currentTimeMillis(); this.visible = false; } TreeNode(TestIdentifier identifier) { this(createCaption(identifier.getDisplayName())); this.identifier = identifier; this.visible = true; } TreeNode(TestIdentifier identifier, String reason) { this(identifier); this.reason = reason; } TreeNode addChild(TreeNode node) { children.add(node); return this; } TreeNode addReportEntry(ReportEntry reportEntry) { reports.add(reportEntry); return this; } TreeNode addFileEntry(FileEntry file) { files.add(file); return this; } TreeNode setResult(TestExecutionResult result) { this.result = result; this.duration = System.currentTimeMillis() - creation; return this; } public String caption() { return caption; } Optional reason() { return Optional.ofNullable(reason); } Optional result() { return Optional.ofNullable(result); } Optional identifier() { return Optional.ofNullable(identifier); } @SuppressWarnings("DataFlowIssue") static String createCaption(String displayName) { boolean normal = displayName.length() <= 80; String caption = normal ? displayName : displayName.substring(0, 80) + "..."; String whites = StringUtils.replaceWhitespaceCharacters(caption, " "); return StringUtils.replaceIsoControlCharacters(whites, "."); } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/output/TreePrinter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.output; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import java.io.PrintWriter; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.junit.platform.commons.util.StringUtils; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.TestExecutionResult.Status; import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; /** * @since 1.0 */ class TreePrinter { private final PrintWriter out; private final Theme theme; private final ColorPalette colorPalette; TreePrinter(PrintWriter out, Theme theme, ColorPalette colorPalette) { this.out = out; this.theme = theme; this.colorPalette = colorPalette; } void print(TreeNode node) { out.println(color(Style.CONTAINER, theme.root())); print(node, "", true); out.flush(); } private void print(TreeNode node, String indent, boolean continuous) { if (node.visible) { printVisible(node, indent, continuous); } if (node.children.isEmpty()) { return; } if (node.visible) { indent += continuous ? theme.vertical() : theme.blank(); } Iterator iterator = node.children.iterator(); while (iterator.hasNext()) { print(iterator.next(), indent, iterator.hasNext()); } } private void printVisible(TreeNode node, String indent, boolean continuous) { String bullet = continuous ? theme.entry() : theme.end(); String prefix = color(Style.CONTAINER, indent + bullet); String tabbed = color(Style.CONTAINER, indent + tab(node, continuous)); String caption = colorCaption(node); String duration = color(Style.CONTAINER, node.duration + " ms"); String icon = color(Style.SKIPPED, theme.skipped()); if (node.result().isPresent()) { TestExecutionResult result = node.result().get(); Style resultStyle = Style.valueOf(result); icon = color(resultStyle, theme.status(result)); } out.print(prefix); out.print(" "); out.print(caption); if (node.duration > 10000 && node.children.isEmpty()) { out.print(" "); out.print(duration); } boolean nodeIsBeingListed = node.duration == 0 && node.result().isEmpty() && node.reason().isEmpty(); if (!nodeIsBeingListed) { out.print(" "); out.print(icon); } node.result().ifPresent(result -> printThrowable(tabbed, result)); node.reason().ifPresent(reason -> printMessage(Style.SKIPPED, tabbed, reason)); node.reports.forEach(e -> printReportEntry(tabbed, e)); out.println(); node.files.forEach(e -> printFileEntry(tabbed, e)); } private String tab(TreeNode node, boolean continuous) { // We might be the "last" node in this level, that means // `continuous == false`, but still need to include a vertical // bar for printing stack traces, messages and reports. // See https://github.com/junit-team/junit-framework/issues/1531 if (node.children.size() > 0) { return theme.blank() + theme.vertical(); } return (continuous ? theme.vertical() : theme.blank()) + theme.blank(); } private String colorCaption(TreeNode node) { String caption = node.caption(); if (node.result().isPresent()) { TestExecutionResult result = node.result().get(); Style resultStyle = Style.valueOf(result); if (result.getStatus() != Status.SUCCESSFUL) { return color(resultStyle, caption); } } if (node.reason().isPresent()) { return color(Style.SKIPPED, caption); } Style style = node.identifier().map(Style::valueOf).orElse(Style.NONE); return color(style, caption); } private void printThrowable(String indent, TestExecutionResult result) { if (result.getThrowable().isEmpty()) { return; } Throwable throwable = result.getThrowable().get(); String message = throwable.getMessage(); if (StringUtils.isBlank(message)) { message = throwable.toString(); } printMessage(Style.FAILED, indent, message); } private void printReportEntry(String indent, ReportEntry reportEntry) { out.println(); out.print(indent); out.print(reportEntry.getTimestamp()); Set> entries = reportEntry.getKeyValuePairs().entrySet(); if (entries.size() == 1) { printReportEntry(" ", getOnlyElement(entries)); return; } for (Map.Entry entry : entries) { out.println(); printReportEntry(indent + theme.blank(), entry); } } private void printReportEntry(String indent, Map.Entry mapEntry) { out.print(indent); out.print(color(Style.ABORTED, mapEntry.getKey())); out.print(" = `"); out.print(color(Style.SUCCESSFUL, mapEntry.getValue())); out.print("`"); } private void printFileEntry(String indent, FileEntry fileEntry) { out.print(indent); out.print(fileEntry.getTimestamp()); out.print(" "); out.print(color(Style.SUCCESSFUL, fileEntry.getPath().toUri().toString())); out.println(); } private void printMessage(Style style, String indent, String message) { String[] lines = message.split("\\R"); out.print(" "); out.print(color(style, lines[0])); if (lines.length > 1) { for (int i = 1; i < lines.length; i++) { out.println(); out.print(indent); if (StringUtils.isNotBlank(lines[i])) { String extra = theme.blank(); out.print(color(style, extra + lines[i])); } } } } private String color(Style style, String text) { return colorPalette.paint(style, text); } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/output/TreePrintingListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.output; import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNullElse; import static org.apiguardian.api.API.Status.INTERNAL; import java.io.PrintWriter; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; /** * @since 1.0 */ @API(status = INTERNAL, since = "1.14") public class TreePrintingListener implements DetailsPrintingListener { private final Map nodesByUniqueId = new ConcurrentHashMap<>(); private final TreePrinter treePrinter; private @Nullable TreeNode root; public TreePrintingListener(PrintWriter out, ColorPalette colorPalette, Theme theme) { this.treePrinter = new TreePrinter(out, theme, colorPalette); } private void addNode(TestIdentifier testIdentifier, TreeNode node) { nodesByUniqueId.put(testIdentifier.getUniqueIdObject(), node); TreeNode parent = testIdentifier.getParentIdObject().map(nodesByUniqueId::get).orElse(null); requireNonNullElse(parent, root).addChild(node); } private TreeNode getNode(TestIdentifier testIdentifier) { return requireNonNull(nodesByUniqueId.get(testIdentifier.getUniqueIdObject())); } @Override public void testPlanExecutionStarted(TestPlan testPlan) { root = new TreeNode(testPlan.toString()); } @Override public void testPlanExecutionFinished(TestPlan testPlan) { treePrinter.print(requireNonNull(root)); } @Override public void executionStarted(TestIdentifier testIdentifier) { addNode(testIdentifier, new TreeNode(testIdentifier)); } @Override public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { getNode(testIdentifier).setResult(testExecutionResult); } @Override public void executionSkipped(TestIdentifier testIdentifier, String reason) { addNode(testIdentifier, new TreeNode(testIdentifier, reason)); } @Override public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry entry) { getNode(testIdentifier).addReportEntry(entry); } @Override public void fileEntryPublished(TestIdentifier testIdentifier, FileEntry file) { getNode(testIdentifier).addFileEntry(file); } @Override public void listTests(TestPlan testPlan) { root = new TreeNode(testPlan.toString()); testPlan.accept(new TestPlan.Visitor() { @Override public void visit(TestIdentifier testIdentifier) { addNode(testIdentifier, new TreeNode(testIdentifier)); } }); treePrinter.print(root); } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/output/VerboseTreePrintingListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.output; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.commons.util.ExceptionUtils.readStackTrace; import static org.junit.platform.console.output.Style.NONE; import java.io.PrintWriter; import java.util.ArrayDeque; import java.util.Deque; import org.apiguardian.api.API; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; /** * @since 1.0 */ @API(status = INTERNAL, since = "1.14") public class VerboseTreePrintingListener implements DetailsPrintingListener { private final PrintWriter out; private final Theme theme; private final ColorPalette colorPalette; private final Deque frames; private final String[] verticals; private long executionStartedMillis; public VerboseTreePrintingListener(PrintWriter out, ColorPalette colorPalette, int maxContainerNestingLevel, Theme theme) { this.out = out; this.colorPalette = colorPalette; this.theme = theme; // create frame stack and push initial root frame this.frames = new ArrayDeque<>(); this.frames.push(0L); // create and populate vertical indentation lookup table this.verticals = new String[Math.max(10, maxContainerNestingLevel) + 1]; this.verticals[0] = ""; // no frame this.verticals[1] = ""; // synthetic root "/" level this.verticals[2] = ""; // "engine" level for (int i = 3; i < verticals.length; i++) { verticals[i] = verticals[i - 1] + theme.vertical(); } } @Override public void testPlanExecutionStarted(TestPlan testPlan) { frames.push(System.currentTimeMillis()); String prefix = "Test plan execution started. Number of static tests: "; printNumberOfTests(testPlan, prefix); printf(Style.CONTAINER, "%s%n", theme.root()); } @Override public void testPlanExecutionFinished(TestPlan testPlan) { frames.pop(); printNumberOfTests(testPlan, "Test plan execution finished. Number of all tests: "); } private void printNumberOfTests(TestPlan testPlan, String prefix) { long tests = testPlan.countTestIdentifiers(TestIdentifier::isTest); printf(NONE, "%s", prefix); printf(Style.TEST, "%d%n", tests); } @Override public void executionStarted(TestIdentifier testIdentifier) { this.executionStartedMillis = System.currentTimeMillis(); if (testIdentifier.isContainer()) { printVerticals(theme.entry()); printf(Style.CONTAINER, " %s", testIdentifier.getDisplayName()); printf(NONE, "%n"); frames.push(System.currentTimeMillis()); } if (testIdentifier.isContainer()) { return; } printVerticals(theme.entry()); printf(Style.valueOf(testIdentifier), " %s%n", testIdentifier.getDisplayName()); printDetails(testIdentifier); } @Override public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { testExecutionResult.getThrowable().ifPresent(t -> printDetail(Style.FAILED, "caught", readStackTrace(t))); if (testIdentifier.isContainer()) { Long creationMillis = frames.pop(); printVerticals(theme.end()); printf(Style.CONTAINER, " %s", testIdentifier.getDisplayName()); printf(NONE, " finished after %d ms.%n", System.currentTimeMillis() - creationMillis); return; } printDetail(NONE, "duration", "%d ms%n", System.currentTimeMillis() - executionStartedMillis); String status = theme.status(testExecutionResult) + " " + testExecutionResult.getStatus(); printDetail(Style.valueOf(testExecutionResult), "status", "%s%n", status); } @Override public void executionSkipped(TestIdentifier testIdentifier, String reason) { printVerticals(theme.entry()); printf(Style.valueOf(testIdentifier), " %s%n", testIdentifier.getDisplayName()); printDetails(testIdentifier); printDetail(Style.SKIPPED, "reason", reason); printDetail(Style.SKIPPED, "status", theme.skipped() + " SKIPPED"); } @Override public void dynamicTestRegistered(TestIdentifier testIdentifier) { printVerticals(theme.entry()); printf(Style.DYNAMIC, " %s", testIdentifier.getDisplayName()); printf(NONE, "%s%n", " dynamically registered"); } @Override public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry entry) { printDetail(Style.REPORTED, "reports", entry.toString()); } @Override public void fileEntryPublished(TestIdentifier testIdentifier, FileEntry file) { printDetail(Style.REPORTED, "reports", file.toString()); } /** * Print static information about the test identifier. */ private void printDetails(TestIdentifier testIdentifier) { printDetail(NONE, "tags", "%s%n", testIdentifier.getTags()); printDetail(NONE, "uniqueId", "%s%n", testIdentifier.getUniqueId()); printDetail(NONE, "parent", "%s%n", testIdentifier.getParentId().orElse("[]")); testIdentifier.getSource().ifPresent(source -> printDetail(NONE, "source", "%s%n", source)); } private String verticals() { return verticals(frames.size()); } private String verticals(int index) { return verticals[Math.min(index, verticals.length - 1)]; } private void printVerticals(String tile) { printf(NONE, verticals()); printf(NONE, tile); } private void printf(Style style, String message, Object... args) { out.printf(colorPalette.paint(style, message), args); out.flush(); } /** * Print single detail with a potential multi-line message. */ private void printDetail(Style style, String detail, String format, Object... args) { // print initial verticals - expecting to be at start of the line String verticals = verticals(frames.size() + 1); printf(NONE, verticals); String detailFormat = "%9s"; // omit detail string if it's empty if (!detail.isEmpty()) { printf(NONE, "%s", (detailFormat + ": ").formatted(detail)); } // trivial case: at least one arg is given? Let printf do the entire work if (args.length > 0) { printf(style, format, args); return; } // still here? Split format into separate lines and indent them from the second line on String[] lines = format.split("\\R"); printf(style, "%s", lines[0]); if (lines.length > 1) { String delimiter = System.lineSeparator() + verticals + (detailFormat + " ").formatted(""); for (int i = 1; i < lines.length; i++) { printf(NONE, "%s", delimiter); printf(style, "%s", lines[i]); } } printf(NONE, "%n"); } @Override public void listTests(TestPlan testPlan) { frames.push(0L); testPlan.accept(new TestPlan.Visitor() { @Override public void preVisitContainer(TestIdentifier testIdentifier) { if (!testPlan.getChildren(testIdentifier).isEmpty()) { printVerticals(theme.entry()); printf(Style.CONTAINER, " %s", testIdentifier.getDisplayName()); printf(NONE, "%n"); frames.push(0L); } } @Override public void visit(TestIdentifier testIdentifier) { if (testPlan.getChildren(testIdentifier).isEmpty()) { printVerticals(theme.entry()); printf(Style.valueOf(testIdentifier), " %s%n", testIdentifier.getDisplayName()); printDetails(testIdentifier); } } @Override public void postVisitContainer(TestIdentifier testIdentifier) { if (!testPlan.getChildren(testIdentifier).isEmpty()) { frames.pop(); printVerticals(theme.end()); printf(Style.CONTAINER, " %s%n", testIdentifier.getDisplayName()); } } }); frames.pop(); } } ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/output/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Output printing utility classes of JUnit's console launcher. */ @NullMarked package org.junit.platform.console.output; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-console/src/main/java/org/junit/platform/console/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Support for launching the JUnit Platform from the console. */ @NullMarked package org.junit.platform.console; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-console/src/main/resources/META-INF/services/java.util.spi.ToolProvider ================================================ org.junit.platform.console.ConsoleLauncherToolProvider ================================================ FILE: junit-platform-console/src/test/README.md ================================================ For compatibility with the Eclipse IDE, the test for this module are in the `platform-tests` project. ================================================ FILE: junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts ================================================ import junitbuild.extensions.withArchiveOperations import junitbuild.java.WriteArtifactsFile plugins { id("junitbuild.java-library-conventions") id("junitbuild.shadow-conventions") } description = "JUnit Platform Console Standalone" dependencies { shadowed(projects.junitPlatformReporting) shadowed(projects.junitPlatformConsole) shadowed(projects.junitPlatformSuiteEngine) shadowed(projects.junitJupiterEngine) shadowed(projects.junitJupiterParams) shadowed(projects.junitVintageEngine) shadowed(libs.apiguardian) { because("downstream projects need it to avoid compiler warnings") } osgiVerification(libs.openTestReporting.tooling.spi) } backwardCompatibilityChecks { enabled = false // already checked by individual projects } tasks { jar { manifest { attributes("Automatic-Module-Name" to "org.junit.platform.console.standalone") attributes("Main-Class" to "org.junit.platform.console.ConsoleLauncher") } } val shadowedArtifactsFile by registering(WriteArtifactsFile::class) { from(configurations.shadowedClasspath) outputFile = layout.buildDirectory.file("shadowed-artifacts") } val extractThirdPartyLicenses by registering(Sync::class) { from(withArchiveOperations { ops -> configurations.shadowedClasspath.flatMap { it.elements }.map { it.map(ops::zipTree) } }) into(layout.buildDirectory.dir("thirdPartyLicenses")) include("LICENSE.txt") include("LICENSE-junit.txt") include("META-INF/LICENSE-*") exclude("META-INF/LICENSE-notice.md") eachFile { val fileName = relativePath.lastName relativePath = RelativePath(true, when (fileName) { "LICENSE.txt" -> "LICENSE-hamcrest" "LICENSE-junit.txt" -> "LICENSE-junit4" else -> fileName }) } includeEmptyDirs = false } shadowJar { // https://github.com/junit-team/junit-framework/issues/2557 // exclude compiled module declarations from any source (e.g. /*, /META-INF/versions/N/*) exclude("**/module-info.class") // https://github.com/junit-team/junit-framework/issues/761 // prevent duplicates, add 3rd-party licenses explicitly exclude("**/COPYRIGHT*") exclude("META-INF/LICENSE*") exclude("LICENSE*.txt") // JUnit 4 and Hamcrest from(extractThirdPartyLicenses) { into("META-INF") } from(shadowedArtifactsFile) { into("META-INF") } bundle { val importAPIGuardian: String by extra val importJSpecify: String by extra bnd(""" # Customize the imports because this is an aggregate jar Import-Package: \ $importAPIGuardian,\ $importJSpecify,\ kotlin.*;resolution:="optional",\ kotlinx.*;resolution:="optional",\ * # Disable the APIGuardian plugin since everything was already # processed, again because this is an aggregate jar -export-apiguardian: """) } duplicatesStrategy = DuplicatesStrategy.INCLUDE mergeServiceFiles() failOnDuplicateEntries = true manifest.apply { attributes(mapOf( "Specification-Title" to project.name, "Implementation-Title" to project.name, // Generate test engine version information in single shared manifest file. // Pattern of key and value: `"Engine-Version-{YourTestEngine#getId()}": "47.11"` "Engine-Version-junit-jupiter" to project.version, "Engine-Version-junit-vintage" to project.version, )) } } } ================================================ FILE: junit-platform-engine/junit-platform-engine.gradle.kts ================================================ plugins { id("junitbuild.java-library-conventions") `java-test-fixtures` } description = "JUnit Platform Engine API" dependencies { api(platform(projects.junitBom)) api(libs.opentest4j) api(projects.junitPlatformCommons) compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) testImplementation(libs.assertj) osgiVerification(projects.junitJupiterEngine) osgiVerification(projects.junitPlatformLauncher) } javadocConventions { addExtraModuleReferences(projects.junitPlatformLauncher) } ================================================ FILE: junit-platform-engine/src/main/java/module-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Public API for test engines. * *

Provides the {@link org.junit.platform.engine.TestEngine} interface, * test discovery, and execution reporting support. * * @since 1.0 */ module org.junit.platform.engine { requires static transitive org.apiguardian.api; requires static transitive org.jspecify; requires transitive org.junit.platform.commons; requires transitive org.opentest4j; exports org.junit.platform.engine; exports org.junit.platform.engine.discovery; exports org.junit.platform.engine.reporting; // exports org.junit.platform.engine.support; empty package exports org.junit.platform.engine.support.config; exports org.junit.platform.engine.support.descriptor; exports org.junit.platform.engine.support.discovery; exports org.junit.platform.engine.support.hierarchical; exports org.junit.platform.engine.support.store; uses org.junit.platform.engine.discovery.DiscoverySelectorIdentifierParser; provides org.junit.platform.engine.discovery.DiscoverySelectorIdentifierParser with org.junit.platform.engine.discovery.ClassSelector.IdentifierParser, org.junit.platform.engine.discovery.ClasspathResourceSelector.IdentifierParser, org.junit.platform.engine.discovery.ClasspathRootSelector.IdentifierParser, org.junit.platform.engine.discovery.DirectorySelector.IdentifierParser, org.junit.platform.engine.discovery.FileSelector.IdentifierParser, org.junit.platform.engine.discovery.IterationSelector.IdentifierParser, org.junit.platform.engine.discovery.MethodSelector.IdentifierParser, org.junit.platform.engine.discovery.ModuleSelector.IdentifierParser, org.junit.platform.engine.discovery.NestedClassSelector.IdentifierParser, org.junit.platform.engine.discovery.NestedMethodSelector.IdentifierParser, org.junit.platform.engine.discovery.PackageSelector.IdentifierParser, org.junit.platform.engine.discovery.UniqueIdSelector.IdentifierParser, org.junit.platform.engine.discovery.UriSelector.IdentifierParser; } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/CancellationToken.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import org.apiguardian.api.API; /** * Token that should be checked to determine whether an operation was requested * to be cancelled. * *

For example, this is used by the * {@link org.junit.platform.launcher.Launcher} and * {@link org.junit.platform.engine.TestEngine} implementations to determine * whether the current test execution should be cancelled. * *

This interface is not intended to be implemented by clients. * * @since 6.0 * @see org.junit.platform.launcher.core.LauncherExecutionRequestBuilder#cancellationToken(CancellationToken) * @see org.junit.platform.launcher.LauncherExecutionRequest#getCancellationToken() * @see ExecutionRequest#getCancellationToken() */ @API(status = EXPERIMENTAL, since = "6.0") public sealed interface CancellationToken permits RegularCancellationToken, DisabledCancellationToken { /** * Create a new, uncancelled cancellation token. */ static CancellationToken create() { return new RegularCancellationToken(); } /** * Get a new cancellation token that cannot be cancelled. * *

This is only useful for cases when a cancellation token is required * but is not supported or irrelevant, for example, in tests. */ static CancellationToken disabled() { return DisabledCancellationToken.INSTANCE; } /** * {@return whether cancellation has been requested} * *

Once this method returns {@code true}, it will never return * {@code false} in a subsequent call. */ boolean isCancellationRequested(); /** * Request cancellation. * *

This will call subsequent calls to {@link #isCancellationRequested()} * to return {@code true}. */ void cancel(); } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/CompositeFilter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine; import static java.util.stream.Collectors.joining; import static org.junit.platform.engine.FilterResult.included; import java.util.ArrayList; import java.util.Collection; import java.util.function.Predicate; import org.junit.platform.commons.util.Preconditions; /** * Combines a collection of {@link Filter Filters} into a new filter that will * include elements if and only if all of the filters in the specified collection * include it. * * @since 1.0 */ class CompositeFilter implements Filter { @SuppressWarnings("rawtypes") private static final Filter ALWAYS_INCLUDED_FILTER = new Filter() { @Override public FilterResult apply(Object obj) { return ALWAYS_INCLUDED_RESULT; } @Override public Predicate toPredicate() { return obj -> true; } }; private static final FilterResult ALWAYS_INCLUDED_RESULT = included("Always included"); private static final FilterResult INCLUDED_BY_ALL_FILTERS = included("Element was included by all filters."); @SuppressWarnings("unchecked") static Filter alwaysIncluded() { return ALWAYS_INCLUDED_FILTER; } private final Collection> filters; CompositeFilter(Collection> filters) { this.filters = new ArrayList<>(Preconditions.notEmpty(filters, "filters must not be empty")); } @Override public FilterResult apply(T element) { // @formatter:off return filters.stream() .map(filter -> filter.apply(element)) .filter(FilterResult::excluded) .findFirst() .orElse(INCLUDED_BY_ALL_FILTERS); // @formatter:on } @Override public Predicate toPredicate() { // @formatter:off return filters.stream() .map(Filter::toPredicate) .reduce(Predicate::and) .get(); // it's safe to call get() here because the constructor ensures filters is not empty // @formatter:on } @Override public String toString() { // @formatter:off return filters.stream() .map(Object::toString) .map("(%s)"::formatted) .collect(joining(" and ")); // @formatter:on } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/CompositeTestDescriptorVisitor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine; import java.util.Arrays; import org.junit.platform.commons.util.Preconditions; /** * @since 1.13 */ final class CompositeTestDescriptorVisitor implements TestDescriptor.Visitor { private final TestDescriptor.Visitor[] visitors; static TestDescriptor.Visitor from(TestDescriptor.Visitor... visitors) { Preconditions.notNull(visitors, "visitors must not be null"); Preconditions.notEmpty(visitors, "visitors must not be empty"); Preconditions.containsNoNullElements(visitors, "visitors must not contain any null elements"); return visitors.length == 1 ? visitors[0] : new CompositeTestDescriptorVisitor(visitors); } private CompositeTestDescriptorVisitor(TestDescriptor.Visitor[] visitors) { this.visitors = Arrays.copyOf(visitors, visitors.length); } @Override public void visit(TestDescriptor descriptor) { for (TestDescriptor.Visitor visitor : visitors) { visitor.visit(descriptor); } } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/ConfigurationParameters.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine; import static org.apiguardian.api.API.Status.STABLE; import java.util.Optional; import java.util.Set; import java.util.function.Function; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.Preconditions; /** * Configuration parameters that {@link TestEngine TestEngines} may use to * influence test discovery and execution. * *

For example, the JUnit Jupiter engine uses a configuration parameter to * enable IDEs and build tools to deactivate conditional test execution. * *

Configuration parameters are also made available to implementations of the * {@link org.junit.platform.launcher.TestExecutionListener} API via the * {@link org.junit.platform.launcher.TestPlan}. * * @since 1.0 * @see TestEngine * @see EngineDiscoveryRequest * @see ExecutionRequest */ @API(status = STABLE, since = "1.0") public interface ConfigurationParameters { /** * Name of the JUnit Platform configuration file: {@value}. * *

If a properties file with this name is present in the root of the * classpath, it will be used as a source for configuration * parameters. If multiple files are present, only the first one * detected in the classpath will be used. * * @see java.util.Properties */ String CONFIG_FILE_NAME = "junit-platform.properties"; /** * Get the configuration parameter stored under the specified {@code key}. * *

If no such key is present in this {@code ConfigurationParameters}, * an attempt will be made to look up the value as a JVM system property. * If no such system property exists, an attempt will be made to look up * the value in the {@linkplain #CONFIG_FILE_NAME JUnit Platform properties * file}. * * @param key the key to look up; never {@code null} or blank * @return an {@code Optional} containing the value; never {@code null} * but potentially empty * * @see #getBoolean(String) * @see System#getProperty(String) * @see #CONFIG_FILE_NAME */ Optional get(String key); /** * Get the boolean configuration parameter stored under the specified * {@code key}. * *

If no such key is present in this {@code ConfigurationParameters}, * an attempt will be made to look up the value as a JVM system property. * If no such system property exists, an attempt will be made to look up * the value in the {@linkplain #CONFIG_FILE_NAME JUnit Platform properties * file}. * * @param key the key to look up; never {@code null} or blank * @return an {@code Optional} containing the value; never {@code null} * but potentially empty * * @see #get(String) * @see Boolean#parseBoolean(String) * @see System#getProperty(String) * @see #CONFIG_FILE_NAME */ Optional getBoolean(String key); /** * Get and transform the configuration parameter stored under the specified * {@code key} using the specified {@code transformer}. * *

If no such key is present in this {@code ConfigurationParameters}, * an attempt will be made to look up the value as a JVM system property. * If no such system property exists, an attempt will be made to look up * the value in the {@linkplain #CONFIG_FILE_NAME JUnit Platform properties * file}. * *

In case the transformer throws an exception, it will be wrapped in a * {@link JUnitException} with a helpful message. * * @param key the key to look up; never {@code null} or blank * @param transformer the transformer to apply in case a value is found; * never {@code null} * @return an {@code Optional} containing the value; never {@code null} * but potentially empty * * @since 1.3 * @see #getBoolean(String) * @see System#getProperty(String) * @see #CONFIG_FILE_NAME */ @API(status = STABLE, since = "1.3") default Optional get(String key, Function transformer) { Preconditions.notNull(transformer, "transformer must not be null"); return get(key).map(input -> { try { return transformer.apply(input); } catch (Exception ex) { String message = "Failed to transform configuration parameter with key '%s' and initial value '%s'".formatted( key, input); throw new JUnitException(message, ex); } }); } /** * Get the keys of all configuration parameters stored in this * {@code ConfigurationParameters}. * * @return the set of keys contained in this {@code ConfigurationParameters} */ @API(status = STABLE, since = "1.9") Set keySet(); } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/DefaultDiscoveryIssue.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine; import java.util.Objects; import java.util.Optional; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.ToStringBuilder; /** * @since 1.13 */ final class DefaultDiscoveryIssue implements DiscoveryIssue { private final Severity severity; private final String message; private final @Nullable TestSource source; private final @Nullable Throwable cause; DefaultDiscoveryIssue(Builder builder) { this.severity = builder.severity; this.message = builder.message; this.source = builder.source; this.cause = builder.cause; } @Override public Severity severity() { return this.severity; } @Override public String message() { return this.message; } @Override public Optional source() { return Optional.ofNullable(this.source); } @Override public Optional cause() { return Optional.ofNullable(this.cause); } @Override public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } DefaultDiscoveryIssue that = (DefaultDiscoveryIssue) o; return this.severity == that.severity // && Objects.equals(this.message, that.message) // && Objects.equals(this.source, that.source) // && Objects.equals(this.cause, that.cause); } @Override public int hashCode() { return Objects.hash(this.severity, this.message, this.source, this.cause); } @Override public String toString() { ToStringBuilder builder = new ToStringBuilder(DiscoveryIssue.class.getSimpleName()) // .append("severity", this.severity) // .append("message", this.message); if (this.source != null) { builder.append("source", this.source); } if (this.cause != null) { builder.append("cause", this.cause); } return builder.toString(); } static class Builder implements DiscoveryIssue.Builder { private final Severity severity; private final String message; @Nullable private TestSource source; @Nullable public Throwable cause; Builder(Severity severity, String message) { this.severity = severity; this.message = message; } @Override public Builder source(@Nullable TestSource source) { this.source = source; return this; } @Override public Builder cause(@Nullable Throwable cause) { this.cause = cause; return this; } @Override public DiscoveryIssue build() { return new DefaultDiscoveryIssue(this); } } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/DisabledCancellationToken.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine; /** * @since 6.0 */ final class DisabledCancellationToken implements CancellationToken { static final DisabledCancellationToken INSTANCE = new DisabledCancellationToken(); private DisabledCancellationToken() { } @Override public boolean isCancellationRequested() { return false; } @Override public void cancel() { } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoveryFilter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; /** * A {@link DiscoveryFilter} is applied during test discovery to determine if * a given container or test should be included in the test plan. * *

{@link TestEngine TestEngines} should apply {@code DiscoveryFilters} * during the test discovery phase. * * @since 1.0 * @see EngineDiscoveryRequest * @see TestEngine */ @API(status = STABLE, since = "1.0") public interface DiscoveryFilter extends Filter { } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoveryIssue.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import java.util.Optional; import java.util.function.UnaryOperator; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; /** * {@code DiscoveryIssue} represents an issue that was encountered during test * discovery by a {@link TestEngine}. * * @since 1.13 */ @API(status = EXPERIMENTAL, since = "6.0") public interface DiscoveryIssue { /** * Create a new {@code DiscoveryIssue} with the supplied {@link Severity} and * message. * * @param severity the severity of the issue; never {@code null} * @param message the message of the issue; never blank * @see #builder(Severity, String) */ static DiscoveryIssue create(Severity severity, String message) { return builder(severity, message).build(); } /** * Create a new {@link Builder} for creating a {@code DiscoveryIssue} with * the supplied {@link Severity} and message. * * @param severity the severity of the issue; never {@code null} * @param message the message of the issue; never blank * @see Builder * @see #create(Severity, String) */ static Builder builder(Severity severity, String message) { Preconditions.notNull(severity, "severity must not be null"); Preconditions.notBlank(message, "message must not be blank"); return new DefaultDiscoveryIssue.Builder(severity, message); } /** * {@return the severity of this issue} */ Severity severity(); /** * {@return the message of this issue} */ String message(); /** * {@return the source of this issue} */ Optional source(); /** * {@return the cause of this issue} */ Optional cause(); /** * Create a copy of this issue with the modified message produced by the * supplied operator. */ default DiscoveryIssue withMessage(UnaryOperator messageModifier) { String oldMessage = message(); String newMessage = messageModifier.apply(oldMessage); if (oldMessage.equals(newMessage)) { return this; } return DiscoveryIssue.builder(severity(), newMessage) // .source(source()) // .cause(cause()) // .build(); } /** * The severity of a {@code DiscoveryIssue}. */ enum Severity { /** * Indicates that the engine encountered something that could be * potentially problematic, but could also happen due to a valid setup * or configuration. */ INFO, /** * Indicates that the engine encountered something that is problematic * and might lead to unexpected behavior or will be removed or changed * in a future release. */ WARNING, /** * Indicates that the engine encountered something that is definitely * problematic and will lead to unexpected behavior. */ ERROR } /** * Builder for creating a {@code DiscoveryIssue}. */ interface Builder { /** * Set the {@link TestSource} for the {@code DiscoveryIssue}. * * @param source the {@link TestSource} for the {@code DiscoveryIssue}; * never {@code null} but potentially empty */ default Builder source(Optional source) { source.ifPresent(this::source); return this; } /** * Set the {@link TestSource} for the {@code DiscoveryIssue}. * * @param source the {@link TestSource} for the {@code DiscoveryIssue}; * may be {@code null} */ Builder source(@Nullable TestSource source); /** * Set the {@link Throwable} that caused the {@code DiscoveryIssue}. * * @param cause the {@link Throwable} that caused the * {@code DiscoveryIssue}; never {@code null} but potentially empty */ default Builder cause(Optional cause) { cause.ifPresent(this::cause); return this; } /** * Set the {@link Throwable} that caused the {@code DiscoveryIssue}. * * @param cause the {@link Throwable} that caused the * {@code DiscoveryIssue}; may be {@code null} */ Builder cause(@Nullable Throwable cause); /** * Build the {@code DiscoveryIssue}. */ DiscoveryIssue build(); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoverySelector.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.util.Optional; import org.apiguardian.api.API; import org.junit.platform.engine.discovery.DiscoverySelectorIdentifierParser; /** * A selector defines what a {@link TestEngine} can use to discover tests * — for example, the name of a Java class, the path to a file or * directory, etc. * * @since 1.0 * @see EngineDiscoveryRequest * @see org.junit.platform.engine.discovery.DiscoverySelectors */ @API(status = STABLE, since = "1.0") public interface DiscoverySelector { /** * Return the {@linkplain DiscoverySelectorIdentifier identifier} of this * selector. * *

The returned identifier must be parsable by a corresponding * {@link DiscoverySelectorIdentifierParser}. * *

The default implementation returns {@link Optional#empty()}. Can be * overridden by concrete implementations. * * @return an {@link Optional} containing the identifier of this selector; * never {@code null} but potentially empty if the selector does not support * identifiers * @since 1.11 */ @API(status = MAINTAINED, since = "1.13.3") default Optional toIdentifier() { return Optional.empty(); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoverySelectorIdentifier.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine; import static org.apiguardian.api.API.Status.MAINTAINED; import java.util.Objects; import org.apiguardian.api.API; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.StringUtils; /** * Identifier for a {@link DiscoverySelector} with a specific prefix. * *

The {@linkplain #toString() string representation} of an identifier is * intended to be human-readable and is formatted as {@code prefix:value}. * * @since 1.11 * @see org.junit.platform.engine.discovery.DiscoverySelectors#parse(String) * @see org.junit.platform.engine.discovery.DiscoverySelectorIdentifierParser */ @API(status = MAINTAINED, since = "1.13.3") public final class DiscoverySelectorIdentifier { private final String prefix; private final String value; /** * Create a new {@code DiscoverySelectorIdentifier} with the supplied prefix and * value. * * @param prefix the prefix; never {@code null} or blank * @param value the value; never {@code null} or blank */ public static DiscoverySelectorIdentifier create(String prefix, String value) { return new DiscoverySelectorIdentifier(prefix, value); } /** * Parse the supplied string representation of a * {@link DiscoverySelectorIdentifier} in the format {@code prefix:value}. * * @param string the string representation of a {@code DiscoverySelectorIdentifier} * @return the parsed {@code DiscoverySelectorIdentifier} * @throws PreconditionViolationException if the supplied string does not * conform to the expected format */ public static DiscoverySelectorIdentifier parse(String string) { return StringUtils.splitIntoTwo(':', string).mapTwo( // () -> new PreconditionViolationException("Identifier string must be 'prefix:value', but was " + string), DiscoverySelectorIdentifier::new // ); } private DiscoverySelectorIdentifier(String prefix, String value) { this.prefix = Preconditions.notBlank(prefix, "prefix must not be blank"); this.value = Preconditions.notBlank(value, "value must not be blank"); } /** * Get the prefix of this identifier. * * @return the prefix; never {@code null} or blank */ public String getPrefix() { return this.prefix; } /** * Get the value of this identifier. * * @return the value; never {@code null} or blank */ public String getValue() { return this.value; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } DiscoverySelectorIdentifier that = (DiscoverySelectorIdentifier) o; return Objects.equals(this.prefix, that.prefix) && Objects.equals(this.value, that.value); } @Override public int hashCode() { return Objects.hash(this.prefix, this.value); } /** * Get the string representation of this identifier in the format * {@code prefix:value}. */ @Override public String toString() { return "%s:%s".formatted(this.prefix, this.value); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; /** * {@code EngineDiscoveryListener} defines the API which enables a {@link TestEngine} * to publish information about events that occur during test discovery. * *

All methods in this interface have empty default implementations. * Concrete implementations may therefore override one or more of these methods * to be notified of the selected events. * * @since 1.6 * @see EngineDiscoveryRequest#getDiscoveryListener() * @see org.junit.platform.launcher.LauncherDiscoveryListener */ @API(status = STABLE, since = "1.10") public interface EngineDiscoveryListener { /** * No-op implementation of {@code EngineDiscoveryListener} */ EngineDiscoveryListener NOOP = new EngineDiscoveryListener() { }; /** * Must be called after a discovery selector has been processed by a test * engine. * *

Exceptions thrown by implementations of this method will cause test * discovery of the current engine to be aborted. * * @param engineId the unique ID of the engine descriptor * @param selector the processed selector * @param result the resolution result of the supplied engine and selector * @see SelectorResolutionResult */ default void selectorProcessed(UniqueId engineId, DiscoverySelector selector, SelectorResolutionResult result) { } /** * Called when the engine with the supplied {@code engineId} encountered an * issue during test discovery. * * @param engineId the unique ID of the engine descriptor * @param issue the encountered issue * @since 1.13 * @see DiscoveryIssue */ @API(status = EXPERIMENTAL, since = "6.0") default void issueEncountered(UniqueId engineId, DiscoveryIssue issue) { } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine; import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.util.List; import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; /** * {@code EngineDiscoveryRequest} provides a {@link TestEngine} access to the * information necessary to discover tests and containers. * *

A request is comprised of {@linkplain DiscoverySelector selectors} and * {@linkplain DiscoveryFilter filters}. While the former select * resources that engines can use to discover tests, the latter specify how * such resources are to be filtered. All of the filters * have to include a resource for it to end up in the test plan. * *

In addition, the supplied {@linkplain ConfigurationParameters * configuration parameters} can be used to influence the discovery process. * * @since 1.0 * @see TestEngine * @see TestDescriptor * @see DiscoverySelector * @see DiscoveryFilter * @see ConfigurationParameters */ @API(status = STABLE, since = "1.0") public interface EngineDiscoveryRequest { /** * Get the {@link DiscoverySelector DiscoverySelectors} for this request, * filtered by a particular type. * * @param selectorType the type of {@link DiscoverySelector} to filter by; * never {@code null} * @return all selectors of this request that are instances of * {@code selectorType}; never {@code null} but potentially empty */ List getSelectorsByType(Class selectorType); /** * Get the {@link DiscoveryFilter DiscoveryFilters} for this request, * filtered by a particular type. * *

The returned filters are to be combined using AND semantics, i.e. all * of them have to include a resource for it to end up in the test plan. * * @param filterType the type of {@link DiscoveryFilter} to filter by; * never {@code null} * @return all filters of this request that are instances of * {@code filterType}; never {@code null} but potentially empty */ > List getFiltersByType(Class filterType); /** * Get the {@link ConfigurationParameters} for this request. * * @return the configuration parameters; never {@code null} */ ConfigurationParameters getConfigurationParameters(); /** * Get the {@link EngineDiscoveryListener} for this request. * * @return the discovery listener; never {@code null} * @since 1.6 */ @API(status = STABLE, since = "1.10") default EngineDiscoveryListener getDiscoveryListener() { return EngineDiscoveryListener.NOOP; } /** * Get the * {@link org.junit.platform.engine.reporting.OutputDirectoryProvider} for * this request. * * @return the output directory provider; never {@code null} * @since 1.12 * @deprecated Please use {@link #getOutputDirectoryCreator()} instead */ @SuppressWarnings("removal") @Deprecated(since = "1.14", forRemoval = true) @API(status = DEPRECATED, since = "1.14") default org.junit.platform.engine.reporting.OutputDirectoryProvider getOutputDirectoryProvider() { return org.junit.platform.engine.reporting.OutputDirectoryProvider.castOrAdapt(getOutputDirectoryCreator()); } /** * Get the {@link OutputDirectoryCreator} for this request. * * @return the output directory creator; never {@code null} * @since 1.14 */ @API(status = MAINTAINED, since = "1.14") default OutputDirectoryCreator getOutputDirectoryCreator() { throw new JUnitException( "OutputDirectoryCreator not available; probably due to unaligned versions of the junit-platform-engine and junit-platform-launcher jars on the classpath/module path."); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/EngineExecutionListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; import org.junit.platform.engine.TestExecutionResult.Status; import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; /** * Listener to be notified of test execution events by * {@linkplain TestEngine test engines}. * *

Contrary to JUnit 4, {@linkplain TestEngine test engines} * must report events not only for {@linkplain TestDescriptor test descriptors} * that represent executable leaves but also for all intermediate containers. * * @since 1.0 * @see TestEngine * @see ExecutionRequest */ @API(status = STABLE, since = "1.0") public interface EngineExecutionListener { /** * No-op implementation of {@code EngineExecutionListener} */ EngineExecutionListener NOOP = new EngineExecutionListener() { }; /** * Must be called when a new, dynamic {@link TestDescriptor} has been * registered. * *

A dynamic test is a test that is not known a-priori and * therefore was not present in the test tree when discovering tests. * * @param testDescriptor the descriptor of the newly registered test * or container */ default void dynamicTestRegistered(TestDescriptor testDescriptor) { } /** * Must be called when the execution of a leaf or subtree of the test tree * has been skipped. * *

The {@link TestDescriptor} may represent a test or a container. In the * case of a container, an engine must not fire any additional events for its * descendants. * *

A skipped test or subtree of tests must not be reported as * {@linkplain #executionStarted started} or * {@linkplain #executionFinished finished}. * * @param testDescriptor the descriptor of the skipped test or container * @param reason a human-readable message describing why the execution * has been skipped */ default void executionSkipped(TestDescriptor testDescriptor, String reason) { } /** * Must be called when the execution of a leaf or subtree of the test tree * is about to be started. * *

The {@link TestDescriptor} may represent a test or a container. In the * case of a container, an engine must fire additional events for its * children. * *

This method may only be called if the test or container has not been * {@linkplain #executionSkipped skipped}. * *

This method must be called for a container {@code TestDescriptor} * before {@linkplain #executionStarted starting} or * {@linkplain #executionSkipped skipping} any of its children. * * @param testDescriptor the descriptor of the started test or container */ default void executionStarted(TestDescriptor testDescriptor) { } /** * Must be called when the execution of a leaf or subtree of the test tree * has finished, regardless of the outcome. * *

The {@link TestDescriptor} may represent a test or a container. * *

This method may only be called if the test or container has not * been {@linkplain #executionSkipped skipped}. * *

This method must be called for a container {@code TestIdentifier} * after all of its children have been * {@linkplain #executionSkipped skipped} or have * {@linkplain #executionFinished finished}. * *

The {@link TestExecutionResult} describes the result of the execution * for the supplied {@code testDescriptor}. The result does not include or * aggregate the results of its children. For example, a container with a * failing test must be reported as {@link Status#SUCCESSFUL SUCCESSFUL} even * if one or more of its children are reported as {@link Status#FAILED FAILED}. * * @param testDescriptor the descriptor of the finished test or container * @param testExecutionResult the (unaggregated) result of the execution for * the supplied {@code TestDescriptor} * * @see TestExecutionResult */ default void executionFinished(TestDescriptor testDescriptor, TestExecutionResult testExecutionResult) { } /** * Can be called for any {@link TestDescriptor} in order to publish additional * information to the reporting infrastructure — for example: * *

    *
  • Output that would otherwise go to {@code System.out}
  • *
  • Information about test context or test data
  • *
* *

The current lifecycle state of the supplied {@code TestDescriptor} is * not relevant: reporting events can occur at any time. * * @param testDescriptor the descriptor of the test or container to which * the reporting entry belongs * @param reportEntry the {@link ReportEntry} to be published */ default void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry reportEntry) { } /** * Can be called for any {@link TestDescriptor} in order to publish a file or * directory by attaching it to a test or container — for example: * *

    *
  • Screenshots
  • *
  • Logs
  • *
  • Output files written by the code under test
  • *
  • Test output directory
  • *
* *

The current lifecycle state of the supplied {@code TestDescriptor} is * not relevant: file events can occur at any time. * * @param testDescriptor the descriptor of the test or container to which * the file entry belongs * @param fileEntry the {@link FileEntry} to be published */ @API(status = MAINTAINED, since = "1.13.3") default void fileEntryPublished(TestDescriptor testDescriptor, FileEntry fileEntry) { } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/ExecutionRequest.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine; import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.support.store.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; /** * Provides a single {@link TestEngine} access to the information necessary to * execute its tests. * *

A request contains an engine's root {@link TestDescriptor}, the * {@link EngineExecutionListener} to be notified of test execution events, the * {@link ConfigurationParameters} that the engine may use to influence test * execution, and an {@link OutputDirectoryCreator} for writing reports and * other output files. * * @since 1.0 * @see TestEngine */ @API(status = STABLE, since = "1.0") public class ExecutionRequest { private final TestDescriptor rootTestDescriptor; private final EngineExecutionListener engineExecutionListener; private final ConfigurationParameters configurationParameters; private final @Nullable OutputDirectoryCreator outputDirectoryCreator; private final @Nullable NamespacedHierarchicalStore requestLevelStore; private final CancellationToken cancellationToken; /** * @deprecated without replacement because it's an internal API. */ @Deprecated(since = "1.11") @API(status = DEPRECATED, since = "1.11") public ExecutionRequest(TestDescriptor rootTestDescriptor, EngineExecutionListener engineExecutionListener, ConfigurationParameters configurationParameters) { this(rootTestDescriptor, engineExecutionListener, configurationParameters, null, null, CancellationToken.disabled()); } private ExecutionRequest(TestDescriptor rootTestDescriptor, EngineExecutionListener engineExecutionListener, ConfigurationParameters configurationParameters, @Nullable OutputDirectoryCreator outputDirectoryCreator, @Nullable NamespacedHierarchicalStore requestLevelStore, CancellationToken cancellationToken) { this.rootTestDescriptor = Preconditions.notNull(rootTestDescriptor, "rootTestDescriptor must not be null"); this.engineExecutionListener = Preconditions.notNull(engineExecutionListener, "engineExecutionListener must not be null"); this.configurationParameters = Preconditions.notNull(configurationParameters, "configurationParameters must not be null"); this.outputDirectoryCreator = outputDirectoryCreator; this.requestLevelStore = requestLevelStore; this.cancellationToken = Preconditions.notNull(cancellationToken, "cancellationToken must not be null"); } /** * Factory for creating an execution request. * * @param rootTestDescriptor the engine's root {@link TestDescriptor} * @param engineExecutionListener the {@link EngineExecutionListener} to be * notified of test execution events * @param configurationParameters {@link ConfigurationParameters} that the * engine may use to influence test execution * @return a new {@code ExecutionRequest}; never {@code null} * @since 1.9 * @deprecated without replacement */ @Deprecated(since = "1.11") @API(status = DEPRECATED, since = "1.11") public static ExecutionRequest create(TestDescriptor rootTestDescriptor, EngineExecutionListener engineExecutionListener, ConfigurationParameters configurationParameters) { return new ExecutionRequest(rootTestDescriptor, engineExecutionListener, configurationParameters); } /** * Factory for creating an execution request. * * @param rootTestDescriptor the engine's root {@link TestDescriptor}; never * {@code null} * @param engineExecutionListener the {@link EngineExecutionListener} to be * notified of test execution events; never {@code null} * @param configurationParameters {@link ConfigurationParameters} that the * engine may use to influence test execution; never {@code null} * @param outputDirectoryCreator {@link OutputDirectoryCreator} for * writing reports and other output files; never {@code null} * @param requestLevelStore {@link NamespacedHierarchicalStore} for storing * request-scoped data; never {@code null} * @return a new {@code ExecutionRequest}; never {@code null} * @since 6.0 */ @API(status = INTERNAL, since = "6.0") public static ExecutionRequest create(TestDescriptor rootTestDescriptor, EngineExecutionListener engineExecutionListener, ConfigurationParameters configurationParameters, OutputDirectoryCreator outputDirectoryCreator, NamespacedHierarchicalStore requestLevelStore, CancellationToken cancellationToken) { return new ExecutionRequest(rootTestDescriptor, engineExecutionListener, configurationParameters, Preconditions.notNull(outputDirectoryCreator, "outputDirectoryCreator must not be null"), Preconditions.notNull(requestLevelStore, "requestLevelStore must not be null"), Preconditions.notNull(cancellationToken, "cancellationToken must not be null")); } /** * {@return the root {@link TestDescriptor} of the engine that processes this * request} * *

Note: the root descriptor is the * {@code TestDescriptor} returned by * {@link TestEngine#discover(EngineDiscoveryRequest, UniqueId)}. */ public TestDescriptor getRootTestDescriptor() { return this.rootTestDescriptor; } /** * {@return the {@link EngineExecutionListener} to be notified of test execution * events} */ public EngineExecutionListener getEngineExecutionListener() { return this.engineExecutionListener; } /** * {@return the {@link ConfigurationParameters} that the engine may use to * influence test execution} */ public ConfigurationParameters getConfigurationParameters() { return this.configurationParameters; } /** * {@return the * {@link org.junit.platform.engine.reporting.OutputDirectoryProvider} for * this request for writing reports and other output files} * * @throws PreconditionViolationException if the output directory provider * is not available * @since 1.12 * @deprecated Please use {@link #getOutputDirectoryCreator()} instead */ @Deprecated(since = "1.14", forRemoval = true) @API(status = DEPRECATED, since = "1.14") @SuppressWarnings("removal") public org.junit.platform.engine.reporting.OutputDirectoryProvider getOutputDirectoryProvider() { return org.junit.platform.engine.reporting.OutputDirectoryProvider.castOrAdapt(getOutputDirectoryCreator()); } /** * {@return the {@link OutputDirectoryCreator} for this request for writing * reports and other output files} * * @throws PreconditionViolationException if the output directory creator is * not available * @since 1.14 */ @API(status = MAINTAINED, since = "1.14") public OutputDirectoryCreator getOutputDirectoryCreator() { return Preconditions.notNull(this.outputDirectoryCreator, "No OutputDirectoryCreator was configured for this request"); } /** * {@return the {@link NamespacedHierarchicalStore} for this request for * storing request-scoped data} * *

All stored values that implement {@link AutoCloseable} are notified by * invoking their {@code close()} methods when this request has been * executed. * * @since 1.13 * @see NamespacedHierarchicalStore */ @API(status = EXPERIMENTAL, since = "6.0") public NamespacedHierarchicalStore getStore() { return Preconditions.notNull(this.requestLevelStore, "No NamespacedHierarchicalStore was configured for this request"); } /** * {@return the {@link CancellationToken} for this request for engines to * check whether they should cancel execution} * * @since 6.0 * @see CancellationToken#isCancellationRequested() */ @API(status = EXPERIMENTAL, since = "6.0") public CancellationToken getCancellationToken() { return cancellationToken; } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/Filter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine; import static java.util.Arrays.asList; import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.STABLE; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import static org.junit.platform.engine.CompositeFilter.alwaysIncluded; import java.util.Collection; import java.util.function.Function; import java.util.function.Predicate; import org.apiguardian.api.API; import org.junit.platform.commons.util.Preconditions; /** * A {@link Filter} can be applied to determine if an object should be * included or excluded in a result set. * *

For example, tests may be filtered during or after test discovery * based on certain criteria. * *

Clients should not implement this interface directly but rather one of * its subinterfaces. * * @since 1.0 * @see DiscoveryFilter */ @FunctionalInterface @API(status = STABLE, since = "1.0") public interface Filter { /** * Return a filter that will include elements if and only if all of the * filters in the supplied array of {@link Filter filters} include it. * *

If the array is empty, the returned filter will include all elements * it is asked to filter. * * @param filters the array of filters to compose; never {@code null} * @see #composeFilters(Collection) */ @SafeVarargs @SuppressWarnings("varargs") static Filter composeFilters(Filter... filters) { Preconditions.notNull(filters, "filters array must not be null"); Preconditions.containsNoNullElements(filters, "individual filters must not be null"); if (filters.length == 0) { return alwaysIncluded(); } if (filters.length == 1) { return filters[0]; } return new CompositeFilter<>(asList(filters)); } /** * Return a filter that will include elements if and only if all of the * filters in the supplied collection of {@link Filter filters} include it. * *

If the collection is empty, the returned filter will include all * elements it is asked to filter. * * @param filters the collection of filters to compose; never {@code null} * @see #composeFilters(Filter...) */ static Filter composeFilters(Collection> filters) { Preconditions.notNull(filters, "Filters must not be null"); if (filters.isEmpty()) { return alwaysIncluded(); } if (filters.size() == 1) { return getOnlyElement(filters); } return new CompositeFilter<>(filters); } /** * Return a filter that will include elements if and only if the adapted * {@code Filter} includes the value converted using the supplied * {@link Function}. * * @param adaptee the filter to be adapted * @param converter the converter function to apply * @deprecated without replacement */ @API(status = DEPRECATED, since = "6.0") @Deprecated(since = "6.0", forRemoval = true) static Filter adaptFilter(Filter adaptee, Function converter) { return input -> adaptee.apply(converter.apply(input)); } /** * Apply this filter to the supplied object. */ FilterResult apply(T object); /** * Return a {@link Predicate} that returns {@code true} if this filter * includes the object supplied to the predicate's * {@link Predicate#test test} method. */ default Predicate toPredicate() { return object -> apply(object).included(); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/FilterResult.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine; import static org.apiguardian.api.API.Status.STABLE; import java.util.Optional; import java.util.function.Supplier; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.ToStringBuilder; /** * The result of applying a {@link Filter}. * * @since 1.0 */ @API(status = STABLE, since = "1.0") public class FilterResult { /** * Factory for creating included results. * * @param reason the reason why the filtered object was included * @return an included {@code FilterResult} with the given reason */ public static FilterResult included(@Nullable String reason) { return new FilterResult(true, reason); } /** * Factory for creating excluded results. * * @param reason the reason why the filtered object was excluded * @return an excluded {@code FilterResult} with the given reason */ public static FilterResult excluded(@Nullable String reason) { return new FilterResult(false, reason); } /** * Factory for creating filter results based on the condition given. * * @param included whether the filtered object should be included * @return a valid {@code FilterResult} for the given condition */ public static FilterResult includedIf(boolean included) { return includedIf(included, () -> null, () -> null); } /** * Factory for creating filter results based on the condition given. * * @param included whether the filtered object should be included * @param inclusionReasonSupplier supplier for the reason in case of inclusion * @param exclusionReasonSupplier supplier for the reason in case of exclusion * @return a valid {@code FilterResult} for the given condition */ public static FilterResult includedIf(boolean included, Supplier<@Nullable String> inclusionReasonSupplier, Supplier<@Nullable String> exclusionReasonSupplier) { return included ? included(inclusionReasonSupplier.get()) : excluded(exclusionReasonSupplier.get()); } private final boolean included; private final Optional reason; private FilterResult(boolean included, @Nullable String reason) { this.included = included; this.reason = Optional.ofNullable(reason); } /** * {@return {@code true} if the filtered object should be included} */ public boolean included() { return this.included; } /** * {@return {@code true} if the filtered object should be excluded} */ public boolean excluded() { return !included(); } /** * Get the reason why the filtered object should be included or excluded, * if available. */ public Optional getReason() { return this.reason; } @Override public String toString() { // @formatter:off return new ToStringBuilder(this) .append("included", this.included) .append("reason", this.reason.orElse("")) .toString(); // @formatter:on } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/OutputDirectoryCreator.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine; import static org.apiguardian.api.API.Status.MAINTAINED; import java.io.IOException; import java.nio.file.Path; import org.apiguardian.api.API; /** * Provider of output directories for test engines to write reports and other * output files to. * * @since 1.14 * @see EngineDiscoveryRequest#getOutputDirectoryCreator() */ @API(status = MAINTAINED, since = "1.14") public interface OutputDirectoryCreator { /** * {@return the root directory for all output files; never {@code null}} */ Path getRootDirectory(); /** * Create an output directory for the supplied test descriptor. * * @param testDescriptor the test descriptor for which to create an output * directory; never {@code null} * @return the output directory * @throws IOException if the output directory could not be created */ Path createOutputDirectory(TestDescriptor testDescriptor) throws IOException; } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/RegularCancellationToken.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine; import java.util.concurrent.atomic.AtomicBoolean; /** * @since 6.0 */ final class RegularCancellationToken implements CancellationToken { private final AtomicBoolean cancelled = new AtomicBoolean(); @Override public boolean isCancellationRequested() { return cancelled.get(); } @Override public void cancel() { cancelled.set(true); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/SelectorResolutionResult.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine; import static org.apiguardian.api.API.Status.STABLE; import java.util.Optional; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.ToStringBuilder; /** * {@code SelectorResolutionResult} encapsulates the result of resolving a * {@link DiscoverySelector} by a {@link TestEngine}. * *

A {@code SelectorResolutionResult} consists of a mandatory * {@link #getStatus() Status} and an optional {@link #getThrowable() Throwable}. * * @since 1.6 */ @API(status = STABLE, since = "1.10") public class SelectorResolutionResult { /** * Status of resolving a {@link DiscoverySelector}. */ public enum Status { /** * Indicates that the {@link TestEngine} has successfully resolved the * selector. */ RESOLVED, /** * Indicates that the {@link TestEngine} was unable to resolve the * selector. */ UNRESOLVED, /** * Indicates that the {@link TestEngine} has encountered an error while * resolving the selector. */ FAILED } private static final SelectorResolutionResult RESOLVED_RESULT = new SelectorResolutionResult(Status.RESOLVED, null); private static final SelectorResolutionResult UNRESOLVED_RESULT = new SelectorResolutionResult(Status.UNRESOLVED, null); /** * Create a {@code SelectorResolutionResult} for a resolved * selector. * @return the {@code SelectorResolutionResult}; never {@code null} */ public static SelectorResolutionResult resolved() { return RESOLVED_RESULT; } /** * Create a {@code SelectorResolutionResult} for an unresolved * selector. * @return the {@code SelectorResolutionResult}; never {@code null} */ public static SelectorResolutionResult unresolved() { return UNRESOLVED_RESULT; } /** * Create a {@code SelectorResolutionResult} for a failed * selector resolution. * @return the {@code SelectorResolutionResult}; never {@code null} */ public static SelectorResolutionResult failed(Throwable throwable) { return new SelectorResolutionResult(Status.FAILED, throwable); } private final Status status; private final @Nullable Throwable throwable; private SelectorResolutionResult(Status status, @Nullable Throwable throwable) { this.status = status; this.throwable = throwable; } /** * Get the {@linkplain Status status} of this result. * * @return the status; never {@code null} */ public Status getStatus() { return status; } /** * Get the throwable that caused this result, if available. * * @return an {@code Optional} containing the throwable; never {@code null} * but potentially empty */ public Optional getThrowable() { return Optional.ofNullable(throwable); } @Override public String toString() { // @formatter:off return new ToStringBuilder(this) .append("status", status) .append("throwable", throwable) .toString(); // @formatter:on } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/TestDescriptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.function.UnaryOperator; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; /** * A descriptor with a mutable hierarchy for a test or container that has been * discovered by a {@link TestEngine}. * * @since 1.0 * @see TestEngine */ @API(status = STABLE, since = "1.0") public interface TestDescriptor { /** * Get the unique identifier (UID) for this descriptor. * *

Uniqueness must be guaranteed across an entire test plan, * regardless of how many engines are used behind the scenes. * *

The implementation must treat this property as immutable after test * discovery has completed. * * @return the {@code UniqueId} for this descriptor; never {@code null} */ UniqueId getUniqueId(); /** * Get the display name for this descriptor. * *

A display name is a human-readable name for a test or * container that is typically used for test reporting in IDEs and build * tools. Display names may contain spaces, special characters, and emoji, * and the format may be customized by {@link TestEngine TestEngines} or * potentially by end users as well. Consequently, display names should * never be parsed; rather, they should be used for display purposes only. * * @return the display name for this descriptor; never {@code null} or blank * @see #getSource() */ String getDisplayName(); /** * Get the name of this descriptor in a format that is suitable for legacy * reporting infrastructure — for example, for reporting systems built * on the Ant-based XML reporting format for JUnit 4. * *

The default implementation delegates to {@link #getDisplayName()}. * * @return the legacy reporting name; never {@code null} or blank */ default String getLegacyReportingName() { return getDisplayName(); } /** * Get the set of {@linkplain TestTag tags} associated with this descriptor. * *

The implementation must treat this property as immutable after test * discovery has completed. * * @return the set of tags associated with this descriptor; never {@code null} * but potentially empty * @see TestTag */ Set getTags(); /** * Get the {@linkplain TestSource source} of the test or container described * by this descriptor, if available. * *

The implementation must treat this property as immutable after test * discovery has completed. * * @see TestSource */ Optional getSource(); /** * Get the parent of this descriptor, if available. */ Optional getParent(); /** * Set the parent of this descriptor. * * @param parent the new parent of this descriptor; may be {@code null}. */ void setParent(@Nullable TestDescriptor parent); /** * Get the immutable set of children of this descriptor. * *

The implementation must be consistent with {@link #isContainer()} such that * {@code !x.container()} implies {@code x.getChildren().isEmpty()}. * * @return the set of children of this descriptor; neither {@code null} * nor mutable, but potentially empty * @see #getDescendants() */ Set getChildren(); /** * Get the immutable set of all ancestors of this descriptor. * *

An ancestor is the parent of this descriptor or the parent of * one of its parents, recursively. * * @see #getParent() */ @API(status = STABLE, since = "1.10") default Set getAncestors() { if (getParent().isEmpty()) { return Collections.emptySet(); } TestDescriptor parent = getParent().get(); Set ancestors = new LinkedHashSet<>(); ancestors.add(parent); // Need to recurse? if (parent.getParent().isPresent()) { ancestors.addAll(parent.getAncestors()); } return Collections.unmodifiableSet(ancestors); } /** * Get the immutable set of all descendants of this descriptor. * *

A descendant is a child of this descriptor or a child of one of * its children, recursively. * *

The implementation must be consistent with {@link #isContainer()} such that * {@code !x.container()} implies {@code x.getDescendants().isEmpty()}. * * @see #getChildren() */ default Set getDescendants() { Set descendants = new LinkedHashSet<>(); descendants.addAll(getChildren()); for (TestDescriptor child : getChildren()) { descendants.addAll(child.getDescendants()); } return Collections.unmodifiableSet(descendants); } /** * Add a child to this descriptor. * * @param descriptor the child to add to this descriptor; never {@code null} */ void addChild(TestDescriptor descriptor); /** * Remove a child from this descriptor. * * @param descriptor the child to remove from this descriptor; never * {@code null} */ void removeChild(TestDescriptor descriptor); /** * Remove this non-root descriptor from its parent and remove all the * children from this descriptor. * *

If this method is invoked on a {@linkplain #isRoot root} descriptor, * this method must throw a {@link org.junit.platform.commons.JUnitException * JUnitException} explaining that a root cannot be removed from the * hierarchy. */ void removeFromHierarchy(); /** * Order the children of this descriptor. * *

The {@code orderer} is provided a modifiable list of child test * descriptors of this test descriptor; never {@code null}. The * {@code orderer} must return a list containing the same descriptors in any * order; potentially the same list, but never {@code null}. If descriptors * are added or removed, an exception is thrown. * * @param orderer a unary operator to order the children of this test descriptor * @since 1.12 */ @API(status = MAINTAINED, since = "1.13.3") default void orderChildren(UnaryOperator> orderer) { Preconditions.notNull(orderer, "orderer must not be null"); Set originalChildren = getChildren(); List suggestedOrder = orderer.apply(new ArrayList<>(originalChildren)); Preconditions.notNull(suggestedOrder, "orderer may not return null"); Set orderedChildren = new LinkedHashSet<>(suggestedOrder); boolean unmodified = originalChildren.equals(orderedChildren); Preconditions.condition(unmodified && originalChildren.size() == suggestedOrder.size(), "orderer may not add or remove test descriptors"); suggestedOrder.stream() // .distinct() // .filter(originalChildren::contains)// .forEach(testDescriptor -> { removeChild(testDescriptor); addChild(testDescriptor); }); } /** * Determine if this descriptor is a root descriptor. * *

A root descriptor is a descriptor without a parent. */ default boolean isRoot() { return getParent().isEmpty(); } /** * Determine the {@link Type} of this descriptor. * *

The implementation must treat this property as immutable after test * discovery has completed. * * @return the descriptor type; never {@code null}. * @see #isContainer() * @see #isTest() */ Type getType(); /** * Determine if this descriptor describes a container. * *

A test descriptor is a container when it may contain other * containers or tests as its children. In addition to being a * container this test descriptor may also be a test. * *

The implementation must be consistent with {@link #getType()} such * that {@code x.isContainer()} equals {@code x.getType().isContainer()}. * *

The default implementation delegates to {@link Type#isContainer()}. */ default boolean isContainer() { return getType().isContainer(); } /** * Determine if this descriptor describes a test. * *

A test descriptor is a test when it verifies expected * behavior when executed. In addition to being a test this * test descriptor may also be a container. * *

The implementation must be consistent with {@link #getType()} such * that {@code x.isTest()} equals {@code x.getType().isTest()}. * *

The default implementation delegates to {@link Type#isTest()}. */ default boolean isTest() { return getType().isTest(); } /** * Determine if this descriptor may register dynamic tests during execution. * *

The implementation must treat this property as immutable after test * discovery has completed and must be consistent with {@link #isContainer()} * such that {@code !x.container()} implies {@code !x.mayRegisterTests()}. * *

The default implementation assumes tests are usually known during * discovery and thus returns {@code false}. */ default boolean mayRegisterTests() { return false; } /** * Determine if the supplied descriptor (or any of its descendants) * {@linkplain TestDescriptor#isTest() is a test} or * {@linkplain TestDescriptor#mayRegisterTests() may potentially register * tests dynamically}. * * @param testDescriptor the {@code TestDescriptor} to check for tests; never * {@code null} * @return {@code true} if the descriptor is a test, contains tests, or may * later register tests dynamically */ static boolean containsTests(TestDescriptor testDescriptor) { Preconditions.notNull(testDescriptor, "TestDescriptor must not be null"); return testDescriptor.isTest() || testDescriptor.mayRegisterTests() || testDescriptor.getChildren().stream().anyMatch(TestDescriptor::containsTests); } /** * Remove this descriptor from the hierarchy unless it is a root or contains * tests. * *

A concrete {@link TestEngine} may override this method in order to * implement a different algorithm or to skip pruning altogether. * * @see #isRoot() * @see #containsTests(TestDescriptor) * @see #removeFromHierarchy() */ default void prune() { if (!isRoot() && !containsTests(this)) { removeFromHierarchy(); } } /** * Find the descriptor with the supplied unique ID. * *

The search algorithm begins with this descriptor and then searches * through its descendants. * * @param uniqueId the {@code UniqueId} to search for; never {@code null} */ Optional findByUniqueId(UniqueId uniqueId); /** * Accept a {@link Visitor} to the subtree starting with this descriptor. * * @param visitor the {@code Visitor} to accept; never {@code null} */ default void accept(Visitor visitor) { Preconditions.notNull(visitor, "Visitor must not be null"); visitor.visit(this); this.getChildren().forEach(child -> child.accept(visitor)); } /** * Visitor for the tree-like {@link TestDescriptor} structure. * * @see TestDescriptor#accept(Visitor) */ @FunctionalInterface interface Visitor { /** * Combine the supplied {@code visitors} into a single {@code Visitor}. * *

If the supplied array contains only a single {@code Visitor}, that * {@code Visitor} is returned as is. * * @param visitors the {@code Visitor}s to combine; never {@code null} * or empty * @return the combined {@code Visitor} * @throws org.junit.platform.commons.PreconditionViolationException if * {@code visitors} is {@code null}, contains {@code null} elements, or * is empty * @since 1.13 */ @API(status = EXPERIMENTAL, since = "6.0") static Visitor composite(Visitor... visitors) { return CompositeTestDescriptorVisitor.from(visitors); } /** * Visit a {@link TestDescriptor}. * * @param descriptor the {@code TestDescriptor} to visit; never {@code null} */ void visit(TestDescriptor descriptor); } /** * Supported types for {@link TestDescriptor TestDescriptors}. */ enum Type { /** * Denotes that the {@link TestDescriptor} is strictly for a * container. I.e. it is not also a test. */ CONTAINER, /** * Denotes that the {@link TestDescriptor} is strictly for a * test. I.e. it is not also a container. */ TEST, /** * Denotes that the {@link TestDescriptor} is for a test * that may potentially also be a container. */ CONTAINER_AND_TEST; /** * {@return {@code true} if this type represents a descriptor that can * contain other descriptors} */ public boolean isContainer() { return this == CONTAINER || this == CONTAINER_AND_TEST; } /** * {@return {@code true} if this type represents a descriptor for a test} */ public boolean isTest() { return this == TEST || this == CONTAINER_AND_TEST; } } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/TestEngine.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine; import static org.apiguardian.api.API.Status.STABLE; import java.lang.module.ModuleDescriptor; import java.util.Optional; import org.apiguardian.api.API; import org.junit.platform.commons.util.ModuleUtils; import org.junit.platform.commons.util.PackageUtils; /** * A {@code TestEngine} facilitates discovery and execution of * tests for a particular programming model. * *

For example, JUnit provides a {@code TestEngine} that discovers and * executes tests written using the JUnit Jupiter programming model. * *

Every {@code TestEngine} must {@linkplain #getId provide its own unique ID}, * {@linkplain #discover discover tests} from * {@link EngineDiscoveryRequest EngineDiscoveryRequests}, * and {@linkplain #execute execute those tests} according to * {@link ExecutionRequest ExecutionRequests}. * *

In order to facilitate test discovery within IDEs and tools prior * to launching the JUnit Platform, {@code TestEngine} implementations are * encouraged to make use of the * {@link org.junit.platform.commons.annotation.Testable @Testable} annotation. * For example, the {@code @Test} and {@code @TestFactory} annotations in JUnit * Jupiter are meta-annotated with {@code @Testable}. Consult the Javadoc for * {@code @Testable} for further details. * * @since 1.0 * @see org.junit.platform.engine.EngineDiscoveryRequest * @see org.junit.platform.engine.ExecutionRequest * @see org.junit.platform.commons.annotation.Testable */ @API(status = STABLE, since = "1.0") public interface TestEngine { /** * Get the ID that uniquely identifies this test engine. * *

Each test engine must provide a unique ID. For example, JUnit Vintage * and JUnit Jupiter use {@code "junit-vintage"} and {@code "junit-jupiter"}, * respectively. When in doubt, you may use the fully qualified name of your * custom {@code TestEngine} implementation class. * * @return the ID of this test engine; never {@code null} or blank */ String getId(); /** * Discover tests according to the supplied {@link EngineDiscoveryRequest}. * *

The supplied {@link UniqueId} must be used as the unique ID of the * returned root {@link TestDescriptor}. In addition, the {@code UniqueId} * must be used to create unique IDs for children of the root's descriptor * by calling {@link UniqueId#append}. * *

Furthermore, implementations must publish events about test discovery * via the supplied {@link EngineDiscoveryRequest#getDiscoveryListener() * EngineDiscoveryListener}. * * @param discoveryRequest the discovery request; never {@code null} * @param uniqueId the unique ID to be used for this test engine's * {@code TestDescriptor}; never {@code null} * @return the root {@code TestDescriptor} of this engine, typically an * instance of {@code EngineDescriptor} * @see org.junit.platform.engine.support.descriptor.EngineDescriptor */ TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId); /** * Execute tests according to the supplied {@link ExecutionRequest}. * *

The {@code request} passed to this method contains the root * {@link TestDescriptor} that was previously returned by {@link #discover}, * the {@link EngineExecutionListener} to be notified of test execution * events, and {@link ConfigurationParameters} that may influence test execution. * * @param request the request to execute tests for; never {@code null} */ void execute(ExecutionRequest request); /** * Get the Group ID of the JAR in which this test engine is packaged. * *

This information is used solely for debugging and reporting purposes. * *

The default implementation returns an empty {@link Optional}, * signaling that the group ID is unknown. * *

Concrete test engine implementations may override this method in * order to provide a known group ID. * * @return an {@code Optional} containing the group ID; never {@code null} * but potentially empty if the group ID is unknown * @see #getArtifactId() * @see #getVersion() */ default Optional getGroupId() { return Optional.empty(); } /** * Get the Module Name or Artifact ID of the JAR in which * this test engine is packaged. * *

This information is used solely for debugging and reporting purposes. * *

The default implementation first attempts to retrieve the * {@linkplain Module#getName() name} of the {@link Module} in which the * engine resides. If the module name is not available, the default * implementation then attempts to retrieve the artifact ID as explained * below. * *

The default implementation assumes the implementation title is equivalent * to the artifact ID and therefore attempts to query the * {@linkplain Package#getImplementationTitle() implementation title} * from the package attributes for the {@link Package} in which the engine * resides. Note that a package only has attributes if the information is * defined in the {@link java.util.jar.Manifest Manifest} of the JAR containing * that package, and if the class loader created the {@link Package} instance * with the attributes from the manifest. * *

If the implementation title cannot be queried from the package * attributes, the default implementation returns an empty {@link Optional}. * *

Concrete test engine implementations may override this method in * order to determine the artifact ID by some other means. * * @return an {@code Optional} containing the module name or artifact ID; never * {@code null} but potentially empty if neither the module name nor the artifact * ID can be determined * @see Module#getName() * @see Class#getPackage() * @see Package#getImplementationTitle() * @see #getGroupId() * @see #getVersion() */ default Optional getArtifactId() { Optional moduleName = ModuleUtils.getModuleName(getClass()); if (moduleName.isPresent()) { return moduleName; } return PackageUtils.getAttribute(getClass(), Package::getImplementationTitle); } /** * Get the version of this test engine. * *

This information is used solely for debugging and reporting purposes. * *

The default implementation first attempts to retrieve the version * from the JAR manifest attribute named {@code "Engine-Version-" + getId()}. * *

If the manifest attribute is not available, an attempt is made to retrieve * the {@linkplain ModuleDescriptor#rawVersion() raw version} of the * {@link Module} in which the engine resides. * *

If the module version is not available, an attempt is made to query the * {@linkplain Package#getImplementationVersion() implementation version} * from the package attributes for the {@link Package} in which the engine * resides. Note that a package only has attributes if the information is * defined in the {@link java.util.jar.Manifest Manifest} of the JAR * containing that package, and if the class loader created the * {@link Package} instance with the attributes from the manifest. * *

If the implementation version cannot be determined, the default * implementation returns {@code "DEVELOPMENT"}. * *

Concrete test engine implementations may override this method to * determine the version by some other means. * * @return an {@code Optional} containing the version; never {@code null} * but potentially empty if the version is unknown * @see Class#getPackage() * @see Package#getImplementationVersion() * @see #getGroupId() * @see #getArtifactId() */ default Optional getVersion() { Optional standalone = PackageUtils.getAttribute(getClass(), "Engine-Version-" + getId()); if (standalone.isPresent()) { return standalone; } return Optional.of(PackageUtils.getModuleOrImplementationVersion(getClass()).orElse("DEVELOPMENT")); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/TestExecutionResult.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine; import static org.apiguardian.api.API.Status.STABLE; import static org.junit.platform.engine.TestExecutionResult.Status.ABORTED; import static org.junit.platform.engine.TestExecutionResult.Status.FAILED; import static org.junit.platform.engine.TestExecutionResult.Status.SUCCESSFUL; import java.util.Optional; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; /** * {@code TestExecutionResult} encapsulates the result of executing a single test * or container. * *

A {@code TestExecutionResult} consists of a mandatory * {@link #getStatus() Status} and an optional {@link #getThrowable() Throwable}. * * @since 1.0 */ @API(status = STABLE, since = "1.0") public class TestExecutionResult { /** * Status of executing a single test or container. */ public enum Status { /** * Indicates that the execution of a test or container was * successful. */ SUCCESSFUL, /** * Indicates that the execution of a test or container was * aborted (started but not finished). */ ABORTED, /** * Indicates that the execution of a test or container failed. */ FAILED } private static final TestExecutionResult SUCCESSFUL_RESULT = new TestExecutionResult(SUCCESSFUL, null); /** * Create a {@code TestExecutionResult} for a successful execution * of a test or container. * * @return the {@code TestExecutionResult}; never {@code null} */ public static TestExecutionResult successful() { return SUCCESSFUL_RESULT; } /** * Create a {@code TestExecutionResult} for an aborted execution * of a test or container with the supplied {@link Throwable throwable}. * * @param throwable the throwable that caused the aborted execution; may be * {@code null} * @return the {@code TestExecutionResult}; never {@code null} */ public static TestExecutionResult aborted(@Nullable Throwable throwable) { return new TestExecutionResult(ABORTED, throwable); } /** * Create a {@code TestExecutionResult} for a failed execution * of a test or container with the supplied {@link Throwable throwable}. * * @param throwable the throwable that caused the failed execution; may be * {@code null} * @return the {@code TestExecutionResult}; never {@code null} */ public static TestExecutionResult failed(@Nullable Throwable throwable) { return new TestExecutionResult(FAILED, throwable); } private final Status status; private final @Nullable Throwable throwable; private TestExecutionResult(Status status, @Nullable Throwable throwable) { this.status = Preconditions.notNull(status, "Status must not be null"); this.throwable = throwable; } /** * Get the {@linkplain Status status} of this result. * * @return the status; never {@code null} */ public Status getStatus() { return status; } /** * Get the throwable that caused this result, if available. * * @return an {@code Optional} containing the throwable; never {@code null} * but potentially empty */ public Optional getThrowable() { return Optional.ofNullable(throwable); } @Override public String toString() { // @formatter:off return new ToStringBuilder(this) .append("status", status) .append("throwable", throwable) .toString(); // @formatter:on } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/TestSource.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine; import static org.apiguardian.api.API.Status.STABLE; import java.io.Serializable; import org.apiguardian.api.API; /** * Representation of the source of a test or container used to navigate to * its location by IDEs and build tools. * *

This is a marker interface. Clients need to check instances for concrete * subclasses or subinterfaces. * *

Implementations of this interface need to ensure that they are * serializable and immutable since they may be used as data * transfer objects. * * @since 1.0 */ @API(status = STABLE, since = "1.0") public interface TestSource extends Serializable { } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/TestTag.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine; import static org.apiguardian.api.API.Status.STABLE; import java.io.Serial; import java.io.Serializable; import java.util.Objects; import java.util.Set; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.StringUtils; /** * Immutable value object for a tag that is assigned to a test or * container. * * @since 1.0 * @see #isValid(String) * @see #create(String) */ @API(status = STABLE, since = "1.0") public final class TestTag implements Serializable { @Serial private static final long serialVersionUID = 1L; private final String name; /** * Reserved characters that are not permissible as part of a tag name. * *

    *
  • {@code ,}: comma
  • *
  • {@code (}: left parenthesis
  • *
  • {@code )}: right parenthesis
  • *
  • {@code &}: ampersand
  • *
  • {@code |}: vertical bar
  • *
  • {@code !}: exclamation point
  • *
*/ public static final Set RESERVED_CHARACTERS = Set.of(",", "(", ")", "&", "|", "!"); /** * Determine if the supplied tag name is valid with regard to the supported * syntax for tags. * *

Syntax Rules for Tags

*
    *
  • A tag must not be {@code null}.
  • *
  • A tag must not be blank.
  • *
  • A stripped tag must not contain whitespace.
  • *
  • A stripped tag must not contain ISO control characters.
  • *
  • A stripped tag must not contain {@linkplain #RESERVED_CHARACTERS * reserved characters}.
  • *
* *

If this method returns {@code true} for a given name, it is then a * valid candidate for the {@link TestTag#create(String) create()} factory * method. * * @param name the name of the tag to validate; may be {@code null} or blank * @return {@code true} if the supplied tag name conforms to the supported * syntax for tags * @see StringUtils#isNotBlank(String) * @see String#strip() * @see StringUtils#doesNotContainWhitespace(String) * @see StringUtils#doesNotContainIsoControlCharacter(String) * @see #RESERVED_CHARACTERS * @see TestTag#create(String) */ public static boolean isValid(@Nullable String name) { if (name == null) { return false; } name = name.strip(); return !name.isEmpty() && // StringUtils.doesNotContainWhitespace(name) && // StringUtils.doesNotContainIsoControlCharacter(name) && // doesNotContainReservedCharacter(name); } private static boolean doesNotContainReservedCharacter(String str) { return RESERVED_CHARACTERS.stream().noneMatch(str::contains); } /** * Create a {@code TestTag} from the supplied {@code name}. * *

Consider checking whether the syntax of the supplied {@code name} * is {@linkplain #isValid(String) valid} before attempting to create a * {@code TestTag} using this factory method. * *

Note: the supplied {@code name} will be {@linkplain String#strip() stripped}. * * @param name the name of the tag; must be syntactically valid * @throws PreconditionViolationException if the supplied tag name is not * syntactically valid * @see TestTag#isValid(String) */ public static TestTag create(String name) throws PreconditionViolationException { return new TestTag(name); } private TestTag(String name) { Preconditions.condition(TestTag.isValid(name), () -> "Tag name [%s] must be syntactically valid".formatted(name)); this.name = name.strip(); } /** * Get the name of this tag. * * @return the name of this tag; never {@code null} or blank */ public String getName() { return this.name; } @Override public boolean equals(Object obj) { return (obj instanceof TestTag that && Objects.equals(this.name, that.name)); } @Override public int hashCode() { return this.name.hashCode(); } @Override public String toString() { return this.name; } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueId.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine; import static org.apiguardian.api.API.Status.STABLE; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.ObjectStreamClass; import java.io.ObjectStreamField; import java.io.Serial; import java.io.Serializable; import java.lang.ref.SoftReference; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Optional; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; /** * {@code UniqueId} encapsulates the creation, parsing, and display of unique IDs * for {@link TestDescriptor TestDescriptors}. * *

Instances of this class have value semantics and are immutable. * * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class UniqueId implements Cloneable, Serializable { @Serial private static final long serialVersionUID = 1L; @Serial @SuppressWarnings("UnusedVariable") private static final ObjectStreamField[] serialPersistentFields = ObjectStreamClass.lookup( SerializedForm.class).getFields(); private static final String ENGINE_SEGMENT_TYPE = "engine"; /** * Parse a {@code UniqueId} from the supplied string representation using the * default format. * * @param uniqueId the string representation to parse; never {@code null} or blank * @return a properly constructed {@code UniqueId} * @throws JUnitException if the string cannot be parsed */ public static UniqueId parse(String uniqueId) throws JUnitException { Preconditions.notBlank(uniqueId, "Unique ID string must not be null or blank"); return UniqueIdFormat.getDefault().parse(uniqueId); } /** * Create an engine's unique ID from its {@code engineId} using the default * format. * *

The engine ID will be stored in a {@link Segment} with * {@link Segment#getType type} {@code "engine"}. * * @param engineId the engine ID; never {@code null} or blank * @see #root(String, String) */ public static UniqueId forEngine(String engineId) { Preconditions.notBlank(engineId, "engineId must not be null or blank"); return root(ENGINE_SEGMENT_TYPE, engineId); } /** * Create a root unique ID from the supplied {@code segmentType} and * {@code value} using the default format. * * @param segmentType the segment type; never {@code null} or blank * @param value the value; never {@code null} or blank * @see #forEngine(String) */ public static UniqueId root(String segmentType, String value) { return new UniqueId(new Segment(segmentType, value)); } @SuppressWarnings({ "serial", "RedundantSuppression" }) // always used with serializable implementation (List.copyOf()) // This is effectively final but not technically due to late initialization when deserializing private /* final */ List segments; // lazily computed private transient int hashCode; // lazily computed private transient @Nullable SoftReference toString; private UniqueId(Segment segment) { this(List.of(segment)); } /** * Initialize a {@code UniqueId} instance. */ UniqueId(List segments) { this.segments = List.copyOf(segments); } Optional getRoot() { return this.segments.isEmpty() ? Optional.empty() : Optional.of(this.segments.get(0)); } /** * Get the engine ID stored in this {@code UniqueId}, if available. * * @see #forEngine(String) */ public Optional getEngineId() { return getRoot().filter(segment -> ENGINE_SEGMENT_TYPE.equals(segment.getType())).map(Segment::getValue); } /** * Get the immutable list of {@linkplain Segment segments} that make up this * {@code UniqueId}. */ public List getSegments() { return this.segments; } /** * Construct a new {@code UniqueId} by appending a new {@link Segment}, based * on the supplied {@code segmentType} and {@code value}, to the end of this * {@code UniqueId}. * *

This {@code UniqueId} will not be modified. * *

Neither the {@code segmentType} nor the {@code value} may contain any * of the special characters used for constructing the string representation * of this {@code UniqueId}. * * @param segmentType the type of the segment; never {@code null} or blank * @param value the value of the segment; never {@code null} or blank */ public UniqueId append(String segmentType, String value) { return append(new Segment(segmentType, value)); } /** * Construct a new {@code UniqueId} by appending a new {@link Segment} to * the end of this {@code UniqueId}. * *

This {@code UniqueId} will not be modified. * * @param segment the segment to be appended; never {@code null} * * @since 1.1 */ @API(status = STABLE, since = "1.1") public UniqueId append(Segment segment) { Preconditions.notNull(segment, "segment must not be null"); List baseSegments = new ArrayList<>(this.segments.size() + 1); baseSegments.addAll(this.segments); baseSegments.add(segment); return new UniqueId(baseSegments); } /** * Construct a new {@code UniqueId} by appending a new {@link Segment}, based * on the supplied {@code engineId}, to the end of this {@code UniqueId}. * *

This {@code UniqueId} will not be modified. * *

The engine ID will be stored in a {@link Segment} with * {@link Segment#getType type} {@value #ENGINE_SEGMENT_TYPE}. * * @param engineId the engine ID; never {@code null} or blank * * @since 1.8 */ @API(status = STABLE, since = "1.10") public UniqueId appendEngine(String engineId) { return append(new Segment(ENGINE_SEGMENT_TYPE, engineId)); } /** * Determine if the supplied {@code UniqueId} is a prefix for this * {@code UniqueId}. * * @param potentialPrefix the {@code UniqueId} to be checked; never {@code null} * * @since 1.1 */ @API(status = STABLE, since = "1.1") public boolean hasPrefix(UniqueId potentialPrefix) { Preconditions.notNull(potentialPrefix, "potentialPrefix must not be null"); int size = this.segments.size(); int prefixSize = potentialPrefix.segments.size(); return size >= prefixSize && this.segments.subList(0, prefixSize).equals(potentialPrefix.segments); } /** * Construct a new {@code UniqueId} and removing the last {@link Segment} of * this {@code UniqueId}. * *

This {@code UniqueId} will not be modified. * * @return a new {@code UniqueId}; never {@code null} * @throws org.junit.platform.commons.PreconditionViolationException * if this {@code UniqueId} contains a single segment * @since 1.5 */ @API(status = STABLE, since = "1.5") public UniqueId removeLastSegment() { Preconditions.condition(this.segments.size() > 1, "Cannot remove last remaining segment"); return new UniqueId(this.segments.subList(0, this.segments.size() - 1)); } /** * Get the last {@link Segment} of this {@code UniqueId}. * * @return the last {@code Segment}; never {@code null} * @since 1.5 */ @API(status = STABLE, since = "1.5") public Segment getLastSegment() { return this.segments.get(this.segments.size() - 1); } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } @Serial private void writeObject(ObjectOutputStream s) throws IOException { SerializedForm serializedForm = new SerializedForm(this); serializedForm.serialize(s); } @Serial private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException { SerializedForm serializedForm = SerializedForm.deserialize(s); segments = serializedForm.segments; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } UniqueId that = (UniqueId) o; return this.segments.equals(that.segments); } @Override public int hashCode() { int value = this.hashCode; if (value == 0) { value = this.segments.hashCode(); if (value == 0) { // handle the edge case of the computed hashCode being 0 value = 1; } // this is a benign race like String#hash // we potentially read and write values from multiple threads // without a happens-before relationship // however the JMM guarantees us that we only ever see values // that were valid at one point, either 0 or the hash code // so we might end up not seeing a value that a different thread // has computed or multiple threads writing the same value this.hashCode = value; } return value; } /** * Generate the unique, formatted string representation of this {@code UniqueId} * using the configured {@link UniqueIdFormat}. */ @Override public String toString() { SoftReference s = this.toString; String value = s == null ? null : s.get(); if (value == null) { value = UniqueIdFormat.getDefault().format(this); // this is a benign race like String#hash // we potentially read and write values from multiple threads // without a happens-before relationship // however the JMM guarantees us that we only ever see values // that were valid at one point, either null or the toString value // so we might end up not seeing a value that a different thread // has computed or multiple threads writing the same value this.toString = new SoftReference<>(value); } return value; } /** * A segment of a {@link UniqueId} comprises a type and a * value. */ @API(status = STABLE, since = "1.0") public static final class Segment implements Serializable { @Serial private static final long serialVersionUID = 1L; private final String type; private final String value; /** * Create a new {@code Segment} using the supplied {@code type} and * {@code value}. * * @param type the type of this segment * @param value the value of this segment */ Segment(String type, String value) { Preconditions.notBlank(type, "type must not be null or blank"); Preconditions.notBlank(value, "value must not be null or blank"); this.type = type; this.value = value; } /** * Get the type of this segment. */ public String getType() { return this.type; } /** * Get the value of this segment. */ public String getValue() { return this.value; } @Override public int hashCode() { return Objects.hash(this.type, this.value); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Segment that = (Segment) o; return Objects.equals(this.type, that.type) && Objects.equals(this.value, that.value); } @Override public String toString() { // @formatter:off return new ToStringBuilder(this) .append("type", this.type) .append("value", this.value) .toString(); // @formatter:on } } /** * Represents the serialized output of {@code UniqueId}. The fields on this * class match the fields that {@code UniqueId} had prior to 6.1. */ private static final class SerializedForm implements Serializable { @Serial private static final long serialVersionUID = 1L; @SuppressWarnings({ "serial", "RedundantSuppression" }) // always used with serializable implementation (List.copyOf()) private final List segments; private final UniqueIdFormat uniqueIdFormat; SerializedForm(UniqueId uniqueId) { this.segments = uniqueId.segments; this.uniqueIdFormat = UniqueIdFormat.getDefault(); } @SuppressWarnings("unchecked") private SerializedForm(ObjectInputStream.GetField fields) throws IOException, ClassNotFoundException { this.segments = (List) fields.get("segments", null); this.uniqueIdFormat = UniqueIdFormat.getDefault(); } void serialize(ObjectOutputStream s) throws IOException { ObjectOutputStream.PutField fields = s.putFields(); fields.put("segments", segments); fields.put("uniqueIdFormat", uniqueIdFormat); s.writeFields(); } static SerializedForm deserialize(ObjectInputStream s) throws IOException, ClassNotFoundException { ObjectInputStream.GetField fields = s.readFields(); return new SerializedForm(fields); } } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueIdFormat.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine; import static java.util.stream.Collectors.joining; import java.io.Serial; import java.io.Serializable; import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.UniqueId.Segment; /** * Used to {@link #parse} a {@link UniqueId} from a string representation * or to {@link #format} a {@link UniqueId} into a string representation. * * @since 1.0 */ class UniqueIdFormat implements Serializable { @Serial private static final long serialVersionUID = 1L; private static final UniqueIdFormat defaultFormat = new UniqueIdFormat('[', ':', ']', '/'); static UniqueIdFormat getDefault() { return defaultFormat; } private static String quote(char c) { return Pattern.quote(String.valueOf(c)); } private static String encode(char c) { return URLEncoder.encode(String.valueOf(c), StandardCharsets.UTF_8); } private final char openSegment; private final char closeSegment; private final char segmentDelimiter; private final char typeValueSeparator; private final Pattern segmentPattern; private final HashMap encodedCharacterMap = new HashMap<>(); UniqueIdFormat(char openSegment, char typeValueSeparator, char closeSegment, char segmentDelimiter) { this.openSegment = openSegment; this.typeValueSeparator = typeValueSeparator; this.closeSegment = closeSegment; this.segmentDelimiter = segmentDelimiter; this.segmentPattern = Pattern.compile( "%s(.+)%s(.+)%s".formatted(quote(openSegment), quote(typeValueSeparator), quote(closeSegment)), Pattern.DOTALL); // Compute "forbidden" character encoding map. // Note that the map is always empty at this point. Thus the use of // computeIfAbsent() is purely syntactic sugar. encodedCharacterMap.computeIfAbsent('%', UniqueIdFormat::encode); encodedCharacterMap.computeIfAbsent('+', UniqueIdFormat::encode); encodedCharacterMap.computeIfAbsent(openSegment, UniqueIdFormat::encode); encodedCharacterMap.computeIfAbsent(typeValueSeparator, UniqueIdFormat::encode); encodedCharacterMap.computeIfAbsent(closeSegment, UniqueIdFormat::encode); encodedCharacterMap.computeIfAbsent(segmentDelimiter, UniqueIdFormat::encode); } /** * Parse a {@code UniqueId} from the supplied string representation. * * @return a properly constructed {@code UniqueId} * @throws JUnitException if the string cannot be parsed */ UniqueId parse(String source) throws JUnitException { String[] parts = source.split(String.valueOf(this.segmentDelimiter)); List segments = Arrays.stream(parts).map(this::createSegment).toList(); return new UniqueId(segments); } private Segment createSegment(String segmentString) throws JUnitException { Matcher segmentMatcher = this.segmentPattern.matcher(segmentString); if (!segmentMatcher.matches()) { throw new JUnitException("'%s' is not a well-formed UniqueId segment".formatted(segmentString)); } String type = decode(checkAllowed(segmentMatcher.group(1))); String value = decode(checkAllowed(segmentMatcher.group(2))); return new Segment(type, value); } private String checkAllowed(String typeOrValue) { checkDoesNotContain(typeOrValue, this.segmentDelimiter); checkDoesNotContain(typeOrValue, this.typeValueSeparator); checkDoesNotContain(typeOrValue, this.openSegment); checkDoesNotContain(typeOrValue, this.closeSegment); return typeOrValue; } private void checkDoesNotContain(String typeOrValue, char forbiddenCharacter) { Preconditions.condition(typeOrValue.indexOf(forbiddenCharacter) < 0, () -> "type or value '%s' must not contain '%s'".formatted(typeOrValue, forbiddenCharacter)); } /** * Format and return the string representation of the supplied {@code UniqueId}. */ String format(UniqueId uniqueId) { // @formatter:off return uniqueId.getSegments().stream() .map(this::describe) .collect(joining(String.valueOf(this.segmentDelimiter))); // @formatter:on } private String describe(Segment segment) { String body = encode(segment.getType()) + typeValueSeparator + encode(segment.getValue()); return openSegment + body + closeSegment; } private String encode(String s) { StringBuilder builder = new StringBuilder(s.length()); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); String value = encodedCharacterMap.get(c); if (value == null) { builder.append(c); continue; } builder.append(value); } return builder.toString(); } private static String decode(String s) { return URLDecoder.decode(s, StandardCharsets.UTF_8); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/AbstractClassNameFilter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static java.util.stream.Collectors.joining; import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.function.Predicate; import java.util.regex.Pattern; import org.junit.platform.commons.util.Preconditions; /** * Abstract {@link ClassNameFilter} that servers as a superclass * for filters including or excluding fully qualified class names * based on pattern-matching. * * @since 1.0 */ abstract class AbstractClassNameFilter implements ClassNameFilter { protected final List patterns; protected final String patternDescription; AbstractClassNameFilter(String... patterns) { Preconditions.notEmpty(patterns, "patterns array must not be null or empty"); Preconditions.containsNoNullElements(patterns, "patterns array must not contain null elements"); this.patterns = Arrays.stream(patterns).map(Pattern::compile).toList(); this.patternDescription = Arrays.stream(patterns).collect(joining("' OR '", "'", "'")); } @Override public abstract Predicate toPredicate(); protected Optional findMatchingPattern(String className) { return this.patterns.stream().filter(pattern -> pattern.matcher(className).matches()).findAny(); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClassNameFilter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; import org.junit.platform.engine.DiscoveryFilter; /** * {@link DiscoveryFilter} that is applied to the name of a {@link Class}. * * @since 1.0 * @see #includeClassNamePatterns(String...) * @see #excludeClassNamePatterns(String...) * @see PackageNameFilter */ @API(status = STABLE, since = "1.0") public interface ClassNameFilter extends DiscoveryFilter { /** * Standard include pattern in the form of a regular expression that is * used to match against fully qualified class names: * {@value org.junit.platform.engine.discovery.ClassNameFilter#STANDARD_INCLUDE_PATTERN} * *

This pattern matches against class names beginning with {@code Test} * or ending with {@code Test} or {@code Tests} (in any package). */ // Implementation notes: // - Test.* :: "Test" prefix for classes in default package // - .+[.$]Test.* :: "Test" prefix for top-level and nested classes in a named package // - .*Tests? :: "Test" and "Tests" suffixes in any package String STANDARD_INCLUDE_PATTERN = "^(Test.*|.+[.$]Test.*|.*Tests?)$"; /** * Create a new include {@link ClassNameFilter} based on the * supplied patterns. * *

The patterns are combined using OR semantics, i.e. if the fully * qualified name of a class matches against at least one of the patterns, * the class will be included in the result set. * * @param patterns regular expressions to match against fully qualified * class names; never {@code null}, empty, or containing {@code null} * @see Class#getName() * @see #excludeClassNamePatterns(String...) */ static ClassNameFilter includeClassNamePatterns(String... patterns) { return new IncludeClassNameFilter(patterns); } /** * Create a new exclude {@link ClassNameFilter} based on the * supplied patterns. * *

The patterns are combined using OR semantics, i.e. if the fully * qualified name of a class matches against at least one of the patterns, * the class will be excluded from the result set. * * @param patterns regular expressions to match against fully qualified * class names; never {@code null}, empty, or containing {@code null} * @see Class#getName() * @see #includeClassNamePatterns(String...) */ static ClassNameFilter excludeClassNamePatterns(String... patterns) { return new ExcludeClassNameFilter(patterns); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClassSelector.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.util.Objects; import java.util.Optional; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.function.Try; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.DiscoverySelectorIdentifier; /** * A {@link DiscoverySelector} that selects a {@link Class} or class name so * that {@link org.junit.platform.engine.TestEngine TestEngines} can discover * tests or containers based on classes. * *

If a Java {@link Class} reference is provided, the selector will return * that {@code Class} and its class name accordingly. If a class name is * provided, the selector will only attempt to lazily load the {@link Class} * if {@link #getJavaClass()} is invoked. * *

In this context, Java {@link Class} means anything that can be referenced * as a {@link Class} on the JVM — for example, classes from other JVM * languages such Groovy, Scala, etc. * * @since 1.0 * @see DiscoverySelectors#selectClass(String) * @see DiscoverySelectors#selectClass(Class) * @see org.junit.platform.engine.support.descriptor.ClassSource */ @API(status = STABLE, since = "1.0") public final class ClassSelector implements DiscoverySelector { private final @Nullable ClassLoader classLoader; private final String className; private @Nullable Class javaClass; ClassSelector(@Nullable ClassLoader classLoader, String className) { this.className = className; this.classLoader = classLoader; } ClassSelector(Class javaClass) { this.className = javaClass.getName(); this.classLoader = javaClass.getClassLoader(); this.javaClass = javaClass; } /** * Get the {@link ClassLoader} used to load the selected class. * * @return the {@code ClassLoader}; potentially {@code null} * @since 1.10 */ @API(status = MAINTAINED, since = "1.13.3") public @Nullable ClassLoader getClassLoader() { return this.classLoader; } /** * Get the selected class name. */ public String getClassName() { return this.className; } /** * Get the selected {@link Class}. * *

If the {@link Class} was not provided, but only the name, this method * attempts to lazily load the {@link Class} based on its name and throws a * {@link PreconditionViolationException} if the class cannot be loaded. */ public Class getJavaClass() { if (this.javaClass == null) { // @formatter:off Try> tryToLoadClass = this.classLoader == null ? ReflectionSupport.tryToLoadClass(this.className) : ReflectionSupport.tryToLoadClass(this.className, this.classLoader); this.javaClass = tryToLoadClass.getNonNullOrThrow(cause -> new PreconditionViolationException("Could not load class with name: " + this.className, cause)); // @formatter:on } return this.javaClass; } /** * @since 1.3 */ @API(status = STABLE, since = "1.3") @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ClassSelector that = (ClassSelector) o; return Objects.equals(this.className, that.className); } /** * @since 1.3 */ @API(status = STABLE, since = "1.3") @Override public int hashCode() { return this.className.hashCode(); } @Override public String toString() { // @formatter:off return new ToStringBuilder(this) .append("className", this.className) .append("classLoader", this.classLoader) .toString(); // @formatter:on } @Override public Optional toIdentifier() { return Optional.of(DiscoverySelectorIdentifier.create(IdentifierParser.PREFIX, this.className)); } /** * The {@link DiscoverySelectorIdentifierParser} for {@link ClassSelector * ClassSelectors}. */ @API(status = INTERNAL, since = "1.11") public static class IdentifierParser implements DiscoverySelectorIdentifierParser { private static final String PREFIX = "class"; public IdentifierParser() { } @Override public String getPrefix() { return PREFIX; } @Override public Optional parse(DiscoverySelectorIdentifier identifier, Context context) { return Optional.of(DiscoverySelectors.selectClass(identifier.getValue())); } } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathResourceSelector.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static java.util.Collections.unmodifiableSet; import static java.util.stream.Collectors.toCollection; import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.util.LinkedHashSet; import java.util.Objects; import java.util.Optional; import java.util.Set; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.function.Try; import org.junit.platform.commons.io.Resource; import org.junit.platform.commons.support.ResourceSupport; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.StringUtils; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.DiscoverySelectorIdentifier; /** * A {@link DiscoverySelector} that selects the name of a classpath resource * so that {@link org.junit.platform.engine.TestEngine TestEngines} can load resources * from the classpath — for example, to load XML or JSON files from the classpath, * potentially within JARs. * *

Since {@linkplain org.junit.platform.engine.TestEngine engines} are not * expected to modify the classpath, the classpath resource represented by this * selector must be on the classpath of the * {@linkplain Thread#getContextClassLoader() context class loader} of the * {@linkplain Thread thread} that uses it. * *

Note: Since Java 9, all resources are on the module path. Either in * named or unnamed modules. These resources are also considered to be * classpath resources. * * @since 1.0 * @see DiscoverySelectors#selectClasspathResource(String) * @see ClasspathRootSelector * @see #getClasspathResourceName() */ @API(status = STABLE, since = "1.0") public final class ClasspathResourceSelector implements DiscoverySelector { private final String classpathResourceName; private final @Nullable FilePosition position; private @Nullable Set resources; ClasspathResourceSelector(String classpathResourceName, @Nullable 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; } ClasspathResourceSelector(Set resources) { this(resources.iterator().next().getName(), null); this.resources = unmodifiableSet(new LinkedHashSet<>(resources)); } /** * Get the name of the selected classpath resource. * *

The name of a classpath resource must follow the semantics * for resource paths as defined in {@link ClassLoader#getResource(String)}. * * @see ClassLoader#getResource(String) * @see ClassLoader#getResourceAsStream(String) * @see ClassLoader#getResources(String) */ public String getClasspathResourceName() { return this.classpathResourceName; } /** * Get the selected {@link Resource resources}. * *

If the {@link Resource resources} were not provided, but only their name, * this method attempts to lazily load the {@link Resource resources} based on * their name and throws a {@link PreconditionViolationException} if the * resource cannot be loaded. * * @since 1.12 * @deprecated Please use {{@link #getResources()}} instead. */ @API(status = DEPRECATED, since = "1.14") @Deprecated(since = "1.14", forRemoval = true) @SuppressWarnings("removal") public Set getClasspathResources() { return getResources().stream() // .map(org.junit.platform.commons.support.Resource::of) // .collect(toCollection(LinkedHashSet::new)); } /** * Get the selected {@link Resource resources}. * *

If the {@link Resource resources} were not provided, but only their name, * this method attempts to lazily load the {@link Resource resources} based on * their name and throws a {@link PreconditionViolationException} if the * resource cannot be loaded. * * @since 1.14 */ @API(status = MAINTAINED, since = "1.14") public Set getResources() { if (this.resources == null) { Try> tryToGetResource = ResourceSupport.tryToGetResources(this.classpathResourceName); Set classpathResources = tryToGetResource.getNonNullOrThrow( // cause -> new PreconditionViolationException( // "Could not load resource(s) with name: " + this.classpathResourceName, cause)); if (classpathResources.isEmpty()) { throw new PreconditionViolationException( "Could not find any resource(s) with name: " + this.classpathResourceName); } this.resources = unmodifiableSet(classpathResources); } return this.resources; } /** * Get the selected {@code FilePosition} within the classpath resource. */ public Optional getPosition() { return Optional.ofNullable(this.position); } /** * @since 1.3 */ @API(status = STABLE, since = "1.3") @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ClasspathResourceSelector that = (ClasspathResourceSelector) o; return Objects.equals(this.classpathResourceName, that.classpathResourceName) && Objects.equals(this.position, that.position); } /** * @since 1.3 */ @API(status = STABLE, since = "1.3") @Override public int hashCode() { return Objects.hash(this.classpathResourceName, this.position); } @Override public String toString() { // @formatter:off return new ToStringBuilder(this) .append("classpathResourceName", this.classpathResourceName) .append("position", this.position) .toString(); // @formatter:on } @Override public Optional toIdentifier() { if (this.position == null) { return Optional.of(DiscoverySelectorIdentifier.create(IdentifierParser.PREFIX, this.classpathResourceName)); } else { return Optional.of(DiscoverySelectorIdentifier.create(IdentifierParser.PREFIX, "%s?%s".formatted(this.classpathResourceName, this.position.toQueryPart()))); } } /** * The {@link DiscoverySelectorIdentifierParser} for * {@link ClasspathResourceSelector ClasspathResourceSelectors}. */ @API(status = INTERNAL, since = "1.11") public static class IdentifierParser implements DiscoverySelectorIdentifierParser { private static final String PREFIX = "resource"; public IdentifierParser() { } @Override public String getPrefix() { return PREFIX; } @Override public Optional parse(DiscoverySelectorIdentifier identifier, Context context) { return Optional.of(StringUtils.splitIntoTwo('?', identifier.getValue()).map( // DiscoverySelectors::selectClasspathResource, // (resourceName, query) -> { FilePosition position = FilePosition.fromQuery(query).orElse(null); return DiscoverySelectors.selectClasspathResource(resourceName, position); } // )); } } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathRootSelector.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; import static org.junit.platform.commons.util.CollectionUtils.getFirstElement; import java.net.URI; import java.nio.file.Path; import java.util.Objects; import java.util.Optional; import java.util.Set; import org.apiguardian.api.API; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.DiscoverySelectorIdentifier; /** * A {@link DiscoverySelector} that selects a classpath root so that * {@link org.junit.platform.engine.TestEngine TestEngines} can search for class * files or resources within the physical classpath — for example, to * scan for test classes. * *

Since {@linkplain org.junit.platform.engine.TestEngine engines} are not * expected to modify the classpath, the classpath root represented by this * selector must be on the classpath of the * {@linkplain Thread#getContextClassLoader() context class loader} of the * {@linkplain Thread thread} that uses this selector. * * @since 1.0 * @see DiscoverySelectors#selectClasspathRoots(java.util.Set) * @see ClasspathResourceSelector * @see Thread#getContextClassLoader() */ @API(status = STABLE, since = "1.0") public final class ClasspathRootSelector implements DiscoverySelector { private final URI classpathRoot; ClasspathRootSelector(URI classpathRoot) { this.classpathRoot = Preconditions.notNull(classpathRoot, "classpathRoot must not be null"); } /** * Get the selected classpath root directory as an {@link URI}. */ public URI getClasspathRoot() { return this.classpathRoot; } /** * @since 1.3 */ @API(status = STABLE, since = "1.3") @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ClasspathRootSelector that = (ClasspathRootSelector) o; return Objects.equals(this.classpathRoot, that.classpathRoot); } /** * @since 1.3 */ @API(status = STABLE, since = "1.3") @Override public int hashCode() { return this.classpathRoot.hashCode(); } @Override public String toString() { return new ToStringBuilder(this).append("classpathRoot", this.classpathRoot).toString(); } @Override public Optional toIdentifier() { return Optional.of(DiscoverySelectorIdentifier.create(IdentifierParser.PREFIX, this.classpathRoot.toString())); } /** * The {@link DiscoverySelectorIdentifierParser} for * {@link ClasspathRootSelector ClasspathRootSelectors}. */ @API(status = INTERNAL, since = "1.11") public static class IdentifierParser implements DiscoverySelectorIdentifierParser { private static final String PREFIX = "classpath-root"; public IdentifierParser() { } @Override public String getPrefix() { return PREFIX; } @Override public Optional parse(DiscoverySelectorIdentifier identifier, Context context) { Path path = Path.of(URI.create(identifier.getValue())); return getFirstElement(DiscoverySelectors.selectClasspathRoots(Set.of(path))); } } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DirectorySelector.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; import java.io.File; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Path; import java.util.Objects; import java.util.Optional; import org.apiguardian.api.API; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.DiscoverySelectorIdentifier; /** * A {@link DiscoverySelector} that selects a directory so that * {@link org.junit.platform.engine.TestEngine TestEngines} * can discover tests or containers based on directories in the * file system. * * @since 1.0 * @see DiscoverySelectors#selectDirectory(String) * @see DiscoverySelectors#selectDirectory(File) * @see FileSelector * @see #getDirectory() * @see #getPath() * @see #getRawPath() */ @API(status = STABLE, since = "1.0") public final class DirectorySelector implements DiscoverySelector { private final String path; DirectorySelector(String path) { this.path = path; } /** * Get the selected directory as a {@link java.io.File}. * * @see #getPath() * @see #getRawPath() */ public File getDirectory() { return new File(this.path); } /** * Get the selected directory as a {@link java.nio.file.Path} using the * {@linkplain FileSystems#getDefault default} {@link FileSystem}. * * @see #getDirectory() * @see #getRawPath() */ public Path getPath() { return Path.of(this.path); } /** * Get the selected directory as a raw path. * * @see #getDirectory() * @see #getPath() */ public String getRawPath() { return this.path; } /** * @since 1.3 */ @API(status = STABLE, since = "1.3") @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } DirectorySelector that = (DirectorySelector) o; return Objects.equals(this.path, that.path); } /** * @since 1.3 */ @API(status = STABLE, since = "1.3") @Override public int hashCode() { return this.path.hashCode(); } @Override public String toString() { return new ToStringBuilder(this).append("path", this.path).toString(); } @Override public Optional toIdentifier() { return Optional.of(DiscoverySelectorIdentifier.create(IdentifierParser.PREFIX, this.path)); } /** * The {@link DiscoverySelectorIdentifierParser} for * {@link DirectorySelector DirectorySelectors}. */ @API(status = INTERNAL, since = "1.11") public static class IdentifierParser implements DiscoverySelectorIdentifierParser { private static final String PREFIX = "directory"; public IdentifierParser() { } @Override public String getPrefix() { return PREFIX; } @Override public Optional parse(DiscoverySelectorIdentifier identifier, Context context) { return Optional.of(DiscoverySelectors.selectDirectory(identifier.getValue())); } } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectorIdentifierParser.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static org.apiguardian.api.API.Status.MAINTAINED; import java.util.Optional; import org.apiguardian.api.API; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.DiscoverySelectorIdentifier; /** * Parser for a {@link DiscoverySelectorIdentifier} with a specific prefix. * *

Implementations of this interface can be registered using the Java service * loader mechanism to extend the set of supported prefixes for * {@link DiscoverySelectorIdentifier DiscoverySelectorIdentifiers}. * * @since 1.11 * @see DiscoverySelectors#parse(String) */ @API(status = MAINTAINED, since = "1.13.3") public interface DiscoverySelectorIdentifierParser { /** * Get the prefix that this parser supports. * * @return the prefix that this parser supports; never {@code null} or blank */ String getPrefix(); /** * Parse the supplied {@link DiscoverySelectorIdentifier}. * *

The JUnit Platform will only invoke this method if the supplied * {@link DiscoverySelectorIdentifier} has a prefix that matches the value * returned by {@link #getPrefix()}. * * @param identifier the {@link DiscoverySelectorIdentifier} to parse * @param context the {@link Context} to use for parsing * @return an {@link Optional} containing the parsed {@link DiscoverySelector}; * never {@code null} but potentially empty */ Optional parse(DiscoverySelectorIdentifier identifier, Context context); /** * Context for parsing {@link DiscoverySelectorIdentifier DiscoverySelectorIdentifiers}. */ interface Context { /** * Parse the supplied selector. * *

This method is intended to be used by implementations of * {@link DiscoverySelectorIdentifierParser#parse} for selectors that * contain other selectors. */ Optional parse(String selector); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectorIdentifierParsers.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static java.util.Collections.unmodifiableMap; import static java.util.Objects.requireNonNull; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.ServiceLoader; import java.util.stream.Stream; import org.junit.platform.commons.util.ClassLoaderUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.DiscoverySelectorIdentifier; /** * Utility class for parsing {@link DiscoverySelectorIdentifier * DiscoverySelectorIdentifiers}. * * @since 1.11 */ class DiscoverySelectorIdentifierParsers { static Stream parseAll(String... identifiers) { Preconditions.notNull(identifiers, "identifiers must not be null"); return Stream.of(identifiers) // .map(DiscoverySelectorIdentifierParsers::parse) // .flatMap(Optional::stream); } static Stream parseAll(Collection identifiers) { Preconditions.notNull(identifiers, "identifiers must not be null"); return identifiers.stream() // .map(DiscoverySelectorIdentifierParsers::parse) // .flatMap(Optional::stream); } static Optional parse(String identifier) { Preconditions.notNull(identifier, "identifier must not be null"); return parse(DiscoverySelectorIdentifier.parse(identifier)); } static Optional parse(DiscoverySelectorIdentifier identifier) { Preconditions.notNull(identifier, "identifier must not be null"); DiscoverySelectorIdentifierParser parser = Preconditions.notNull( Singleton.INSTANCE.parsersByPrefix.get(identifier.getPrefix()), "No parser for prefix: " + identifier.getPrefix()); return parser.parse(identifier, DiscoverySelectorIdentifierParsers::parse); } private enum Singleton { INSTANCE; private final Map parsersByPrefix; Singleton() { Map parsersByPrefix = new HashMap<>(); Iterable loadedParsers = ServiceLoader.load( DiscoverySelectorIdentifierParser.class, ClassLoaderUtils.getDefaultClassLoader()); for (DiscoverySelectorIdentifierParser parser : loadedParsers) { DiscoverySelectorIdentifierParser previous = parsersByPrefix.put(parser.getPrefix(), parser); Preconditions.condition(previous == null, () -> "Duplicate parser for prefix: [%s]; candidate a: [%s]; candidate b: [%s]".formatted( parser.getPrefix(), requireNonNull(previous).getClass().getName(), parser.getClass().getName())); } this.parsersByPrefix = unmodifiableMap(parsersByPrefix); } } private DiscoverySelectorIdentifierParsers() { } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectors.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.io.File; import java.io.IOException; import java.lang.reflect.Method; import java.net.URI; import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.stream.Stream; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.io.Resource; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.DiscoverySelectorIdentifier; import org.junit.platform.engine.UniqueId; /** * Collection of {@code static} factory methods for creating * {@link DiscoverySelector DiscoverySelectors}. * * @since 1.0 * @see UriSelector * @see FileSelector * @see DirectorySelector * @see ClasspathRootSelector * @see ClasspathResourceSelector * @see ModuleSelector * @see PackageSelector * @see ClassSelector * @see MethodSelector * @see NestedClassSelector * @see NestedMethodSelector * @see UniqueIdSelector * @see DiscoverySelectorIdentifier */ @API(status = STABLE, since = "1.0") public final class DiscoverySelectors { private DiscoverySelectors() { /* no-op */ } /** * Create a {@code UriSelector} for the supplied URI. * * @param uri the URI to select; never {@code null} or blank * @see UriSelector * @see #selectUri(URI) * @see #selectFile(String) * @see #selectFile(File) * @see #selectDirectory(String) * @see #selectDirectory(File) */ public static UriSelector selectUri(String uri) { Preconditions.notBlank(uri, "URI must not be null or blank"); try { return new UriSelector(new URI(uri)); } catch (URISyntaxException ex) { throw new PreconditionViolationException("Failed to create a java.net.URI from: " + uri, ex); } } /** * Create a {@code UriSelector} for the supplied {@link URI}. * * @param uri the URI to select; never {@code null} * @see UriSelector * @see #selectUri(String) * @see #selectFile(String) * @see #selectFile(File) * @see #selectDirectory(String) * @see #selectDirectory(File) */ public static UriSelector selectUri(URI uri) { Preconditions.notNull(uri, "URI must not be null"); return new UriSelector(uri); } /** * Create a {@code FileSelector} for the supplied file path. * *

This method selects the file using the supplied path as is, * without verifying if the file exists. * * @param path the path to the file to select; never {@code null} or blank * @see FileSelector * @see #selectFile(File) * @see #selectFile(String, FilePosition) * @see #selectFile(File, FilePosition) * @see #selectDirectory(String) * @see #selectDirectory(File) */ public static FileSelector selectFile(String path) { return selectFile(path, null); } /** * Create a {@code FileSelector} for the supplied {@linkplain File file}. * *

This method selects the file in its {@linkplain File#getCanonicalPath() * canonical} form and throws a {@link PreconditionViolationException} if the * file does not exist. * * @param file the file to select; never {@code null} * @see FileSelector * @see #selectFile(String) * @see #selectFile(File, FilePosition) * @see #selectFile(String, FilePosition) * @see #selectDirectory(String) * @see #selectDirectory(File) */ public static FileSelector selectFile(File file) { return selectFile(file, null); } /** * Create a {@code FileSelector} for the supplied file path. * *

This method selects the file using the supplied path as is, * without verifying if the file exists. * * @param path the path to the file to select; never {@code null} or blank * @param position the position inside the file; may be {@code null} * @see FileSelector * @see #selectFile(String) * @see #selectFile(File) * @see #selectFile(File, FilePosition) * @see #selectDirectory(String) * @see #selectDirectory(File) */ public static FileSelector selectFile(String path, @Nullable FilePosition position) { Preconditions.notBlank(path, "File path must not be null or blank"); return new FileSelector(path, position); } /** * Create a {@code FileSelector} for the supplied {@linkplain File file}. * *

This method selects the file in its {@linkplain File#getCanonicalPath() * canonical} form and throws a {@link PreconditionViolationException} if the * file does not exist. * * @param file the file to select; never {@code null} * @param position the position inside the file; may be {@code null} * @see FileSelector * @see #selectFile(File) * @see #selectFile(String) * @see #selectFile(String, FilePosition) * @see #selectDirectory(String) * @see #selectDirectory(File) */ public static FileSelector selectFile(File file, @Nullable FilePosition position) { Preconditions.notNull(file, "File must not be null"); Preconditions.condition(file.isFile(), () -> "The supplied java.io.File [%s] must represent an existing file".formatted(file)); try { return new FileSelector(file.getCanonicalPath(), position); } catch (IOException ex) { throw new PreconditionViolationException("Failed to retrieve canonical path for file: " + file, ex); } } /** * Create a {@code DirectorySelector} for the supplied directory path. * *

This method selects the directory using the supplied path as is, * without verifying if the directory exists. * * @param path the path to the directory to select; never {@code null} or blank * @see DirectorySelector * @see #selectDirectory(File) * @see #selectFile(String) * @see #selectFile(File) */ public static DirectorySelector selectDirectory(String path) { Preconditions.notBlank(path, "Directory path must not be null or blank"); return new DirectorySelector(path); } /** * Create a {@code DirectorySelector} for the supplied {@linkplain File directory}. * *

This method selects the directory in its {@linkplain File#getCanonicalPath() * canonical} form and throws a {@link PreconditionViolationException} if the * directory does not exist. * * @param directory the directory to select; never {@code null} * @see DirectorySelector * @see #selectDirectory(String) * @see #selectFile(String) * @see #selectFile(File) */ public static DirectorySelector selectDirectory(File directory) { Preconditions.notNull(directory, "Directory must not be null"); Preconditions.condition(directory.isDirectory(), () -> "The supplied java.io.File [%s] must represent an existing directory".formatted(directory)); try { return new DirectorySelector(directory.getCanonicalPath()); } catch (IOException ex) { throw new PreconditionViolationException("Failed to retrieve canonical path for directory: " + directory, ex); } } /** * Create a list of {@code ClasspathRootSelectors} for the supplied * classpath roots (directories or JAR files). * *

Since the supplied paths are converted to {@link URI URIs}, the * {@link java.nio.file.FileSystem} that created them must be the * {@linkplain java.nio.file.FileSystems#getDefault() default} or one that * has been created by an installed * {@link java.nio.file.spi.FileSystemProvider}. * *

Since {@linkplain org.junit.platform.engine.TestEngine engines} are not * expected to modify the classpath, the classpath roots represented by the * resulting selectors must be on the classpath of the * {@linkplain Thread#getContextClassLoader() context class loader} of the * {@linkplain Thread thread} that uses these selectors. * *

The {@link Set} supplied to this method should have a reliable iteration * order to support reliable discovery and execution order. It is therefore * recommended that the set be a {@link java.util.SequencedSet} (on Java 21 * or higher), {@link java.util.SortedSet}, {@link java.util.LinkedHashSet}, * or similar. Note that {@link Set#of(Object[])} and related {@code Set.of()} * methods do not guarantee a reliable iteration order. * * @param classpathRoots set of directories and JAR files in the filesystem * that represent classpath roots; never {@code null} * @return a list of selectors for the supplied classpath roots; elements * which do not physically exist in the filesystem will be filtered out * @see ClasspathRootSelector * @see Thread#getContextClassLoader() */ public static List selectClasspathRoots(Set classpathRoots) { Preconditions.notNull(classpathRoots, "classpathRoots must not be null"); // @formatter:off return classpathRoots.stream() .filter(Files::exists) .map(Path::toUri) .map(ClasspathRootSelector::new) // unmodifiable since selectClasspathRoots is a public, non-internal method .toList(); // @formatter:on } /** * Create a {@code ClasspathResourceSelector} for the supplied classpath * resource name. * *

The name of a classpath resource must follow the semantics * for resource paths as defined in {@link ClassLoader#getResource(String)}. * *

If the supplied classpath resource name is prefixed with a slash * ({@code /}), the slash will be removed. * *

Since {@linkplain org.junit.platform.engine.TestEngine engines} are not * expected to modify the classpath, the supplied classpath resource must be * on the classpath of the * {@linkplain Thread#getContextClassLoader() context class loader} of the * {@linkplain Thread thread} that uses the resulting selector. * * @param classpathResourceName the name of the classpath resource; never * {@code null} or blank * @see #selectClasspathResource(String, FilePosition) * @see #selectClasspathResources(String...) * @see #selectClasspathResourceByName(Set) * @see ClasspathResourceSelector * @see ClassLoader#getResource(String) * @see ClassLoader#getResourceAsStream(String) * @see ClassLoader#getResources(String) */ public static ClasspathResourceSelector selectClasspathResource(String classpathResourceName) { return selectClasspathResource(classpathResourceName, null); } /** * Create a {@code ClasspathResourceSelector} for the supplied classpath * resource name. * *

The name of a classpath resource must follow the semantics * for resource paths as defined in {@link ClassLoader#getResource(String)}. * *

If the supplied classpath resource name is prefixed with a slash * ({@code /}), the slash will be removed. * *

Since {@linkplain org.junit.platform.engine.TestEngine engines} are not * expected to modify the classpath, the supplied classpath resource must be * on the classpath of the * {@linkplain Thread#getContextClassLoader() context class loader} of the * {@linkplain Thread thread} that uses the resulting selector. * * @param classpathResourceName the name of the classpath resource; never * {@code null} or blank * @param position the position inside the classpath resource; may be {@code null} * @see #selectClasspathResource(String) * @see #selectClasspathResources(String...) * @see #selectClasspathResourceByName(Set) * @see ClasspathResourceSelector * @see ClassLoader#getResource(String) * @see ClassLoader#getResourceAsStream(String) * @see ClassLoader#getResources(String) */ public static ClasspathResourceSelector selectClasspathResource(String classpathResourceName, @Nullable FilePosition position) { Preconditions.notBlank(classpathResourceName, "classpath resource name must not be null or blank"); return new ClasspathResourceSelector(classpathResourceName, position); } /** * Create a {@code ClasspathResourceSelector} for the supplied classpath * resources. * *

Since {@linkplain org.junit.platform.engine.TestEngine engines} are not * expected to modify the classpath, the supplied resource must be on the * classpath of the * {@linkplain Thread#getContextClassLoader() context class loader} of the * {@linkplain Thread thread} that uses the resulting selector. * *

Note: Since Java 9, all resources are on the module path. Either in * named or unnamed modules. These resources are also considered to be * classpath resources. * *

The {@link Set} supplied to this method should have a reliable iteration * order to support reliable discovery and execution order. It is therefore * recommended that the set be a {@link java.util.SequencedSet} (on Java 21 * or higher), {@link java.util.SortedSet}, {@link java.util.LinkedHashSet}, * or similar. Note that {@link Set#of(Object[])} and related {@code Set.of()} * methods do not guarantee a reliable iteration order. * * @param classpathResources a set of classpath resources; never * {@code null} or empty. All resources must have the same name, may not * be {@code null} or blank. * @since 1.12 * @see #selectClasspathResource(String, FilePosition) * @see #selectClasspathResource(String) * @see ClasspathResourceSelector * @see ReflectionSupport#tryToGetResources(String) * @deprecated Please use {@link #selectClasspathResourceByName(Set)} instead. */ @API(status = DEPRECATED, since = "1.14") @Deprecated(since = "1.14", forRemoval = true) @SuppressWarnings("removal") public static ClasspathResourceSelector selectClasspathResource( Set classpathResources) { return selectClasspathResourceByName(classpathResources); } /** * Create a {@code ClasspathResourceSelector} for the supplied classpath * resources. * *

Since {@linkplain org.junit.platform.engine.TestEngine engines} are not * expected to modify the classpath, the supplied resource must be on the * classpath of the * {@linkplain Thread#getContextClassLoader() context class loader} of the * {@linkplain Thread thread} that uses the resulting selector. * *

Note: Since Java 9, all resources are on the module path. Either in * named or unnamed modules. These resources are also considered to be * classpath resources. * *

The {@link Set} supplied to this method should have a reliable iteration * order to support reliable discovery and execution order. It is therefore * recommended that the set be a {@link java.util.SequencedSet} (on Java 21 * or higher), {@link java.util.SortedSet}, {@link java.util.LinkedHashSet}, * or similar. Note that {@link Set#of(Object[])} and related {@code Set.of()} * methods do not guarantee a reliable iteration order. * * @param classpathResources a set of classpath resources; never * {@code null} or empty. All resources must have the same name, may not * be {@code null} or blank. * @since 1.14 * @see #selectClasspathResource(String, FilePosition) * @see #selectClasspathResource(String) * @see ClasspathResourceSelector * @see org.junit.platform.commons.support.ResourceSupport#tryToGetResources(String) */ @API(status = MAINTAINED, since = "1.14") public static ClasspathResourceSelector selectClasspathResourceByName(Set classpathResources) { Preconditions.notEmpty(classpathResources, "classpath resources must not be null or empty"); Preconditions.containsNoNullElements(classpathResources, "individual classpath resources must not be null"); List resourceNames = classpathResources.stream().map(Resource::getName).distinct().toList(); Preconditions.condition(resourceNames.size() == 1, "all classpath resources must have the same name"); Preconditions.notBlank(resourceNames.get(0), "classpath resource names must not be null or blank"); return new ClasspathResourceSelector(classpathResources); } /** * Create a {@code ClasspathResourceSelector} for each supplied classpath * resource name. * * @param classpathResourceNames the names of the classpath resource; never * {@code null} and never containing {@code null} or blank references. * @since 6.1 * @see #selectClasspathResource(String) * @see #selectClasspathResources(String...) * @see ClasspathResourceSelector */ @API(status = EXPERIMENTAL, since = "6.1") public static List selectClasspathResources(String... classpathResourceNames) { Preconditions.notNull(classpathResourceNames, "classpathResourceNames must not be null"); return selectClasspathResources(Arrays.asList(classpathResourceNames)); } /** * Create a {@code ClasspathResourceSelector} for each supplied classpath * resource name. * * @param classpathResourceNames the names of the classpath resource; never * {@code null} and never containing {@code null} or blank references. * @since 6.1 * @see #selectClasspathResource(String) * @see #selectClasspathResources(String...) * @see ClasspathResourceSelector */ @API(status = EXPERIMENTAL, since = "6.1") public static List selectClasspathResources(List classpathResourceNames) { Preconditions.notNull(classpathResourceNames, "classpathResourceNames must not be null"); Preconditions.containsNoBlankElements(classpathResourceNames, "Individual classpathResourceNames must not be null or blank"); return classpathResourceNames.stream() // .distinct() // .map(DiscoverySelectors::selectClasspathResource) // .toList(); } /** * Create a {@code ModuleSelector} for the supplied module name. * *

The unnamed module is not supported. * * @param moduleName the module name to select; never {@code null} or blank * @since 1.1 * @see ModuleSelector */ @API(status = STABLE, since = "1.10") public static ModuleSelector selectModule(String moduleName) { Preconditions.notBlank(moduleName, "Module name must not be null or blank"); return new ModuleSelector(moduleName.strip()); } /** * Create a {@code ModuleSelector} for the supplied module. * *

The unnamed module is not supported. * * @param module the module to select; never {@code null} or unnamed * @since 6.1 * @see ModuleSelector */ @API(status = EXPERIMENTAL, since = "6.1") public static ModuleSelector selectModule(Module module) { Preconditions.notNull(module, "Module must not be null"); Preconditions.condition(module.isNamed(), "Module must be named"); return new ModuleSelector(module); } /** * Create a list of {@code ModuleSelectors} for the supplied module names. * *

The unnamed module is not supported. * *

The {@link Set} supplied to this method should have a reliable iteration * order to support reliable discovery and execution order. It is therefore * recommended that the set be a {@link java.util.SequencedSet} (on Java 21 * or higher), {@link java.util.SortedSet}, {@link java.util.LinkedHashSet}, * or similar. Note that {@link Set#of(Object[])} and related {@code Set.of()} * methods do not guarantee a reliable iteration order. * * @param moduleNames the module names to select; never {@code null}, never * containing {@code null} or blank * @since 1.1 * @see ModuleSelector */ @API(status = STABLE, since = "1.10") public static List selectModules(Set moduleNames) { Preconditions.notNull(moduleNames, "Module names must not be null"); Preconditions.containsNoNullElements(moduleNames, "Individual module name must not be null"); // @formatter:off return moduleNames.stream() .map(DiscoverySelectors::selectModule) // unmodifiable since this is a public, non-internal method .toList(); // @formatter:on } /** * Create a {@code PackageSelector} for the supplied package name. * *

The default package is represented by an empty string ({@code ""}). * * @param packageName the package name to select; never {@code null} and * never containing whitespace only * @see PackageSelector */ public static PackageSelector selectPackage(String packageName) { Preconditions.notNull(packageName, "Package name must not be null"); Preconditions.condition(packageName.isEmpty() || !packageName.isBlank(), "Package name must not contain only whitespace"); return new PackageSelector(packageName.strip()); } /** * Create a {@code ClassSelector} for the supplied {@link Class}. * * @param clazz the class to select; never {@code null} * @see ClassSelector */ public static ClassSelector selectClass(Class clazz) { Preconditions.notNull(clazz, "Class must not be null"); return new ClassSelector(clazz); } /** * Create a {@code ClassSelector} for the supplied class name. * * @param className the fully qualified name of the class to select; never * {@code null} or blank * @see ClassSelector */ public static ClassSelector selectClass(String className) { return selectClass(null, className); } /** * Create a {@code ClassSelector} for the supplied class name and class loader. * * @param classLoader the class loader to use to load the class, or {@code null} * to signal that the default {@code ClassLoader} should be used * @param className the fully qualified name of the class to select; never * {@code null} or blank * @since 1.10 * @see ClassSelector */ @API(status = MAINTAINED, since = "1.13.3") public static ClassSelector selectClass(@Nullable ClassLoader classLoader, String className) { Preconditions.notBlank(className, "Class name must not be null or blank"); return new ClassSelector(classLoader, className); } /** * Create a {@code ClassSelector} for each supplied {@link Class}. * * @param classes the classes to select; never {@code null} and never containing * {@code null} class references * @since 6.0 * @see #selectClass(Class) * @see #selectClasses(List) * @see ClassSelector */ @API(status = EXPERIMENTAL, since = "6.0") public static List selectClasses(Class... classes) { Preconditions.notNull(classes, "classes must not be null"); return selectClasses(Arrays.asList(classes)); } /** * Create a {@code ClassSelector} for each supplied {@link Class}. * * @param classes the classes to select; never {@code null} and never containing * {@code null} class references * @since 6.0 * @see #selectClass(Class) * @see #selectClasses(Class...) * @see ClassSelector */ @API(status = EXPERIMENTAL, since = "6.0") public static List selectClasses(List> classes) { Preconditions.notNull(classes, "classes must not be null"); Preconditions.containsNoNullElements(classes, "Individual classes must not be null"); // @formatter:off return classes.stream() .distinct() .map(DiscoverySelectors::selectClass) .toList(); // @formatter:on } /** * Create a {@code ClassSelector} for each supplied class name. * * @param classNames the fully qualified names of the classes to select; * never {@code null} and never containing {@code null} or blank names * @since 6.0 * @see #selectClass(String) * @see #selectClassesByName(List) * @see #selectClassesByName(ClassLoader, String...) * @see ClassSelector */ @API(status = EXPERIMENTAL, since = "6.0") public static List selectClassesByName(String... classNames) { Preconditions.notNull(classNames, "classNames must not be null"); return selectClassesByName(Arrays.asList(classNames)); } /** * Create a {@code ClassSelector} for each supplied class name. * * @param classNames the fully qualified names of the classes to select; * never {@code null} and never containing {@code null} or blank names * @since 6.0 * @see #selectClass(String) * @see #selectClassesByName(String...) * @see #selectClassesByName(ClassLoader, List) * @see ClassSelector */ @API(status = EXPERIMENTAL, since = "6.0") public static List selectClassesByName(List classNames) { Preconditions.notNull(classNames, "classNames must not be null"); return selectClassesByName(null, classNames); } /** * Create a {@code ClassSelector} for each supplied class name, using the * supplied class loader. * * @param classLoader the class loader to use to load the classes, or {@code null} * to signal that the default {@code ClassLoader} should be used * @param classNames the fully qualified names of the classes to select; * never {@code null} and never containing {@code null} or blank names * @since 6.0 * @see #selectClass(ClassLoader, String) * @see #selectClassesByName(ClassLoader, List) * @see ClassSelector */ @API(status = EXPERIMENTAL, since = "6.0") public static List selectClassesByName(@Nullable ClassLoader classLoader, String... classNames) { Preconditions.notNull(classNames, "classNames must not be null"); return selectClassesByName(classLoader, Arrays.asList(classNames)); } /** * Create a {@code ClassSelector} for each supplied class name, using the * supplied class loader. * * @param classLoader the class loader to use to load the classes, or {@code null} * to signal that the default {@code ClassLoader} should be used * @param classNames the fully qualified names of the classes to select; * never {@code null} and never containing {@code null} or blank names * @since 6.0 * @see #selectClass(ClassLoader, String) * @see ClassSelector */ @API(status = EXPERIMENTAL, since = "6.0") public static List selectClassesByName(@Nullable ClassLoader classLoader, List classNames) { Preconditions.notNull(classNames, "classNames must not be null"); Preconditions.containsNoBlankElements(classNames, "Individual class names must not be null or blank"); // @formatter:off return classNames.stream() .distinct() .map(className -> selectClass(classLoader, className)) .toList(); // @formatter:on } /** * Create a {@code MethodSelector} for the supplied fully qualified * method name. * *

The following formats are supported. * *

    *
  • {@code [fully qualified class name]#[methodName]}
  • *
  • {@code [fully qualified class name]#[methodName](parameter type list)} *
* *

The parameter type list is a comma-separated list of primitive * names or fully qualified class names for the types of parameters accepted * by the method. * *

Array parameter types may be specified using either the JVM's internal * String representation (e.g., {@code [[I} for {@code int[][]}, * {@code [Ljava.lang.String;} for {@code java.lang.String[]}, etc.) or * source code syntax (e.g., {@code int[][]}, {@code java.lang.String[]}, * etc.). * * * * * * * * * * * * * * * * *
Examples
MethodFully Qualified Method Name
{@code java.lang.String.chars()}{@code java.lang.String#chars}
{@code java.lang.String.chars()}{@code java.lang.String#chars()}
{@code java.lang.String.equalsIgnoreCase(String)}{@code java.lang.String#equalsIgnoreCase(java.lang.String)}
{@code java.lang.String.substring(int, int)}{@code java.lang.String#substring(int, int)}
{@code example.Calc.avg(int[])}{@code example.Calc#avg([I)}
{@code example.Calc.avg(int[])}{@code example.Calc#avg(int[])}
{@code example.Matrix.multiply(double[][])}{@code example.Matrix#multiply([[D)}
{@code example.Matrix.multiply(double[][])}{@code example.Matrix#multiply(double[][])}
{@code example.Service.process(String[])}{@code example.Service#process([Ljava.lang.String;)}
{@code example.Service.process(String[])}{@code example.Service#process(java.lang.String[])}
{@code example.Service.process(String[][])}{@code example.Service#process([[Ljava.lang.String;)}
{@code example.Service.process(String[][])}{@code example.Service#process(java.lang.String[][])}
* * @param fullyQualifiedMethodName the fully qualified name of the method to * select; never {@code null} or blank * @see MethodSelector */ public static MethodSelector selectMethod(String fullyQualifiedMethodName) throws PreconditionViolationException { return selectMethod((ClassLoader) null, fullyQualifiedMethodName); } /** * Create a {@code MethodSelector} for the supplied fully qualified * method name and class loader. * *

See {@link #selectMethod(String)} for the supported formats for a * fully qualified method name. * * @param classLoader the class loader to use to load the method's declaring * class, or {@code null} to signal that the default {@code ClassLoader} * should be used * @param fullyQualifiedMethodName the fully qualified name of the method to * select; never {@code null} or blank * @since 1.10 * @see #selectMethod(String) * @see MethodSelector */ @API(status = MAINTAINED, since = "1.13.3") public static MethodSelector selectMethod(@Nullable ClassLoader classLoader, String fullyQualifiedMethodName) throws PreconditionViolationException { String[] methodParts = ReflectionUtils.parseFullyQualifiedMethodName(fullyQualifiedMethodName); return selectMethod(classLoader, methodParts[0], methodParts[1], methodParts[2]); } /** * Create a {@code MethodSelector} for the supplied class name and method name * using the default class loader. * * @param className the fully qualified name of the class in which the method * is declared, or a subclass thereof; never {@code null} or blank * @param methodName the name of the method to select; never {@code null} or blank * @see MethodSelector */ public static MethodSelector selectMethod(String className, String methodName) { return selectMethod((ClassLoader) null, className, methodName); } /** * Create a {@code MethodSelector} for the supplied class name, method name, * and class loader. * * @param classLoader the class loader to use to load the class, or {@code null} * to signal that the default {@code ClassLoader} should be used * @param className the fully qualified name of the class in which the method * is declared, or a subclass thereof; never {@code null} or blank * @param methodName the name of the method to select; never {@code null} or blank * @since 1.10 * @see MethodSelector */ @API(status = MAINTAINED, since = "1.13.3") public static MethodSelector selectMethod(@Nullable ClassLoader classLoader, String className, String methodName) { return selectMethod(classLoader, className, methodName, ""); } /** * Create a {@code MethodSelector} for the supplied class name, method name, * and parameter type names. * *

The parameter type names {@code String} is typically a comma-separated * list of atomic types, fully qualified class names, or array types; however, * the exact syntax depends on the underlying test engine. * * @param className the fully qualified name of the class in which the method * is declared, or a subclass thereof; never {@code null} or blank * @param methodName the name of the method to select; never {@code null} or blank * @param parameterTypeNames the parameter type names as a single string; never * {@code null} though potentially an empty string if the method does not declare * parameters * @see MethodSelector */ public static MethodSelector selectMethod(String className, String methodName, String parameterTypeNames) { return selectMethod(null, className, methodName, parameterTypeNames); } /** * Create a {@code MethodSelector} for the supplied class name, method name, * parameter type names, and class loader. * *

The parameter type names {@code String} is typically a comma-separated * list of atomic types, fully qualified class names, or array types; however, * the exact syntax depends on the underlying test engine. * * @param classLoader the class loader to use to load the class, or {@code null} * to signal that the default {@code ClassLoader} should be used * @param className the fully qualified name of the class in which the method * is declared, or a subclass thereof; never {@code null} or blank * @param methodName the name of the method to select; never {@code null} or blank * @param parameterTypeNames the parameter type names as a single string; never * {@code null} though potentially an empty string if the method does not declare * any parameters * @since 1.10 * @see MethodSelector */ @API(status = MAINTAINED, since = "1.13.3") public static MethodSelector selectMethod(@Nullable ClassLoader classLoader, String className, String methodName, String parameterTypeNames) { Preconditions.notBlank(className, "Class name must not be null or blank"); Preconditions.notBlank(methodName, "Method name must not be null or blank"); Preconditions.notNull(parameterTypeNames, "Parameter type names must not be null"); return new MethodSelector(classLoader, className, methodName, parameterTypeNames.strip()); } /** * Create a {@code MethodSelector} for the supplied {@link Class} and method name. * * @param javaClass the class in which the method is declared, or a subclass thereof; * never {@code null} * @param methodName the name of the method to select; never {@code null} or blank * @see MethodSelector */ public static MethodSelector selectMethod(Class javaClass, String methodName) { return selectMethod(javaClass, methodName, ""); } /** * Create a {@code MethodSelector} for the supplied {@link Class}, method name, * and parameter type names. * *

The parameter type names {@code String} is typically a comma-separated * list of atomic types, fully qualified class names, or array types; however, * the exact syntax depends on the underlying test engine. * * @param javaClass the class in which the method is declared, or a subclass thereof; * never {@code null} * @param methodName the name of the method to select; never {@code null} or blank * @param parameterTypeNames the parameter type names as a single string; never * {@code null} though potentially an empty string if the method does not declare * any parameters * @see MethodSelector */ public static MethodSelector selectMethod(Class javaClass, String methodName, String parameterTypeNames) { Preconditions.notNull(javaClass, "Class must not be null"); Preconditions.notBlank(methodName, "Method name must not be null or blank"); Preconditions.notNull(parameterTypeNames, "Parameter type names must not be null"); return new MethodSelector(javaClass, methodName, parameterTypeNames.strip()); } /** * Create a {@code MethodSelector} for the supplied class name, method name, * and parameter types. * * @param className the fully qualified name of the class in which the method * is declared, or a subclass thereof; never {@code null} or blank * @param methodName the name of the method to select; never {@code null} or blank * @param parameterTypes the formal parameter types of the method; never * {@code null} though potentially empty if the method does not declare parameters * @since 1.10 * @see MethodSelector */ @API(status = MAINTAINED, since = "1.13.3") public static MethodSelector selectMethod(String className, String methodName, Class... parameterTypes) { Preconditions.notBlank(className, "Class name must not be null or blank"); Preconditions.notBlank(methodName, "Method name must not be null or blank"); Preconditions.notNull(parameterTypes, "Parameter types array must not be null"); Preconditions.containsNoNullElements(parameterTypes, "Parameter types array must not contain null elements"); return new MethodSelector(null, className, methodName, parameterTypes); } /** * Create a {@code MethodSelector} for the supplied {@link Class}, method name, * and parameter types. * * @param javaClass the class in which the method is declared, or a subclass thereof; * never {@code null} * @param methodName the name of the method to select; never {@code null} or blank * @param parameterTypes the formal parameter types of the method; never * {@code null} though potentially empty if the method does not declare parameters * @since 1.10 * @see MethodSelector */ @API(status = MAINTAINED, since = "1.13.3") public static MethodSelector selectMethod(Class javaClass, String methodName, Class... parameterTypes) { Preconditions.notNull(javaClass, "Class must not be null"); Preconditions.notBlank(methodName, "Method name must not be null or blank"); Preconditions.notNull(parameterTypes, "Parameter types array must not be null"); Preconditions.containsNoNullElements(parameterTypes, "Parameter types array must not contain null elements"); return new MethodSelector(javaClass, methodName, parameterTypes); } /** * Create a {@code MethodSelector} for the supplied {@link Class} and {@link Method}. * * @param javaClass the class in which the method is declared, or a subclass thereof; * never {@code null} * @param method the method to select; never {@code null} * @see MethodSelector */ public static MethodSelector selectMethod(Class javaClass, Method method) { Preconditions.notNull(javaClass, "Class must not be null"); Preconditions.notNull(method, "Method must not be null"); return new MethodSelector(javaClass, method); } /** * Create a {@code NestedClassSelector} for the supplied nested {@link Class} and its * enclosing classes. * * @param enclosingClasses the path to the nested class to select; never {@code null} or empty * @param nestedClass the nested class to select; never {@code null} * @since 1.6 * @see NestedClassSelector */ @API(status = STABLE, since = "1.6") public static NestedClassSelector selectNestedClass(List> enclosingClasses, Class nestedClass) { Preconditions.notEmpty(enclosingClasses, "Enclosing classes must not be null or empty"); Preconditions.notNull(nestedClass, "Nested class must not be null"); return new NestedClassSelector(enclosingClasses, nestedClass); } /** * Create a {@code NestedClassSelector} for the supplied class name and its enclosing * classes' names. * * @param enclosingClassNames the names of the enclosing classes; never {@code null} or empty * @param nestedClassName the name of the nested class to select; never {@code null} or blank * @since 1.6 * @see NestedClassSelector */ @API(status = STABLE, since = "1.6") public static NestedClassSelector selectNestedClass(List enclosingClassNames, String nestedClassName) { return selectNestedClass(null, enclosingClassNames, nestedClassName); } /** * Create a {@code NestedClassSelector} for the supplied class name, its enclosing * classes' names, and class loader. * * @param classLoader the class loader to use to load the enclosing and nested classes, or * {@code null} to signal that the default {@code ClassLoader} should be used * @param enclosingClassNames the names of the enclosing classes; never {@code null} or empty * @param nestedClassName the name of the nested class to select; never {@code null} or blank * @since 1.10 * @see NestedClassSelector */ @API(status = MAINTAINED, since = "1.13.3") public static NestedClassSelector selectNestedClass(@Nullable ClassLoader classLoader, List enclosingClassNames, String nestedClassName) { Preconditions.notEmpty(enclosingClassNames, "Enclosing class names must not be null or empty"); Preconditions.notBlank(nestedClassName, "Nested class name must not be null or blank"); return new NestedClassSelector(classLoader, enclosingClassNames, nestedClassName); } /** * Create a {@code NestedMethodSelector} for the supplied nested class name and method name. * * @param enclosingClassNames the names of the enclosing classes; never {@code null} or empty * @param nestedClassName the name of the nested class to select; never {@code null} or blank * @param methodName the name of the method to select; never {@code null} or blank * @since 1.6 * @see NestedMethodSelector */ @API(status = STABLE, since = "1.6") public static NestedMethodSelector selectNestedMethod(List enclosingClassNames, String nestedClassName, String methodName) { return selectNestedMethod(null, enclosingClassNames, nestedClassName, methodName); } /** * Create a {@code NestedMethodSelector} for the supplied nested class name, method name, * and class loader. * * @param classLoader the class loader to use to load the method's declaring * class, or {@code null} to signal that the default {@code ClassLoader} * should be used * @param enclosingClassNames the names of the enclosing classes; never {@code null} or empty * @param nestedClassName the name of the nested class to select; never {@code null} or blank * @param methodName the name of the method to select; never {@code null} or blank * @since 1.10 * @see NestedMethodSelector */ @API(status = MAINTAINED, since = "1.13.3") public static NestedMethodSelector selectNestedMethod(@Nullable ClassLoader classLoader, List enclosingClassNames, String nestedClassName, String methodName) throws PreconditionViolationException { Preconditions.notEmpty(enclosingClassNames, "Enclosing class names must not be null or empty"); Preconditions.notBlank(nestedClassName, "Nested class name must not be null or blank"); Preconditions.notBlank(methodName, "Method name must not be null or blank"); return new NestedMethodSelector(classLoader, enclosingClassNames, nestedClassName, methodName, ""); } /** * Create a {@code NestedMethodSelector} for the supplied nested class name, method name, * and parameter type names. * *

The parameter type names {@code String} is typically a comma-separated * list of atomic types, fully qualified class names, or array types; however, * the exact syntax depends on the underlying test engine. * * @param enclosingClassNames the names of the enclosing classes; never {@code null} or empty * @param nestedClassName the name of the nested class to select; never {@code null} or blank * @param methodName the name of the method to select; never {@code null} or blank * @param parameterTypeNames the parameter type names as a single string; never * {@code null} though potentially an empty string if the method does not declare * parameters * @since 1.6 * @see NestedMethodSelector */ @API(status = STABLE, since = "1.6") public static NestedMethodSelector selectNestedMethod(List enclosingClassNames, String nestedClassName, String methodName, String parameterTypeNames) { return selectNestedMethod(null, enclosingClassNames, nestedClassName, methodName, parameterTypeNames); } /** * Create a {@code NestedMethodSelector} for the supplied nested class name, method name, * parameter type names, and class loader. * * @param classLoader the class loader to use to load the method's declaring * class, or {@code null} to signal that the default {@code ClassLoader} * should be used * @param enclosingClassNames the names of the enclosing classes; never {@code null} or empty * @param nestedClassName the name of the nested class to select; never {@code null} or blank * @param methodName the name of the method to select; never {@code null} or blank * @param parameterTypeNames the parameter type names as a single string; never * {@code null} though potentially an empty string if the method does not declare * parameters * @since 1.10 * @see #selectNestedMethod(List, String, String, String) */ @API(status = MAINTAINED, since = "1.13.3") public static NestedMethodSelector selectNestedMethod(@Nullable ClassLoader classLoader, List enclosingClassNames, String nestedClassName, String methodName, String parameterTypeNames) { Preconditions.notEmpty(enclosingClassNames, "Enclosing class names must not be null or empty"); Preconditions.notBlank(nestedClassName, "Nested class name must not be null or blank"); Preconditions.notBlank(methodName, "Method name must not be null or blank"); Preconditions.notNull(parameterTypeNames, "Parameter types must not be null"); return new NestedMethodSelector(classLoader, enclosingClassNames, nestedClassName, methodName, parameterTypeNames.strip()); } /** * Create a {@code NestedMethodSelector} for the supplied enclosing class names, * nested class name, method name, and parameter types. * * @param enclosingClassNames the names of the enclosing classes; never {@code null} * or empty * @param nestedClassName the name of the nested class to select; never {@code null} * or blank * @param methodName the name of the method to select; never {@code null} or blank * @param parameterTypes the formal parameter types of the method; never {@code null} * though potentially empty if the method does not declare parameters * @since 1.10 * @see NestedMethodSelector */ @API(status = MAINTAINED, since = "1.13.3") public static NestedMethodSelector selectNestedMethod(List enclosingClassNames, String nestedClassName, String methodName, Class... parameterTypes) { Preconditions.notEmpty(enclosingClassNames, "Enclosing class names must not be null or empty"); Preconditions.notBlank(nestedClassName, "Nested class name must not be null or blank"); Preconditions.notBlank(methodName, "Method name must not be null or blank"); Preconditions.notNull(parameterTypes, "Parameter types array must not be null"); Preconditions.containsNoNullElements(parameterTypes, "Parameter types array must not contain null elements"); return new NestedMethodSelector(null, enclosingClassNames, nestedClassName, methodName, parameterTypes); } /** * Create a {@code NestedMethodSelector} for the supplied nested {@link Class} and method name. * * @param enclosingClasses the path to the nested class to select; never {@code null} or empty * @param nestedClass the nested class to select; never {@code null} * @param methodName the name of the method to select; never {@code null} or blank * @since 1.6 * @see NestedMethodSelector */ @API(status = STABLE, since = "1.6") public static NestedMethodSelector selectNestedMethod(List> enclosingClasses, Class nestedClass, String methodName) { return selectNestedMethod(enclosingClasses, nestedClass, methodName, ""); } /** * Create a {@code NestedMethodSelector} for the supplied {@link Class}, method name, * and parameter type names. * *

The parameter type names {@code String} is typically a comma-separated * list of atomic types, fully qualified class names, or array types; however, * the exact syntax depends on the underlying test engine. * * @param enclosingClasses the path to the nested class to select; never {@code null} or empty * @param nestedClass the nested class to select; never {@code null} * @param methodName the name of the method to select; never {@code null} or blank * @param parameterTypeNames the parameter type names as a single string; never * {@code null} though potentially an empty string if the method does not declare * parameters * @since 1.6 * @see NestedMethodSelector */ @API(status = STABLE, since = "1.6") public static NestedMethodSelector selectNestedMethod(List> enclosingClasses, Class nestedClass, String methodName, String parameterTypeNames) { Preconditions.notEmpty(enclosingClasses, "Enclosing classes must not be null or empty"); Preconditions.notNull(nestedClass, "Nested class must not be null"); Preconditions.notBlank(methodName, "Method name must not be null or blank"); Preconditions.notNull(parameterTypeNames, "Parameter types must not be null"); return new NestedMethodSelector(enclosingClasses, nestedClass, methodName, parameterTypeNames.strip()); } /** * Create a {@code NestedMethodSelector} for the supplied enclosing classes, * nested class, method name, and parameter types. * * @param enclosingClasses the path to the nested class to select; never {@code null} * or empty * @param nestedClass the nested class to select; never {@code null} * @param methodName the name of the method to select; never {@code null} or blank * @param parameterTypes the formal parameter types of the method; never {@code null} * though potentially empty if the method does not declare parameters * @since 1.10 * @see NestedMethodSelector */ @API(status = MAINTAINED, since = "1.13.3") public static NestedMethodSelector selectNestedMethod(List> enclosingClasses, Class nestedClass, String methodName, Class... parameterTypes) { Preconditions.notEmpty(enclosingClasses, "Enclosing classes must not be null or empty"); Preconditions.notNull(nestedClass, "Nested class must not be null"); Preconditions.notBlank(methodName, "Method name must not be null or blank"); Preconditions.notNull(parameterTypes, "Parameter types array must not be null"); Preconditions.containsNoNullElements(parameterTypes, "Parameter types array must not contain null elements"); return new NestedMethodSelector(enclosingClasses, nestedClass, methodName, parameterTypes); } /** * Create a {@code NestedMethodSelector} for the supplied nested {@link Class} and {@link Method}. * * @param enclosingClasses the path to the nested class to select; never {@code null} or empty * @param nestedClass the nested class to select; never {@code null} * @param method the method to select; never {@code null} * @since 1.6 * @see NestedMethodSelector */ @API(status = STABLE, since = "1.6") public static NestedMethodSelector selectNestedMethod(List> enclosingClasses, Class nestedClass, Method method) { Preconditions.notEmpty(enclosingClasses, "Enclosing classes must not be null or empty"); Preconditions.notNull(nestedClass, "Nested class must not be null"); Preconditions.notNull(method, "Method must not be null"); return new NestedMethodSelector(enclosingClasses, nestedClass, method); } /** * Create a {@code UniqueIdSelector} for the supplied {@link UniqueId}. * * @param uniqueId the {@code UniqueId} to select; never {@code null} * @see UniqueIdSelector */ public static UniqueIdSelector selectUniqueId(UniqueId uniqueId) { Preconditions.notNull(uniqueId, "UniqueId must not be null"); return new UniqueIdSelector(uniqueId); } /** * Create a {@code UniqueIdSelector} for the supplied unique ID. * * @param uniqueId the unique ID to select; never {@code null} or blank * @see UniqueIdSelector */ public static UniqueIdSelector selectUniqueId(String uniqueId) { Preconditions.notBlank(uniqueId, "Unique ID must not be null or blank"); return new UniqueIdSelector(UniqueId.parse(uniqueId)); } /** * Create an {@code IterationSelector} for the supplied parent selector and * iteration indices. * * @param parentSelector the parent selector to select iterations for; never * {@code null} * @param iterationIndices the iteration indices to select; never {@code null} * or empty * @since 1.9 * @see IterationSelector */ @API(status = MAINTAINED, since = "1.13.3") public static IterationSelector selectIteration(DiscoverySelector parentSelector, int... iterationIndices) { Preconditions.notNull(parentSelector, "Parent selector must not be null"); Preconditions.notEmpty(iterationIndices, "iteration indices must not be empty"); return new IterationSelector(parentSelector, iterationIndices); } /** * Parse the supplied string representation of a {@link DiscoverySelectorIdentifier}. * * @param identifier the string representation of a {@code DiscoverySelectorIdentifier}; * never {@code null} or blank * @return an {@link Optional} containing the corresponding {@link DiscoverySelector}; * never {@code null} but potentially empty * @since 1.11 * @see DiscoverySelectorIdentifierParser */ @API(status = MAINTAINED, since = "1.13.3") public static Optional parse(String identifier) { return DiscoverySelectorIdentifierParsers.parse(identifier); } /** * Parse the supplied {@link DiscoverySelectorIdentifier}. * * @param identifier the {@code DiscoverySelectorIdentifier} to parse; * never {@code null} * @return an {@link Optional} containing the corresponding {@link DiscoverySelector}; * never {@code null} but potentially empty * @since 1.11 * @see DiscoverySelectorIdentifierParser */ @API(status = MAINTAINED, since = "1.13.3") public static Optional parse(DiscoverySelectorIdentifier identifier) { return DiscoverySelectorIdentifierParsers.parse(identifier); } /** * Parse the supplied string representations of * {@link DiscoverySelectorIdentifier DiscoverySelectorIdentifiers}. * * @param identifiers the string representations of * {@code DiscoverySelectorIdentifiers} to parse; never {@code null} * @return a stream of the corresponding {@link DiscoverySelector DiscoverySelectors}; * never {@code null} but potentially empty * @since 1.11 * @see DiscoverySelectorIdentifierParser */ @API(status = MAINTAINED, since = "1.13.3") public static Stream parseAll(String... identifiers) { return DiscoverySelectorIdentifierParsers.parseAll(identifiers); } /** * Parse the supplied {@link DiscoverySelectorIdentifier * DiscoverySelectorIdentifiers}. * * @param identifiers the {@code DiscoverySelectorIdentifiers} to parse; * never {@code null} * @return a stream of the corresponding {@link DiscoverySelector DiscoverySelectors}; * never {@code null} but potentially empty * @since 1.11 * @see DiscoverySelectorIdentifierParser */ @API(status = MAINTAINED, since = "1.13.3") public static Stream parseAll(Collection identifiers) { return DiscoverySelectorIdentifierParsers.parseAll(identifiers); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ExcludeClassNameFilter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static org.junit.platform.engine.FilterResult.excluded; import static org.junit.platform.engine.FilterResult.included; import java.util.function.Predicate; import java.util.regex.Pattern; import org.junit.platform.engine.FilterResult; /** * {@link ClassNameFilter} that matches fully qualified class names against * patterns in the form of regular expressions. * *

If the fully qualified name of a class matches against at least one * pattern, the class will be excluded. * * @since 1.0 */ class ExcludeClassNameFilter extends AbstractClassNameFilter { ExcludeClassNameFilter(String... patterns) { super(patterns); } @Override public FilterResult apply(String className) { return findMatchingPattern(className) // .map(pattern -> excluded(formatExclusionReason(className, pattern))) // .orElseGet(() -> included(formatInclusionReason(className))); } private String formatInclusionReason(String className) { return "Class name [%s] does not match any excluded pattern: %s".formatted(className, patternDescription); } private String formatExclusionReason(String className, Pattern pattern) { return "Class name [%s] matches excluded pattern: '%s'".formatted(className, pattern); } @Override public Predicate toPredicate() { return className -> findMatchingPattern(className).isEmpty(); } @Override public String toString() { return "%s that excludes class names that match one of the following regular expressions: %s".formatted( getClass().getSimpleName(), this.patternDescription); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ExcludePackageNameFilter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static java.util.stream.Collectors.joining; import static org.junit.platform.engine.FilterResult.excluded; import static org.junit.platform.engine.FilterResult.included; import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.function.Predicate; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.FilterResult; /** * {@link PackageNameFilter} that matches fully qualified package names that * are not prefixed by one of the package names provided to the filter. * *

If the fully qualified name of a package starts with at least one of the * packages names of the filter, the package will be excluded. * * @since 1.0 */ class ExcludePackageNameFilter implements PackageNameFilter { private final List packageNames; private final String patternDescription; ExcludePackageNameFilter(String... packageNames) { Preconditions.notEmpty(packageNames, "packageNames must not be null or empty"); Preconditions.containsNoNullElements(packageNames, "packageNames must not contain null elements"); this.packageNames = Arrays.asList(packageNames); this.patternDescription = Arrays.stream(packageNames).collect(joining("' OR '", "'", "'")); } @Override public FilterResult apply(String packageName) { return findMatchingName(packageName) // .map(matchedName -> excluded(formatExclusionReason(packageName, matchedName))) // .orElseGet(() -> included(formatInclusionReason(packageName))); } private String formatInclusionReason(String packageName) { return "Package name [%s] does not match any excluded names: %s".formatted(packageName, this.patternDescription); } private String formatExclusionReason(String packageName, String matchedName) { return "Package name [%s] matches excluded name: '%s'".formatted(packageName, matchedName); } @Override public Predicate toPredicate() { return packageName -> findMatchingName(packageName).isEmpty(); } private Optional findMatchingName(String packageName) { return this.packageNames.stream().filter( name -> name.equals(packageName) || packageName.startsWith(name + ".")).findAny(); } @Override public String toString() { return "%s that excludes packages whose names are either equal to or start with one of the following: %s".formatted( getClass().getSimpleName(), this.patternDescription); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/FilePosition.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static org.apiguardian.api.API.Status.STABLE; import java.io.Serial; import java.io.Serializable; import java.util.Objects; import java.util.Optional; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.StringUtils; import org.junit.platform.commons.util.ToStringBuilder; /** * Position inside a file represented by {@linkplain #getLine line} and * {@linkplain #getColumn column} numbers. * * @implNote This class is a copy of * {@link org.junit.platform.engine.support.descriptor.FilePosition FilePosition}, * which is not accessible from this package. The decision to duplicate it is * motivated by an eventual divergence between the two classes in the future. * * @since 1.7 */ @API(status = STABLE, since = "1.7") public final class FilePosition implements Serializable { @Serial private static final long serialVersionUID = 1L; private static final Logger logger = LoggerFactory.getLogger(FilePosition.class); /** * Create a new {@code FilePosition} using the supplied {@code line} number * and an undefined column number. * * @param line the line number; must be greater than zero * @return a {@link FilePosition} with the given line number */ public static FilePosition from(int line) { return new FilePosition(line); } /** * Create a new {@code FilePosition} using the supplied {@code line} and * {@code column} numbers. * * @param line the line number; must be greater than zero * @param column the column number; must be greater than zero * @return a {@link FilePosition} with the given line and column numbers */ public static FilePosition from(int line, int column) { return new FilePosition(line, column); } /** * Create an optional {@code FilePosition} by parsing the supplied * {@code query} string. * *

Examples of valid {@code query} strings: *

    *
  • {@code "line=23"}
  • *
  • {@code "line=23&column=42"}
  • *
* * @param query the query string; may be {@code null} * @return an {@link Optional} containing a {@link FilePosition} with * the parsed line and column numbers; never {@code null} but potentially * empty * @since 1.3 * @see #from(int) * @see #from(int, int) */ public static Optional fromQuery(String query) { FilePosition result = null; Integer line = null; Integer column = null; if (StringUtils.isNotBlank(query)) { try { for (String pair : query.split("&")) { String[] data = pair.split("="); if (data.length == 2) { String key = data[0]; if (line == null && "line".equals(key)) { line = Integer.valueOf(data[1]); } else if (column == null && "column".equals(key)) { column = Integer.valueOf(data[1]); } } // Already found what we're looking for? if (line != null && column != null) { break; } } } catch (IllegalArgumentException ex) { logger.debug(ex, () -> "Failed to parse 'line' and/or 'column' from query string: " + query); // fall-through and continue } if (line != null) { result = column == null ? new FilePosition(line) : new FilePosition(line, column); } } return Optional.ofNullable(result); } private final int line; private final @Nullable Integer column; private FilePosition(int line) { Preconditions.condition(line > 0, "line number must be greater than zero"); this.line = line; this.column = null; } private FilePosition(int line, int column) { Preconditions.condition(line > 0, "line number must be greater than zero"); Preconditions.condition(column > 0, "column number must be greater than zero"); this.line = line; this.column = column; } /** * Get the line number of this {@code FilePosition}. * * @return the line number */ public int getLine() { return this.line; } /** * Get the column number of this {@code FilePosition}, if available. * * @return an {@code Optional} containing the column number; never * {@code null} but potentially empty */ public Optional getColumn() { return Optional.ofNullable(this.column); } String toQueryPart() { StringBuilder builder = new StringBuilder("line=").append(this.line); if (this.column != null) { builder.append("&column=").append(this.column); } return builder.toString(); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } FilePosition that = (FilePosition) o; return (this.line == that.line) && Objects.equals(this.column, that.column); } @Override public int hashCode() { return Objects.hash(this.line, this.column); } @Override public String toString() { // @formatter:off return new ToStringBuilder(this) .append("line", this.line) .append("column", getColumn().orElse(-1)) .toString(); // @formatter:on } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/FileSelector.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; import java.io.File; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Path; import java.util.Objects; import java.util.Optional; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.StringUtils; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.DiscoverySelectorIdentifier; /** * A {@link DiscoverySelector} that selects a file so that * {@link org.junit.platform.engine.TestEngine TestEngines} * can discover tests or containers based on files in the * file system. * * @since 1.0 * @see DiscoverySelectors#selectFile(String) * @see DiscoverySelectors#selectFile(File) * @see DirectorySelector * @see #getFile() * @see #getPath() * @see #getRawPath() */ @API(status = STABLE, since = "1.0") public final class FileSelector implements DiscoverySelector { private final String path; private final @Nullable FilePosition position; FileSelector(String path, @Nullable FilePosition position) { this.path = path; this.position = position; } /** * Get the selected file as a {@link java.io.File}. * * @see #getPath() * @see #getRawPath() */ public File getFile() { return new File(this.path); } /** * Get the selected file as a {@link java.nio.file.Path} using the * {@linkplain FileSystems#getDefault default} {@link FileSystem}. * * @see #getFile() * @see #getRawPath() */ public Path getPath() { return Path.of(this.path); } /** * Get the selected file as a raw path. * * @see #getFile() * @see #getPath() */ public String getRawPath() { return this.path; } /** * Get the selected position within the file as a {@link FilePosition}. */ public Optional getPosition() { return Optional.ofNullable(this.position); } /** * @since 1.3 */ @API(status = STABLE, since = "1.3") @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } FileSelector that = (FileSelector) o; return Objects.equals(this.path, that.path) && Objects.equals(this.position, that.position); } /** * @since 1.3 */ @API(status = STABLE, since = "1.3") @Override public int hashCode() { return Objects.hash(this.path, this.position); } @Override public String toString() { return new ToStringBuilder(this).append("path", this.path).append("position", this.position).toString(); } @Override public Optional toIdentifier() { if (this.position == null) { return Optional.of(DiscoverySelectorIdentifier.create(IdentifierParser.PREFIX, this.path)); } else { return Optional.of(DiscoverySelectorIdentifier.create(IdentifierParser.PREFIX, "%s?%s".formatted(this.path, this.position.toQueryPart()))); } } /** * The {@link DiscoverySelectorIdentifierParser} for {@link FileSelector * FileSelectors}. */ @API(status = INTERNAL, since = "1.11") public static class IdentifierParser implements DiscoverySelectorIdentifierParser { private static final String PREFIX = "file"; public IdentifierParser() { } @Override public String getPrefix() { return PREFIX; } @Override public Optional parse(DiscoverySelectorIdentifier identifier, Context context) { return Optional.of(StringUtils.splitIntoTwo('?', identifier.getValue()).map( // DiscoverySelectors::selectFile, // (path, query) -> { FilePosition position = FilePosition.fromQuery(query).orElse(null); return DiscoverySelectors.selectFile(path, position); } // )); } } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IncludeClassNameFilter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static org.junit.platform.engine.FilterResult.excluded; import static org.junit.platform.engine.FilterResult.included; import java.util.function.Predicate; import java.util.regex.Pattern; import org.junit.platform.engine.FilterResult; /** * {@link ClassNameFilter} that matches fully qualified class names against * patterns in the form of regular expressions. * *

If the fully qualified name of a class matches against at least one * pattern, the class will be included. * * @since 1.0 */ class IncludeClassNameFilter extends AbstractClassNameFilter { IncludeClassNameFilter(String... patterns) { super(patterns); } @Override public FilterResult apply(String className) { return findMatchingPattern(className) // .map(pattern -> included(formatInclusionReason(className, pattern))) // .orElseGet(() -> excluded(formatExclusionReason(className))); } private String formatInclusionReason(String className, Pattern pattern) { return "Class name [%s] matches included pattern: '%s'".formatted(className, pattern); } private String formatExclusionReason(String className) { return "Class name [%s] does not match any included pattern: %s".formatted(className, this.patternDescription); } @Override public Predicate toPredicate() { return className -> findMatchingPattern(className).isPresent(); } @Override public String toString() { return "%s that includes class names that match one of the following regular expressions: %s".formatted( getClass().getSimpleName(), this.patternDescription); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IncludePackageNameFilter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static java.util.stream.Collectors.joining; import static org.junit.platform.engine.FilterResult.excluded; import static org.junit.platform.engine.FilterResult.included; import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.function.Predicate; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.FilterResult; /** * {@link PackageNameFilter} that matches fully qualified package names that * are prefixed by one of the package names provided to the filter. * *

If the fully qualified name of a package starts with at least one of the * packages names of the filter, the package will be included. * * @since 1.0 */ class IncludePackageNameFilter implements PackageNameFilter { private final List packageNames; private final String patternDescription; IncludePackageNameFilter(String... packageNames) { Preconditions.notEmpty(packageNames, "packageNames array must not be null or empty"); Preconditions.containsNoNullElements(packageNames, "packageNames array must not contain null elements"); this.packageNames = Arrays.asList(packageNames); this.patternDescription = Arrays.stream(packageNames).collect(joining("' OR '", "'", "'")); } @Override public FilterResult apply(String packageName) { return findMatchingName(packageName) // .map(matchedName -> included(formatInclusionReason(packageName, matchedName))) // .orElseGet(() -> excluded(formatExclusionReason(packageName))); } private String formatInclusionReason(String packageName, String matchedName) { return "Package name [%s] matches included name: '%s'".formatted(packageName, matchedName); } private String formatExclusionReason(String packageName) { return "Package name [%s] does not match any included names: %s".formatted(packageName, this.patternDescription); } @Override public Predicate toPredicate() { return packageName -> findMatchingName(packageName).isPresent(); } private Optional findMatchingName(String packageName) { return this.packageNames.stream().filter( name -> name.equals(packageName) || packageName.startsWith(name + ".")).findAny(); } @Override public String toString() { return "%s that includes packages whose names are either equal to or start with one of the following: %s".formatted( getClass().getSimpleName(), this.patternDescription); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IterationSelector.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toCollection; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.MAINTAINED; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.SortedSet; import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.IntStream; import org.apiguardian.api.API; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.StringUtils; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.DiscoverySelectorIdentifier; /** * A {@link DiscoverySelector} that selects the iterations of a parent * {@code DiscoverySelector} via their indices so that * {@link org.junit.platform.engine.TestEngine TestEngines} can discover * a subset of the iterations of tests or containers. * * @since 1.9 * @see DiscoverySelectors#selectIteration(DiscoverySelector, int...) */ @API(status = MAINTAINED, since = "1.13.3") public final class IterationSelector implements DiscoverySelector { private final DiscoverySelector parentSelector; private final SortedSet iterationIndices; IterationSelector(DiscoverySelector parentSelector, int... iterationIndices) { this.parentSelector = parentSelector; this.iterationIndices = toSortedSet(iterationIndices); } private SortedSet toSortedSet(int[] iterationIndices) { return Arrays.stream(iterationIndices) // .boxed() // .collect(collectingAndThen(toCollection(TreeSet::new), Collections::unmodifiableSortedSet)); } /** * Get the selected parent {@link DiscoverySelector}. */ public DiscoverySelector getParentSelector() { return this.parentSelector; } /** * Get the selected iteration indices. */ public SortedSet getIterationIndices() { return this.iterationIndices; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } IterationSelector that = (IterationSelector) o; return this.parentSelector.equals(that.parentSelector) && this.iterationIndices.equals(that.iterationIndices); } @Override public int hashCode() { return Objects.hash(this.parentSelector, this.iterationIndices); } @Override public String toString() { // @formatter:off return new ToStringBuilder(this) .append("parentSelector", this.parentSelector) .append("iterationIndices", this.iterationIndices) .toString(); // @formatter:on } @Override public Optional toIdentifier() { return this.parentSelector.toIdentifier().map(parentSelectorString -> DiscoverySelectorIdentifier.create( // IdentifierParser.PREFIX, // "%s[%s]".formatted(parentSelectorString, formatIterationIndicesAsRanges())) // ); } private String formatIterationIndicesAsRanges() { class Range { final int start; int end; Range(int start) { this.start = start; this.end = start; } } List ranges = new ArrayList<>(); Range current = new Range(this.iterationIndices.first()); ranges.add(current); for (int n : this.iterationIndices.tailSet(current.start + 1)) { if (n == current.end + 1) { current.end = n; } else { current = new Range(n); ranges.add(current); } } return ranges.stream() // .map(range -> { if (range.start == range.end) { return String.valueOf(range.start); } if (range.start == range.end - 1) { return range.start + "," + range.end; } return range.start + ".." + range.end; }) // .collect(joining(",")); } /** * The {@link DiscoverySelectorIdentifierParser} for * {@link IterationSelector IterationSelectors}. */ @API(status = INTERNAL, since = "1.11") public static class IdentifierParser implements DiscoverySelectorIdentifierParser { public static final String PREFIX = "iteration"; private static final Pattern PATTERN = Pattern.compile( "(?.+)\\[(?(\\d+)(\\.\\.\\d+)?(\\s*,\\s*(\\d+)(\\.\\.\\d+)?)*)]"); public IdentifierParser() { } @Override public String getPrefix() { return PREFIX; } @Override public Optional parse(DiscoverySelectorIdentifier identifier, Context context) { Matcher matcher = PATTERN.matcher(identifier.getValue()); Preconditions.condition(matcher.matches(), "Invalid format: must be IDENTIFIER[INDEX(,INDEX)*]"); return context.parse(matcher.group("parentIdentifier")) // .map(parent -> { int[] iterationIndices = Arrays.stream(matcher.group("indices").split(",")) // .flatMapToInt(this::parseIndexDefinition) // .toArray(); return DiscoverySelectors.selectIteration(parent, iterationIndices); }); } private IntStream parseIndexDefinition(String value) { return StringUtils.splitIntoTwo("..", value).map( // index -> IntStream.of(Integer.parseInt(index)), // (firstIndex, lastIndex) -> IntStream.rangeClosed(Integer.parseInt(firstIndex), Integer.parseInt(lastIndex))// ); } } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/MethodSelector.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.lang.reflect.Method; import java.util.Objects; import java.util.Optional; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.function.Try; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.ClassUtils; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.DiscoverySelectorIdentifier; /** * A {@link DiscoverySelector} that selects a {@link Method} or a combination of * class name, method name, and parameter types so that * {@link org.junit.platform.engine.TestEngine TestEngines} can discover tests * or containers based on methods. * *

If a Java {@link Method} is provided, the selector will return that * {@linkplain #getJavaMethod() method} and its method name, class name, and * parameter types accordingly. If a {@link Class} and method name, a class name * and method name, or a fully qualified method name is provided, * this selector will only attempt to lazily load the class, method, or parameter * types if {@link #getJavaClass()}, {@link #getJavaMethod()}, or * {@link #getParameterTypes()} is invoked. * *

In this context, a Java {@code Method} means anything that can be referenced * as a {@link Method} on the JVM — for example, methods from Java classes * or methods from other JVM languages such Groovy, Scala, etc. * * @since 1.0 * @see DiscoverySelectors#selectMethod(String) * @see DiscoverySelectors#selectMethod(String, String) * @see DiscoverySelectors#selectMethod(String, String, String) * @see DiscoverySelectors#selectMethod(Class, String) * @see DiscoverySelectors#selectMethod(Class, String, String) * @see DiscoverySelectors#selectMethod(Class, Method) * @see org.junit.platform.engine.support.descriptor.MethodSource */ @API(status = STABLE, since = "1.0") public final class MethodSelector implements DiscoverySelector { private final @Nullable ClassLoader classLoader; private final String className; private final String methodName; private final String parameterTypeNames; private volatile @Nullable Class javaClass; private volatile @Nullable Method javaMethod; private volatile Class @Nullable [] parameterTypes; /** * @since 1.10 */ MethodSelector(@Nullable ClassLoader classLoader, String className, String methodName, String parameterTypeNames) { this.classLoader = classLoader; this.className = className; this.methodName = methodName; this.parameterTypeNames = parameterTypeNames; } MethodSelector(Class javaClass, String methodName, String parameterTypeNames) { this(javaClass.getClassLoader(), javaClass.getName(), methodName, parameterTypeNames); this.javaClass = javaClass; } /** * @since 1.10 */ MethodSelector(@Nullable ClassLoader classLoader, String className, String methodName, Class... parameterTypes) { this(classLoader, className, methodName, ClassUtils.nullSafeToString(Class::getTypeName, parameterTypes)); this.parameterTypes = parameterTypes.clone(); } /** * @since 1.10 */ MethodSelector(Class javaClass, String methodName, Class... parameterTypes) { this(javaClass.getClassLoader(), javaClass.getName(), methodName, ClassUtils.nullSafeToString(Class::getTypeName, parameterTypes)); this.javaClass = javaClass; this.parameterTypes = parameterTypes.clone(); } MethodSelector(Class javaClass, Method method) { this(javaClass, method, method.getParameterTypes()); } private MethodSelector(Class javaClass, Method method, Class... parameterTypes) { this(javaClass.getClassLoader(), javaClass.getName(), method.getName(), ClassUtils.nullSafeToString(Class::getTypeName, parameterTypes)); this.javaClass = javaClass; this.javaMethod = method; this.parameterTypes = parameterTypes; } /** * Get the {@link ClassLoader} used to load the specified class. * * @return the {@code ClassLoader}; potentially {@code null} * @since 1.10 */ @API(status = MAINTAINED, since = "1.13.3") public @Nullable ClassLoader getClassLoader() { return this.classLoader; } /** * Get the selected class name. */ public String getClassName() { return this.className; } /** * Get the selected method name. */ public String getMethodName() { return this.methodName; } /** * Get the names of parameter types for the selected method as a {@link String}, * typically a comma-separated list of primitive types, fully qualified class * names, or array types. * *

Note: the names of parameter types are provided as a single string instead * of a collection in order to allow this selector to be used in a generic * fashion by various test engines. It is therefore the responsibility of * the caller of this method to determine how to parse the returned string. * * @return the names of parameter types supplied to this {@code MethodSelector} * via a constructor or deduced from a {@code Method} or parameter types supplied * via a constructor; never {@code null} but potentially an empty string * @since 1.10 * @see #getParameterTypes() */ @API(status = STABLE, since = "1.10") public String getParameterTypeNames() { return this.parameterTypeNames; } /** * Get the {@link Class} in which the selected {@linkplain #getJavaMethod * method} is declared, or a subclass thereof. * *

If the {@link Class} was not provided, but only the name, this method * attempts to lazily load the {@code Class} based on its name and throws a * {@link PreconditionViolationException} if the class cannot be loaded. * * @see #getJavaMethod() */ public Class getJavaClass() { return lazyLoadJavaClass(); } /** * Get the selected {@link Method}. * *

If the {@link Method} was not provided, but only the name, this method * attempts to lazily load the {@code Method} based on its name and throws a * {@link PreconditionViolationException} if the method cannot be loaded. * * @see #getJavaClass() */ public Method getJavaMethod() { return lazyLoadJavaMethod(); } /** * Get the parameter types for the selected method. * *

If the parameter types were not provided as {@link Class} references * (or could not be deduced as {@code Class} references in the constructor), * this method attempts to lazily load the class reference for each parameter * type based on its name and throws a {@link JUnitException} if the class * cannot be loaded. * * @return the method's parameter types; never {@code null} but potentially * an empty array if the selected method does not declare parameters * @since 1.10 * @see #getParameterTypeNames() * @see Method#getParameterTypes() */ @API(status = MAINTAINED, since = "1.13.3") public Class[] getParameterTypes() { return lazyLoadParameterTypes().clone(); } private Class lazyLoadJavaClass() { Class value = this.javaClass; if (value == null) { // @formatter:off Try> tryToLoadClass = this.classLoader == null ? ReflectionSupport.tryToLoadClass(this.className) : ReflectionSupport.tryToLoadClass(this.className, this.classLoader); value = tryToLoadClass.getNonNullOrThrow(cause -> new PreconditionViolationException("Could not load class with name: " + this.className, cause)); // @formatter:on this.javaClass = value; } return value; } private Method lazyLoadJavaMethod() { var value = this.javaMethod; if (value == null) { Class javaClass = lazyLoadJavaClass(); var parameterTypes = lazyLoadParameterTypes(); if (parameterTypes.length > 0) { value = ReflectionSupport.findMethod(javaClass, this.methodName, parameterTypes).orElseThrow( () -> new PreconditionViolationException( "Could not find method with name [%s] and parameter types [%s] in class [%s].".formatted( this.methodName, this.parameterTypeNames, javaClass.getName()))); } else { value = ReflectionSupport.findMethod(javaClass, this.methodName).orElseThrow( () -> new PreconditionViolationException( "Could not find method with name [%s] in class [%s].".formatted(this.methodName, javaClass.getName()))); } this.javaMethod = value; } return value; } private Class[] lazyLoadParameterTypes() { var value = this.parameterTypes; if (value == null) { value = ReflectionUtils.resolveParameterTypes(lazyLoadJavaClass(), this.methodName, this.parameterTypeNames); this.parameterTypes = value; } return value; } /** * @since 1.3 */ @API(status = STABLE, since = "1.3") @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } MethodSelector that = (MethodSelector) o; return Objects.equals(this.className, that.className)// && Objects.equals(this.methodName, that.methodName)// && Objects.equals(this.parameterTypeNames, that.parameterTypeNames); } /** * @since 1.3 */ @API(status = STABLE, since = "1.3") @Override public int hashCode() { return Objects.hash(this.className, this.methodName, this.parameterTypeNames); } @Override public String toString() { // @formatter:off return new ToStringBuilder(this) .append("className", getClassName()) .append("methodName", getMethodName()) .append("parameterTypes", getParameterTypeNames()) .append("classLoader", getClassLoader()) .toString(); // @formatter:on } @Override public Optional toIdentifier() { String fullyQualifiedMethodName = ReflectionUtils.getFullyQualifiedMethodName(this.className, this.methodName, this.parameterTypeNames); return Optional.of(DiscoverySelectorIdentifier.create(IdentifierParser.PREFIX, fullyQualifiedMethodName)); } /** * The {@link DiscoverySelectorIdentifierParser} for {@link MethodSelector * MethodSelectors}. */ @API(status = INTERNAL, since = "1.11") public static class IdentifierParser implements DiscoverySelectorIdentifierParser { private static final String PREFIX = "method"; public IdentifierParser() { } @Override public String getPrefix() { return PREFIX; } @Override public Optional parse(DiscoverySelectorIdentifier identifier, Context context) { return Optional.of(DiscoverySelectors.selectMethod(identifier.getValue())); } } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ModuleSelector.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; import java.util.Objects; import java.util.Optional; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.DiscoverySelectorIdentifier; /** * A {@link DiscoverySelector} that selects a module name so that * {@link org.junit.platform.engine.TestEngine TestEngines} can discover * tests or containers based on modules. * * @since 1.1 * @see DiscoverySelectors#selectModule(String) * @see DiscoverySelectors#selectModules(java.util.Set) */ @API(status = STABLE, since = "1.1") public final class ModuleSelector implements DiscoverySelector { @Nullable private final Module module; private final String moduleName; ModuleSelector(Module module) { this.module = module; this.moduleName = module.getName(); } ModuleSelector(String moduleName) { this.module = null; this.moduleName = moduleName; } /** * {@return the selected {@link Module}, if available} * * @since 6.1 * @see DiscoverySelectors#selectModule(Module) */ @API(status = EXPERIMENTAL, since = "6.1") public Optional getModule() { return Optional.ofNullable(module); } /** * Get the selected module name. */ public String getModuleName() { return this.moduleName; } /** * @since 1.3 */ @API(status = STABLE, since = "1.3") @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ModuleSelector that = (ModuleSelector) o; return Objects.equals(this.moduleName, that.moduleName); } /** * @since 1.3 */ @API(status = STABLE, since = "1.3") @Override public int hashCode() { return this.moduleName.hashCode(); } @Override public String toString() { return new ToStringBuilder(this).append("moduleName", this.moduleName).toString(); } @Override public Optional toIdentifier() { return Optional.of(DiscoverySelectorIdentifier.create(IdentifierParser.PREFIX, this.moduleName)); } /** * The {@link DiscoverySelectorIdentifierParser} for {@link ModuleSelector * ModuleSelectors}. */ @API(status = INTERNAL, since = "1.11") public static class IdentifierParser implements DiscoverySelectorIdentifierParser { private static final String PREFIX = "module"; public IdentifierParser() { } @Override public String getPrefix() { return PREFIX; } @Override public Optional parse(DiscoverySelectorIdentifier identifier, Context context) { return Optional.of(DiscoverySelectors.selectModule(identifier.getValue())); } } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedClassSelector.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static java.util.stream.Collectors.joining; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.stream.Stream; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.DiscoverySelectorIdentifier; /** * A {@link DiscoverySelector} that selects a nested {@link Class} * or class name enclosed in other classes so that * {@link org.junit.platform.engine.TestEngine TestEngines} can discover * tests or containers based on classes. * *

If Java {@link Class} references are provided for the nested class or * the enclosing classes, the selector will return those classes and their class * names accordingly. If class names are provided, the selector will only attempt * to lazily load classes if {@link #getEnclosingClasses()} or * {@link #getNestedClass()} is invoked. * *

In this context, Java {@link Class} means anything that can be referenced * as a {@link Class} on the JVM — for example, classes from other JVM * languages such Groovy, Scala, etc. * * @since 1.6 * @see DiscoverySelectors#selectNestedClass(List, Class) * @see DiscoverySelectors#selectNestedClass(List, String) * @see org.junit.platform.engine.support.descriptor.ClassSource * @see ClassSelector */ @API(status = STABLE, since = "1.6") public final class NestedClassSelector implements DiscoverySelector { private final @Nullable ClassLoader classLoader; private final List enclosingClassSelectors; private final ClassSelector nestedClassSelector; NestedClassSelector(@Nullable ClassLoader classLoader, List enclosingClassNames, String nestedClassName) { this.classLoader = classLoader; this.enclosingClassSelectors = enclosingClassNames.stream() // .map(className -> new ClassSelector(classLoader, className)) // .toList(); this.nestedClassSelector = new ClassSelector(classLoader, nestedClassName); } NestedClassSelector(List> enclosingClasses, Class nestedClass) { this.classLoader = nestedClass.getClassLoader(); this.enclosingClassSelectors = enclosingClasses.stream().map(ClassSelector::new).toList(); this.nestedClassSelector = new ClassSelector(nestedClass); } /** * Get the {@link ClassLoader} used to load the selected nested class. * * @return the {@code ClassLoader}; potentially {@code null} * @since 1.10 */ @API(status = MAINTAINED, since = "1.13.3") public @Nullable ClassLoader getClassLoader() { return this.classLoader; } /** * Get the names of the classes enclosing the selected nested class. */ public List getEnclosingClassNames() { return this.enclosingClassSelectors.stream().map(ClassSelector::getClassName).toList(); } /** * Get the list of {@link Class} enclosing the selected nested * {@link Class}. * *

If the {@link Class} were not provided, but only the name of the * nested class and its enclosing classes, this method attempts to lazily * load the list of enclosing {@link Class} and throws a * {@link PreconditionViolationException} if the classes cannot be loaded. */ public List> getEnclosingClasses() { return this.enclosingClassSelectors.stream().> map(ClassSelector::getJavaClass).toList(); } /** * Get the name of the selected nested class. */ public String getNestedClassName() { return this.nestedClassSelector.getClassName(); } /** * Get the selected nested {@link Class}. * *

If the {@link Class} were not provided, but only the name of the * nested class and its enclosing classes, this method attempts to lazily * load the nested {@link Class} and throws a * {@link PreconditionViolationException} if the class cannot be loaded. */ public Class getNestedClass() { return this.nestedClassSelector.getJavaClass(); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } NestedClassSelector that = (NestedClassSelector) o; return this.enclosingClassSelectors.equals(that.enclosingClassSelectors) && this.nestedClassSelector.equals(that.nestedClassSelector); } @Override public int hashCode() { return Objects.hash(this.enclosingClassSelectors, this.nestedClassSelector); } @Override public String toString() { return new ToStringBuilder(this) // .append("enclosingClassNames", getEnclosingClassNames()) // .append("nestedClassName", getNestedClassName()) // .append("classLoader", getClassLoader()) // .toString(); } @Override public Optional toIdentifier() { String allClassNames = Stream.concat(enclosingClassSelectors.stream(), Stream.of(nestedClassSelector)) // .map(ClassSelector::getClassName) // .collect(joining("/")); return Optional.of(DiscoverySelectorIdentifier.create(IdentifierParser.PREFIX, allClassNames)); } /** * The {@link DiscoverySelectorIdentifierParser} for * {@link NestedClassSelector NestedClassSelectors}. */ @API(status = INTERNAL, since = "1.11") public static class IdentifierParser implements DiscoverySelectorIdentifierParser { static final String PREFIX = "nested-class"; public IdentifierParser() { } @Override public String getPrefix() { return PREFIX; } @Override public Optional parse(DiscoverySelectorIdentifier identifier, Context context) { List parts = Arrays.asList(identifier.getValue().split("/")); return Optional.of( DiscoverySelectors.selectNestedClass(parts.subList(0, parts.size() - 1), parts.get(parts.size() - 1))); } } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedMethodSelector.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Optional; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.DiscoverySelectorIdentifier; /** * A {@link DiscoverySelector} that selects a nested {@link Method} or a * combination of enclosing class names, class name, method name, and parameter * types so that {@link org.junit.platform.engine.TestEngine TestEngines} can * discover tests or containers based on methods. * *

If a Java {@link Method} is provided, the selector will return that * {@linkplain #getMethod() method} and its method name, class name, enclosing * class names, and parameter types accordingly. If class names or method names * are provided, this selector will only attempt to lazily load a class or method * if {@link #getEnclosingClasses()}, {@link #getNestedClass()}, * {@link #getMethod()}, or {@link #getParameterTypes()} is invoked. * *

In this context, a Java {@code Method} means anything that can be referenced * as a {@link Method} on the JVM — for example, methods from Java classes * or methods from other JVM languages such Groovy, Scala, etc. * * @since 1.6 * @see DiscoverySelectors#selectNestedMethod(List, String, String) * @see DiscoverySelectors#selectNestedMethod(List, String, String, String) * @see DiscoverySelectors#selectNestedMethod(List, Class, String) * @see DiscoverySelectors#selectNestedMethod(List, Class, String, String) * @see DiscoverySelectors#selectNestedMethod(List, Class, Method) * @see org.junit.platform.engine.support.descriptor.MethodSource * @see NestedClassSelector * @see MethodSelector */ @API(status = STABLE, since = "1.6") public final class NestedMethodSelector implements DiscoverySelector { private final NestedClassSelector nestedClassSelector; private final MethodSelector methodSelector; NestedMethodSelector(@Nullable ClassLoader classLoader, List enclosingClassNames, String nestedClassName, String methodName, String parameterTypeNames) { this.nestedClassSelector = new NestedClassSelector(classLoader, enclosingClassNames, nestedClassName); this.methodSelector = new MethodSelector(classLoader, nestedClassName, methodName, parameterTypeNames); } /** * @since 1.10 */ NestedMethodSelector(@Nullable ClassLoader classLoader, List enclosingClassNames, String nestedClassName, String methodName, Class... parameterTypes) { this.nestedClassSelector = new NestedClassSelector(classLoader, enclosingClassNames, nestedClassName); this.methodSelector = new MethodSelector(classLoader, nestedClassName, methodName, parameterTypes); } NestedMethodSelector(List> enclosingClasses, Class nestedClass, String methodName, String parameterTypeNames) { this.nestedClassSelector = new NestedClassSelector(enclosingClasses, nestedClass); this.methodSelector = new MethodSelector(nestedClass, methodName, parameterTypeNames); } /** * @since 1.10 */ NestedMethodSelector(List> enclosingClasses, Class nestedClass, String methodName, Class... parameterTypes) { this.nestedClassSelector = new NestedClassSelector(enclosingClasses, nestedClass); this.methodSelector = new MethodSelector(nestedClass, methodName, parameterTypes); } NestedMethodSelector(List> enclosingClasses, Class nestedClass, Method method) { this.nestedClassSelector = new NestedClassSelector(enclosingClasses, nestedClass); this.methodSelector = new MethodSelector(nestedClass, method); } /** * Get the {@link ClassLoader} used to load the nested class. * * @since 1.10 */ @API(status = MAINTAINED, since = "1.13.3") public @Nullable ClassLoader getClassLoader() { return this.nestedClassSelector.getClassLoader(); } /** * Get the names of the classes enclosing the nested class * containing the selected method. */ public List getEnclosingClassNames() { return this.nestedClassSelector.getEnclosingClassNames(); } /** * Get the list of {@link Class} enclosing the nested {@link Class} * containing the selected {@link Method}. * *

If the {@link Class} were not provided, but only the name of the * nested class and its enclosing classes, this method attempts to lazily * load the list of enclosing {@link Class} and throws a * {@link PreconditionViolationException} if the classes cannot be loaded. */ public List> getEnclosingClasses() { return this.nestedClassSelector.getEnclosingClasses(); } /** * Get the name of the nested class containing the selected method. */ public String getNestedClassName() { return this.nestedClassSelector.getNestedClassName(); } /** * Get the nested {@link Class} containing the selected {@link Method}. * *

If the {@link Class} were not provided, but only the name of the * nested class and its enclosing classes, this method attempts to lazily * load the nested {@link Class} and throws a * {@link PreconditionViolationException} if the class cannot be loaded. */ public Class getNestedClass() { return this.nestedClassSelector.getNestedClass(); } /** * Get the name of the selected method. */ public String getMethodName() { return this.methodSelector.getMethodName(); } /** * Get the selected {@link Method}. * *

If the {@link Method} was not provided, but only the name, this method * attempts to lazily load the {@code Method} based on its name and throws a * {@link PreconditionViolationException} if the method cannot be loaded. */ public Method getMethod() { return this.methodSelector.getJavaMethod(); } /** * Get the names of parameter types for the selected method as a {@link String}. * *

See {@link MethodSelector#getParameterTypeNames()} for details. * * @return the names of parameter types supplied to this {@code NestedMethodSelector} * via a constructor or deduced from a {@code Method} or parameter types supplied * via a constructor; never {@code null} but potentially an empty string * @since 1.10 * @see MethodSelector#getParameterTypeNames() * */ @API(status = STABLE, since = "1.10") public String getParameterTypeNames() { return this.methodSelector.getParameterTypeNames(); } /** * Get the parameter types for the selected method. * *

See {@link MethodSelector#getParameterTypes()} for details. * * @return the method's parameter types; never {@code null} but potentially * an empty array if the selected method does not declare parameters * @since 1.10 * @see #getParameterTypeNames() * @see MethodSelector#getParameterTypes() */ @API(status = MAINTAINED, since = "1.13.3") public Class[] getParameterTypes() { return this.methodSelector.getParameterTypes(); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } NestedMethodSelector that = (NestedMethodSelector) o; return this.nestedClassSelector.equals(that.nestedClassSelector) && this.methodSelector.equals(that.methodSelector); } @Override public int hashCode() { return Objects.hash(this.nestedClassSelector, this.methodSelector); } @Override public String toString() { return new ToStringBuilder(this) // .append("enclosingClassNames", getEnclosingClassNames()) // .append("nestedClassName", getNestedClassName()) // .append("methodName", getMethodName()) // .append("parameterTypes", getParameterTypeNames()) // .append("classLoader", getClassLoader()) // .toString(); } @Override public Optional toIdentifier() { return nestedClassSelector.toIdentifier() // .map(parent -> { String fullyQualifiedMethodName = ReflectionUtils.getFullyQualifiedMethodName(parent.getValue(), methodSelector.getMethodName(), methodSelector.getParameterTypeNames()); return DiscoverySelectorIdentifier.create(IdentifierParser.PREFIX, fullyQualifiedMethodName); }); } /** * The {@link DiscoverySelectorIdentifierParser} for * {@link NestedMethodSelector NestedMethodSelectors}. */ @API(status = INTERNAL, since = "1.11") public static class IdentifierParser implements DiscoverySelectorIdentifierParser { private static final String PREFIX = "nested-method"; public IdentifierParser() { } @Override public String getPrefix() { return PREFIX; } @Override public Optional parse(DiscoverySelectorIdentifier identifier, Context context) { List parts = Arrays.asList(identifier.getValue().split("/")); List enclosingClassNames = parts.subList(0, parts.size() - 1); String[] methodParts = ReflectionUtils.parseFullyQualifiedMethodName(parts.get(parts.size() - 1)); String nestedClassName = methodParts[0]; String methodName = methodParts[1]; String parameterTypeNames = methodParts[2]; return Optional.of(DiscoverySelectors.selectNestedMethod(enclosingClassNames, nestedClassName, methodName, parameterTypeNames)); } } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/PackageNameFilter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static org.apiguardian.api.API.Status.STABLE; import java.util.List; import org.apiguardian.api.API; import org.junit.platform.engine.DiscoveryFilter; /** * {@link DiscoveryFilter} that is applied to the name of a {@link Package}. * * @since 1.0 * @see #includePackageNames(String...) * @see #excludePackageNames(String...) * @see ClassNameFilter */ @API(status = STABLE, since = "1.0") public interface PackageNameFilter extends DiscoveryFilter { /** * Create a new include {@link PackageNameFilter} based on the * supplied package names. * *

The names are combined using OR semantics, i.e. if the fully * qualified name of a package starts with at least one of the names, * the package will be included in the result set. * * @param names package names that we be compared against fully qualified * package names; never {@code null}, empty, or containing {@code null} * @see Package#getName() * @see #includePackageNames(List) * @see #excludePackageNames(String...) */ static PackageNameFilter includePackageNames(String... names) { return new IncludePackageNameFilter(names); } /** * Create a new include {@link PackageNameFilter} based on the * supplied package names. * *

The names are combined using OR semantics, i.e. if the fully * qualified name of a package starts with at least one of the names, * the package will be included in the result set. * * @param names package names that we be compared against fully qualified * package names; never {@code null}, empty, or containing {@code null} * @see Package#getName() * @see #includePackageNames(String...) * @see #excludePackageNames(String...) */ static PackageNameFilter includePackageNames(List names) { return includePackageNames(names.toArray(new String[0])); } /** * Create a new exclude {@link PackageNameFilter} based on the * supplied package names. * *

The names are combined using OR semantics, i.e. if the fully * qualified name of a package starts with at least one of the names, * the package will be excluded in the result set. * * @param names package names that we be compared against fully qualified * package names; never {@code null}, empty, or containing {@code null} * @see Package#getName() * @see #excludePackageNames(List) * @see #includePackageNames(String...) */ static PackageNameFilter excludePackageNames(String... names) { return new ExcludePackageNameFilter(names); } /** * Create a new exclude {@link PackageNameFilter} based on the * supplied package names. * *

The names are combined using OR semantics, i.e. if the fully * qualified name of a package starts with at least one of the names, * the package will be excluded in the result set. * * @param names package names that we be compared against fully qualified * package names; never {@code null}, empty, or containing {@code null} * @see Package#getName() * @see #excludePackageNames(String...) * @see #includePackageNames(String...) */ static PackageNameFilter excludePackageNames(List names) { return excludePackageNames(names.toArray(new String[0])); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/PackageSelector.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; import java.util.Objects; import java.util.Optional; import org.apiguardian.api.API; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.DiscoverySelectorIdentifier; /** * A {@link DiscoverySelector} that selects a package name so that * {@link org.junit.platform.engine.TestEngine TestEngines} can discover * tests or containers based on packages. * * @since 1.0 * @see DiscoverySelectors#selectPackage(String) * @see org.junit.platform.engine.support.descriptor.PackageSource */ @API(status = STABLE, since = "1.0") public final class PackageSelector implements DiscoverySelector { private final String packageName; PackageSelector(String packageName) { this.packageName = packageName; } /** * Get the selected package name. */ public String getPackageName() { return this.packageName; } /** * @since 1.3 */ @API(status = STABLE, since = "1.3") @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } PackageSelector that = (PackageSelector) o; return Objects.equals(this.packageName, that.packageName); } /** * @since 1.3 */ @API(status = STABLE, since = "1.3") @Override public int hashCode() { return this.packageName.hashCode(); } @Override public String toString() { return new ToStringBuilder(this).append("packageName", this.packageName).toString(); } @Override public Optional toIdentifier() { return Optional.of(DiscoverySelectorIdentifier.create(IdentifierParser.PREFIX, this.packageName)); } /** * The {@link DiscoverySelectorIdentifierParser} for {@link PackageSelector * PackageSelectors}. */ @API(status = INTERNAL, since = "1.11") public static class IdentifierParser implements DiscoverySelectorIdentifierParser { private static final String PREFIX = "package"; public IdentifierParser() { } @Override public String getPrefix() { return PREFIX; } @Override public Optional parse(DiscoverySelectorIdentifier identifier, Context context) { return Optional.of(DiscoverySelectors.selectPackage(identifier.getValue())); } } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/UniqueIdSelector.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; import java.util.Objects; import java.util.Optional; import org.apiguardian.api.API; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.DiscoverySelectorIdentifier; import org.junit.platform.engine.UniqueId; /** * A {@link DiscoverySelector} that selects a {@link UniqueId} so that * {@link org.junit.platform.engine.TestEngine TestEngines} can discover * tests or containers based on unique IDs. * * @since 1.0 * @see DiscoverySelectors#selectUniqueId(String) * @see DiscoverySelectors#selectUniqueId(UniqueId) */ @API(status = STABLE, since = "1.0") public final class UniqueIdSelector implements DiscoverySelector { private final UniqueId uniqueId; UniqueIdSelector(UniqueId uniqueId) { this.uniqueId = uniqueId; } /** * Get the selected {@link UniqueId}. */ public UniqueId getUniqueId() { return this.uniqueId; } /** * @since 1.3 */ @API(status = STABLE, since = "1.3") @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } UniqueIdSelector that = (UniqueIdSelector) o; return Objects.equals(this.uniqueId, that.uniqueId); } /** * @since 1.3 */ @API(status = STABLE, since = "1.3") @Override public int hashCode() { return this.uniqueId.hashCode(); } @Override public String toString() { return new ToStringBuilder(this).append("uniqueId", this.uniqueId).toString(); } @Override public Optional toIdentifier() { return Optional.of(DiscoverySelectorIdentifier.create(IdentifierParser.PREFIX, this.uniqueId.toString())); } /** * The {@link DiscoverySelectorIdentifierParser} for * {@link UniqueIdSelector UniqueIdSelectors}. */ @API(status = INTERNAL, since = "1.11") public static class IdentifierParser implements DiscoverySelectorIdentifierParser { private static final String PREFIX = "uid"; public IdentifierParser() { } @Override public String getPrefix() { return PREFIX; } @Override public Optional parse(DiscoverySelectorIdentifier identifier, Context context) { return Optional.of(DiscoverySelectors.selectUniqueId(identifier.getValue())); } } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/UriSelector.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; import java.net.URI; import java.util.Objects; import java.util.Optional; import org.apiguardian.api.API; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.DiscoverySelectorIdentifier; /** * A {@link DiscoverySelector} that selects a {@link URI} so that * {@link org.junit.platform.engine.TestEngine TestEngines} * can discover tests or containers based on URIs. * * @since 1.0 * @see DiscoverySelectors#selectUri(String) * @see DiscoverySelectors#selectUri(URI) * @see FileSelector * @see DirectorySelector * @see org.junit.platform.engine.support.descriptor.UriSource */ @API(status = STABLE, since = "1.0") public final class UriSelector implements DiscoverySelector { private final URI uri; UriSelector(URI uri) { this.uri = uri; } /** * Get the selected {@link URI}. */ public URI getUri() { return this.uri; } /** * @since 1.3 */ @API(status = STABLE, since = "1.3") @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } UriSelector that = (UriSelector) o; return Objects.equals(this.uri, that.uri); } /** * @since 1.3 */ @API(status = STABLE, since = "1.3") @Override public int hashCode() { return this.uri.hashCode(); } @Override public String toString() { return new ToStringBuilder(this).append("uri", this.uri).toString(); } @Override public Optional toIdentifier() { return Optional.of(DiscoverySelectorIdentifier.create(IdentifierParser.PREFIX, this.uri.toString())); } /** * The {@link DiscoverySelectorIdentifierParser} for {@link UriSelector * UriSelectors}. */ @API(status = INTERNAL, since = "1.11") public static class IdentifierParser implements DiscoverySelectorIdentifierParser { private static final String PREFIX = "uri"; public IdentifierParser() { } @Override public String getPrefix() { return PREFIX; } @Override public Optional parse(DiscoverySelectorIdentifier identifier, Context context) { return Optional.of(DiscoverySelectors.selectUri(identifier.getValue())); } } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Concrete {@linkplain org.junit.platform.engine.DiscoverySelector selectors} and * {@linkplain org.junit.platform.engine.DiscoveryFilter filters} to be used in * {@linkplain org.junit.platform.engine.EngineDiscoveryRequest discovery requests}. */ @NullMarked package org.junit.platform.engine.discovery; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Public API for test engines. */ @NullMarked package org.junit.platform.engine; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/FileEntry.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.reporting; import static org.apiguardian.api.API.Status.MAINTAINED; import java.nio.file.Path; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.Optional; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; /** * {@code FileEntry} encapsulates a file or directory to be published to the * reporting infrastructure. * * @since 1.12 * @see #from(Path, String) */ @API(status = MAINTAINED, since = "1.13.3") public final class FileEntry { /** * Factory for creating a new {@code FileEntry} from the supplied path and * media type. * *

The {@link Path} may represent a file or a directory. * * @param path the path to publish; never {@code null} * @param mediaType the media type of the path to publish; may be * {@code null} — for example, if the path represents a directory */ public static FileEntry from(Path path, @Nullable String mediaType) { return new FileEntry(path, mediaType); } private final LocalDateTime timestamp = LocalDateTime.now(ZoneId.systemDefault()); private final Path path; private final @Nullable String mediaType; private FileEntry(Path path, @Nullable String mediaType) { this.path = Preconditions.notNull(path, "path must not be null"); this.mediaType = mediaType; } /** * Get the timestamp for when this {@code FileEntry} was created. * * @return when this entry was created; never {@code null} */ public LocalDateTime getTimestamp() { return this.timestamp; } /** * Get the path for the file or directory to be published. * * @return the path to publish; never {@code null} */ public Path getPath() { return path; } /** * Get the media type of the path to be published. * * @return the media type of the path to publish; never {@code null} but * potentially empty — for example, if the path represents a directory */ public Optional getMediaType() { return Optional.ofNullable(mediaType); } @Override public String toString() { ToStringBuilder builder = new ToStringBuilder(this); builder.append("timestamp", this.timestamp); builder.append("path", this.path); if (this.mediaType != null) { builder.append("mediaType", this.mediaType); } return builder.toString(); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/OutputDirectoryProvider.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.reporting; import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.INTERNAL; import org.apiguardian.api.API; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.OutputDirectoryCreator; /** * Provider of output directories for test engines to write reports and other * output files to. * * @since 1.12 * @see EngineDiscoveryRequest#getOutputDirectoryProvider() * @deprecated Please implement {@link OutputDirectoryCreator} instead */ @SuppressWarnings("removal") @Deprecated(since = "1.14", forRemoval = true) @API(status = DEPRECATED, since = "1.14") public interface OutputDirectoryProvider extends OutputDirectoryCreator { /** * Cast or adapt an {@link OutputDirectoryCreator} to a * {@code OutputDirectoryProvider}. * * @since 1.14 */ @API(status = INTERNAL, since = "1.14") static OutputDirectoryProvider castOrAdapt(OutputDirectoryCreator outputDirectoryCreator) { if (outputDirectoryCreator instanceof OutputDirectoryProvider provider) { return provider; } return new OutputDirectoryProviderAdapter(outputDirectoryCreator); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/OutputDirectoryProviderAdapter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.reporting; import java.io.IOException; import java.nio.file.Path; import org.junit.platform.engine.OutputDirectoryCreator; import org.junit.platform.engine.TestDescriptor; /** * @since 1.14 */ @SuppressWarnings("removal") class OutputDirectoryProviderAdapter implements OutputDirectoryProvider { private final OutputDirectoryCreator outputDirectoryCreator; OutputDirectoryProviderAdapter(OutputDirectoryCreator outputDirectoryCreator) { this.outputDirectoryCreator = outputDirectoryCreator; } @Override public Path getRootDirectory() { return this.outputDirectoryCreator.getRootDirectory(); } @Override public Path createOutputDirectory(TestDescriptor testDescriptor) throws IOException { return this.outputDirectoryCreator.createOutputDirectory(testDescriptor); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/ReportEntry.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.reporting; import static org.apiguardian.api.API.Status.STABLE; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import org.apiguardian.api.API; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; /** * {@code ReportEntry} encapsulates a time-stamped map of {@code String}-based * key-value pairs to be published to the reporting infrastructure. * * @since 1.0 * @see #from(Map) * @see #from(String, String) */ @API(status = STABLE, since = "1.0") public final class ReportEntry { private final LocalDateTime timestamp = LocalDateTime.now(ZoneId.systemDefault()); private final Map keyValuePairs = new LinkedHashMap<>(); private ReportEntry() { } /** * Factory for creating a new {@code ReportEntry} from a map of key-value pairs. * * @param keyValuePairs the map of key-value pairs to be published; never * {@code null}; keys and values within entries in the map also must not be * {@code null} or blank */ public static ReportEntry from(Map keyValuePairs) { Preconditions.notNull(keyValuePairs, "keyValuePairs must not be null"); ReportEntry reportEntry = new ReportEntry(); keyValuePairs.forEach(reportEntry::add); return reportEntry; } /** * Factory for creating a new {@code ReportEntry} from a key-value pair. * * @param key the key under which the value should published; never * {@code null} or blank * @param value the value to publish; never {@code null} or blank */ public static ReportEntry from(String key, String value) { ReportEntry reportEntry = new ReportEntry(); reportEntry.add(key, value); return reportEntry; } private void add(String key, String value) { Preconditions.notBlank(key, "key must not be null or blank"); Preconditions.notBlank(value, "value must not be null or blank"); this.keyValuePairs.put(key, value); } /** * Get an unmodifiable copy of the map of key-value pairs to be published. * * @return a copy of the map of key-value pairs; never {@code null} */ public Map getKeyValuePairs() { return Collections.unmodifiableMap(this.keyValuePairs); } /** * Get the timestamp for when this {@code ReportEntry} was created. * *

Can be used, for example, to order entries. * * @return when this entry was created; never {@code null} */ public LocalDateTime getTimestamp() { return this.timestamp; } @Override public String toString() { ToStringBuilder builder = new ToStringBuilder(this); builder.append("timestamp", this.timestamp); this.keyValuePairs.forEach(builder::append); return builder.toString(); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Classes used by test engines to report additional data to execution * listeners. */ @NullMarked package org.junit.platform.engine.reporting; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/config/PrefixedConfigurationParameters.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.config; import static org.apiguardian.api.API.Status.STABLE; import java.util.Optional; import java.util.Set; import java.util.function.Function; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.ConfigurationParameters; /** * View of {@link ConfigurationParameters} that applies a supplied prefix to all * queries. * * @since 1.3 */ @API(status = STABLE, since = "1.10") public class PrefixedConfigurationParameters implements ConfigurationParameters { private final ConfigurationParameters delegate; private final String prefix; /** * Create a new view of the supplied {@link ConfigurationParameters} that * applies the supplied prefix to all queries. * * @param delegate the {@link ConfigurationParameters} to delegate to; never * {@code null} * @param prefix the prefix to apply to all queries; never {@code null} or * blank */ public PrefixedConfigurationParameters(ConfigurationParameters delegate, String prefix) { this.delegate = Preconditions.notNull(delegate, "delegate must not be null"); this.prefix = Preconditions.notBlank(prefix, "prefix must not be null or blank"); } @Override public Optional get(String key) { return delegate.get(prefixed(key)); } @Override public Optional getBoolean(String key) { return delegate.getBoolean(prefixed(key)); } @Override public Optional get(String key, Function transformer) { return delegate.get(prefixed(key), transformer); } private String prefixed(String key) { return prefix + key; } @Override public Set keySet() { return delegate.keySet(); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/config/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * {@link org.junit.platform.engine.ConfigurationParameters}-related support * classes intended to be used by test engine implementations. */ @NullMarked package org.junit.platform.engine.support.config; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/AbstractTestDescriptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.descriptor; import static java.util.Collections.emptySet; import static org.apiguardian.api.API.Status.STABLE; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.function.UnaryOperator; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestSource; import org.junit.platform.engine.TestTag; import org.junit.platform.engine.UniqueId; /** * Abstract base implementation of {@link TestDescriptor} that may be used by * custom {@link org.junit.platform.engine.TestEngine TestEngines}. * *

Subclasses should provide a {@link TestSource} in their constructor, if * possible, and override {@link #getTags()}, if appropriate. * * @since 1.0 */ @API(status = STABLE, since = "1.0") public abstract class AbstractTestDescriptor implements TestDescriptor { /** * The Unicode replacement character, often displayed as a black diamond with * a white question mark in it: {@value} */ private static final String UNICODE_REPLACEMENT_CHARACTER = "\uFFFD"; private final UniqueId uniqueId; private final String displayName; private final @Nullable TestSource source; private @Nullable TestDescriptor parent; /** * The synchronized set of children associated with this {@code TestDescriptor}. * *

This set is used in methods such as {@link #addChild(TestDescriptor)}, * {@link #removeChild(TestDescriptor)}, {@link #removeFromHierarchy()}, and * {@link #findByUniqueId(UniqueId)}, and an immutable copy of this set is * returned by {@link #getChildren()}. * *

If a subclass overrides any of the methods related to children, this * set should be used instead of a set local to the subclass. */ protected final Set children = Collections.synchronizedSet(new LinkedHashSet<>(16)); /** * Create a new {@code AbstractTestDescriptor} with the supplied * {@link UniqueId} and display name. * *

As of JUnit 6.0, ISO control characters in the provided display name * will be replaced. See {@link #AbstractTestDescriptor(UniqueId, String, TestSource)} * for details. * * @param uniqueId the unique ID of this {@code TestDescriptor}; never * {@code null} * @param displayName the display name for this {@code TestDescriptor}; * never {@code null} or blank * @see #AbstractTestDescriptor(UniqueId, String, TestSource) */ protected AbstractTestDescriptor(UniqueId uniqueId, String displayName) { this(uniqueId, displayName, null); } /** * Create a new {@code AbstractTestDescriptor} with the supplied * {@link UniqueId}, display name, and source. * *

As of JUnit 6.0, ISO control characters in the provided display name * will be replaced according to the following table. * * * * * * * *
Control Character Replacement
Original Replacement Description
{@code \r} {@code } Textual representation of a carriage return
{@code \n} {@code } Textual representation of a line feed
Other control character Unicode replacement character (U+FFFD)
* * @param uniqueId the unique ID of this {@code TestDescriptor}; never * {@code null} * @param displayName the display name for this {@code TestDescriptor}; * never {@code null} or blank * @param source the source of the test or container described by this * {@code TestDescriptor}; can be {@code null} * @see #AbstractTestDescriptor(UniqueId, String) */ protected AbstractTestDescriptor(UniqueId uniqueId, String displayName, @Nullable TestSource source) { this.uniqueId = Preconditions.notNull(uniqueId, "UniqueId must not be null"); this.displayName = replaceControlCharacters( Preconditions.notBlank(displayName, "displayName must not be null or blank")); this.source = source; } @Override public final UniqueId getUniqueId() { return this.uniqueId; } @Override public final String getDisplayName() { return this.displayName; } @Override public Set getTags() { return emptySet(); } @Override public Optional getSource() { return Optional.ofNullable(this.source); } @Override public final Optional getParent() { return Optional.ofNullable(this.parent); } @Override public final void setParent(@Nullable TestDescriptor parent) { this.parent = parent; } @Override public final Set getChildren() { return Collections.unmodifiableSet(new LinkedHashSet<>(this.children)); } @Override public void addChild(TestDescriptor child) { Preconditions.notNull(child, "child must not be null"); child.setParent(this); this.children.add(child); } @Override public void removeChild(TestDescriptor child) { Preconditions.notNull(child, "child must not be null"); this.children.remove(child); child.setParent(null); } @Override public void removeFromHierarchy() { var parent = Preconditions.notNull(this.parent, "cannot remove the root of a hierarchy"); parent.removeChild(this); this.children.forEach(child -> child.setParent(null)); this.children.clear(); } /** * {@inheritDoc} */ @Override public void orderChildren(UnaryOperator> orderer) { Preconditions.notNull(orderer, "orderer must not be null"); List suggestedOrder = orderer.apply(new ArrayList<>(this.children)); Preconditions.notNull(suggestedOrder, "orderer may not return null"); Set orderedChildren = new LinkedHashSet<>(suggestedOrder); boolean unmodified = this.children.equals(orderedChildren); Preconditions.condition(unmodified && this.children.size() == suggestedOrder.size(), "orderer may not add or remove test descriptors"); this.children.clear(); this.children.addAll(orderedChildren); } @Override public Optional findByUniqueId(UniqueId uniqueId) { Preconditions.notNull(uniqueId, "UniqueId must not be null"); if (getUniqueId().equals(uniqueId)) { return Optional.of(this); } // @formatter:off return this.children.stream() .map(child -> child.findByUniqueId(uniqueId)) .filter(Optional::isPresent) .findAny() .orElse(Optional.empty()); // @formatter:on } @Override public final int hashCode() { return this.uniqueId.hashCode(); } @Override @SuppressWarnings("EqualsGetClass") public final boolean equals(Object other) { if (other == null) { return false; } if (this.getClass() != other.getClass()) { return false; } TestDescriptor that = (TestDescriptor) other; return this.getUniqueId().equals(that.getUniqueId()); } @Override public String toString() { return getClass().getSimpleName() + ": " + getUniqueId(); } private static String replaceControlCharacters(String text) { StringBuilder builder = new StringBuilder(); for (int i = 0; i < text.length(); i++) { builder.append(replaceControlCharacter(text.charAt(i))); } return builder.toString(); } private static String replaceControlCharacter(char ch) { return switch (ch) { case '\r' -> ""; case '\n' -> ""; default -> Character.isISOControl(ch) ? UNICODE_REPLACEMENT_CHARACTER : String.valueOf(ch); }; } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ClassSource.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.descriptor; import static org.apiguardian.api.API.Status.STABLE; import java.io.Serial; import java.net.URI; import java.util.Objects; import java.util.Optional; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.TestSource; /** * Class based {@link org.junit.platform.engine.TestSource TestSource} with * an optional {@linkplain FilePosition file position}. * *

If a Java {@link Class} reference is provided, the {@code ClassSource} * will contain that {@code Class} and its class name accordingly. If a class * name is provided, the {@code ClassSource} will contain the class name and * will only attempt to lazily load the {@link Class} if {@link #getJavaClass()} * is invoked. * *

In this context, Java {@link Class} means anything that can be referenced * as a {@link Class} on the JVM — for example, classes from other JVM * languages such Groovy, Scala, etc. * * @since 1.0 * @see org.junit.platform.engine.discovery.ClassSelector */ @API(status = STABLE, since = "1.0") public final class ClassSource implements TestSource { @Serial private static final long serialVersionUID = 1L; /** * {@link URI} {@linkplain URI#getScheme() scheme} for class sources: {@value} * * @since 1.8 */ @API(status = STABLE, since = "1.8") public static final String CLASS_SCHEME = "class"; /** * Create a new {@code ClassSource} using the supplied class name. * * @param className the class name; must not be {@code null} or blank */ public static ClassSource from(String className) { return new ClassSource(className); } /** * Create a new {@code ClassSource} using the supplied class name and * {@linkplain FilePosition file position}. * * @param className the class name; must not be {@code null} or blank * @param filePosition the position in the source file; may be {@code null} */ public static ClassSource from(String className, @Nullable FilePosition filePosition) { return new ClassSource(className, filePosition); } /** * Create a new {@code ClassSource} using the supplied {@linkplain Class class}. * * @param javaClass the Java class; must not be {@code null} */ public static ClassSource from(Class javaClass) { return new ClassSource(javaClass); } /** * Create a new {@code ClassSource} using the supplied {@linkplain Class class} * and {@linkplain FilePosition file position}. * * @param javaClass the Java class; must not be {@code null} * @param filePosition the position in the Java source file; may be {@code null} */ public static ClassSource from(Class javaClass, FilePosition filePosition) { return new ClassSource(javaClass, filePosition); } /** * Create a new {@code ClassSource} from the supplied {@link URI}. * *

URIs should be formatted as {@code class:fully.qualified.class.Name}. * The {@linkplain URI#getQuery() query} component of the {@code URI}, if * present, will be used to retrieve the {@link FilePosition} via * {@link FilePosition#fromQuery(String)}. For example, line 42 and column * 13 can be referenced in class {@code org.example.MyType} via the following * URI: {@code class:com.example.MyType?line=42&column=13}. The URI fragment, * if present, will be ignored. * * @param uri the {@code URI} for the class source; never {@code null} * @return a new {@code ClassSource}; never {@code null} * @throws PreconditionViolationException if the supplied {@code URI} is * {@code null}, if the scheme of the supplied {@code URI} is not equal * to the {@link #CLASS_SCHEME}, or if the specified class name is empty * @since 1.8 * @see #CLASS_SCHEME */ @API(status = STABLE, since = "1.8") public static ClassSource from(URI uri) { Preconditions.notNull(uri, "URI must not be null"); Preconditions.condition(CLASS_SCHEME.equals(uri.getScheme()), () -> "URI [" + uri + "] must have [" + CLASS_SCHEME + "] scheme"); String className = uri.getSchemeSpecificPart(); FilePosition filePosition = null; int indexOfQuery = className.indexOf('?'); if (indexOfQuery >= 0) { filePosition = FilePosition.fromQuery(className.substring(indexOfQuery + 1)).orElse(null); className = className.substring(0, indexOfQuery); } return ClassSource.from(className, filePosition); } private final String className; private final @Nullable FilePosition filePosition; private @Nullable Class javaClass; private ClassSource(String className) { this(className, null); } private ClassSource(String className, @Nullable FilePosition filePosition) { this.className = Preconditions.notBlank(className, "Class name must not be null or blank"); this.filePosition = filePosition; } private ClassSource(Class javaClass) { this(javaClass, null); } private ClassSource(Class javaClass, @Nullable FilePosition filePosition) { this.javaClass = Preconditions.notNull(javaClass, "Class must not be null"); this.className = this.javaClass.getName(); this.filePosition = filePosition; } /** * Get the class name of this source. * * @see #getJavaClass() * @see #getPosition() */ public String getClassName() { return this.className; } /** * Get the {@linkplain Class Java class} of this source. * *

If the {@link Class} was not provided, but only the name, this method * attempts to lazily load the {@link Class} based on its name and throws a * {@link PreconditionViolationException} if the class cannot be loaded. * * @see #getClassName() * @see #getPosition() */ public Class getJavaClass() { if (this.javaClass == null) { // @formatter:off this.javaClass = ReflectionSupport.tryToLoadClass(this.className).getNonNullOrThrow( cause -> new PreconditionViolationException("Could not load class with name: " + this.className, cause)); // @formatter:on } return this.javaClass; } /** * Get the {@linkplain FilePosition position} in the source file for * the associated {@linkplain #getClassName class}, if available. * * @see #getClassName() * @see #getJavaClass() */ public Optional getPosition() { return Optional.ofNullable(this.filePosition); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ClassSource that = (ClassSource) o; return Objects.equals(this.className, that.className) && Objects.equals(this.filePosition, that.filePosition); } @Override public int hashCode() { return Objects.hash(this.className, this.filePosition); } @Override public String toString() { // @formatter:off return new ToStringBuilder(this) .append("className", this.className) .append("filePosition", this.filePosition) .toString(); // @formatter:on } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSource.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.descriptor; import static org.apiguardian.api.API.Status.STABLE; import java.io.Serial; import java.net.URI; import java.util.Objects; import java.util.Optional; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ResourceUtils; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.TestSource; /** * Classpath resource based {@link org.junit.platform.engine.TestSource} * with an optional {@linkplain FilePosition position}. * * @since 1.0 * @see org.junit.platform.engine.discovery.ClasspathResourceSelector */ @API(status = STABLE, since = "1.0") public final class ClasspathResourceSource implements TestSource { @Serial private static final long serialVersionUID = 1L; /** * {@link URI} {@linkplain URI#getScheme() scheme} for classpath * resources: {@value} * * @since 1.3 */ public static final String CLASSPATH_SCHEME = "classpath"; /** * Create a new {@code ClasspathResourceSource} using the supplied classpath * resource name. * *

The name of a classpath resource must follow the semantics * for resource paths as defined in {@link ClassLoader#getResource(String)}. * *

If the supplied classpath resource name is prefixed with a slash * ({@code /}), the slash will be removed. * * @param classpathResourceName the name of the classpath resource; never * {@code null} or blank * @see ClassLoader#getResource(String) * @see ClassLoader#getResourceAsStream(String) * @see ClassLoader#getResources(String) */ public static ClasspathResourceSource from(String classpathResourceName) { return new ClasspathResourceSource(classpathResourceName); } /** * Create a new {@code ClasspathResourceSource} using the supplied classpath * resource name and {@link FilePosition}. * *

The name of a classpath resource must follow the semantics * for resource paths as defined in {@link ClassLoader#getResource(String)}. * *

If the supplied classpath resource name is prefixed with a slash * ({@code /}), the slash will be removed. * * @param classpathResourceName the name of the classpath resource; never * {@code null} or blank * @param filePosition the position in the classpath resource; may be {@code null} */ public static ClasspathResourceSource from(String classpathResourceName, @Nullable FilePosition filePosition) { return new ClasspathResourceSource(classpathResourceName, filePosition); } /** * Create a new {@code ClasspathResourceSource} from the supplied {@link URI}. * *

The {@link URI#getPath() path} component of the {@code URI} (excluding * the query) will be used as the classpath resource name. The * {@linkplain URI#getQuery() query} component of the {@code URI}, if present, * will be used to retrieve the {@link FilePosition} via * {@link FilePosition#fromQuery(String)}. * * @param uri the {@code URI} for the classpath resource; never {@code null} * @return a new {@code ClasspathResourceSource}; never {@code null} * @throws PreconditionViolationException if the supplied {@code URI} is * {@code null} or if the scheme of the supplied {@code URI} is not equal * to the {@link #CLASSPATH_SCHEME} * @since 1.3 * @see #CLASSPATH_SCHEME */ @API(status = STABLE, since = "1.3") public static ClasspathResourceSource from(URI uri) { Preconditions.notNull(uri, "URI must not be null"); Preconditions.condition(CLASSPATH_SCHEME.equals(uri.getScheme()), () -> "URI [" + uri + "] must have [" + CLASSPATH_SCHEME + "] scheme"); String classpathResource = ResourceUtils.stripQueryComponent(uri).getPath(); FilePosition filePosition = FilePosition.fromQuery(uri.getQuery()).orElse(null); return ClasspathResourceSource.from(classpathResource, filePosition); } private final String classpathResourceName; private final @Nullable FilePosition filePosition; private ClasspathResourceSource(String classpathResourceName) { this(classpathResourceName, null); } private ClasspathResourceSource(String classpathResourceName, @Nullable FilePosition filePosition) { Preconditions.notBlank(classpathResourceName, "Classpath resource name must not be null or blank"); boolean startsWithSlash = classpathResourceName.startsWith("/"); this.classpathResourceName = (startsWithSlash ? classpathResourceName.substring(1) : classpathResourceName); this.filePosition = filePosition; } /** * Get the name of the source classpath resource. * *

The name of a classpath resource follows the semantics for * resource paths as defined in {@link ClassLoader#getResource(String)}. * * @see ClassLoader#getResource(String) * @see ClassLoader#getResourceAsStream(String) * @see ClassLoader#getResources(String) */ public String getClasspathResourceName() { return this.classpathResourceName; } /** * Get the {@link FilePosition}, if available. */ public Optional getPosition() { return Optional.ofNullable(this.filePosition); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ClasspathResourceSource that = (ClasspathResourceSource) o; return Objects.equals(this.classpathResourceName, that.classpathResourceName) && Objects.equals(this.filePosition, that.filePosition); } @Override public int hashCode() { return Objects.hash(this.classpathResourceName, this.filePosition); } @Override public String toString() { // @formatter:off return new ToStringBuilder(this) .append("classpathResourceName", this.classpathResourceName) .append("filePosition", this.filePosition) .toString(); // @formatter:on } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/CompositeTestSource.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.descriptor; import static org.apiguardian.api.API.Status.STABLE; import java.io.Serial; import java.util.Collection; import java.util.List; import org.apiguardian.api.API; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.TestSource; /** * A {@code CompositeTestSource} contains one or more {@link TestSource TestSources}. * *

{@code CompositeTestSource} and its {@link #getSources sources} are immutable. * * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class CompositeTestSource implements TestSource { @Serial private static final long serialVersionUID = 1L; /** * Create a new {@code CompositeTestSource} based on the supplied * collection of {@link TestSource sources}. * *

This constructor makes a defensive copy of the supplied collection * and stores the sources as a list in the order in which they are * returned by the collection's iterator. * * @param sources the collection of sources to store in this * {@code CompositeTestSource}; never {@code null} or empty */ public static CompositeTestSource from(Collection sources) { return new CompositeTestSource(sources); } @SuppressWarnings({ "serial", "RedundantSuppression" }) // always used with serializable implementation (unmodifiableList()) private final List sources; private CompositeTestSource(Collection sources) { Preconditions.notEmpty(sources, "TestSource collection must not be null or empty"); Preconditions.containsNoNullElements(sources, "individual TestSources must not be null"); this.sources = List.copyOf(sources); } /** * Get an immutable list of the {@linkplain TestSource sources} stored in this * {@code CompositeTestSource}. * * @return the sources stored in this {@code CompositeTestSource}; never {@code null} */ public List getSources() { return this.sources; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } CompositeTestSource that = (CompositeTestSource) obj; return this.sources.equals(that.sources); } @Override public int hashCode() { return this.sources.hashCode(); } @Override public String toString() { return new ToStringBuilder(this).append("sources", this.sources).toString(); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/DefaultUriSource.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.descriptor; import java.io.Serial; import java.net.URI; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; /** * Default implementation of {@link UriSource}. * * @since 1.3 */ record DefaultUriSource(URI uri) implements UriSource { @Serial private static final long serialVersionUID = 1L; DefaultUriSource { Preconditions.notNull(uri, "URI must not be null"); } @Override public URI getUri() { return uri; } @Override public String toString() { return new ToStringBuilder(this).append("uri", this.uri).toString(); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/DirectorySource.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.descriptor; import static org.apiguardian.api.API.Status.STABLE; import java.io.File; import java.io.IOException; import java.io.Serial; import java.net.URI; import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; /** * Directory based {@link org.junit.platform.engine.TestSource}. * * @since 1.0 * @see org.junit.platform.engine.discovery.DirectorySelector */ @API(status = STABLE, since = "1.0") public final class DirectorySource implements FileSystemSource { @Serial private static final long serialVersionUID = 1L; /** * Create a new {@code DirectorySource} using the supplied * {@linkplain File directory}. * * @param directory the source directory; must not be {@code null} */ public static DirectorySource from(File directory) { return new DirectorySource(directory); } private final File directory; private DirectorySource(File directory) { Preconditions.notNull(directory, "directory must not be null"); try { this.directory = directory.getCanonicalFile(); } catch (IOException ex) { throw new JUnitException("Failed to retrieve canonical path for directory: " + directory, ex); } } /** * Get the {@link URI} for the source {@linkplain #getFile directory}. * * @return the source {@code URI}; never {@code null} */ @Override public URI getUri() { return getFile().toURI(); } /** * Get the source {@linkplain File directory}. * * @return the source directory; never {@code null} */ @Override public File getFile() { return this.directory; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } DirectorySource that = (DirectorySource) o; return this.directory.equals(that.directory); } @Override public int hashCode() { return this.directory.hashCode(); } @Override public String toString() { return new ToStringBuilder(this).append("directory", this.directory).toString(); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/EngineDescriptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.descriptor; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; import org.junit.platform.engine.UniqueId; /** * An {@code EngineDescriptor} is a {@link org.junit.platform.engine.TestDescriptor * TestDescriptor} for a specific {@link org.junit.platform.engine.TestEngine TestEngine}. * * @since 1.0 */ @API(status = STABLE, since = "1.0") public class EngineDescriptor extends AbstractTestDescriptor { /** * Create a new {@code EngineDescriptor} with the supplied {@link UniqueId} * and display name. * * @param uniqueId the {@code UniqueId} for the described {@code TestEngine}; * never {@code null} * @param displayName the display name for the described {@code TestEngine}; * never {@code null} or blank * @see org.junit.platform.engine.TestEngine#getId() * @see org.junit.platform.engine.TestDescriptor#getDisplayName() */ public EngineDescriptor(UniqueId uniqueId, String displayName) { super(uniqueId, displayName); } /** * Returns {@link org.junit.platform.engine.TestDescriptor.Type#CONTAINER}. * * @see org.junit.platform.engine.TestDescriptor#isContainer() * @see org.junit.platform.engine.TestDescriptor#isTest() */ @Override public Type getType() { return Type.CONTAINER; } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FilePosition.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.descriptor; import static org.apiguardian.api.API.Status.STABLE; import java.io.Serial; import java.io.Serializable; import java.util.Objects; import java.util.Optional; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.StringUtils; import org.junit.platform.commons.util.ToStringBuilder; /** * Position inside a file represented by {@linkplain #getLine line} and * {@linkplain #getColumn column} numbers. * * @since 1.0 */ @API(status = STABLE, since = "1.0") public final class FilePosition implements Serializable { @Serial private static final long serialVersionUID = 1L; private static final Logger logger = LoggerFactory.getLogger(FilePosition.class); /** * Create a new {@code FilePosition} using the supplied {@code line} number * and an undefined column number. * * @param line the line number; must be greater than zero * @return a {@link FilePosition} with the given line number */ public static FilePosition from(int line) { return new FilePosition(line); } /** * Create a new {@code FilePosition} using the supplied {@code line} and * {@code column} numbers. * * @param line the line number; must be greater than zero * @param column the column number; must be greater than zero * @return a {@link FilePosition} with the given line and column numbers */ public static FilePosition from(int line, int column) { return new FilePosition(line, column); } /** * Create an optional {@code FilePosition} by parsing the supplied * {@code query} string. * *

Examples of valid {@code query} strings: *

    *
  • {@code "line=23"}
  • *
  • {@code "line=23&column=42"}
  • *
* * @param query the query string; may be {@code null} * @return an {@link Optional} containing a {@link FilePosition} with * the parsed line and column numbers; never {@code null} but potentially * empty * @since 1.3 * @see #from(int) * @see #from(int, int) */ public static Optional fromQuery(String query) { FilePosition result = null; Integer line = null; Integer column = null; if (StringUtils.isNotBlank(query)) { try { for (String pair : query.split("&")) { String[] data = pair.split("="); if (data.length == 2) { String key = data[0]; if (line == null && "line".equals(key)) { line = Integer.valueOf(data[1]); } else if (column == null && "column".equals(key)) { column = Integer.valueOf(data[1]); } } // Already found what we're looking for? if (line != null && column != null) { break; } } } catch (IllegalArgumentException ex) { logger.debug(ex, () -> "Failed to parse 'line' and/or 'column' from query string: " + query); // fall-through and continue } if (line != null) { result = column == null ? new FilePosition(line) : new FilePosition(line, column); } } return Optional.ofNullable(result); } private final int line; private final @Nullable Integer column; private FilePosition(int line) { Preconditions.condition(line > 0, "line number must be greater than zero"); this.line = line; this.column = null; } private FilePosition(int line, int column) { Preconditions.condition(line > 0, "line number must be greater than zero"); Preconditions.condition(column > 0, "column number must be greater than zero"); this.line = line; this.column = column; } /** * Get the line number of this {@code FilePosition}. * * @return the line number */ public int getLine() { return this.line; } /** * Get the column number of this {@code FilePosition}, if available. * * @return an {@code Optional} containing the column number; never * {@code null} but potentially empty */ public Optional getColumn() { return Optional.ofNullable(this.column); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } FilePosition that = (FilePosition) o; return (this.line == that.line) && Objects.equals(this.column, that.column); } @Override public int hashCode() { return Objects.hash(this.line, this.column); } @Override public String toString() { // @formatter:off return new ToStringBuilder(this) .append("line", this.line) .append("column", getColumn().orElse(-1)) .toString(); // @formatter:on } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FileSource.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.descriptor; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import java.io.File; import java.io.IOException; import java.io.Serial; import java.net.URI; import java.util.Objects; import java.util.Optional; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; /** * File based {@link org.junit.platform.engine.TestSource} with an optional * {@linkplain FilePosition position}. * * @since 1.0 * @see org.junit.platform.engine.discovery.FileSelector */ @API(status = STABLE, since = "1.0") public final class FileSource implements FileSystemSource { @Serial private static final long serialVersionUID = 1L; /** * Create a new {@code FileSource} using the supplied {@link File file}. * * @param file the source file; must not be {@code null} */ public static FileSource from(File file) { return new FileSource(file); } /** * Create a new {@code FileSource} using the supplied {@link File file} and * {@link FilePosition filePosition}. * * @param file the source file; must not be {@code null} * @param filePosition the position in the source file; may be {@code null} * @see #withPosition(FilePosition) */ public static FileSource from(File file, @Nullable FilePosition filePosition) { return new FileSource(file, filePosition); } private final File file; private final @Nullable FilePosition filePosition; private FileSource(File file) { this(file, null); } private FileSource(File file, @Nullable FilePosition filePosition) { Preconditions.notNull(file, "file must not be null"); try { this.file = file.getCanonicalFile(); } catch (IOException ex) { throw new JUnitException("Failed to retrieve canonical path for file: " + file, ex); } this.filePosition = filePosition; } private FileSource(FileSource fileSource, @Nullable FilePosition filePosition) { this.file = fileSource.file; this.filePosition = filePosition; } /** * Get the {@link URI} for the source {@linkplain #getFile file}. * * @return the source {@code URI}; never {@code null} */ @Override public URI getUri() { return getFile().toURI(); } /** * Get the source {@linkplain File file}. * * @return the source file; never {@code null} */ @Override public File getFile() { return this.file; } /** * Get the {@link FilePosition}, if available. */ public Optional getPosition() { return Optional.ofNullable(this.filePosition); } /** * {@return a {@code FileSource} based on this instance but with the * supplied {@link FilePosition}} * *

If the supplied {@code FilePosition} * {@linkplain Objects#equals(Object, Object) equals} the existing one, this * method returns {@code this}. Otherwise, a new instance is created and * returned. * *

Calling this method rather than creating a new {@code FileSource} via * {@link #from(File, FilePosition)} avoids the overhead of redundant * canonical path resolution. * * @param filePosition the position in the source file; may be {@code null} * @since 1.14 */ @API(status = EXPERIMENTAL, since = "1.14") public FileSource withPosition(@Nullable FilePosition filePosition) { if (Objects.equals(this.filePosition, filePosition)) { return this; } return new FileSource(this, filePosition); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } FileSource that = (FileSource) o; return Objects.equals(this.file, that.file) // && Objects.equals(this.filePosition, that.filePosition); } @Override public int hashCode() { return Objects.hash(this.file, this.filePosition); } @Override public String toString() { // @formatter:off return new ToStringBuilder(this) .append("file", this.file) .append("filePosition", this.filePosition) .toString(); // @formatter:on } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FileSystemSource.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.descriptor; import static org.apiguardian.api.API.Status.STABLE; import java.io.File; import org.apiguardian.api.API; import org.junit.platform.engine.TestSource; /** * File system based {@link TestSource}. * *

This interface uses {@link File} instead of {@link java.nio.file.Path} * because the latter does not implement {@link java.io.Serializable}. * * @since 1.0 */ @API(status = STABLE, since = "1.0") public interface FileSystemSource extends UriSource { /** * Get the source file or directory. * * @return the source file or directory; never {@code null} */ File getFile(); } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/MethodSource.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.descriptor; import static org.apiguardian.api.API.Status.STABLE; import static org.junit.platform.commons.util.ClassUtils.nullSafeToString; import java.io.Serial; import java.lang.reflect.Method; import java.util.Objects; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.StringUtils; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.TestSource; /** * Method based {@link org.junit.platform.engine.TestSource TestSource}. * *

This class stores the method name along with the names of its parameter * types because {@link Method} does not implement {@link java.io.Serializable}. * * @since 1.0 * @see org.junit.platform.engine.discovery.MethodSelector */ @API(status = STABLE, since = "1.0") public final class MethodSource implements TestSource { @Serial private static final long serialVersionUID = 1L; /** * Create a new {@code MethodSource} using the supplied class name and * method name. * * @param className the class name; must not be {@code null} or blank * @param methodName the method name; must not be {@code null} or blank */ public static MethodSource from(String className, String methodName) { return new MethodSource(className, methodName); } /** * Create a new {@code MethodSource} using the supplied class name, method * name, and method parameter types. * * @param className the class name; must not be {@code null} or blank * @param methodName the method name; must not be {@code null} or blank * @param methodParameterTypes a comma-separated list of fully qualified * class names representing the method parameter types */ public static MethodSource from(String className, String methodName, String methodParameterTypes) { return new MethodSource(className, methodName, methodParameterTypes); } /** * Create a new {@code MethodSource} using the supplied class name, method * name, and method parameter types. * * @param className the class name; must not be {@code null} or blank * @param methodName the method name; must not be {@code null} or blank * @param methodParameterTypes a varargs array of classes representing the * method parameter types * @since 1.5 */ @API(status = STABLE, since = "1.5") public static MethodSource from(String className, String methodName, Class... methodParameterTypes) { return new MethodSource(className, methodName, nullSafeToString(methodParameterTypes)); } /** * Create a new {@code MethodSource} using the supplied {@link Method method}. * * @param testMethod the Java method; must not be {@code null} * @see #from(Class, Method) */ public static MethodSource from(Method testMethod) { return new MethodSource(testMethod); } /** * Create a new {@code MethodSource} using the supplied * {@link Class class} and {@link Method method}. * *

This method should be used in favor of {@link #from(Method)} if the * test method is inherited from a superclass or present as an interface * {@code default} method. * * @param testClass the Java class; must not be {@code null} * @param testMethod the Java method; must not be {@code null} * @since 1.3 */ @API(status = STABLE, since = "1.3") public static MethodSource from(Class testClass, Method testMethod) { return new MethodSource(testClass, testMethod); } private final String className; private final String methodName; private final @Nullable String methodParameterTypes; private @Nullable Class javaClass; private transient @Nullable Method javaMethod; private MethodSource(String className, String methodName) { this(className, methodName, null); } private MethodSource(String className, String methodName, @Nullable String methodParameterTypes) { Preconditions.notBlank(className, "Class name must not be null or blank"); Preconditions.notBlank(methodName, "Method name must not be null or blank"); this.className = className; this.methodName = methodName; this.methodParameterTypes = methodParameterTypes; } private MethodSource(Method testMethod) { this(Preconditions.notNull(testMethod, "Method must not be null").getDeclaringClass(), testMethod); } /** * @since 1.3 */ private MethodSource(Class testClass, Method testMethod) { Preconditions.notNull(testClass, "Class must not be null"); Preconditions.notNull(testMethod, "Method must not be null"); this.className = testClass.getName(); this.methodName = testMethod.getName(); this.methodParameterTypes = nullSafeToString(testMethod.getParameterTypes()); this.javaClass = testClass; this.javaMethod = testMethod; } /** * Get the class name of this source. */ public String getClassName() { return this.className; } /** * Get the method name of this source. */ public String getMethodName() { return this.methodName; } /** * Get the method parameter types of this source. */ public @Nullable String getMethodParameterTypes() { return this.methodParameterTypes; } /** * Get the {@linkplain Class Java class} of this source. * *

If the {@link Class} was not provided, but only the name, this method * attempts to lazily load the {@link Class} based on its name and throws a * {@link PreconditionViolationException} if the class cannot be loaded. * * @since 1.7 * @see #getClassName() */ @API(status = STABLE, since = "1.7") public Class getJavaClass() { return lazyLoadJavaClass(); } /** * Get the {@linkplain Method Java method} of this source. * *

If the {@link Method} was not provided, but only the name, this method * attempts to lazily load the {@code Method} based on its name and throws a * {@link PreconditionViolationException} if the method cannot be loaded. * * @since 1.7 * @see #getMethodName() */ @API(status = STABLE, since = "1.7") public Method getJavaMethod() { return lazyLoadJavaMethod(); } private Class lazyLoadJavaClass() { if (this.javaClass == null) { // @formatter:off this.javaClass = ReflectionSupport.tryToLoadClass(this.className).getNonNullOrThrow( cause -> new PreconditionViolationException("Could not load class with name: " + this.className, cause)); // @formatter:on } return this.javaClass; } private Method lazyLoadJavaMethod() { if (this.javaMethod == null) { Class javaClass = getJavaClass(); if (StringUtils.isNotBlank(this.methodParameterTypes)) { this.javaMethod = ReflectionSupport.findMethod(javaClass, this.methodName, this.methodParameterTypes).orElseThrow( () -> new PreconditionViolationException( "Could not find method with name [%s] and parameter types [%s] in class [%s].".formatted( this.methodName, this.methodParameterTypes, javaClass.getName()))); } else { this.javaMethod = ReflectionSupport.findMethod(javaClass, this.methodName).orElseThrow( () -> new PreconditionViolationException( "Could not find method with name [%s] in class [%s].".formatted(this.methodName, javaClass.getName()))); } } return this.javaMethod; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } MethodSource that = (MethodSource) o; return Objects.equals(this.className, that.className)// && Objects.equals(this.methodName, that.methodName)// && Objects.equals(this.methodParameterTypes, that.methodParameterTypes); } @Override public int hashCode() { return Objects.hash(this.className, this.methodName, this.methodParameterTypes); } @Override public String toString() { // @formatter:off return new ToStringBuilder(this) .append("className", this.className) .append("methodName", this.methodName) .append("methodParameterTypes", this.methodParameterTypes) .toString(); // @formatter:on } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/PackageSource.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.descriptor; import static org.apiguardian.api.API.Status.STABLE; import java.io.Serial; import java.util.Objects; import org.apiguardian.api.API; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.TestSource; /** * Package based {@link org.junit.platform.engine.TestSource}. * *

This class stores the package name because {@link Package} does not * implement {@link java.io.Serializable}. * * @since 1.0 * @see org.junit.platform.engine.discovery.PackageSelector */ @API(status = STABLE, since = "1.0") public final class PackageSource implements TestSource { @Serial private static final long serialVersionUID = 1L; /** * Create a new {@code PackageSource} using the supplied Java {@link Package}. * * @param javaPackage the Java package; must not be {@code null} */ public static PackageSource from(Package javaPackage) { return new PackageSource(javaPackage); } /** * Create a new {@code PackageSource} using the supplied {@code packageName}. * * @param packageName the package name; must not be {@code null} or blank */ public static PackageSource from(String packageName) { return new PackageSource(packageName); } private final String packageName; private PackageSource(Package javaPackage) { this(Preconditions.notNull(javaPackage, "package must not be null").getName()); } private PackageSource(String packageName) { Preconditions.notNull(packageName, "package name must not be null"); Preconditions.condition(packageName.isEmpty() || !packageName.isBlank(), "package name must not contain only whitespace"); this.packageName = packageName; } /** * Get the package name of this test source. */ public String getPackageName() { return this.packageName; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } PackageSource that = (PackageSource) o; return Objects.equals(this.packageName, that.packageName); } @Override public int hashCode() { return this.packageName.hashCode(); } @Override public String toString() { return new ToStringBuilder(this).append("packageName", this.packageName).toString(); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/UriSource.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.descriptor; import static org.apiguardian.api.API.Status.STABLE; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import org.apiguardian.api.API; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ResourceUtils; import org.junit.platform.engine.TestSource; /** * A {@link TestSource} that can be represented as a {@link URI}. * * @since 1.0 * @see org.junit.platform.engine.discovery.UriSelector */ @API(status = STABLE, since = "1.0") public interface UriSource extends TestSource { /** * Get the {@link URI} that represents this source. * * @return the source {@code URI}; never {@code null} */ URI getUri(); /** * Create a new {@code UriSource} using the supplied {@code URI}. * *

This implementation first attempts to resolve the supplied {@code URI} * to a path-based {@code UriSource} in the local filesystem. If that fails * for any reason, an instance of the default {@code UriSource} * implementation storing the supplied {@code URI} as-is will be * returned. * * @param uri the URI to use as the source; never {@code null} * @return an appropriate {@code UriSource} for the supplied {@code URI} * @since 1.3 * @see org.junit.platform.engine.support.descriptor.FileSource * @see org.junit.platform.engine.support.descriptor.DirectorySource */ static UriSource from(URI uri) { Preconditions.notNull(uri, "URI must not be null"); try { URI uriWithoutQuery = ResourceUtils.stripQueryComponent(uri); Path path = Path.of(uriWithoutQuery); if (Files.isRegularFile(path)) { return FileSource.from(path.toFile(), FilePosition.fromQuery(uri.getQuery()).orElse(null)); } if (Files.isDirectory(path)) { return DirectorySource.from(path.toFile()); } } catch (RuntimeException ex) { LoggerFactory.getLogger(UriSource.class).debug(ex, () -> "The supplied URI [%s] is not path-based. Falling back to default UriSource implementation.".formatted( uri)); } // Store supplied URI as-is return new DefaultUriSource(uri); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * {@link org.junit.platform.engine.TestDescriptor}-related support classes * intended to be used by test engine implementations and clients of * the launcher. */ @NullMarked package org.junit.platform.engine.support.descriptor; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ClassContainerSelectorResolver.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.discovery; import static java.util.stream.Collectors.toSet; import static org.junit.platform.commons.support.ReflectionSupport.findAllClassesInClasspathRoot; import static org.junit.platform.commons.support.ReflectionSupport.findAllClassesInModule; import static org.junit.platform.commons.support.ReflectionSupport.findAllClassesInPackage; import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.selectors; import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.unresolved; import java.util.List; import java.util.function.Predicate; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.discovery.ClasspathRootSelector; import org.junit.platform.engine.discovery.DiscoverySelectors; import org.junit.platform.engine.discovery.ModuleSelector; import org.junit.platform.engine.discovery.PackageSelector; /** * @since 1.5 */ class ClassContainerSelectorResolver implements SelectorResolver { private final Predicate> classFilter; private final Predicate classNameFilter; ClassContainerSelectorResolver(Predicate> classFilter, Predicate classNameFilter) { this.classFilter = Preconditions.notNull(classFilter, "classFilter must not be null"); this.classNameFilter = Preconditions.notNull(classNameFilter, "classNameFilter must not be null"); } @Override public Resolution resolve(ClasspathRootSelector selector, Context context) { return classSelectors(findAllClassesInClasspathRoot(selector.getClasspathRoot(), classFilter, classNameFilter)); } @Override public Resolution resolve(ModuleSelector selector, Context context) { if (selector.getModule().isPresent()) { Module module = selector.getModule().get(); return classSelectors(findAllClassesInModule(module, classFilter, classNameFilter)); } return classSelectors(findAllClassesInModule(selector.getModuleName(), classFilter, classNameFilter)); } @Override public Resolution resolve(PackageSelector selector, Context context) { return classSelectors(findAllClassesInPackage(selector.getPackageName(), classFilter, classNameFilter)); } private Resolution classSelectors(List> classes) { if (classes.isEmpty()) { return unresolved(); } return selectors(classes.stream().map(DiscoverySelectors::selectClass).collect(toSet())); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/DiscoveryIssueReporter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.discovery; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import java.util.Collection; import java.util.HashSet; import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import org.apiguardian.api.API; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.EngineDiscoveryListener; import org.junit.platform.engine.UniqueId; /** * {@code DiscoveryIssueReporter} defines the API for reporting * {@link DiscoveryIssue DiscoveryIssues}. * *

This interface is not intended to be implemented by clients. * * @since 1.13 * @see SelectorResolver.Context */ @API(status = EXPERIMENTAL, since = "6.0") public interface DiscoveryIssueReporter { /** * Create a new {@code DiscoveryIssueReporter} that reports issues to the * supplied {@link EngineDiscoveryListener} for the specified engine. * * @param engineDiscoveryListener the listener to report issues to; never * {@code null} * @param engineId the unique identifier of the engine; never {@code null} */ static DiscoveryIssueReporter forwarding(EngineDiscoveryListener engineDiscoveryListener, UniqueId engineId) { Preconditions.notNull(engineDiscoveryListener, "engineDiscoveryListener must not be null"); Preconditions.notNull(engineId, "engineId must not be null"); return issue -> engineDiscoveryListener.issueEncountered(engineId, issue); } /** * Create a new {@code DiscoveryIssueReporter} that adds reported issues to * the supplied collection. * * @param collection the collection to add issues to; never {@code null} */ static DiscoveryIssueReporter collecting(Collection collection) { Preconditions.notNull(collection, "collection must not be null"); return consuming(collection::add); } /** * Create a new {@code DiscoveryIssueReporter} that adds reported issues to * the supplied consumer. * * @param consumer the consumer to report issues to; never {@code null} */ static DiscoveryIssueReporter consuming(Consumer consumer) { Preconditions.notNull(consumer, "consumer must not be null"); return consumer::accept; } /** * Create a new {@code DiscoveryIssueReporter} that avoids reporting * duplicate issues. * *

The implementation returned by this method is not thread-safe. * * @param delegate the delegate to forward issues to; never {@code null} */ static DiscoveryIssueReporter deduplicating(DiscoveryIssueReporter delegate) { Preconditions.notNull(delegate, "delegate must not be null"); Set seen = new HashSet<>(); return issue -> { boolean notSeen = seen.add(issue); if (notSeen) { delegate.reportIssue(issue); } }; } /** * Build the supplied {@link DiscoveryIssue.Builder Builder} and report the * resulting {@link DiscoveryIssue}. */ default void reportIssue(DiscoveryIssue.Builder builder) { reportIssue(builder.build()); } /** * Report the supplied {@link DiscoveryIssue}. */ void reportIssue(DiscoveryIssue issue); /** * Create a {@link Condition} that reports a {@link DiscoveryIssue} when the * supplied {@link Predicate} is not met. * * @param predicate the predicate to test; never {@code null} * @param issueCreator the function to create the issue with; never {@code null} * @return a new {@code Condition}; never {@code null} */ default Condition createReportingCondition(Predicate predicate, Function issueCreator) { Preconditions.notNull(predicate, "predicate must not be null"); Preconditions.notNull(issueCreator, "issueCreator must not be null"); return value -> { if (predicate.test(value)) { return true; } else { reportIssue(issueCreator.apply(value)); return false; } }; } /** * A {@code Condition} is a union of {@link Predicate} and {@link Consumer}. * *

Instances of this type may be used as {@link Predicate Predicates} or * {@link Consumer Consumers}. For example, a {@code Condition} may be * passed to {@link java.util.stream.Stream#filter(Predicate)} if it is used * for filtering, or to {@link java.util.stream.Stream#peek(Consumer)} if it * is only used for reporting or other side effects. * *

This interface is not intended to be implemented by clients. * * @see #createReportingCondition(Predicate, Function) */ interface Condition { /** * Create a {@link Condition} that is always satisfied. */ static Condition alwaysSatisfied() { return __ -> true; } /** * Evaluate this condition to potentially report an issue. */ boolean check(T value); /** * Return a composed condition that represents a logical AND of this * and the supplied condition. * *

The default implementation avoids short-circuiting so * both conditions will be evaluated even if this condition * returns {@code false} to ensure that all issues are reported. * * @return the composed condition; never {@code null} */ @SuppressWarnings("ShortCircuitBoolean") default Condition and(Condition that) { Preconditions.notNull(that, "condition must not be null"); return value -> this.check(value) & that.check(value); } /** * {@return this condition as a {@link Predicate}} */ default Predicate toPredicate() { return this::check; } /** * {@return this condition as a {@link Consumer}} */ default Consumer toConsumer() { return this::check; } } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolution.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.discovery; import static java.util.Objects.requireNonNullElse; import static java.util.stream.Collectors.joining; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import static org.junit.platform.engine.SelectorResolutionResult.failed; import static org.junit.platform.engine.SelectorResolutionResult.resolved; import static org.junit.platform.engine.SelectorResolutionResult.unresolved; import java.util.ArrayDeque; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Queue; import java.util.Set; import java.util.function.Function; import java.util.function.Supplier; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.UnrecoverableExceptions; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.EngineDiscoveryListener; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.discovery.ClassSelector; import org.junit.platform.engine.discovery.ClasspathResourceSelector; import org.junit.platform.engine.discovery.ClasspathRootSelector; import org.junit.platform.engine.discovery.DirectorySelector; import org.junit.platform.engine.discovery.FileSelector; import org.junit.platform.engine.discovery.IterationSelector; import org.junit.platform.engine.discovery.MethodSelector; import org.junit.platform.engine.discovery.ModuleSelector; import org.junit.platform.engine.discovery.NestedClassSelector; import org.junit.platform.engine.discovery.NestedMethodSelector; import org.junit.platform.engine.discovery.PackageSelector; import org.junit.platform.engine.discovery.UniqueIdSelector; import org.junit.platform.engine.discovery.UriSelector; import org.junit.platform.engine.support.discovery.SelectorResolver.Context; import org.junit.platform.engine.support.discovery.SelectorResolver.Match; import org.junit.platform.engine.support.discovery.SelectorResolver.Resolution; /** * @since 1.5 */ class EngineDiscoveryRequestResolution { private final EngineDiscoveryRequest request; private final Context defaultContext; private final List resolvers; private final List visitors; private final TestDescriptor engineDescriptor; private final Map resolvedSelectors = new LinkedHashMap<>(); private final Map resolvedUniqueIds = new LinkedHashMap<>(); private final Queue remainingSelectors = new ArrayDeque<>(); private final Map contextBySelector = new HashMap<>(); EngineDiscoveryRequestResolution(EngineDiscoveryRequest request, TestDescriptor engineDescriptor, List resolvers, List visitors) { this.request = request; this.engineDescriptor = engineDescriptor; this.resolvers = resolvers; this.visitors = visitors; this.defaultContext = new DefaultContext(null); this.resolvedUniqueIds.put(engineDescriptor.getUniqueId(), Match.exact(engineDescriptor)); } void run() { remainingSelectors.addAll(request.getSelectorsByType(DiscoverySelector.class)); while (!remainingSelectors.isEmpty()) { resolveCompletely(remainingSelectors.poll()); } visitors.forEach(engineDescriptor::accept); } private void resolveCompletely(DiscoverySelector selector) { EngineDiscoveryListener discoveryListener = request.getDiscoveryListener(); UniqueId engineId = engineDescriptor.getUniqueId(); try { Optional result = resolve(selector); if (result.isPresent()) { discoveryListener.selectorProcessed(engineId, selector, resolved()); enqueueAdditionalSelectors(result.get()); } else { discoveryListener.selectorProcessed(engineId, selector, unresolved()); } } catch (Throwable t) { UnrecoverableExceptions.rethrowIfUnrecoverable(t); discoveryListener.selectorProcessed(engineId, selector, failed(t)); } } private void enqueueAdditionalSelectors(Resolution resolution) { remainingSelectors.addAll(resolution.getSelectors()); resolution.getMatches().stream().filter(Match::isExact).forEach(match -> { Set childSelectors = match.expand(); if (!childSelectors.isEmpty()) { remainingSelectors.addAll(childSelectors); DefaultContext context = new DefaultContext(match.getTestDescriptor()); childSelectors.forEach(selector -> contextBySelector.put(selector, context)); } }); } private Optional resolve(DiscoverySelector selector) { if (resolvedSelectors.containsKey(selector)) { return Optional.of(resolvedSelectors.get(selector)); } if (selector instanceof UniqueIdSelector uniqueIdSelector) { return resolveUniqueId(uniqueIdSelector); } return resolve(selector, resolver -> { Context context = getContext(selector); if (selector instanceof ClasspathResourceSelector classpathResourceSelector) { return resolver.resolve(classpathResourceSelector, context); } if (selector instanceof ClasspathRootSelector classpathRootSelector) { return resolver.resolve(classpathRootSelector, context); } if (selector instanceof ClassSelector classSelector) { return resolver.resolve(classSelector, context); } if (selector instanceof IterationSelector iterationSelector) { return resolver.resolve(iterationSelector, context); } if (selector instanceof NestedClassSelector nestedClassSelector) { return resolver.resolve(nestedClassSelector, context); } if (selector instanceof DirectorySelector directorySelector) { return resolver.resolve(directorySelector, context); } if (selector instanceof FileSelector fileSelector) { return resolver.resolve(fileSelector, context); } if (selector instanceof MethodSelector methodSelector) { return resolver.resolve(methodSelector, context); } if (selector instanceof NestedMethodSelector nestedMethodSelector) { return resolver.resolve(nestedMethodSelector, context); } if (selector instanceof ModuleSelector moduleSelector) { return resolver.resolve(moduleSelector, context); } if (selector instanceof PackageSelector packageSelector) { return resolver.resolve(packageSelector, context); } if (selector instanceof UriSelector uriSelector) { return resolver.resolve(uriSelector, context); } return resolver.resolve(selector, context); }); } private Optional resolveUniqueId(UniqueIdSelector selector) { UniqueId uniqueId = selector.getUniqueId(); if (resolvedUniqueIds.containsKey(uniqueId)) { return Optional.of(Resolution.match(resolvedUniqueIds.get(uniqueId))); } if (!uniqueId.hasPrefix(engineDescriptor.getUniqueId())) { return Optional.empty(); } return resolve(selector, resolver -> resolver.resolve(selector, getContext(selector))); } private Context getContext(DiscoverySelector selector) { return contextBySelector.getOrDefault(selector, defaultContext); } private Optional resolve(DiscoverySelector selector, Function resolutionFunction) { // @formatter:off return resolvers.stream() .map(resolutionFunction) .filter(Resolution::isResolved) .findFirst() .map(resolution -> { contextBySelector.remove(selector); resolvedSelectors.put(selector, resolution); resolution.getMatches() .forEach(match -> resolvedUniqueIds.put(match.getTestDescriptor().getUniqueId(), match)); return resolution; }); // @formatter:on } private class DefaultContext implements Context { @Nullable private final TestDescriptor parent; DefaultContext(@Nullable TestDescriptor parent) { this.parent = parent; } @Override public Optional addToParent(Function> creator) { return createAndAdd(requireNonNullElse(parent, engineDescriptor), creator); } @Override public Optional addToParent(Supplier parentSelectorSupplier, Function> creator) { if (parent != null) { return createAndAdd(parent, creator); } return resolve(parentSelectorSupplier.get()).flatMap(parent -> createAndAdd(parent, creator)); } @Override public Optional resolve(DiscoverySelector selector) { // @formatter:off return EngineDiscoveryRequestResolution.this.resolve(selector) .map(Resolution::getMatches) .flatMap(matches -> { if (matches.size() > 1) { String stringRepresentation = matches.stream() .map(Match::getTestDescriptor) .map(Objects::toString) .collect(joining(", ")); throw new JUnitException( "Selector " + selector + " did not yield unique test descriptor: " + stringRepresentation); } if (matches.size() == 1) { return Optional.of(getOnlyElement(matches).getTestDescriptor()); } return Optional.empty(); }); // @formatter:on } @SuppressWarnings("unchecked") private Optional createAndAdd(TestDescriptor parent, Function> creator) { Optional child = creator.apply(parent); if (child.isPresent()) { UniqueId uniqueId = child.get().getUniqueId(); if (resolvedUniqueIds.containsKey(uniqueId)) { return Optional.of((T) resolvedUniqueIds.get(uniqueId).getTestDescriptor()); } parent.addChild(child.get()); } return child; } } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolver.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.discovery; import static java.util.stream.Collectors.toCollection; import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.util.ArrayList; import java.util.List; import java.util.function.Function; import java.util.function.Predicate; import org.apiguardian.api.API; import org.junit.platform.commons.io.ResourceFilter; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.DiscoveryFilter; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.Filter; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.discovery.ClassNameFilter; import org.junit.platform.engine.discovery.ClassSelector; import org.junit.platform.engine.discovery.ClasspathResourceSelector; import org.junit.platform.engine.discovery.ClasspathRootSelector; import org.junit.platform.engine.discovery.ModuleSelector; import org.junit.platform.engine.discovery.PackageNameFilter; import org.junit.platform.engine.discovery.PackageSelector; import org.junit.platform.engine.support.discovery.SelectorResolver.Match; import org.junit.platform.engine.support.discovery.SelectorResolver.Resolution; /** * Configurable test discovery implementation based on {@link SelectorResolver} * and {@link TestDescriptor.Visitor} that can be reused by different * {@link org.junit.platform.engine.TestEngine TestEngines}. * *

This resolver takes care of notifying registered * {@link org.junit.platform.engine.EngineDiscoveryListener * EngineDiscoveryListeners} about the results of processed * {@link org.junit.platform.engine.DiscoverySelector DiscoverySelectors}. * * @param the type of the engine's descriptor * @since 1.5 * @see #builder() * @see #resolve(EngineDiscoveryRequest, TestDescriptor) */ @API(status = STABLE, since = "1.10") public class EngineDiscoveryRequestResolver { private final List, SelectorResolver>> resolverCreators; private final List, TestDescriptor.Visitor>> visitorCreators; private EngineDiscoveryRequestResolver(List, SelectorResolver>> resolverCreators, List, TestDescriptor.Visitor>> visitorCreators) { this.resolverCreators = new ArrayList<>(resolverCreators); this.visitorCreators = new ArrayList<>(visitorCreators); } /** * Resolve the supplied {@link EngineDiscoveryRequest} and collect the * results into the supplied {@link TestDescriptor} while forwarding * encountered discovery issues to the {@link EngineDiscoveryRequest}'s * {@link org.junit.platform.engine.EngineDiscoveryListener}. * *

The algorithm works as follows: * *

    *
  1. Enqueue all selectors in the supplied * {@linkplain EngineDiscoveryRequest request} to be resolved. *
  2. *
  3. * While there are selectors to be resolved, get the next one. * Otherwise, the resolution is finished. *
      *
    1. * Iterate over all registered {@linkplain SelectorResolver * resolvers} in the order they were registered in and find the * first one that returns a {@linkplain Resolution resolution} * other than {@link Resolution#unresolved() unresolved()}. *
    2. *
    3. * If such a {@linkplain Resolution resolution} exists, enqueue * its {@linkplain Resolution#getSelectors() selectors}. *
    4. *
    5. * For each exact {@linkplain Match match} in the {@linkplain * Resolution resolution}, {@linkplain Match#expand() expand} * its children and enqueue them as well. *
    6. *
    *
  4. *
  5. * Iterate over all registered {@linkplain TestDescriptor.Visitor * visitors} and let the engine test descriptor {@linkplain * TestDescriptor#accept(TestDescriptor.Visitor) accept} them. *
  6. *
* * @param request the request to be resolved; never {@code null} * @param engineDescriptor the engine's {@code TestDescriptor} to be used * for adding direct children * @see SelectorResolver * @see TestDescriptor.Visitor */ public void resolve(EngineDiscoveryRequest request, T engineDescriptor) { Preconditions.notNull(request, "request must not be null"); Preconditions.notNull(engineDescriptor, "engineDescriptor must not be null"); DiscoveryIssueReporter issueReporter = DiscoveryIssueReporter.forwarding(request.getDiscoveryListener(), engineDescriptor.getUniqueId()); resolve(request, engineDescriptor, issueReporter); } /** * Resolve the supplied {@link EngineDiscoveryRequest} and collect the * results into the supplied {@link TestDescriptor} using the supplied * {@link DiscoveryIssueReporter} to report issues encountered during * resolution. * *

The algorithm works as described in * {@link #resolve(EngineDiscoveryRequest, TestDescriptor)}. * * @param request the request to be resolved; never {@code null} * @param engineDescriptor the engine's {@code TestDescriptor} to be used * for adding direct children * @param issueReporter the {@link DiscoveryIssueReporter} to report issues * encountered during resolution * @since 1.13 * @see #resolve(EngineDiscoveryRequest, TestDescriptor) * @see SelectorResolver * @see TestDescriptor.Visitor */ @API(status = EXPERIMENTAL, since = "6.0") public void resolve(EngineDiscoveryRequest request, T engineDescriptor, DiscoveryIssueReporter issueReporter) { Preconditions.notNull(request, "request must not be null"); Preconditions.notNull(engineDescriptor, "engineDescriptor must not be null"); Preconditions.notNull(issueReporter, "issueReporter must not be null"); InitializationContext initializationContext = new DefaultInitializationContext<>(request, engineDescriptor, issueReporter); List resolvers = instantiate(resolverCreators, initializationContext); List visitors = instantiate(visitorCreators, initializationContext); new EngineDiscoveryRequestResolution(request, engineDescriptor, resolvers, visitors).run(); } private List instantiate(List, R>> creators, InitializationContext context) { return creators.stream().map(creator -> creator.apply(context)).collect(toCollection(ArrayList::new)); } /** * Create a new {@link Builder} for creating a {@link EngineDiscoveryRequestResolver}. * * @param the type of the engine's descriptor * @return a new builder; never {@code null} */ public static Builder builder() { return new Builder<>(); } /** * Builder for {@link EngineDiscoveryRequestResolver}. * * @param the type of the engine's descriptor * @since 1.5 */ @API(status = STABLE, since = "1.10") public static class Builder { private final List, SelectorResolver>> resolverCreators = new ArrayList<>(); private final List, TestDescriptor.Visitor>> visitorCreators = new ArrayList<>(); private Builder() { } /** * Add a predefined resolver that resolves {@link ClasspathRootSelector * ClasspathRootSelectors}, {@link ModuleSelector ModuleSelectors}, and * {@link PackageSelector PackageSelectors} into {@link ClassSelector * ClassSelectors} by scanning for classes that satisfy the supplied * predicate in the respective class containers to this builder. * * @param classFilter predicate the resolved classes must satisfy; never * {@code null} * @return this builder for method chaining */ public Builder addClassContainerSelectorResolver(Predicate> classFilter) { Preconditions.notNull(classFilter, "classFilter must not be null"); return addClassContainerSelectorResolverWithContext(__ -> classFilter); } /** * Add a predefined resolver that resolves {@link ClasspathRootSelector * ClasspathRootSelectors}, {@link ModuleSelector ModuleSelectors}, and * {@link PackageSelector PackageSelectors} into {@link ClassSelector * ClassSelectors} by scanning for classes that satisfy the predicate * created by the supplied {@code Function} in the respective class * containers to this builder. * * @param classFilterCreator the function that will be called to create * the predicate the resolved classes must satisfy; never * {@code null} * @return this builder for method chaining */ @API(status = EXPERIMENTAL, since = "6.0") public Builder addClassContainerSelectorResolverWithContext( Function, Predicate>> classFilterCreator) { Preconditions.notNull(classFilterCreator, "classFilterCreator must not be null"); return addSelectorResolver(context -> new ClassContainerSelectorResolver(classFilterCreator.apply(context), context.getClassNameFilter())); } /** * Add a predefined resolver that resolves {@link ClasspathRootSelector * ClasspathRootSelectors}, {@link ModuleSelector ModuleSelectors}, and * {@link PackageSelector PackageSelectors} into {@link ClasspathResourceSelector * ClasspathResourceSelectors} by scanning for resources that satisfy the supplied * predicate in the respective class containers to this builder. * * @param resourceFilter predicate the resolved classes must satisfy; never * {@code null} * @return this builder for method chaining * @since 1.12 * @deprecated Please use {@link #addResourceContainerSelectorResolver(ResourceFilter)} instead. */ @API(status = DEPRECATED, since = "1.14") @Deprecated(since = "1.14", forRemoval = true) @SuppressWarnings("removal") public Builder addResourceContainerSelectorResolver( Predicate resourceFilter) { Preconditions.notNull(resourceFilter, "resourceFilter must not be null"); return addResourceContainerSelectorResolver( ResourceFilter.of(r -> resourceFilter.test(org.junit.platform.commons.support.Resource.of(r)))); } /** * Add a predefined resolver that resolves {@link ClasspathRootSelector * ClasspathRootSelectors}, {@link ModuleSelector ModuleSelectors}, and * {@link PackageSelector PackageSelectors} into * {@link ClasspathResourceSelector ClasspathResourceSelectors} by * scanning for resources that match the supplied resource filter in the * respective class containers to this builder. * * @param resourceFilter filter the resolved classes must match; never * {@code null} * @return this builder for method chaining * @since 1.14 */ @API(status = MAINTAINED, since = "1.14") public Builder addResourceContainerSelectorResolver(ResourceFilter resourceFilter) { Preconditions.notNull(resourceFilter, "resourceFilter must not be null"); return addSelectorResolver( context -> new ResourceContainerSelectorResolver(resourceFilter, context.getPackageFilter())); } /** * Add a context insensitive {@link SelectorResolver} to this builder. * * @param resolver the resolver to add; never {@code null} * @return this builder for method chaining */ public Builder addSelectorResolver(SelectorResolver resolver) { Preconditions.notNull(resolver, "resolver must not be null"); return addSelectorResolver(context -> resolver); } /** * Add a context sensitive {@link SelectorResolver} to this builder. * * @param resolverCreator the function that will be called to create the * {@link SelectorResolver} to be added. * @return this builder for method chaining * @see InitializationContext */ public Builder addSelectorResolver(Function, SelectorResolver> resolverCreator) { resolverCreators.add(resolverCreator); return this; } /** * Add a context sensitive {@link TestDescriptor.Visitor} to this * builder. * *

If multiple {@linkplain TestDescriptor.Visitor visitors} are registered, * they will iterate over the test tree separately. To avoid the overhead of * multiple iterations, consider combining multiple visitors into a single * visitor using * {@link TestDescriptor.Visitor#composite(TestDescriptor.Visitor...)}. * * @param visitorCreator the function that will be called to create the * {@link TestDescriptor.Visitor} to be added. * @return this builder for method chaining * @see InitializationContext */ public Builder addTestDescriptorVisitor( Function, TestDescriptor.Visitor> visitorCreator) { visitorCreators.add(visitorCreator); return this; } /** * Build the {@link EngineDiscoveryRequestResolver} that has been * configured via this builder. */ public EngineDiscoveryRequestResolver build() { return new EngineDiscoveryRequestResolver<>(resolverCreators, visitorCreators); } } /** * The initialization context for creating {@linkplain SelectorResolver * resolvers} and {@linkplain TestDescriptor.Visitor visitors} that depend * on the {@link EngineDiscoveryRequest} to be resolved or the engine * descriptor that will be used to collect the results. * * @since 1.5 * @see Builder#addSelectorResolver(Function) * @see Builder#addTestDescriptorVisitor(Function) */ @API(status = STABLE, since = "1.10") public interface InitializationContext { /** * Get the {@link EngineDiscoveryRequest} that is about to be resolved. * * @return the {@link EngineDiscoveryRequest}; never {@code null} */ EngineDiscoveryRequest getDiscoveryRequest(); /** * Get the engine's {@link TestDescriptor} that will be used to collect * the results. * * @return engine's {@link TestDescriptor}; never {@code null} */ T getEngineDescriptor(); /** * Get the class name filter built from the {@link ClassNameFilter * ClassNameFilters} and {@link PackageNameFilter PackageNameFilters} * in the {@link EngineDiscoveryRequest} that is about to be resolved. * * @return the predicate for filtering the resolved class names; never * {@code null} */ Predicate getClassNameFilter(); /** * Get the package name filter built from the {@link PackageNameFilter * PackageNameFilters} in the {@link EngineDiscoveryRequest} that is * about to be resolved. * * @return the predicate for filtering the resolved resource names; never * {@code null} * @since 1.12 */ @API(status = MAINTAINED, since = "1.13.3") Predicate getPackageFilter(); /** * {@return the {@link DiscoveryIssueReporter} for the current * resolution} * * @since 1.13 */ @API(status = EXPERIMENTAL, since = "6.0") DiscoveryIssueReporter getIssueReporter(); } private static class DefaultInitializationContext implements InitializationContext { private final EngineDiscoveryRequest request; private final T engineDescriptor; private final Predicate classNameFilter; private final Predicate packageFilter; private final DiscoveryIssueReporter issueReporter; DefaultInitializationContext(EngineDiscoveryRequest request, T engineDescriptor, DiscoveryIssueReporter issueReporter) { this.request = request; this.engineDescriptor = engineDescriptor; this.classNameFilter = buildClassNamePredicate(request); this.packageFilter = buildPackagePredicate(request); this.issueReporter = issueReporter; } /** * Build a {@link Predicate} for fully qualified class names to be used for * classpath scanning from an {@link EngineDiscoveryRequest}. * * @param request the request to build a predicate from */ private Predicate buildClassNamePredicate(EngineDiscoveryRequest request) { List> filters = new ArrayList<>(); filters.addAll(request.getFiltersByType(ClassNameFilter.class)); filters.addAll(request.getFiltersByType(PackageNameFilter.class)); return Filter.composeFilters(filters).toPredicate(); } private Predicate buildPackagePredicate(EngineDiscoveryRequest request) { List> filters = new ArrayList<>(); filters.addAll(request.getFiltersByType(PackageNameFilter.class)); return Filter.composeFilters(filters).toPredicate(); } @Override public EngineDiscoveryRequest getDiscoveryRequest() { return request; } @Override public T getEngineDescriptor() { return engineDescriptor; } @Override public Predicate getClassNameFilter() { return classNameFilter; } @Override public Predicate getPackageFilter() { return packageFilter; } @Override public DiscoveryIssueReporter getIssueReporter() { return issueReporter; } } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ResourceContainerSelectorResolver.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.discovery; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.toSet; import static org.junit.platform.commons.support.ResourceSupport.findAllResourcesInClasspathRoot; import static org.junit.platform.commons.support.ResourceSupport.findAllResourcesInModule; import static org.junit.platform.commons.support.ResourceSupport.findAllResourcesInPackage; import static org.junit.platform.engine.support.discovery.ResourceUtils.packageName; import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.selectors; import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.unresolved; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.function.Predicate; import org.junit.platform.commons.io.Resource; import org.junit.platform.commons.io.ResourceFilter; import org.junit.platform.engine.discovery.ClasspathResourceSelector; import org.junit.platform.engine.discovery.ClasspathRootSelector; import org.junit.platform.engine.discovery.DiscoverySelectors; import org.junit.platform.engine.discovery.ModuleSelector; import org.junit.platform.engine.discovery.PackageSelector; /** * @since 1.12 */ class ResourceContainerSelectorResolver implements SelectorResolver { private final ResourceFilter resourceFilter; ResourceContainerSelectorResolver(ResourceFilter resourceFilter, Predicate packageFilter) { this.resourceFilter = ResourceFilter.of(packageName(packageFilter).and(resourceFilter::match)); } @Override public Resolution resolve(ClasspathRootSelector selector, Context context) { return resourceSelectors(findAllResourcesInClasspathRoot(selector.getClasspathRoot(), resourceFilter)); } @Override public Resolution resolve(ModuleSelector selector, Context context) { if (selector.getModule().isPresent()) { Module module = selector.getModule().get(); return resourceSelectors(findAllResourcesInModule(module, resourceFilter)); } return resourceSelectors(findAllResourcesInModule(selector.getModuleName(), resourceFilter)); } @Override public Resolution resolve(PackageSelector selector, Context context) { return resourceSelectors(findAllResourcesInPackage(selector.getPackageName(), resourceFilter)); } private Resolution resourceSelectors(List resources) { Set selectors = resources.stream() // .collect(groupingBy(Resource::getName)) // .values() // .stream() // .map(LinkedHashSet::new) // .map(DiscoverySelectors::selectClasspathResourceByName) // .collect(toSet()); if (selectors.isEmpty()) { return unresolved(); } return selectors(selectors); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ResourceUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.discovery; import java.util.function.Predicate; import org.junit.platform.commons.io.Resource; import org.junit.platform.commons.support.ReflectionSupport; /** * Resource-related utilities to be used in conjunction with {@link ReflectionSupport}. * * @since 1.12 */ class ResourceUtils { public static final String DEFAULT_PACKAGE_NAME = ""; private static final char CLASSPATH_RESOURCE_PATH_SEPARATOR = '/'; private static final char PACKAGE_SEPARATOR_CHAR = '.'; /** * Match resources against a package filter. * *

The {@code /} separated path of a resource is rewritten to a * {@code .} separated package names. The package filter is applied to that * package name. */ static Predicate packageName(Predicate packageFilter) { return resource -> packageFilter.test(packageName(resource.getName())); } private static String packageName(String classpathResourceName) { int lastIndexOf = classpathResourceName.lastIndexOf(CLASSPATH_RESOURCE_PATH_SEPARATOR); if (lastIndexOf < 0) { return DEFAULT_PACKAGE_NAME; } // classpath resource names do not start with / String resourcePackagePath = classpathResourceName.substring(0, lastIndexOf); return resourcePackagePath.replace(CLASSPATH_RESOURCE_PATH_SEPARATOR, PACKAGE_SEPARATOR_CHAR); } private ResourceUtils() { } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/SelectorResolver.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.discovery; import static java.util.Collections.emptySet; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.util.Collections; import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.function.Supplier; import org.apiguardian.api.API; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.discovery.ClassSelector; import org.junit.platform.engine.discovery.ClasspathResourceSelector; import org.junit.platform.engine.discovery.ClasspathRootSelector; import org.junit.platform.engine.discovery.DirectorySelector; import org.junit.platform.engine.discovery.FileSelector; import org.junit.platform.engine.discovery.IterationSelector; import org.junit.platform.engine.discovery.MethodSelector; import org.junit.platform.engine.discovery.ModuleSelector; import org.junit.platform.engine.discovery.NestedClassSelector; import org.junit.platform.engine.discovery.NestedMethodSelector; import org.junit.platform.engine.discovery.PackageSelector; import org.junit.platform.engine.discovery.UniqueIdSelector; import org.junit.platform.engine.discovery.UriSelector; /** * A resolver that supports resolving one or multiple types of * {@link DiscoverySelector DiscoverySelectors}. * *

An implementation of a {@code resolve()} method is typically comprised * of the following steps: * *

    *
  1. * Check whether the selector is applicable for the current * {@link org.junit.platform.engine.TestEngine} and the current * {@link org.junit.platform.engine.EngineDiscoveryRequest} (e.g. * for a test class: is it relevant for the current engine and does * it pass all filters in the request?). *
  2. *
  3. * If so, use the supplied {@link Context Context}, to add one or * multiple {@link TestDescriptor TestDescriptors} to the designated * parent (see {@link Context Context} for details) and return a * {@linkplain Resolution#match(Match) match} or multiple {@linkplain * Resolution#matches(Set) matches}. Alternatively, convert the supplied * selector into one or multiple other * {@linkplain Resolution#selectors(Set) selectors} (e.g. a {@link * PackageSelector} into a set of {@link ClassSelector ClassSelectors}). * Otherwise, return {@link Resolution#unresolved() unresolved()}. *
  4. *
* * @since 1.5 */ @API(status = STABLE, since = "1.10") public interface SelectorResolver { /** * Resolve the supplied {@link ClasspathResourceSelector} using the supplied * {@link Context Context}. * *

The default implementation delegates to {@link * #resolve(DiscoverySelector, Context)}. * * @param selector the selector to be resolved; never {@code null} * @param context the context to be used for resolving the selector; never * {@code null} * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link * Resolution#matches(Set) matches()}; never {@code null} * @see #resolve(DiscoverySelector, Context) */ default Resolution resolve(ClasspathResourceSelector selector, Context context) { return resolve((DiscoverySelector) selector, context); } /** * Resolve the supplied {@link ClasspathRootSelector} using the supplied * {@link Context Context}. * *

The default implementation delegates to {@link * #resolve(DiscoverySelector, Context)}. * * @param selector the selector to be resolved; never {@code null} * @param context the context to be used for resolving the selector; never * {@code null} * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link * Resolution#matches(Set) matches()}; never {@code null} * @see #resolve(DiscoverySelector, Context) */ default Resolution resolve(ClasspathRootSelector selector, Context context) { return resolve((DiscoverySelector) selector, context); } /** * Resolve the supplied {@link ClassSelector} using the supplied * {@link Context Context}. * *

The default implementation delegates to {@link * #resolve(DiscoverySelector, Context)}. * * @param selector the selector to be resolved; never {@code null} * @param context the context to be used for resolving the selector; never * {@code null} * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link * Resolution#matches(Set) matches()}; never {@code null} * @see #resolve(DiscoverySelector, Context) */ default Resolution resolve(ClassSelector selector, Context context) { return resolve((DiscoverySelector) selector, context); } /** * Resolve the supplied {@link NestedClassSelector} using the supplied * {@link Context Context}. * *

The default implementation delegates to {@link * #resolve(DiscoverySelector, Context)}. * * @param selector the selector to be resolved; never {@code null} * @param context the context to be used for resolving the selector; never * {@code null} * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link * Resolution#matches(Set) matches()}; never {@code null} * @see #resolve(DiscoverySelector, Context) */ default Resolution resolve(NestedClassSelector selector, Context context) { return resolve((DiscoverySelector) selector, context); } /** * Resolve the supplied {@link DirectorySelector} using the supplied * {@link Context Context}. * *

The default implementation delegates to {@link * #resolve(DiscoverySelector, Context)}. * * @param selector the selector to be resolved; never {@code null} * @param context the context to be used for resolving the selector; never * {@code null} * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link * Resolution#matches(Set) matches()}; never {@code null} * @see #resolve(DiscoverySelector, Context) */ default Resolution resolve(DirectorySelector selector, Context context) { return resolve((DiscoverySelector) selector, context); } /** * Resolve the supplied {@link FileSelector} using the supplied * {@link Context Context}. * *

The default implementation delegates to {@link * #resolve(DiscoverySelector, Context)}. * * @param selector the selector to be resolved; never {@code null} * @param context the context to be used for resolving the selector; never * {@code null} * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link * Resolution#matches(Set) matches()}; never {@code null} * @see #resolve(DiscoverySelector, Context) */ default Resolution resolve(FileSelector selector, Context context) { return resolve((DiscoverySelector) selector, context); } /** * Resolve the supplied {@link MethodSelector} using the supplied * {@link Context Context}. * *

The default implementation delegates to {@link * #resolve(DiscoverySelector, Context)}. * * @param selector the selector to be resolved; never {@code null} * @param context the context to be used for resolving the selector; never * {@code null} * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link * Resolution#matches(Set) matches()}; never {@code null} * @see #resolve(DiscoverySelector, Context) */ default Resolution resolve(MethodSelector selector, Context context) { return resolve((DiscoverySelector) selector, context); } /** * Resolve the supplied {@link NestedMethodSelector} using the supplied * {@link Context Context}. * *

The default implementation delegates to {@link * #resolve(DiscoverySelector, Context)}. * * @param selector the selector to be resolved; never {@code null} * @param context the context to be used for resolving the selector; never * {@code null} * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link * Resolution#matches(Set) matches()}; never {@code null} * @see #resolve(DiscoverySelector, Context) */ default Resolution resolve(NestedMethodSelector selector, Context context) { return resolve((DiscoverySelector) selector, context); } /** * Resolve the supplied {@link ModuleSelector} using the supplied * {@link Context Context}. * *

The default implementation delegates to {@link * #resolve(DiscoverySelector, Context)}. * * @param selector the selector to be resolved; never {@code null} * @param context the context to be used for resolving the selector; never * {@code null} * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link * Resolution#matches(Set) matches()}; never {@code null} * @see #resolve(DiscoverySelector, Context) */ default Resolution resolve(ModuleSelector selector, Context context) { return resolve((DiscoverySelector) selector, context); } /** * Resolve the supplied {@link PackageSelector} using the supplied * {@link Context Context}. * *

The default implementation delegates to {@link * #resolve(DiscoverySelector, Context)}. * * @param selector the selector to be resolved; never {@code null} * @param context the context to be used for resolving the selector; never * {@code null} * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link * Resolution#matches(Set) matches()}; never {@code null} * @see #resolve(DiscoverySelector, Context) */ default Resolution resolve(PackageSelector selector, Context context) { return resolve((DiscoverySelector) selector, context); } /** * Resolve the supplied {@link UniqueIdSelector} using the supplied * {@link Context Context}. * *

The default implementation delegates to {@link * #resolve(DiscoverySelector, Context)}. * * @param selector the selector to be resolved; never {@code null} * @param context the context to be used for resolving the selector; never * {@code null} * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link * Resolution#matches(Set) matches()}; never {@code null} * @see #resolve(DiscoverySelector, Context) */ default Resolution resolve(UniqueIdSelector selector, Context context) { return resolve((DiscoverySelector) selector, context); } /** * Resolve the supplied {@link UriSelector} using the supplied * {@link Context Context}. * *

The default implementation delegates to {@link * #resolve(DiscoverySelector, Context)}. * * @param selector the selector to be resolved; never {@code null} * @param context the context to be used for resolving the selector; never * {@code null} * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link * Resolution#matches(Set) matches()}; never {@code null} * @see #resolve(DiscoverySelector, Context) */ default Resolution resolve(UriSelector selector, Context context) { return resolve((DiscoverySelector) selector, context); } /** * Resolve the supplied {@link IterationSelector} using the supplied * {@link Context Context}. * *

The default implementation delegates to {@link * #resolve(DiscoverySelector, Context)}. * * @param selector the selector to be resolved; never {@code null} * @param context the context to be used for resolving the selector; never * {@code null} * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link * Resolution#matches(Set) matches()}; never {@code null} * @see #resolve(DiscoverySelector, Context) */ @API(status = MAINTAINED, since = "1.13.3") default Resolution resolve(IterationSelector selector, Context context) { return resolve((DiscoverySelector) selector, context); } /** * Resolve the supplied {@link DiscoverySelector} using the supplied * {@link Context Context}. * *

This method is only called if none of the overloaded variants match. * *

The default implementation returns {@link Resolution#unresolved() * unresolved()}. * * @param selector the selector to be resolved; never {@code null} * @param context the context to be used for resolving the selector; never * {@code null} * @return a {@link Resolution Resolution} of {@link Resolution#unresolved() * unresolved()}, {@link Resolution#selectors(Set) selectors()}, or {@link * Resolution#matches(Set) matches()}; never {@code null} * @see Context */ default Resolution resolve(DiscoverySelector selector, Context context) { return Resolution.unresolved(); } /** * The context for resolving a {@link DiscoverySelector} and adding it to * the test tree. * *

The context is used to add resolved {@link TestDescriptor * TestDescriptors} to the test tree if and only if the parent * {@code TestDescriptor} could be found. Alternatively, a resolver may * use the context to {@linkplain #resolve(DiscoverySelector) resolve} a * certain {@code DiscoverySelector} into a {@code TestDescriptor} (e.g. for * adding a filter and returning a {@linkplain Match#partial(TestDescriptor) * partial match}). * * @since 1.5 * @see SelectorResolver */ @API(status = STABLE, since = "1.10") interface Context { /** * Resolve the supplied {@link TestDescriptor}, if possible. * *

Calling this method has the same effect as returning a {@linkplain * Match#partial(TestDescriptor) partial match} from a {@link * SelectorResolver}: the children of the resulting {@link * TestDescriptor} will only be resolved if a subsequent resolution * finds an exact match that contains a {@code TestDescriptor} with the * same {@linkplain TestDescriptor#getUniqueId() unique ID}. * * @param selector the selector to resolve * @return the resolved {@code TestDescriptor}; never {@code null} but * potentially empty */ Optional resolve(DiscoverySelector selector); /** * Add a {@link TestDescriptor} to an unspecified parent, usually the * engine descriptor, by applying the supplied {@code Function} to the * new parent. * *

The parent will be the engine descriptor unless another parent has * already been determined, i.e. if the selector that is being resolved * is the result of {@linkplain Match#expand() expanding} a {@link * Match}. * *

If the result of applying the {@code Function} is {@linkplain * Optional#isPresent() present}, it will be added as a child of the * parent {@code TestDescriptor} unless a descriptor with the same * {@linkplain TestDescriptor#getUniqueId() unique ID} was added * earlier. * * @param creator {@code Function} that will be called with the new * parent to determine the new {@code TestDescriptor} to be added; must * not return {@code null} * @param the type of the new {@code TestDescriptor} * @return the new {@code TestDescriptor} or the previously existing one * with the same unique ID; never {@code null} but potentially empty * @throws ClassCastException if the previously existing {@code * TestDescriptor} is not an instance of {@code T} */ Optional addToParent(Function> creator); /** * Add a {@link TestDescriptor} to a parent, specified by the {@link * DiscoverySelector} returned by the supplied {@code Supplier}, by * applying the supplied {@code Function} to the new parent. * *

Unless another parent has already been determined, i.e. if the * selector that is being resolved is the result of {@linkplain * Match#expand() expanding} a {@link Match}, the {@link * DiscoverySelector} returned by the supplied {@code Supplier} will * be used to determine the parent. If no parent is found, the supplied * {@code Function} will not be called. If there are multiple potential * parents, an exception will be thrown. Otherwise, the resolved * {@code TestDescriptor} will be used as the parent and passed to the * supplied {@code Function}. * *

If the result of applying the {@code Function} is {@linkplain * Optional#isPresent() present}, it will be added as a child of the * parent {@code TestDescriptor} unless a descriptor with the same * {@linkplain TestDescriptor#getUniqueId() unique ID} was added * earlier. * * @param creator {@code Function} that will be called with the new * parent to determine the new {@code TestDescriptor} to be added; must * not return {@code null} * @param the type of the new {@code TestDescriptor} * @return the new {@code TestDescriptor} or the previously existing one * with the same unique ID; never {@code null} but potentially empty * @throws ClassCastException if the previously existing {@code * TestDescriptor} is not an instance of {@code T} */ Optional addToParent(Supplier parentSelectorSupplier, Function> creator); } /** * The result of an attempt to resolve a {@link DiscoverySelector}. * *

A resolution is either {@linkplain #unresolved unresolved}, contains a * {@linkplain #match match} or multiple {@linkplain #matches}, or a set of * {@linkplain #selectors selectors}. * * @since 1.5 * @see SelectorResolver */ @API(status = STABLE, since = "1.10") class Resolution { private static final Resolution UNRESOLVED = new Resolution(emptySet(), emptySet()); private final Set matches; private final Set selectors; /** * Factory for creating unresolved resolutions. * * @return an unresolved resolution; never {@code null} */ public static Resolution unresolved() { return UNRESOLVED; } /** * Factory for creating a resolution that contains the supplied * {@link Match Match}. * * @param match the resolved {@code Match}; never {@code null} * @return a resolution that contains the supplied {@code Match}; never * {@code null} */ public static Resolution match(Match match) { Preconditions.notNull(match, "match must not be null"); return new Resolution(Set.of(match), emptySet()); } /** * Factory for creating a resolution that contains the supplied * {@link Match Matches}. * * @param matches the resolved {@code Matches}; never {@code null} or * empty * @return a resolution that contains the supplied {@code Matches}; * never {@code null} */ public static Resolution matches(Set matches) { Preconditions.notNull(matches, "matches must not be null"); Preconditions.notEmpty(matches, "matches must not be empty"); Preconditions.containsNoNullElements(matches, "matches must not contain null elements"); return new Resolution(matches, emptySet()); } /** * Factory for creating a resolution that contains the supplied * {@link DiscoverySelector DiscoverySelectors}. * * @param selectors the resolved {@code DiscoverySelectors}; never * {@code null} or empty * @return a resolution that contains the supplied * {@code DiscoverySelectors}; never {@code null} */ public static Resolution selectors(Set selectors) { Preconditions.notNull(selectors, "selectors must not be null"); Preconditions.notEmpty(selectors, "selectors must not be empty"); Preconditions.containsNoNullElements(selectors, "selectors must not contain null elements"); return new Resolution(emptySet(), selectors); } private Resolution(Set matches, Set selectors) { this.matches = matches; this.selectors = selectors; } /** * Whether this resolution contains matches or selectors. * * @return {@code true} if this resolution contains matches or selectors */ public boolean isResolved() { return this != UNRESOLVED; } /** * Returns the matches contained by this resolution. * * @return the set of matches; never {@code null} but potentially empty */ public Set getMatches() { return matches; } /** * Returns the selectors contained by this resolution. * * @return the set of selectors; never {@code null} but potentially empty */ public Set getSelectors() { return selectors; } } /** * An exact or partial match for resolving a {@link DiscoverySelector} into * a {@link TestDescriptor}. * *

A match is exact if the {@link DiscoverySelector} directly * represents the resulting {@link TestDescriptor}, e.g. if a * {@link ClassSelector} was resolved into the {@link TestDescriptor} that * represents the test class. It is partial if the matching * {@link TestDescriptor} does not directly correspond to the resolved * {@link DiscoverySelector}, e.g. when resolving a {@link UniqueIdSelector} * that represents a dynamic child of the resolved {@link TestDescriptor}. * *

In addition to the {@link TestDescriptor}, a match may contain a * {@code Supplier} of {@link DiscoverySelector DiscoverySelectors} that may * be used to discover the children of the {@link TestDescriptor}. The * algorithm implemented by {@link EngineDiscoveryRequestResolver} * {@linkplain #expand() expands} all exact matches immediately, i.e. it * resolves all of their children. Partial matches will only be expanded in * case a subsequent resolution finds an exact match that contains a {@link * TestDescriptor} with the same {@linkplain TestDescriptor#getUniqueId() * unique ID}. * * @since 1.5 * @see SelectorResolver * @see Resolution#match(Match) * @see Resolution#matches(Set) */ @API(status = STABLE, since = "1.10") class Match { private final TestDescriptor testDescriptor; private final Supplier> childSelectorsSupplier; private final Type type; /** * Factory for creating an exact match without any children. * * @param testDescriptor the resolved {@code TestDescriptor}; never * {@code null} * @return a match that contains the supplied {@code TestDescriptor}; * never {@code null} */ public static Match exact(TestDescriptor testDescriptor) { return exact(testDescriptor, Collections::emptySet); } /** * Factory for creating an exact match with potential children. * * @param testDescriptor the resolved {@code TestDescriptor}; never * {@code null} * @param childSelectorsSupplier a {@code Supplier} of children * selectors that will be resolved when this match is expanded; never * {@code null} * @return a match that contains the supplied {@code TestDescriptor}; * never {@code null} */ public static Match exact(TestDescriptor testDescriptor, Supplier> childSelectorsSupplier) { return new Match(testDescriptor, childSelectorsSupplier, Type.EXACT); } /** * Factory for creating a partial match without any children. * * @param testDescriptor the resolved {@code TestDescriptor}; never * {@code null} * @return a match that contains the supplied {@code TestDescriptor}; * never {@code null} */ public static Match partial(TestDescriptor testDescriptor) { return partial(testDescriptor, Collections::emptySet); } /** * Factory for creating a partial match with potential children. * * @param testDescriptor the resolved {@code TestDescriptor}; never * {@code null} * @param childSelectorsSupplier a {@code Supplier} of children * selectors that will be resolved when this match is expanded; never * {@code null} * @return a match that contains the supplied {@code TestDescriptor}; * never {@code null} */ public static Match partial(TestDescriptor testDescriptor, Supplier> childSelectorsSupplier) { return new Match(testDescriptor, childSelectorsSupplier, Type.PARTIAL); } private Match(TestDescriptor testDescriptor, Supplier> childSelectorsSupplier, Type type) { this.testDescriptor = Preconditions.notNull(testDescriptor, "testDescriptor must not be null"); this.childSelectorsSupplier = Preconditions.notNull(childSelectorsSupplier, "childSelectorsSupplier must not be null"); this.type = type; } /** * Whether this match is exact. * * @return {@code true} if this match is exact; {@code false} if it's * partial */ public boolean isExact() { return type == Type.EXACT; } /** * Get the contained {@link TestDescriptor}. * * @return the contained {@code TestDescriptor}; never {@code null} */ public TestDescriptor getTestDescriptor() { return testDescriptor; } /** * Expand this match in order to resolve the children of the contained * {@link TestDescriptor}. * * @return the set of {@code DiscoverySelectors} that represent the * children of the contained {@code TestDescriptor}; never {@code null} */ public Set expand() { return childSelectorsSupplier.get(); } private enum Type { EXACT, PARTIAL } } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Configurable test discovery implementation that can be reused by different test engines. * * @see org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver#builder() * @see org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver#resolve * @see org.junit.platform.engine.support.discovery.SelectorResolver */ @NullMarked package org.junit.platform.engine.support.discovery; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/BlockingAwareFuture.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static org.junit.platform.commons.util.ExceptionUtils.throwAsUncheckedException; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.jspecify.annotations.Nullable; /** * @since 6.1 */ abstract class BlockingAwareFuture extends DelegatingFuture { BlockingAwareFuture(Future delegate) { super(delegate); } @Override public T get() throws InterruptedException, ExecutionException { if (delegate.isDone()) { return delegate.get(); } return handleSafely(delegate::get); } @Override public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { if (delegate.isDone()) { return delegate.get(); } return handleSafely(() -> delegate.get(timeout, unit)); } private T handleSafely(Callable callable) { try { return handle(callable); } catch (Exception e) { throw throwAsUncheckedException(e); } } protected abstract T handle(Callable callable) throws Exception; } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/CompositeLock.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.locks.Lock; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; /** * @since 1.3 */ class CompositeLock implements ResourceLock { private final List resources; private final List locks; private final boolean exclusive; CompositeLock(List resources, List locks) { Preconditions.condition(resources.size() == locks.size(), "Resources and locks must have the same size"); this.resources = List.copyOf(resources); this.locks = Preconditions.notEmpty(locks, "Locks must not be empty"); this.exclusive = resources.stream().anyMatch( resource -> resource.getLockMode() == ExclusiveResource.LockMode.READ_WRITE); } @Override public List getResources() { return resources; } // for tests only List getLocks() { return this.locks; } @Override public boolean tryAcquire() { List acquiredLocks = new ArrayList<>(this.locks.size()); for (Lock lock : this.locks) { if (lock.tryLock()) { acquiredLocks.add(lock); } else { break; } } if (acquiredLocks.size() == this.locks.size()) { return true; } else { release(acquiredLocks); return false; } } @Override public ResourceLock acquire() throws InterruptedException { ForkJoinPool.managedBlock(new CompositeLockManagedBlocker()); return this; } private void acquireAllLocks() throws InterruptedException { List acquiredLocks = new ArrayList<>(this.locks.size()); try { for (Lock lock : this.locks) { lock.lockInterruptibly(); acquiredLocks.add(lock); } } catch (InterruptedException e) { release(acquiredLocks); throw e; } } @Override public void release() { release(this.locks); } private void release(List acquiredLocks) { for (int i = acquiredLocks.size() - 1; i >= 0; i--) { acquiredLocks.get(i).unlock(); } } @Override public boolean isExclusive() { return exclusive; } @Override public String toString() { return new ToStringBuilder(this) // .append("resources", resources) // .toString(); } private class CompositeLockManagedBlocker implements ForkJoinPool.ManagedBlocker { private volatile boolean acquired; @Override public boolean block() throws InterruptedException { if (!this.acquired) { acquireAllLocks(); this.acquired = true; } return true; } @Override public boolean isReleasable() { return this.acquired; } } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfiguration.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import java.util.concurrent.ForkJoinPool; import java.util.function.Predicate; /** * @since 1.3 */ class DefaultParallelExecutionConfiguration implements ParallelExecutionConfiguration { private final int parallelism; private final int minimumRunnable; private final int maxPoolSize; private final int corePoolSize; private final int keepAliveSeconds; private final Predicate saturate; DefaultParallelExecutionConfiguration(int parallelism, int minimumRunnable, int maxPoolSize, int corePoolSize, int keepAliveSeconds, Predicate saturate) { this.parallelism = parallelism; this.minimumRunnable = minimumRunnable; this.maxPoolSize = maxPoolSize; this.corePoolSize = corePoolSize; this.keepAliveSeconds = keepAliveSeconds; this.saturate = saturate; } @Override public int getParallelism() { return parallelism; } @Override public int getMinimumRunnable() { return minimumRunnable; } @Override public int getMaxPoolSize() { return maxPoolSize; } @Override public int getCorePoolSize() { return corePoolSize; } @Override public int getKeepAliveSeconds() { return keepAliveSeconds; } @Override public Predicate getSaturatePredicate() { return saturate; } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static java.util.Objects.requireNonNull; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.math.BigDecimal; import java.util.Locale; import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.ConfigurationParameters; /** * Default implementations of configuration strategies for parallel test * execution. * * @since 1.3 */ @API(status = STABLE, since = "1.10") public enum DefaultParallelExecutionConfigurationStrategy implements ParallelExecutionConfigurationStrategy { /** * Uses the mandatory {@value #CONFIG_FIXED_PARALLELISM_PROPERTY_NAME} configuration * parameter as the desired parallelism. */ FIXED { @Override public ParallelExecutionConfiguration createConfiguration(ConfigurationParameters configurationParameters) { int parallelism = configurationParameters.get(CONFIG_FIXED_PARALLELISM_PROPERTY_NAME, Integer::valueOf).orElseThrow( () -> new JUnitException( "Configuration parameter '%s' must be set".formatted(CONFIG_FIXED_PARALLELISM_PROPERTY_NAME))); int maxPoolSize = configurationParameters.get(CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME, Integer::valueOf).orElse(parallelism + 256); boolean saturate = configurationParameters.get(CONFIG_FIXED_SATURATE_PROPERTY_NAME, Boolean::valueOf).orElse(true); return new DefaultParallelExecutionConfiguration(parallelism, parallelism, maxPoolSize, parallelism, KEEP_ALIVE_SECONDS, __ -> saturate); } }, /** * Computes the desired parallelism based on the number of available * processors/cores multiplied by the {@value #CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME} * configuration parameter. */ DYNAMIC { @Override public ParallelExecutionConfiguration createConfiguration(ConfigurationParameters configurationParameters) { BigDecimal factor = configurationParameters.get(CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME, BigDecimal::new).orElse(BigDecimal.ONE); Preconditions.condition(factor.compareTo(BigDecimal.ZERO) > 0, () -> "Factor '%s' specified via configuration parameter '%s' must be greater than 0".formatted(factor, CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME)); int parallelism = Math.max(1, factor.multiply(BigDecimal.valueOf(Runtime.getRuntime().availableProcessors())).intValue()); int maxPoolSize = configurationParameters.get(CONFIG_DYNAMIC_MAX_POOL_SIZE_FACTOR_PROPERTY_NAME, BigDecimal::new).map(maxPoolSizeFactor -> { Preconditions.condition(maxPoolSizeFactor.compareTo(BigDecimal.ONE) >= 0, () -> "Factor '%s' specified via configuration parameter '%s' must be greater than or equal to 1".formatted( factor, CONFIG_DYNAMIC_MAX_POOL_SIZE_FACTOR_PROPERTY_NAME)); return maxPoolSizeFactor.multiply(BigDecimal.valueOf(parallelism)).intValue(); }).orElseGet(() -> 256 + parallelism); boolean saturate = configurationParameters.get(CONFIG_DYNAMIC_SATURATE_PROPERTY_NAME, Boolean::valueOf).orElse(true); return new DefaultParallelExecutionConfiguration(parallelism, parallelism, maxPoolSize, parallelism, KEEP_ALIVE_SECONDS, __ -> saturate); } }, /** * Allows the specification of a custom {@link ParallelExecutionConfigurationStrategy} * implementation via the mandatory {@value #CONFIG_CUSTOM_CLASS_PROPERTY_NAME} * configuration parameter to determine the desired configuration. */ CUSTOM { @Override public ParallelExecutionConfiguration createConfiguration(ConfigurationParameters configurationParameters) { String className = configurationParameters.get(CONFIG_CUSTOM_CLASS_PROPERTY_NAME).orElseThrow( () -> new JUnitException(CONFIG_CUSTOM_CLASS_PROPERTY_NAME + " must be set")); return ReflectionSupport.tryToLoadClass(className) // .andThenTry(strategyClass -> { Preconditions.condition( ParallelExecutionConfigurationStrategy.class.isAssignableFrom(strategyClass), CONFIG_CUSTOM_CLASS_PROPERTY_NAME + " does not implement " + ParallelExecutionConfigurationStrategy.class); return (ParallelExecutionConfigurationStrategy) ReflectionSupport.newInstance(strategyClass); }) // .andThenTry(strategy -> requireNonNull(strategy).createConfiguration(configurationParameters)) // .getNonNullOrThrow(cause -> new JUnitException( "Could not create configuration for strategy class: " + className, cause)); } }; private static final int KEEP_ALIVE_SECONDS = 30; /** * Property name used to determine the desired configuration strategy. * *

Value must be one of {@code dynamic}, {@code fixed}, or * {@code custom}. */ public static final String CONFIG_STRATEGY_PROPERTY_NAME = "strategy"; /** * Property name used to determine the desired parallelism for the * {@link #FIXED} configuration strategy. * *

No default value; must be an integer. * * @see #FIXED */ public static final String CONFIG_FIXED_PARALLELISM_PROPERTY_NAME = "fixed.parallelism"; /** * Property name used to configure the maximum pool size of the underlying * fork-join pool for the {@link #FIXED} configuration strategy. * *

Value must be an integer and greater than or equal to * {@value #CONFIG_FIXED_PARALLELISM_PROPERTY_NAME}; defaults to * {@code 256 + fixed.parallelism}. * * @since 1.10 * @see #FIXED */ @API(status = MAINTAINED, since = "1.13.3") public static final String CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME = "fixed.max-pool-size"; /** * Property name used to disable saturation of the underlying fork-join pool * for the {@link #FIXED} configuration strategy. * *

When set to {@code false} the underlying fork-join pool will reject * additional tasks if all available workers are busy and the maximum * pool-size would be exceeded. *

Value must either {@code true} or {@code false}; defaults to {@code true}. * * @since 1.10 * @see #FIXED * @see #CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME */ @API(status = MAINTAINED, since = "1.13.3") public static final String CONFIG_FIXED_SATURATE_PROPERTY_NAME = "fixed.saturate"; /** * Property name of the factor used to determine the desired parallelism for the * {@link #DYNAMIC} configuration strategy. * *

Value must be a non-negative decimal number; defaults to {@code 1}. * * @see #DYNAMIC */ public static final String CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME = "dynamic.factor"; /** * Property name of the factor used to determine the maximum pool size of * the underlying fork-join pool for the {@link #DYNAMIC} configuration * strategy. * *

Value must be a decimal number equal and greater than or equal to * {@code 1}. When set the maximum pool size is calculated as * {@code dynamic.max-pool-size-factor * dynamic.factor * Runtime.getRuntime().availableProcessors()} * When not set the maximum pool size is calculated as * {@code 256 + dynamic.factor * Runtime.getRuntime().availableProcessors()} * instead. * * @since 1.10 * @see #DYNAMIC */ @API(status = MAINTAINED, since = "1.13.3") public static final String CONFIG_DYNAMIC_MAX_POOL_SIZE_FACTOR_PROPERTY_NAME = "dynamic.max-pool-size-factor"; /** * Property name used to disable saturation of the underlying fork-join pool * for the {@link #DYNAMIC} configuration strategy. * *

When set to {@code false} the underlying fork-join pool will reject * additional tasks if all available workers are busy and the maximum * pool-size would be exceeded. *

Value must either {@code true} or {@code false}; defaults to {@code true}. * * @since 1.10 * @see #DYNAMIC * @see #CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME */ @API(status = MAINTAINED, since = "1.13.3") public static final String CONFIG_DYNAMIC_SATURATE_PROPERTY_NAME = "dynamic.saturate"; /** * Property name used to specify the fully qualified class name of the * {@link ParallelExecutionConfigurationStrategy} to be used by the * {@link #CUSTOM} configuration strategy. * * @see #CUSTOM */ public static final String CONFIG_CUSTOM_CLASS_PROPERTY_NAME = "custom.class"; static ParallelExecutionConfiguration toConfiguration(ConfigurationParameters configurationParameters) { return getStrategy(configurationParameters).createConfiguration(configurationParameters); } static ParallelExecutionConfigurationStrategy getStrategy(ConfigurationParameters configurationParameters) { return valueOf( configurationParameters.get(CONFIG_STRATEGY_PROPERTY_NAME).orElse("dynamic").toUpperCase(Locale.ROOT)); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DelegatingFuture.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.jspecify.annotations.Nullable; /** * @since 6.1 */ class DelegatingFuture implements Future { protected final Future delegate; DelegatingFuture(Future delegate) { this.delegate = delegate; } @Override public boolean cancel(boolean mayInterruptIfRunning) { return delegate.cancel(mayInterruptIfRunning); } @Override public boolean isCancelled() { return delegate.isCancelled(); } @Override public boolean isDone() { return delegate.isDone(); } @Override public T get() throws InterruptedException, ExecutionException { return delegate.get(); } @Override public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return delegate.get(timeout, unit); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/EngineExecutionContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static org.apiguardian.api.API.Status.MAINTAINED; import org.apiguardian.api.API; /** * Marker interface for an execution context used by a concrete implementation * of {@link HierarchicalTestEngine} and its collaborators. * * @since 1.0 * @see HierarchicalTestEngine */ @API(status = MAINTAINED, since = "1.0") public interface EngineExecutionContext { } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ExclusiveResource.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static java.util.Comparator.comparing; import static java.util.Comparator.naturalOrder; import static org.apiguardian.api.API.Status.STABLE; import java.util.Comparator; import java.util.Objects; import java.util.concurrent.locks.ReadWriteLock; import org.apiguardian.api.API; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.support.hierarchical.Node.ExecutionMode; /** * An exclusive resource identified by a key with a lock mode that is used to * synchronize access to shared resources when executing nodes in parallel. * * @since 1.3 * @see Node#getExecutionMode() */ @API(status = STABLE, since = "1.10") public class ExclusiveResource { /** * Key of the global resource lock that all direct children of the engine * descriptor acquire in {@linkplain LockMode#READ read mode} by default: * {@value} * *

If any node {@linkplain Node#getExclusiveResources() requires} an * exclusive resource with the same key in * {@linkplain LockMode#READ_WRITE read-write mode}, the lock will be * coarsened to be acquired by the node's ancestor that is a direct child of * the engine descriptor and all of the ancestor's descendants will be * forced to run in the {@linkplain ExecutionMode#SAME_THREAD same thread}. * * @since 1.7 */ @API(status = STABLE, since = "1.10") public static final String GLOBAL_KEY = "org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_KEY"; static final ExclusiveResource GLOBAL_READ = new ExclusiveResource(GLOBAL_KEY, LockMode.READ); static final ExclusiveResource GLOBAL_READ_WRITE = new ExclusiveResource(GLOBAL_KEY, LockMode.READ_WRITE); static final Comparator COMPARATOR // = comparing(ExclusiveResource::getKey, globalKeyFirst().thenComparing(naturalOrder())) // .thenComparing(ExclusiveResource::getLockMode); private static Comparator globalKeyFirst() { return comparing(key -> !GLOBAL_KEY.equals(key)); } private final String key; private final LockMode lockMode; private int hash; /** * Create a new {@code ExclusiveResource}. * * @param key the identifier of the resource; never {@code null} or blank * @param lockMode the lock mode to use to synchronize access to the * resource; never {@code null} */ public ExclusiveResource(String key, LockMode lockMode) { this.key = Preconditions.notBlank(key, "key must not be blank"); this.lockMode = Preconditions.notNull(lockMode, "lockMode must not be null"); } /** * Get the key of this resource. */ public String getKey() { return key; } /** * Get the lock mode of this resource. */ public LockMode getLockMode() { return lockMode; } @Override @SuppressWarnings("EqualsGetClass") public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ExclusiveResource that = (ExclusiveResource) o; return Objects.equals(key, that.key) && lockMode == that.lockMode; } @Override public int hashCode() { int h = hash; if (h == 0) { h = hash = Objects.hash(key, lockMode); } return h; } @Override public String toString() { return new ToStringBuilder(this).append("key", key).append("lockMode", lockMode).toString(); } /** * {@code LockMode} translates to the respective {@link ReadWriteLock} * locks. * * @implNote Enum order is important, since it can be used to sort locks, so * the stronger mode has to be first. */ public enum LockMode { /** * Require read and write access to the resource. * * @see ReadWriteLock#writeLock() */ READ_WRITE, /** * Require only read access to the resource. * * @see ReadWriteLock#readLock() */ READ } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorService.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static java.util.concurrent.CompletableFuture.completedFuture; import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_READ_WRITE; import static org.junit.platform.engine.support.hierarchical.Node.ExecutionMode.CONCURRENT; import static org.junit.platform.engine.support.hierarchical.Node.ExecutionMode.SAME_THREAD; import java.io.Serial; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.List; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinTask; import java.util.concurrent.ForkJoinWorkerThread; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.support.hierarchical.ParallelHierarchicalTestExecutorServiceFactory.ParallelExecutorServiceType; /** * A {@link ForkJoinPool}-based * {@linkplain HierarchicalTestExecutorService executor service} that executes * {@linkplain TestTask test tasks} with the configured parallelism. * * @since 1.3 * @see ParallelHierarchicalTestExecutorServiceFactory * @see ParallelExecutorServiceType#FORK_JOIN_POOL * @see DefaultParallelExecutionConfigurationStrategy * @see ForkJoinPool */ @API(status = MAINTAINED, since = "1.10") public class ForkJoinPoolHierarchicalTestExecutorService implements HierarchicalTestExecutorService { // package-private for testing final ForkJoinPool forkJoinPool; private final TaskEventListener taskEventListener; private final int parallelism; private final ThreadLocal threadLocks = ThreadLocal.withInitial(ThreadLock::new); /** * Create a new {@code ForkJoinPoolHierarchicalTestExecutorService} based on * the supplied {@link ConfigurationParameters}. * * @see DefaultParallelExecutionConfigurationStrategy * @deprecated Please use * {@link ParallelHierarchicalTestExecutorServiceFactory#create(ConfigurationParameters)} * with configuration parameter * {@value ParallelHierarchicalTestExecutorServiceFactory#EXECUTOR_SERVICE_PROPERTY_NAME} * set to * {@link ParallelExecutorServiceType#FORK_JOIN_POOL FORK_JOIN_POOL} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(since = "6.1") public ForkJoinPoolHierarchicalTestExecutorService(ConfigurationParameters configurationParameters) { this(DefaultParallelExecutionConfigurationStrategy.toConfiguration(configurationParameters)); } /** * Create a new {@code ForkJoinPoolHierarchicalTestExecutorService} based on * the supplied {@link ParallelExecutionConfiguration}. * * @since 1.7 * @deprecated Please use * {@link ParallelHierarchicalTestExecutorServiceFactory#create(ParallelExecutorServiceType, ParallelExecutionConfiguration)} * with * {@link ParallelExecutorServiceType#FORK_JOIN_POOL ParallelExecutorServiceType.FORK_JOIN_POOL} * instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(since = "6.1") public ForkJoinPoolHierarchicalTestExecutorService(ParallelExecutionConfiguration configuration) { this(configuration, TaskEventListener.NOOP); } ForkJoinPoolHierarchicalTestExecutorService(ParallelExecutionConfiguration configuration, TaskEventListener taskEventListener) { forkJoinPool = createForkJoinPool(configuration); this.taskEventListener = taskEventListener; parallelism = forkJoinPool.getParallelism(); LoggerFactory.getLogger(getClass()).config(() -> "Using ForkJoinPool with parallelism of " + parallelism); } private ForkJoinPool createForkJoinPool(ParallelExecutionConfiguration configuration) { try { return new ForkJoinPool(configuration.getParallelism(), new WorkerThreadFactory(), null, false, configuration.getCorePoolSize(), configuration.getMaxPoolSize(), configuration.getMinimumRunnable(), configuration.getSaturatePredicate(), configuration.getKeepAliveSeconds(), TimeUnit.SECONDS); } catch (Exception cause) { throw new JUnitException("Failed to create ForkJoinPool", cause); } } @Override public Future<@Nullable Void> submit(TestTask testTask) { ExclusiveTask exclusiveTask = new ExclusiveTask(testTask); if (!isAlreadyRunningInForkJoinPool()) { // ensure we're running inside the ForkJoinPool so we // can use ForkJoinTask API in invokeAll etc. return forkJoinPool.submit(exclusiveTask); } // Limit the amount of queued work so we don't consume dynamic tests too eagerly // by forking only if the current worker thread's queue length is below the // desired parallelism. This optimistically assumes that the already queued tasks // can be stolen by other workers and the new task requires about the same // execution time as the already queued tasks. If the other workers are busy, // the parallelism is already at its desired level. If all already queued tasks // can be stolen by otherwise idle workers and the new task takes significantly // longer, parallelism will drop. However, that only happens if the enclosing test // task is the only one remaining which should rarely be the case. if (testTask.getExecutionMode() == CONCURRENT && ForkJoinTask.getSurplusQueuedTaskCount() < parallelism) { return exclusiveTask.fork(); } exclusiveTask.execSync(); return completedFuture(null); } private boolean isAlreadyRunningInForkJoinPool() { return ForkJoinTask.getPool() == forkJoinPool; } @Override public void invokeAll(List tasks) { if (tasks.size() == 1) { new ExclusiveTask(tasks.get(0)).execSync(); return; } Deque isolatedTasks = new ArrayDeque<>(); Deque sameThreadTasks = new ArrayDeque<>(); Deque concurrentTasksInReverseOrder = new ArrayDeque<>(); forkConcurrentTasks(tasks, isolatedTasks, sameThreadTasks, concurrentTasksInReverseOrder); executeSync(sameThreadTasks); joinConcurrentTasksInReverseOrderToEnableWorkStealing(concurrentTasksInReverseOrder); executeSync(isolatedTasks); } private void forkConcurrentTasks(List tasks, Deque isolatedTasks, Deque sameThreadTasks, Deque concurrentTasksInReverseOrder) { for (TestTask testTask : tasks) { ExclusiveTask exclusiveTask = new ExclusiveTask(testTask); if (requiresGlobalReadWriteLock(testTask)) { isolatedTasks.add(exclusiveTask); } else if (testTask.getExecutionMode() == SAME_THREAD) { sameThreadTasks.add(exclusiveTask); } else { exclusiveTask.fork(); concurrentTasksInReverseOrder.addFirst(exclusiveTask); } } } private static boolean requiresGlobalReadWriteLock(TestTask testTask) { return testTask.getResourceLock().getResources().contains(GLOBAL_READ_WRITE); } private void executeSync(Deque tasks) { for (ExclusiveTask task : tasks) { task.execSync(); } } private void joinConcurrentTasksInReverseOrderToEnableWorkStealing( Deque concurrentTasksInReverseOrder) { for (ExclusiveTask forkedTask : concurrentTasksInReverseOrder) { forkedTask.join(); resubmitDeferredTasks(); } } private void resubmitDeferredTasks() { List deferredTasks = threadLocks.get().deferredTasks; for (ExclusiveTask deferredTask : deferredTasks) { if (!deferredTask.isDone()) { deferredTask.fork(); } } deferredTasks.clear(); } @Override public void close() { forkJoinPool.shutdownNow(); } // this class cannot not be serialized because TestTask is not Serializable @SuppressWarnings({ "serial", "RedundantSuppression" }) class ExclusiveTask extends ForkJoinTask<@Nullable Void> { @Serial private static final long serialVersionUID = 1; private final TestTask testTask; ExclusiveTask(TestTask testTask) { this.testTask = testTask; } /** * Always returns {@code null}. * * @return {@code null} always */ @Override public final Void getRawResult() { return null; } /** * Requires null completion value. */ @Override protected final void setRawResult(Void mustBeNull) { } void execSync() { boolean completed = exec(); if (!completed) { throw new IllegalStateException( "Task was deferred but should have been executed synchronously: " + testTask); } } @SuppressWarnings("try") @Override public boolean exec() { // Check if this task is compatible with the current resource lock, if there is any. // If not, we put this task in the thread local as a deferred task // and let the worker thread fork it once it is done with the current task. ResourceLock resourceLock = testTask.getResourceLock(); ThreadLock threadLock = threadLocks.get(); if (!threadLock.areAllHeldLocksCompatibleWith(resourceLock)) { threadLock.addDeferredTask(this); taskEventListener.deferred(testTask); // Return false to indicate that this task is not done yet // this means that .join() will wait. return false; } try ( // ResourceLock lock = resourceLock.acquire(); // @SuppressWarnings("unused") ThreadLock.NestedResourceLock nested = threadLock.withNesting(lock) // ) { testTask.execute(); return true; } catch (InterruptedException e) { throw ExceptionUtils.throwAsUncheckedException(e); } } @Override public String toString() { return "ExclusiveTask [" + testTask + "]"; } } static class WorkerThreadFactory implements ForkJoinPool.ForkJoinWorkerThreadFactory { private final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); @Override public ForkJoinWorkerThread newThread(ForkJoinPool pool) { return new WorkerThread(pool, contextClassLoader); } } static class WorkerThread extends ForkJoinWorkerThread { WorkerThread(ForkJoinPool pool, ClassLoader contextClassLoader) { super(pool); setContextClassLoader(contextClassLoader); } } static class ThreadLock { private final Deque locks = new ArrayDeque<>(2); private final List deferredTasks = new ArrayList<>(); void addDeferredTask(ExclusiveTask task) { deferredTasks.add(task); } NestedResourceLock withNesting(ResourceLock lock) { locks.push(lock); return locks::pop; } boolean areAllHeldLocksCompatibleWith(ResourceLock lock) { return locks.stream().allMatch(l -> l.isCompatible(lock)); } interface NestedResourceLock extends AutoCloseable { @Override void close(); } } interface TaskEventListener { TaskEventListener NOOP = __ -> { }; void deferred(TestTask testTask); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestEngine.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestEngine; /** * Abstract base class for all {@link TestEngine} implementations that wish * to organize test suites hierarchically based on the {@link Node} abstraction. * * @param the type of {@code EngineExecutionContext} used by this engine * @since 1.0 * @see Node */ @API(status = MAINTAINED, since = "1.0") public abstract class HierarchicalTestEngine implements TestEngine { public HierarchicalTestEngine() { } /** * Create an {@linkplain #createExecutorService(ExecutionRequest) executor * service}; create an initial {@linkplain #createExecutionContext execution * context}; execute the behavior of all {@linkplain Node nodes} in the * hierarchy starting with the supplied {@code request}'s * {@linkplain ExecutionRequest#getRootTestDescriptor() root} and notify * its {@linkplain ExecutionRequest#getEngineExecutionListener() execution * listener} of test execution events. * *

Supports cancellation via the {@link CancellationToken} passed in the * supplied {@code request}. * * @see Node * @see #createExecutorService * @see #createExecutionContext */ @Override public final void execute(ExecutionRequest request) { try (HierarchicalTestExecutorService executorService = createExecutorService(request)) { C executionContext = createExecutionContext(request); ThrowableCollector.Factory throwableCollectorFactory = createThrowableCollectorFactory(request); new HierarchicalTestExecutor<>(request, executionContext, executorService, throwableCollectorFactory).execute().get(); } catch (Exception exception) { throw new JUnitException("Error executing tests for engine " + getId(), exception); } } /** * Create the {@linkplain HierarchicalTestExecutorService executor service} * to use for executing the supplied {@linkplain ExecutionRequest request}. * *

An engine may use the information in the supplied request * such as the contained * {@linkplain ExecutionRequest#getConfigurationParameters() configuration parameters} * to decide what kind of service to return or how to configure it. * *

By default, this method returns an instance of * {@link SameThreadHierarchicalTestExecutorService}. * * @param request the request about to be executed * @since 1.3 * @see ForkJoinPoolHierarchicalTestExecutorService * @see SameThreadHierarchicalTestExecutorService */ @API(status = STABLE, since = "1.10") protected HierarchicalTestExecutorService createExecutorService(ExecutionRequest request) { return new SameThreadHierarchicalTestExecutorService(); } /** * Create the {@linkplain ThrowableCollector.Factory factory} for creating * {@link ThrowableCollector} instances used to handle exceptions that occur * during execution of this engine's tests. * *

An engine may use the information in the supplied request * such as the contained * {@linkplain ExecutionRequest#getConfigurationParameters() configuration parameters} * to decide what kind of factory to return or how to configure it. * *

By default, this method returns a factory that always creates instances of * {@link OpenTest4JAwareThrowableCollector}. * * @param request the request about to be executed * @since 1.3 * @see OpenTest4JAwareThrowableCollector * @see ThrowableCollector */ @API(status = STABLE, since = "1.10") protected ThrowableCollector.Factory createThrowableCollectorFactory(ExecutionRequest request) { return OpenTest4JAwareThrowableCollector::new; } /** * Create the initial execution context for executing the supplied * {@linkplain ExecutionRequest request}. * * @param request the request about to be executed * @return the initial context that will be passed to nodes in the hierarchy */ protected abstract C createExecutionContext(ExecutionRequest request); } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import java.util.concurrent.Future; import org.jspecify.annotations.Nullable; import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestEngine; /** * Implementation core of all {@link TestEngine TestEngines} that wish to * use the {@link Node} abstraction as the driving principle for structuring * and executing test suites. * *

A {@code HierarchicalTestExecutor} is instantiated by a concrete * implementation of {@link HierarchicalTestEngine} and takes care of * executing nodes in the hierarchy in the appropriate order as well as * firing the necessary events in the {@link EngineExecutionListener}. * * @param the type of {@code EngineExecutionContext} used by the * {@code HierarchicalTestEngine} * @since 1.0 */ class HierarchicalTestExecutor { private final ExecutionRequest request; private final C rootContext; private final HierarchicalTestExecutorService executorService; private final ThrowableCollector.Factory throwableCollectorFactory; HierarchicalTestExecutor(ExecutionRequest request, C rootContext, HierarchicalTestExecutorService executorService, ThrowableCollector.Factory throwableCollectorFactory) { this.request = request; this.rootContext = rootContext; this.executorService = executorService; this.throwableCollectorFactory = throwableCollectorFactory; } Future<@Nullable Void> execute() { return this.executorService.submit(createRootTestTask()); } private NodeTestTask createRootTestTask() { NodeTestTaskContext taskContext = createTaskContext(); TestDescriptor rootTestDescriptor = this.request.getRootTestDescriptor(); NodeTestTask rootTestTask = new NodeTestTask<>(taskContext, rootTestDescriptor); rootTestTask.setParentContext(this.rootContext); return rootTestTask; } private NodeTestTaskContext createTaskContext() { EngineExecutionListener executionListener = this.request.getEngineExecutionListener(); NodeExecutionAdvisor executionAdvisor = new NodeTreeWalker().walk(this.request.getRootTestDescriptor()); CancellationToken cancellationToken = this.request.getCancellationToken(); return new NodeTestTaskContext(executionListener, this.executorService, this.throwableCollectorFactory, executionAdvisor, cancellationToken); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorService.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import java.util.List; import java.util.concurrent.Future; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.support.hierarchical.Node.ExecutionMode; /** * A closeable service that executes {@linkplain TestTask test tasks}. * * @since 1.3 * @see HierarchicalTestEngine#createExecutorService(ExecutionRequest) * @see SameThreadHierarchicalTestExecutorService * @see ForkJoinPoolHierarchicalTestExecutorService */ @API(status = STABLE, since = "1.10") public interface HierarchicalTestExecutorService extends AutoCloseable { /** * Submit the supplied {@linkplain TestTask test task} to be executed by * this service. * *

Implementations may {@linkplain TestTask#execute() execute} the task * asynchronously as long as its * {@linkplain TestTask#getExecutionMode() execution mode} is * {@linkplain ExecutionMode#CONCURRENT concurrent}. * *

Implementations must generally acquire and release the task's * {@linkplain TestTask#getResourceLock() resource lock} before and after its * execution unless they execute all tests in the same thread which * upholds the same guarantees. * * @param testTask the test task to be executed * @return a future that the caller can use to wait for the task's execution * to be finished * @see #invokeAll(List) */ Future<@Nullable Void> submit(TestTask testTask); /** * Invoke all supplied {@linkplain TestTask test tasks} and block until * their execution has finished. * *

Implementations may {@linkplain TestTask#execute() execute} one or * multiple of the supplied tasks in parallel as long as their * {@linkplain TestTask#getExecutionMode() execution mode} is * {@linkplain ExecutionMode#CONCURRENT concurrent}. * *

Implementations must generally acquire and release each task's * {@linkplain TestTask#getResourceLock() resource lock} before and after its * execution unless they execute all tests in the same thread which * upholds the same guarantees. * * @param testTasks the test tasks to be executed * @see #submit(TestTask) */ void invokeAll(List testTasks); /** * Close this service and let it perform any required cleanup work. * *

For example, thread-based implementations should usually close their * thread pools in this method. */ @Override void close(); /** * An executable task that represents a single test or container. */ interface TestTask { /** * Get the {@linkplain ExecutionMode execution mode} of this task. */ ExecutionMode getExecutionMode(); /** * Get the {@linkplain ResourceLock resource lock} of this task. */ ResourceLock getResourceLock(); /** * Get the {@linkplain TestDescriptor test descriptor} of this task. * * @throws UnsupportedOperationException if not supported for this TestTask implementation * @since 6.0 */ @API(status = EXPERIMENTAL, since = "6.0") default TestDescriptor getTestDescriptor() { throw new UnsupportedOperationException(); } /** * Execute this task. */ void execute(); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/LockManager.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static java.util.Collections.emptyList; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.toList; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_READ; import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_READ_WRITE; import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode.READ; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * @since 1.3 */ class LockManager { private final Map locksByKey = new ConcurrentHashMap<>(); private final SingleLock globalReadLock; private final SingleLock globalReadWriteLock; LockManager() { globalReadLock = new SingleLock(GLOBAL_READ, toLock(GLOBAL_READ)); globalReadWriteLock = new SingleLock(GLOBAL_READ_WRITE, toLock(GLOBAL_READ_WRITE)); } ResourceLock getLockForResources(Collection resources) { return toResourceLock(toDistinctSortedResources(resources)); } ResourceLock getLockForResource(ExclusiveResource resource) { return toResourceLock(List.of(resource)); } private List toDistinctSortedResources(Collection resources) { if (resources.isEmpty()) { return emptyList(); } if (resources.size() == 1) { return List.of(getOnlyElement(resources)); } // @formatter:off Map> resourcesByKey = resources.stream() .sorted(ExclusiveResource.COMPARATOR) .distinct() .collect(groupingBy(ExclusiveResource::getKey, LinkedHashMap::new, toList())); return resourcesByKey.values().stream() .map(resourcesWithSameKey -> resourcesWithSameKey.get(0)) .toList(); // @formatter:on } private ResourceLock toResourceLock(List resources) { return switch (resources.size()) { case 0 -> NopLock.INSTANCE; case 1 -> toSingleLock(getOnlyElement(resources)); default -> new CompositeLock(resources, toLocks(resources)); }; } private SingleLock toSingleLock(ExclusiveResource resource) { if (GLOBAL_READ.equals(resource)) { return globalReadLock; } if (GLOBAL_READ_WRITE.equals(resource)) { return globalReadWriteLock; } return new SingleLock(resource, toLock(resource)); } private List toLocks(List resources) { return resources.stream().map(this::toLock).toList(); } private Lock toLock(ExclusiveResource resource) { ReadWriteLock lock = this.locksByKey.computeIfAbsent(resource.getKey(), key -> new ReentrantReadWriteLock()); return resource.getLockMode() == READ ? lock.readLock() : lock.writeLock(); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/Node.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static java.util.Collections.emptySet; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.util.Optional; import java.util.Set; import java.util.concurrent.Future; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; /** * A node within the execution hierarchy. * * @param the type of {@code EngineExecutionContext} used by the * {@code HierarchicalTestEngine} * @since 1.0 * @see HierarchicalTestEngine */ @API(status = MAINTAINED, since = "1.0", consumers = "org.junit.platform.engine.support.hierarchical") public interface Node { /** * Prepare the supplied {@code context} prior to execution. * *

The default implementation returns the supplied {@code context} unmodified. * * @see #cleanUp(EngineExecutionContext) */ default C prepare(C context) throws Exception { return context; } /** * Clean up the supplied {@code context} after execution. * *

The default implementation does nothing. * * @param context the context to execute in * @since 1.1 * @see #prepare(EngineExecutionContext) */ default void cleanUp(C context) throws Exception { } /** * Determine if the execution of the supplied {@code context} should be * skipped. * *

The default implementation returns {@link SkipResult#doNotSkip()}. */ default SkipResult shouldBeSkipped(C context) throws Exception { return SkipResult.doNotSkip(); } /** * Execute the before behavior of this node. * *

This method will be called once before {@linkplain #execute * execution} of this node. * *

The default implementation returns the supplied {@code context} unmodified. * * @param context the context to execute in * @return the new context to be used for children of this node; never * {@code null} * @see #execute(EngineExecutionContext, DynamicTestExecutor) * @see #after(EngineExecutionContext) */ default C before(C context) throws Exception { return context; } /** * Execute the behavior of this node. * *

Containers typically do not implement this method since the * {@link HierarchicalTestEngine} handles execution of their children. * *

The supplied {@code dynamicTestExecutor} may be used to submit * additional dynamic tests for immediate execution. * *

The default implementation returns the supplied {@code context} unmodified. * * @param context the context to execute in * @param dynamicTestExecutor the executor to submit dynamic tests to * @return the new context to be used for children of this node and for the * after behavior of the parent of this node, if any * @see #before * @see #after */ default C execute(C context, DynamicTestExecutor dynamicTestExecutor) throws Exception { return context; } /** * Execute the after behavior of this node. * *

This method will be called once after {@linkplain #execute * execution} of this node. * *

The default implementation does nothing. * * @param context the context to execute in * @see #before * @see #execute */ default void after(C context) throws Exception { } /** * Wraps around the invocation of {@link #before(EngineExecutionContext)}, * {@link #execute(EngineExecutionContext, DynamicTestExecutor)}, and * {@link #after(EngineExecutionContext)}. * * @param context context the context to execute in * @param invocation the wrapped invocation (must be invoked exactly once) * @since 1.4 */ @API(status = STABLE, since = "1.10") default void around(C context, Invocation invocation) throws Exception { invocation.invoke(context); } /** * Callback invoked when the execution of this node has been skipped. * *

The default implementation does nothing. * * @param context the execution context * @param testDescriptor the test descriptor that was skipped * @param result the result of skipped execution * @since 1.4 */ @API(status = STABLE, since = "1.10", consumers = "org.junit.platform.engine.support.hierarchical") default void nodeSkipped(C context, TestDescriptor testDescriptor, SkipResult result) { } /** * Callback invoked when the execution of this node has finished. * *

The default implementation does nothing. * * @param context the execution context * @param testDescriptor the test descriptor that was executed * @param result the result of the execution * @since 1.4 */ @API(status = STABLE, since = "1.10", consumers = "org.junit.platform.engine.support.hierarchical") default void nodeFinished(C context, TestDescriptor testDescriptor, TestExecutionResult result) { } /** * Get the set of {@linkplain ExclusiveResource exclusive resources} * required to execute this node. * *

The default implementation returns an empty set. * * @return the set of exclusive resources required by this node; never * {@code null} but potentially empty * @since 1.3 * @see ExclusiveResource */ @API(status = STABLE, since = "1.10", consumers = "org.junit.platform.engine.support.hierarchical") default Set getExclusiveResources() { return emptySet(); } /** * {@return whether this node requires the global read-write lock} * *

Engines should return {@code true} for all nodes that have behavior, * including tests but also containers with before/after lifecycle hooks. * *

The default implementation returns {@code true}. The value returned by * engine-level nodes is ignored. Otherwise, if a container node returns * {@code true}, the value returned by its descendants is ignored. * * @since 6.1 */ @API(status = EXPERIMENTAL, since = "6.1") default boolean isGlobalReadLockRequired() { return true; } /** * Get the preferred of {@linkplain ExecutionMode execution mode} for * parallel execution of this node. * *

The default implementation returns {@link ExecutionMode#CONCURRENT}. * * @return the preferred execution mode of this node; never {@code null} * @since 1.3 * @see ExecutionMode */ @API(status = STABLE, since = "1.10", consumers = "org.junit.platform.engine.support.hierarchical") default ExecutionMode getExecutionMode() { return ExecutionMode.CONCURRENT; } /** * The result of determining whether the execution of a given {@code context} * should be skipped. * * @see Node#shouldBeSkipped(EngineExecutionContext) */ class SkipResult { private static final SkipResult alwaysExecuteSkipResult = new SkipResult(false, null); private final boolean skipped; private final Optional reason; /** * Factory for creating skipped results. * *

A context that is skipped will be not be executed. * * @param reason the reason that the context should be skipped, * may be {@code null} * @return a skipped {@code SkipResult} with the given reason */ public static SkipResult skip(@Nullable String reason) { return new SkipResult(true, reason); } /** * Factory for creating do not skip results. * *

A context that is not skipped will be executed as normal. * * @return a do not skip {@code SkipResult} */ public static SkipResult doNotSkip() { return alwaysExecuteSkipResult; } private SkipResult(boolean skipped, @Nullable String reason) { this.skipped = skipped; this.reason = Optional.ofNullable(reason); } /** * Whether execution of the context should be skipped. * * @return {@code true} if the execution should be skipped */ public boolean isSkipped() { return this.skipped; } /** * Get the reason that execution of the context should be skipped, * if available. */ public Optional getReason() { return this.reason; } @Override public String toString() { // @formatter:off return new ToStringBuilder(this) .append("skipped", this.skipped) .append("reason", this.reason.orElse("")) .toString(); // @formatter:on } } /** * Executor for additional, dynamic test descriptors discovered during * execution of a {@link Node}. * *

The test descriptors will be executed by the same * {@link HierarchicalTestExecutor} that executes the submitting node. * *

This interface is not intended to be implemented by clients. * * @see Node#execute(EngineExecutionContext, DynamicTestExecutor) * @see HierarchicalTestExecutor */ interface DynamicTestExecutor { /** * Submit a dynamic test descriptor for immediate execution. * * @param testDescriptor the test descriptor to be executed; never * {@code null} */ void execute(TestDescriptor testDescriptor); /** * Submit a dynamic test descriptor for immediate execution with a * custom, potentially no-op, execution listener. * * @param testDescriptor the test descriptor to be executed; never * {@code null} * @param executionListener the executionListener to be notified; never * {@code null} * @return a future to cancel or wait for the execution * @since 1.7 * @see EngineExecutionListener#NOOP */ @API(status = STABLE, since = "1.10") Future execute(TestDescriptor testDescriptor, EngineExecutionListener executionListener); /** * Block until all dynamic test descriptors submitted to this executor * are finished. * *

This method is useful if the node needs to perform actions in its * {@link #execute(EngineExecutionContext, DynamicTestExecutor)} method * after all its dynamic children have finished. * * @throws InterruptedException if interrupted while waiting */ void awaitFinished() throws InterruptedException; } /** * Supported execution modes for parallel execution. * * @since 1.3 * @see #SAME_THREAD * @see #CONCURRENT * @see Node#getExecutionMode() */ @API(status = STABLE, since = "1.10", consumers = "org.junit.platform.engine.support.hierarchical") enum ExecutionMode { /** * Force execution in same thread as the parent node. * * @see #CONCURRENT */ SAME_THREAD, /** * Allow concurrent execution with any other node. * * @see #SAME_THREAD */ CONCURRENT } /** * Represents an invocation that runs with the supplied context. * * @param the type of {@code EngineExecutionContext} used by the {@code HierarchicalTestEngine} * @since 1.4 */ @API(status = STABLE, since = "1.10") interface Invocation { /** * Invoke this invocation with the supplied context. * * @param context the context to invoke in */ void invoke(C context) throws Exception; } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeExecutionAdvisor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import java.util.HashMap; import java.util.Map; import java.util.Optional; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.support.hierarchical.Node.ExecutionMode; /** * @since 1.3.1 */ class NodeExecutionAdvisor { private final Map forcedDescendantExecutionModeByTestDescriptor = new HashMap<>(); private final Map resourceLocksByTestDescriptor = new HashMap<>(); void forceDescendantExecutionMode(TestDescriptor testDescriptor, ExecutionMode executionMode) { forcedDescendantExecutionModeByTestDescriptor.put(testDescriptor, executionMode); } void useResourceLock(TestDescriptor testDescriptor, ResourceLock resourceLock) { resourceLocksByTestDescriptor.put(testDescriptor, resourceLock); } void removeResourceLock(TestDescriptor testDescriptor) { resourceLocksByTestDescriptor.remove(testDescriptor); } Optional getForcedExecutionMode(TestDescriptor testDescriptor) { return testDescriptor.getParent().flatMap(this::lookupExecutionModeForcedByAncestor); } private Optional lookupExecutionModeForcedByAncestor(TestDescriptor testDescriptor) { ExecutionMode value = forcedDescendantExecutionModeByTestDescriptor.get(testDescriptor); if (value != null) { return Optional.of(value); } return testDescriptor.getParent().flatMap(this::lookupExecutionModeForcedByAncestor); } ResourceLock getResourceLock(TestDescriptor testDescriptor) { return resourceLocksByTestDescriptor.getOrDefault(testDescriptor, NopLock.INSTANCE); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTask.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNullElse; import static java.util.concurrent.CompletableFuture.completedFuture; import static java.util.stream.Collectors.toCollection; import static org.junit.platform.engine.TestExecutionResult.failed; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CancellationException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.UnrecoverableExceptions; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutorService.TestTask; import org.junit.platform.engine.support.hierarchical.Node.DynamicTestExecutor; import org.junit.platform.engine.support.hierarchical.Node.ExecutionMode; import org.junit.platform.engine.support.hierarchical.Node.SkipResult; /** * @since 1.3 */ class NodeTestTask implements TestTask { private static final Logger logger = LoggerFactory.getLogger(NodeTestTask.class); private static final Runnable NOOP = () -> { }; static final SkipResult CANCELLED_SKIP_RESULT = SkipResult.skip("Execution cancelled"); private final NodeTestTaskContext taskContext; private final TestDescriptor testDescriptor; private final Node node; private final Runnable finalizer; private volatile @Nullable C parentContext; private @Nullable C context; private @Nullable SkipResult skipResult; private boolean started; private @Nullable ThrowableCollector throwableCollector; NodeTestTask(NodeTestTaskContext taskContext, TestDescriptor testDescriptor) { this(taskContext, testDescriptor, NOOP); } NodeTestTask(NodeTestTaskContext taskContext, TestDescriptor testDescriptor, Runnable finalizer) { this.taskContext = taskContext; this.testDescriptor = testDescriptor; this.node = NodeUtils.asNode(testDescriptor); this.finalizer = finalizer; } @Override public ResourceLock getResourceLock() { return taskContext.executionAdvisor().getResourceLock(testDescriptor); } @Override public ExecutionMode getExecutionMode() { return taskContext.executionAdvisor().getForcedExecutionMode(testDescriptor) // .orElseGet(node::getExecutionMode); } @Override public TestDescriptor getTestDescriptor() { return testDescriptor; } @Override public String toString() { return "NodeTestTask [" + testDescriptor + "]"; } void setParentContext(@Nullable C parentContext) { this.parentContext = parentContext; } @Override public void execute() { try { throwableCollector = taskContext.throwableCollectorFactory().create(); if (!taskContext.cancellationToken().isCancellationRequested()) { prepare(); } if (throwableCollector.isEmpty()) { throwableCollector.execute(() -> skipResult = checkWhetherSkipped()); } if (throwableCollector.isEmpty() && !requiredSkipResult().isSkipped()) { executeRecursively(); } if (context != null) { cleanUp(); } reportCompletion(); } finally { // Ensure that the 'interrupted status' flag for the current thread // is cleared for reuse of the thread in subsequent task executions. // See https://github.com/junit-team/junit-framework/issues/1688 if (Thread.interrupted()) { logger.debug(() -> """ Execution of TestDescriptor with display name [%s] \ and unique ID [%s] failed to clear the 'interrupted status' flag for the \ current thread. JUnit has cleared the flag, but you may wish to investigate \ why the flag was not cleared by user code.""".formatted(this.testDescriptor.getDisplayName(), this.testDescriptor.getUniqueId())); } finalizer.run(); } // Clear reference to context to allow it to be garbage collected. // See https://github.com/junit-team/junit-framework/issues/1578 context = null; } private void prepare() { requiredThrowableCollector().execute(() -> context = node.prepare(requireNonNull(parentContext))); // Clear reference to parent context to allow it to be garbage collected. // See https://github.com/junit-team/junit-framework/issues/1578 parentContext = null; } private SkipResult checkWhetherSkipped() throws Exception { return taskContext.cancellationToken().isCancellationRequested() // ? CANCELLED_SKIP_RESULT // : node.shouldBeSkipped(requiredContext()); } private void executeRecursively() { taskContext.listener().executionStarted(testDescriptor); started = true; var throwableCollector = requiredThrowableCollector(); throwableCollector.execute(() -> { node.around(requiredContext(), ctx -> { context = ctx; throwableCollector.execute(() -> { // @formatter:off List> children = testDescriptor.getChildren().stream() .map(descriptor -> new NodeTestTask(taskContext, descriptor)) .collect(toCollection(ArrayList::new)); // @formatter:on context = node.before(requiredContext()); final DynamicTestExecutor dynamicTestExecutor = new DefaultDynamicTestExecutor(); context = node.execute(requiredContext(), dynamicTestExecutor); if (!children.isEmpty()) { children.forEach(child -> child.setParentContext(context)); taskContext.executorService().invokeAll(children); } throwableCollector.execute(dynamicTestExecutor::awaitFinished); }); throwableCollector.execute(() -> node.after(requiredContext())); }); }); } private void cleanUp() { requiredThrowableCollector().execute(() -> node.cleanUp(requiredContext())); } private void reportCompletion() { var throwableCollector = requiredThrowableCollector(); if (throwableCollector.isEmpty() && requiredSkipResult().isSkipped()) { var skipResult = requiredSkipResult(); try { node.nodeSkipped(requireNonNullElse(context, parentContext), testDescriptor, skipResult); } catch (Throwable throwable) { UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); logger.debug(throwable, () -> "Failed to invoke nodeSkipped() on Node %s".formatted(testDescriptor.getUniqueId())); } taskContext.listener().executionSkipped(testDescriptor, skipResult.getReason().orElse("")); return; } if (!started) { // Call executionStarted first to comply with the contract of EngineExecutionListener. taskContext.listener().executionStarted(testDescriptor); } try { node.nodeFinished(requiredContext(), testDescriptor, throwableCollector.toTestExecutionResult()); } catch (Throwable throwable) { UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); logger.debug(throwable, () -> "Failed to invoke nodeFinished() on Node %s".formatted(testDescriptor.getUniqueId())); } taskContext.listener().executionFinished(testDescriptor, throwableCollector.toTestExecutionResult()); this.throwableCollector = null; } private C requiredContext() { return requireNonNull(context); } private SkipResult requiredSkipResult() { return requireNonNull(skipResult); } private ThrowableCollector requiredThrowableCollector() { return requireNonNull(throwableCollector); } private class DefaultDynamicTestExecutor implements DynamicTestExecutor { private final Map unfinishedTasks = new ConcurrentHashMap<>(); @Override @SuppressWarnings("FutureReturnValueIgnored") public void execute(TestDescriptor testDescriptor) { execute(testDescriptor, taskContext.listener()); } @Override public Future execute(TestDescriptor testDescriptor, EngineExecutionListener executionListener) { Preconditions.notNull(testDescriptor, "testDescriptor must not be null"); Preconditions.notNull(executionListener, "executionListener must not be null"); executionListener.dynamicTestRegistered(testDescriptor); Set exclusiveResources = NodeUtils.asNode(testDescriptor).getExclusiveResources(); if (!exclusiveResources.isEmpty()) { executionListener.executionStarted(testDescriptor); String message = "Dynamic test descriptors must not declare exclusive resources: " + exclusiveResources; executionListener.executionFinished(testDescriptor, failed(new JUnitException(message))); return completedFuture(null); } else { UniqueId uniqueId = testDescriptor.getUniqueId(); NodeTestTask nodeTestTask = new NodeTestTask<>(taskContext.withListener(executionListener), testDescriptor, () -> unfinishedTasks.remove(uniqueId)); nodeTestTask.setParentContext(context); unfinishedTasks.put(uniqueId, DynamicTaskState.unscheduled()); var future = taskContext.executorService().submit(nodeTestTask); unfinishedTasks.computeIfPresent(uniqueId, (__, state) -> DynamicTaskState.scheduled(future)); return future; } } @Override public void awaitFinished() throws InterruptedException { for (DynamicTaskState state : unfinishedTasks.values()) { try { state.awaitFinished(); } catch (CancellationException ignore) { // Futures returned by execute() may have been cancelled } catch (ExecutionException e) { throw ExceptionUtils.throwAsUncheckedException(requireNonNullElse(e.getCause(), e)); } } } } @FunctionalInterface private interface DynamicTaskState { DynamicTaskState UNSCHEDULED = () -> { }; static DynamicTaskState unscheduled() { return UNSCHEDULED; } static DynamicTaskState scheduled(Future<@Nullable Void> future) { return future::get; } void awaitFinished() throws CancellationException, ExecutionException, InterruptedException; } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTaskContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.EngineExecutionListener; /** * @since 1.3.1 */ record NodeTestTaskContext(EngineExecutionListener listener, HierarchicalTestExecutorService executorService, ThrowableCollector.Factory throwableCollectorFactory, NodeExecutionAdvisor executionAdvisor, CancellationToken cancellationToken) { NodeTestTaskContext withListener(EngineExecutionListener listener) { if (this.listener == listener) { return this; } return new NodeTestTaskContext(listener, executorService, throwableCollectorFactory, executionAdvisor, cancellationToken); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTreeWalker.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_READ; import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_READ_WRITE; import static org.junit.platform.engine.support.hierarchical.Node.ExecutionMode.SAME_THREAD; import static org.junit.platform.engine.support.hierarchical.NodeUtils.asNode; import java.util.HashSet; import java.util.Set; import java.util.function.Consumer; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.TestDescriptor; /** * @since 1.3 */ class NodeTreeWalker { private final LockManager lockManager; private final ResourceLock globalReadLock; private final ResourceLock globalReadWriteLock; NodeTreeWalker() { this(new LockManager()); } NodeTreeWalker(LockManager lockManager) { this.lockManager = lockManager; this.globalReadLock = lockManager.getLockForResource(GLOBAL_READ); this.globalReadWriteLock = lockManager.getLockForResource(GLOBAL_READ_WRITE); } NodeExecutionAdvisor walk(TestDescriptor rootDescriptor) { Preconditions.condition(getExclusiveResources(rootDescriptor).isEmpty(), "Engine descriptor must not declare exclusive resources"); NodeExecutionAdvisor advisor = new NodeExecutionAdvisor(); rootDescriptor.getChildren().forEach(child -> walk(nullUnlessRequiresGlobalReadLock(child), child, advisor)); return advisor; } private void walk(@Nullable TestDescriptor globalLockDescriptor, TestDescriptor testDescriptor, NodeExecutionAdvisor advisor) { if (globalLockDescriptor != null && advisor.getResourceLock(globalLockDescriptor) == globalReadWriteLock) { // Global read-write lock is already being enforced, so no additional locks are needed return; } Set exclusiveResources = getExclusiveResources(testDescriptor); if (exclusiveResources.isEmpty()) { if (globalLockDescriptor != null && globalLockDescriptor.equals(testDescriptor)) { advisor.useResourceLock(globalLockDescriptor, globalReadLock); } testDescriptor.getChildren().forEach(child -> { var newGlobalLockDescriptor = globalLockDescriptor == null // ? nullUnlessRequiresGlobalReadLock(child) // : globalLockDescriptor; walk(newGlobalLockDescriptor, child, advisor); }); } else { Preconditions.notNull(globalLockDescriptor, () -> "Node requiring exclusive resources must also require global read lock: " + testDescriptor); Set allResources = new HashSet<>(exclusiveResources); if (isReadOnly(allResources)) { doForChildrenRecursively(testDescriptor, child -> allResources.addAll(getExclusiveResources(child))); if (!isReadOnly(allResources)) { forceDescendantExecutionModeRecursively(advisor, testDescriptor); } } else { advisor.forceDescendantExecutionMode(testDescriptor, SAME_THREAD); doForChildrenRecursively(testDescriptor, child -> { allResources.addAll(getExclusiveResources(child)); advisor.forceDescendantExecutionMode(child, SAME_THREAD); }); } if (allResources.contains(GLOBAL_READ_WRITE)) { advisor.forceDescendantExecutionMode(globalLockDescriptor, SAME_THREAD); doForChildrenRecursively(globalLockDescriptor, child -> { advisor.forceDescendantExecutionMode(child, SAME_THREAD); // Remove any locks that may have been set for siblings or their descendants advisor.removeResourceLock(child); }); advisor.useResourceLock(globalLockDescriptor, globalReadWriteLock); } else { if (globalLockDescriptor.equals(testDescriptor)) { allResources.add(GLOBAL_READ); } else { allResources.remove(GLOBAL_READ); } advisor.useResourceLock(testDescriptor, lockManager.getLockForResources(allResources)); } } } private void forceDescendantExecutionModeRecursively(NodeExecutionAdvisor advisor, TestDescriptor testDescriptor) { advisor.forceDescendantExecutionMode(testDescriptor, SAME_THREAD); doForChildrenRecursively(testDescriptor, child -> advisor.forceDescendantExecutionMode(child, SAME_THREAD)); } private boolean isReadOnly(Set exclusiveResources) { return exclusiveResources.stream().allMatch(it -> it.getLockMode() == ExclusiveResource.LockMode.READ); } private Set getExclusiveResources(TestDescriptor testDescriptor) { return asNode(testDescriptor).getExclusiveResources(); } private static @Nullable TestDescriptor nullUnlessRequiresGlobalReadLock(TestDescriptor testDescriptor) { return asNode(testDescriptor).isGlobalReadLockRequired() ? testDescriptor : null; } private void doForChildrenRecursively(TestDescriptor parent, Consumer consumer) { parent.getChildren().forEach(child -> { consumer.accept(child); doForChildrenRecursively(child, consumer); }); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import org.junit.platform.engine.TestDescriptor; /** * @since 1.3.1 */ final class NodeUtils { private NodeUtils() { /* no-op */ } @SuppressWarnings({ "unchecked", "rawtypes" }) static Node asNode(TestDescriptor testDescriptor) { return (testDescriptor instanceof Node node ? node : noOpNode); } @SuppressWarnings("rawtypes") private static final Node noOpNode = new Node() { }; } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NopLock.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static java.util.Collections.emptyList; import java.util.List; import org.junit.platform.commons.util.ToStringBuilder; /** * No-op {@link ResourceLock} implementation. * * @since 1.3 */ class NopLock implements ResourceLock { static final ResourceLock INSTANCE = new NopLock(); private NopLock() { } @Override public List getResources() { return emptyList(); } @Override public boolean tryAcquire() { return true; } @Override public ResourceLock acquire() { return this; } @Override public void release() { // nothing to do } @Override public boolean isExclusive() { return false; } @Override public String toString() { return new ToStringBuilder(this).toString(); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/OpenTest4JAwareThrowableCollector.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static org.apiguardian.api.API.Status.MAINTAINED; import org.apiguardian.api.API; import org.opentest4j.TestAbortedException; /** * Specialization of {@link ThrowableCollector} that treats instances of * {@link TestAbortedException} as aborting. * * @since 1.3 * @see ThrowableCollector */ @API(status = MAINTAINED, since = "1.3") public class OpenTest4JAwareThrowableCollector extends ThrowableCollector { public OpenTest4JAwareThrowableCollector() { super(TestAbortedException.class::isInstance); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfiguration.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static org.apiguardian.api.API.Status.STABLE; import java.util.concurrent.ForkJoinPool; import java.util.function.Predicate; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; /** * Configuration to use for parallel test execution. * *

Instances of this class are intended to be used to configure * implementations of {@link HierarchicalTestExecutorService}. Such * implementations may use all of the properties in this class or * only a subset. * * @since 1.3 * @see ForkJoinPoolHierarchicalTestExecutorService * @see ParallelExecutionConfigurationStrategy * @see DefaultParallelExecutionConfigurationStrategy */ @API(status = STABLE, since = "1.10") public interface ParallelExecutionConfiguration { /** * Get the parallelism to be used. * * @see ForkJoinPool#getParallelism() */ int getParallelism(); /** * Get the minimum number of runnable threads to be used. */ int getMinimumRunnable(); /** * Get the maximum thread pool size to be used. */ int getMaxPoolSize(); /** * Get the core thread pool size to be used. */ int getCorePoolSize(); /** * Get the number of seconds for which inactive threads should be kept alive * before terminating them and shrinking the thread pool. */ int getKeepAliveSeconds(); /** * Get the saturate predicate to be used for the execution's {@link ForkJoinPool}. * @return the saturate predicate to be passed to the {@code ForkJoinPool} constructor; may be {@code null} * @since 1.9 * @see ForkJoinPool#ForkJoinPool(int, ForkJoinPool.ForkJoinWorkerThreadFactory, Thread.UncaughtExceptionHandler, * boolean, int, int, int, Predicate, long, java.util.concurrent.TimeUnit) */ @API(status = STABLE, since = "1.11") default @Nullable Predicate getSaturatePredicate() { return null; } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfigurationStrategy.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; import org.junit.platform.engine.ConfigurationParameters; /** * A strategy to use for configuring parallel test execution. * * @since 1.3 * @see DefaultParallelExecutionConfigurationStrategy */ @API(status = STABLE, since = "1.10") public interface ParallelExecutionConfigurationStrategy { /** * Create a configuration for parallel test execution based on the supplied * {@link ConfigurationParameters}. */ ParallelExecutionConfiguration createConfiguration(ConfigurationParameters configurationParameters); } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelHierarchicalTestExecutorServiceFactory.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import java.util.Locale; import org.apiguardian.api.API; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.support.config.PrefixedConfigurationParameters; import org.junit.platform.engine.support.hierarchical.ForkJoinPoolHierarchicalTestExecutorService.TaskEventListener; /** * Factory for {@link HierarchicalTestExecutorService} instances that support * parallel execution. * * @since 6.1 * @see ParallelExecutorServiceType * @see ForkJoinPoolHierarchicalTestExecutorService * @see WorkerThreadPoolHierarchicalTestExecutorService */ @API(status = MAINTAINED, since = "6.1") public final class ParallelHierarchicalTestExecutorServiceFactory { /** * Property name used to determine the desired * {@link ParallelExecutorServiceType ParallelExecutorServiceType}. * *

Value must be * {@link ParallelExecutorServiceType#FORK_JOIN_POOL FORK_JOIN_POOL} or * {@link ParallelExecutorServiceType#WORKER_THREAD_POOL WORKER_THREAD_POOL}, * ignoring case. */ public static final String EXECUTOR_SERVICE_PROPERTY_NAME = "executor-service"; /** * Create a new {@link HierarchicalTestExecutorService} based on the * supplied {@link ConfigurationParameters}. * *

This method is typically invoked with an instance of * {@link PrefixedConfigurationParameters} that was created with an * engine-specific prefix. * *

The {@value #EXECUTOR_SERVICE_PROPERTY_NAME} key is used to determine * which service implementation is to be used. Which other parameters are * read depends on the configured * {@link ParallelExecutionConfigurationStrategy} which is determined by the * {@value DefaultParallelExecutionConfigurationStrategy#CONFIG_STRATEGY_PROPERTY_NAME} * key. * * @see #EXECUTOR_SERVICE_PROPERTY_NAME * @see ParallelExecutorServiceType * @see ParallelExecutionConfigurationStrategy * @see PrefixedConfigurationParameters */ public static HierarchicalTestExecutorService create(ConfigurationParameters configurationParameters) { var type = configurationParameters.get(EXECUTOR_SERVICE_PROPERTY_NAME, ParallelExecutorServiceType::parse) // .orElse(ParallelExecutorServiceType.FORK_JOIN_POOL); var configuration = DefaultParallelExecutionConfigurationStrategy.toConfiguration(configurationParameters); return create(type, configuration); } /** * Create a new {@link HierarchicalTestExecutorService} based on the * supplied {@link ConfigurationParameters}. * *

The {@value #EXECUTOR_SERVICE_PROPERTY_NAME} key is ignored in favor * of the supplied {@link ParallelExecutorServiceType} parameter when * invoking this method. * * @see ParallelExecutorServiceType * @see ParallelExecutionConfigurationStrategy */ public static HierarchicalTestExecutorService create(ParallelExecutorServiceType executorServiceType, ParallelExecutionConfiguration configuration) { return switch (executorServiceType) { case FORK_JOIN_POOL -> new ForkJoinPoolHierarchicalTestExecutorService(configuration, TaskEventListener.NOOP); case WORKER_THREAD_POOL -> new WorkerThreadPoolHierarchicalTestExecutorService(configuration); }; } private ParallelHierarchicalTestExecutorServiceFactory() { } /** * Type of {@link HierarchicalTestExecutorService} that supports parallel * execution. * * @since 6.1 */ @API(status = MAINTAINED, since = "6.1") public enum ParallelExecutorServiceType { /** * Indicates that {@link ForkJoinPoolHierarchicalTestExecutorService} * should be used. */ FORK_JOIN_POOL, /** * Indicates that {@link WorkerThreadPoolHierarchicalTestExecutorService} * should be used. */ @API(status = EXPERIMENTAL, since = "6.1") WORKER_THREAD_POOL; private static ParallelExecutorServiceType parse(String value) { return valueOf(value.toUpperCase(Locale.ROOT)); } } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ResourceLock.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import java.util.List; import java.util.Optional; import org.apiguardian.api.API; /** * A lock for a one or more resources. * * @since 1.3 * @see HierarchicalTestExecutorService.TestTask#getResourceLock() */ @API(status = STABLE, since = "1.10") public interface ResourceLock extends AutoCloseable { /** * Try to acquire this resource lock, without blocking. * * @return {@code true} if the lock was acquired and {@code false} otherwise * @since 6.1 */ @API(status = EXPERIMENTAL, since = "6.1") default boolean tryAcquire() { return false; } /** * Acquire this resource lock, potentially blocking. * * @return this lock so it can easily be used in a try-with-resources * statement. * @throws InterruptedException if the calling thread is interrupted * while waiting to acquire this lock */ ResourceLock acquire() throws InterruptedException; /** * Release this resource lock. */ void release(); @Override default void close() { release(); } /** * {@return the exclusive resources this lock represents} */ List getResources(); /** * {@return whether this lock requires exclusiveness} */ boolean isExclusive(); /** * {@return whether the given lock is compatible with this lock} * @param other the other lock to check for compatibility */ default boolean isCompatible(ResourceLock other) { List ownResources = this.getResources(); List otherResources = other.getResources(); if (ownResources.isEmpty() || otherResources.isEmpty()) { return true; } // Whenever there's a READ_WRITE lock, it's incompatible with any other lock // because we guarantee that all children will have exclusive access to the // resource in question. In practice, whenever a READ_WRITE lock is present, // NodeTreeWalker will force all children to run in the same thread so that // it should never attempt to steal work from another thread, and we shouldn't // actually reach this point. // The global read lock (which is always on direct children of the engine node) // needs special treatment so that it is compatible with the first write lock // (which may be on a test method). boolean isGlobalReadLock = ownResources.size() == 1 && ExclusiveResource.GLOBAL_READ.equals(ownResources.get(0)); if ((!isGlobalReadLock && other.isExclusive()) || this.isExclusive()) { return false; } Optional potentiallyDeadlockCausingAdditionalResource = otherResources.stream() // .filter(resource -> !ownResources.contains(resource)) // .findFirst() // .filter(resource -> ExclusiveResource.COMPARATOR.compare(resource, ownResources.get(ownResources.size() - 1)) < 0); return potentiallyDeadlockCausingAdditionalResource.isEmpty(); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SameThreadHierarchicalTestExecutorService.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static java.util.concurrent.CompletableFuture.completedFuture; import static org.apiguardian.api.API.Status.STABLE; import java.util.List; import java.util.concurrent.Future; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; /** * A simple {@linkplain HierarchicalTestExecutorService executor service} that * executes all {@linkplain TestTask test tasks} in the caller's thread. * * @since 1.3 */ @API(status = STABLE, since = "1.10") public class SameThreadHierarchicalTestExecutorService implements HierarchicalTestExecutorService { public SameThreadHierarchicalTestExecutorService() { } @Override public Future<@Nullable Void> submit(TestTask testTask) { testTask.execute(); return completedFuture(null); } @Override public void invokeAll(List tasks) { tasks.forEach(TestTask::execute); } @Override public void close() { // nothing to do } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SingleLock.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import java.util.List; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.locks.Lock; import org.junit.platform.commons.util.ToStringBuilder; /** * @since 1.3 */ class SingleLock implements ResourceLock { private final List resources; private final Lock lock; SingleLock(ExclusiveResource resource, Lock lock) { this.resources = List.of(resource); this.lock = lock; } @Override public List getResources() { return resources; } // for tests only Lock getLock() { return this.lock; } @Override public boolean tryAcquire() { return this.lock.tryLock(); } @Override public ResourceLock acquire() throws InterruptedException { ForkJoinPool.managedBlock(new SingleLockManagedBlocker()); return this; } @Override public void release() { this.lock.unlock(); } @Override public boolean isExclusive() { return resources.get(0).getLockMode() == ExclusiveResource.LockMode.READ_WRITE; } @Override public String toString() { return new ToStringBuilder(this) // .append("resource", getOnlyElement(resources)) // .toString(); } private class SingleLockManagedBlocker implements ForkJoinPool.ManagedBlocker { private volatile boolean acquired; @Override public boolean block() throws InterruptedException { if (!this.acquired) { SingleLock.this.lock.lockInterruptibly(); this.acquired = true; } return true; } @Override public boolean isReleasable() { return this.acquired || (this.acquired = SingleLock.this.lock.tryLock()); } } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ThrowableCollector.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.junit.platform.engine.TestExecutionResult.aborted; import static org.junit.platform.engine.TestExecutionResult.failed; import static org.junit.platform.engine.TestExecutionResult.successful; import java.util.function.Predicate; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.UnrecoverableExceptions; import org.junit.platform.engine.TestExecutionResult; /** * Simple component that can be used to collect one or more instances of * {@link Throwable}. * *

This class distinguishes between {@code Throwables} that abort * and those that fail test execution. The latter take precedence over * the former, i.e. if both types of {@code Throwables} were collected, the ones * that abort execution are reported as * {@linkplain Throwable#addSuppressed(Throwable) suppressed} {@code Throwables} * of the first {@code Throwable} that failed execution. * * @since 1.3 * @see OpenTest4JAwareThrowableCollector */ @API(status = MAINTAINED, since = "1.3") public class ThrowableCollector { private final Predicate abortedExecutionPredicate; private @Nullable Throwable throwable; /** * Create a new {@code ThrowableCollector} that uses the supplied * {@link Predicate} to determine whether a {@link Throwable} * aborted or failed execution. * * @param abortedExecutionPredicate the predicate used to decide whether a * {@code Throwable} aborted execution; never {@code null}. */ public ThrowableCollector(Predicate abortedExecutionPredicate) { this.abortedExecutionPredicate = Preconditions.notNull(abortedExecutionPredicate, "abortedExecutionPredicate must not be null"); } /** * Execute the supplied {@link Executable} and collect any {@link Throwable} * thrown during the execution. * *

If the {@code Executable} throws an unrecoverable exception * — for example, an {@link OutOfMemoryError} — this method will * rethrow it. * * @param executable the {@code Executable} to execute * @see #assertEmpty() */ public void execute(Executable executable) { try { executable.execute(); } catch (Throwable t) { UnrecoverableExceptions.rethrowIfUnrecoverable(t); add(t); } } /** * Add the supplied {@link Throwable} to this {@code ThrowableCollector}. * * @param t the {@code Throwable} to add * @see #execute(Executable) * @see #assertEmpty() */ private void add(Throwable t) { Preconditions.notNull(t, "Throwable must not be null"); if (this.throwable == null) { this.throwable = t; } else if (hasAbortedExecution(this.throwable) && !hasAbortedExecution(t)) { t.addSuppressed(this.throwable); this.throwable = t; } else if (throwable != t) { // Jupiter does not throw the same Throwable from Node.after() anymore but other engines might this.throwable.addSuppressed(t); } } /** * Get the first {@link Throwable} collected by this * {@code ThrowableCollector}. * *

If this collector is not empty, the first collected {@code Throwable} * will be returned with any additional {@code Throwables} * {@linkplain Throwable#addSuppressed(Throwable) suppressed} in the * first {@code Throwable}. * *

If the first collected {@code Throwable} aborted execution * and at least one later collected {@code Throwable} failed * execution, the first failing {@code Throwable} will be returned * with the previous aborting and any additional {@code Throwables} * {@linkplain Throwable#addSuppressed(Throwable) suppressed} inside. * * @return the first collected {@code Throwable} or {@code null} if this * {@code ThrowableCollector} is empty * @see #isEmpty() * @see #assertEmpty() */ public @Nullable Throwable getThrowable() { return this.throwable; } /** * Determine if this {@code ThrowableCollector} is empty (i.e., * has not collected any {@code Throwables}). */ public boolean isEmpty() { return (this.throwable == null); } /** * Determine if this {@code ThrowableCollector} is not empty (i.e., * has collected at least one {@code Throwable}). */ public boolean isNotEmpty() { return (this.throwable != null); } /** * Assert that this {@code ThrowableCollector} is empty (i.e., * has not collected any {@code Throwables}). * *

If this collector is not empty, the first collected {@code Throwable} * will be thrown with any additional {@code Throwables} * {@linkplain Throwable#addSuppressed(Throwable) suppressed} in the * first {@code Throwable}. Note, however, that the {@code Throwable} * will not be wrapped. Rather, it will be * {@linkplain ExceptionUtils#throwAsUncheckedException masked} * as an unchecked exception. * * @see #getThrowable() * @see ExceptionUtils#throwAsUncheckedException(Throwable) */ public void assertEmpty() { if (this.throwable != null) { throw ExceptionUtils.throwAsUncheckedException(this.throwable); } } /** * Convert the collected {@link Throwable Throwables} into a {@link TestExecutionResult}. * * @return {@linkplain TestExecutionResult#aborted aborted} if the collected * {@code Throwable} aborted execution; * {@linkplain TestExecutionResult#failed failed} if it failed * execution; and {@linkplain TestExecutionResult#successful successful} * otherwise * @since 1.6 */ @API(status = MAINTAINED, since = "1.6") public TestExecutionResult toTestExecutionResult() { if (this.throwable == null) { return successful(); } if (hasAbortedExecution(this.throwable)) { return aborted(this.throwable); } return failed(this.throwable); } private boolean hasAbortedExecution(Throwable t) { return this.abortedExecutionPredicate.test(t); } /** * Functional interface for an executable block of code that may throw a * {@link Throwable}. */ @FunctionalInterface public interface Executable { /** * Execute this executable, potentially throwing a {@link Throwable} * that signals abortion or failure. */ void execute() throws Throwable; } /** * Factory for {@code ThrowableCollector} instances. */ public interface Factory { /** * Create a new instance of a {@code ThrowableCollector}. */ ThrowableCollector create(); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/WorkerThreadPoolHierarchicalTestExecutorService.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static java.util.Comparator.comparing; import static java.util.Objects.requireNonNull; import static java.util.concurrent.CompletableFuture.completedFuture; import static java.util.concurrent.TimeUnit.SECONDS; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.junit.platform.commons.util.ExceptionUtils.throwAsUncheckedException; import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_READ_WRITE; import static org.junit.platform.engine.support.hierarchical.Node.ExecutionMode.SAME_THREAD; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.Deque; import java.util.EnumMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.Semaphore; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BooleanSupplier; import java.util.function.Consumer; import java.util.function.Function; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.ClassLoaderUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.hierarchical.ParallelHierarchicalTestExecutorServiceFactory.ParallelExecutorServiceType; /** * An {@linkplain HierarchicalTestExecutorService executor service} based on a * regular thread pool that executes {@linkplain TestTask test tasks} with the * configured parallelism. * * @since 6.1 * @see ParallelHierarchicalTestExecutorServiceFactory * @see ParallelExecutorServiceType#WORKER_THREAD_POOL * @see DefaultParallelExecutionConfigurationStrategy */ @API(status = EXPERIMENTAL, since = "6.1") public final class WorkerThreadPoolHierarchicalTestExecutorService implements HierarchicalTestExecutorService { /* This implementation is based on a regular thread pool and a work queue shared among all worker threads. Each worker thread scans the shared work queue for tasks to run. Since the tasks represent hierarchically structured tests, container tasks will call `submit(TestTask)` or `invokeAll(List)` for their children, recursively. To maintain the desired parallelism -- regardless whether the user code performs any blocking operations -- a fixed number of worker leases is configured. Whenever a task is submitted to the work queue to be executed concurrently, an attempt is made to acquire a worker lease. If a worker lease was acquired, a worker thread is started. Each worker thread attempts to "steal" queue entries for its children and execute them itself prior to waiting for its children to finish. To optimize CPU utilization, whenever a worker thread does need to block, it temporarily gives up its worker lease and attempts to start another worker thread to compensate for the reduced `parallelism`. If the max pool size does not permit starting another thread, the attempt is ignored in case there are still other active worker threads. The same happens in case a resource lock needs to be acquired. To minimize the number of idle workers, worker threads will prefer to steal top level tasks, while working through their own task hierarchy in a depth first fashion. Furthermore, child tasks with execution mode `CONCURRENT` are submitted to the shared queue prior to executing those with execution mode `SAME_THREAD` directly. */ private static final Logger logger = LoggerFactory.getLogger(WorkerThreadPoolHierarchicalTestExecutorService.class); private final WorkQueue workQueue = new WorkQueue(); private final ExecutorService executor; private final int parallelism; private final WorkerLeaseManager workerLeaseManager; /** * Create a new {@code WorkerThreadPoolHierarchicalTestExecutorService} * based on the supplied {@link ParallelExecutionConfiguration}. * *

The following attributes of the supplied configuration are applied to * the thread pool used by this executor service: * *

    *
  • {@link ParallelExecutionConfiguration#getParallelism()}
  • *
  • {@link ParallelExecutionConfiguration#getCorePoolSize()}
  • *
  • {@link ParallelExecutionConfiguration#getMaxPoolSize()}
  • *
  • {@link ParallelExecutionConfiguration#getKeepAliveSeconds()}
  • *
* *

The remaining attributes, such as * {@link ParallelExecutionConfiguration#getMinimumRunnable()} and * {@link ParallelExecutionConfiguration#getSaturatePredicate()}, are * ignored. * * @see ParallelHierarchicalTestExecutorServiceFactory#create(ConfigurationParameters) */ WorkerThreadPoolHierarchicalTestExecutorService(ParallelExecutionConfiguration configuration) { this(configuration, ClassLoaderUtils.getDefaultClassLoader()); } // package-private for testing WorkerThreadPoolHierarchicalTestExecutorService(ParallelExecutionConfiguration configuration, ClassLoader classLoader) { ThreadFactory threadFactory = new WorkerThreadFactory(classLoader); parallelism = configuration.getParallelism(); workerLeaseManager = new WorkerLeaseManager(parallelism, this::maybeStartWorker); var rejectedExecutionHandler = new LeaseAwareRejectedExecutionHandler(workerLeaseManager); executor = new ThreadPoolExecutor(configuration.getCorePoolSize(), configuration.getMaxPoolSize(), configuration.getKeepAliveSeconds(), SECONDS, new SynchronousQueue<>(), threadFactory, rejectedExecutionHandler); logger.trace(() -> "initialized thread pool for parallelism of " + configuration.getParallelism()); } @Override public void close() { logger.trace(() -> "shutting down thread pool"); executor.shutdownNow(); } @Override public Future<@Nullable Void> submit(TestTask testTask) { logger.trace(() -> "submit: " + testTask); var workerThread = WorkerThread.get(); if (workerThread == null) { return enqueue(testTask, 0).future(); } if (testTask.getExecutionMode() == SAME_THREAD) { workerThread.executeTask(testTask); return completedFuture(null); } var entry = enqueue(testTask, workerThread.nextChildIndex()); workerThread.trackSubmittedChild(entry); return new WorkStealingFuture(entry); } /** * {@inheritDoc} * * @implNote This method must be called from within a worker thread that * belongs to this executor. */ @Override public void invokeAll(List testTasks) { logger.trace(() -> "invokeAll: " + testTasks); var workerThread = WorkerThread.get(); Preconditions.condition(workerThread != null && workerThread.executor() == this, "invokeAll() must be called from a worker thread that belongs to this executor"); workerThread.invokeAll(testTasks); } private WorkQueue.Entry enqueue(TestTask testTask, int index) { var entry = workQueue.add(testTask, index); maybeStartWorker(); return entry; } private void forkAll(Collection entries) { if (entries.isEmpty()) { return; } workQueue.addAll(entries); // start at most (parallelism - 1) new workers as this method is called from a worker thread holding a lease for (int i = 0; i < Math.min(parallelism - 1, entries.size()); i++) { maybeStartWorker(); } } private void maybeStartWorker() { maybeStartWorker(() -> false); } private void maybeStartWorker(BooleanSupplier doneCondition) { if (executor.isShutdown() || workQueue.isEmpty() || doneCondition.getAsBoolean()) { return; } var workerLease = workerLeaseManager.tryAcquire(); if (workerLease == null) { return; } executor.execute(new RunLeaseAwareWorker(workerLease, doneCondition)); } private record RunLeaseAwareWorker(WorkerLease workerLease, BooleanSupplier parentDoneCondition) implements Runnable { @Override public void run() { logger.trace(() -> "starting worker"); try { WorkerThread.getOrThrow().processQueueEntries(workerLease, parentDoneCondition); } finally { workerLease.release(parentDoneCondition); logger.trace(() -> "stopping worker"); } } } private class WorkerThreadFactory implements ThreadFactory { private static final AtomicInteger POOL_NUMBER = new AtomicInteger(1); private final AtomicInteger threadNumber = new AtomicInteger(1); private final int poolNumber; private final ClassLoader classLoader; WorkerThreadFactory(ClassLoader classLoader) { this.classLoader = classLoader; this.poolNumber = POOL_NUMBER.getAndIncrement(); } @Override public Thread newThread(Runnable runnable) { var thread = new WorkerThread(runnable, "junit-%d-worker-%d".formatted(poolNumber, threadNumber.getAndIncrement())); thread.setContextClassLoader(classLoader); return thread; } } private class WorkerThread extends Thread { private final Deque stateStack = new ArrayDeque<>(); @Nullable WorkerLease workerLease; WorkerThread(Runnable runnable, String name) { super(runnable, name); } static @Nullable WorkerThread get() { if (Thread.currentThread() instanceof WorkerThread workerThread) { return workerThread; } return null; } static WorkerThread getOrThrow() { var workerThread = get(); if (workerThread == null) { throw new IllegalStateException("Not on a worker thread"); } return workerThread; } WorkerThreadPoolHierarchicalTestExecutorService executor() { return WorkerThreadPoolHierarchicalTestExecutorService.this; } void processQueueEntries(WorkerLease workerLease, BooleanSupplier doneCondition) { this.workerLease = workerLease; while (!executor.isShutdown()) { if (doneCondition.getAsBoolean()) { logger.trace(() -> "yielding resource lock"); break; } if (workQueue.isEmpty()) { logger.trace(() -> "no queue entries available"); break; } processQueueEntries(); } } private void processQueueEntries() { var entriesRequiringResourceLocks = new ArrayList(); for (var entry : workQueue) { var result = tryToStealWork(entry, BlockingMode.NON_BLOCKING); if (result == WorkStealResult.EXECUTED_BY_THIS_WORKER) { // After executing a test a significant amount of time has passed. // Process the queue from the beginning return; } if (result == WorkStealResult.RESOURCE_LOCK_UNAVAILABLE) { entriesRequiringResourceLocks.add(entry); } } for (var entry : entriesRequiringResourceLocks) { var result = tryToStealWork(entry, BlockingMode.BLOCKING); if (result == WorkStealResult.EXECUTED_BY_THIS_WORKER) { return; } } } T runBlocking(BooleanSupplier doneCondition, BlockingAction blockingAction) throws InterruptedException { var workerLease = requireNonNull(this.workerLease); workerLease.release(doneCondition); try { return blockingAction.run(); } finally { try { workerLease.reacquire(); } catch (InterruptedException e) { interrupt(); } } } void invokeAll(List testTasks) { if (testTasks.isEmpty()) { return; } if (testTasks.size() == 1) { executeTask(testTasks.get(0)); return; } List isolatedTasks = new ArrayList<>(testTasks.size()); List sameThreadTasks = new ArrayList<>(testTasks.size()); var queueEntries = forkConcurrentChildren(testTasks, isolatedTasks::add, sameThreadTasks); executeAll(sameThreadTasks); var queueEntriesByResult = tryToStealWorkWithoutBlocking(queueEntries); tryToStealWorkWithBlocking(queueEntriesByResult); waitFor(queueEntriesByResult); executeAll(isolatedTasks); } private List forkConcurrentChildren(List children, Consumer isolatedTaskCollector, List sameThreadTasks) { List queueEntries = new ArrayList<>(children.size()); for (TestTask child : children) { if (requiresGlobalReadWriteLock(child)) { isolatedTaskCollector.accept(child); } else if (child.getExecutionMode() == SAME_THREAD) { sameThreadTasks.add(child); } else { queueEntries.add(new WorkQueue.Entry(child, nextChildIndex())); } } if (!queueEntries.isEmpty()) { queueEntries.sort(WorkQueue.Entry.CHILD_COMPARATOR); if (sameThreadTasks.isEmpty()) { // hold back one task for this thread var firstEntry = queueEntries.remove(0); sameThreadTasks.add(firstEntry.task); } forkAll(queueEntries); } return queueEntries; } private Map> tryToStealWorkWithoutBlocking( Iterable queueEntries) { Map> queueEntriesByResult = new EnumMap<>(WorkStealResult.class); tryToStealWork(queueEntries, BlockingMode.NON_BLOCKING, queueEntriesByResult); return queueEntriesByResult; } private void tryToStealWorkWithBlocking(Map> queueEntriesByResult) { var entriesRequiringResourceLocks = queueEntriesByResult.remove(WorkStealResult.RESOURCE_LOCK_UNAVAILABLE); if (entriesRequiringResourceLocks == null) { return; } tryToStealWork(entriesRequiringResourceLocks, BlockingMode.BLOCKING, queueEntriesByResult); } private void tryToStealWork(Iterable entries, BlockingMode blocking, Map> queueEntriesByResult) { for (var entry : entries) { var result = tryToStealWork(entry, blocking); queueEntriesByResult.computeIfAbsent(result, __ -> new ArrayList<>()).add(entry); } } private WorkStealResult tryToStealWork(WorkQueue.Entry entry, BlockingMode blockingMode) { if (entry.future.isDone()) { return WorkStealResult.EXECUTED_BY_DIFFERENT_WORKER; } var claimed = workQueue.remove(entry); if (claimed) { logger.trace(() -> "stole work: " + entry.task); var executed = executeStolenWork(entry, blockingMode); if (executed) { return WorkStealResult.EXECUTED_BY_THIS_WORKER; } workQueue.reAdd(entry); return WorkStealResult.RESOURCE_LOCK_UNAVAILABLE; } return WorkStealResult.EXECUTED_BY_DIFFERENT_WORKER; } private void waitFor(Map> queueEntriesByResult) { var children = queueEntriesByResult.get(WorkStealResult.EXECUTED_BY_DIFFERENT_WORKER); if (children == null) { return; } var future = toCombinedFuture(children); try { if (future.isDone()) { // no need to release worker lease future.join(); } else { runBlocking(future::isDone, () -> { logger.trace(() -> "blocking for forked children : %s".formatted(children)); return future.join(); }); } } catch (InterruptedException e) { currentThread().interrupt(); } } private static boolean requiresGlobalReadWriteLock(TestTask testTask) { return testTask.getResourceLock().getResources().contains(GLOBAL_READ_WRITE); } private void executeAll(List children) { if (children.isEmpty()) { return; } logger.trace(() -> "running %d children directly".formatted(children.size())); if (children.size() == 1) { executeTask(children.get(0)); return; } for (var testTask : children) { executeTask(testTask); } } private boolean executeStolenWork(WorkQueue.Entry entry, BlockingMode blockingMode) { return switch (blockingMode) { case NON_BLOCKING -> tryExecute(entry); case BLOCKING -> { execute(entry); yield true; } }; } private boolean tryExecute(WorkQueue.Entry entry) { try { var executed = tryExecuteTask(entry.task); if (executed) { entry.future.complete(null); } return executed; } catch (Throwable t) { entry.future.completeExceptionally(t); return true; } } private void execute(WorkQueue.Entry entry) { try { executeTask(entry.task); } catch (Throwable t) { entry.future.completeExceptionally(t); } finally { entry.future.complete(null); } } @SuppressWarnings("try") private void executeTask(TestTask testTask) { var executed = tryExecuteTask(testTask); if (!executed) { var resourceLock = testTask.getResourceLock(); try (var ignored = runBlocking(() -> false, () -> { logger.trace(() -> "blocking for resource lock: " + resourceLock); return resourceLock.acquire(); })) { logger.trace(() -> "acquired resource lock: " + resourceLock); doExecute(testTask); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } finally { logger.trace(() -> "released resource lock: " + resourceLock); } } } private boolean tryExecuteTask(TestTask testTask) { var resourceLock = testTask.getResourceLock(); if (resourceLock.tryAcquire()) { logger.trace(() -> "acquired resource lock: " + resourceLock); try (resourceLock) { doExecute(testTask); return true; } finally { logger.trace(() -> "released resource lock: " + resourceLock); } } else { logger.trace(() -> "failed to acquire resource lock: " + resourceLock); } return false; } private void doExecute(TestTask testTask) { logger.trace(() -> "executing: " + testTask); stateStack.push(new State()); try { testTask.execute(); } finally { stateStack.pop(); logger.trace(() -> "finished executing: " + testTask); } } private static CompletableFuture toCombinedFuture(List entries) { if (entries.size() == 1) { return entries.get(0).future(); } var futures = entries.stream().map(WorkQueue.Entry::future).toArray(CompletableFuture[]::new); return CompletableFuture.allOf(futures); } private int nextChildIndex() { return stateStack.element().nextChildIndex(); } private void trackSubmittedChild(WorkQueue.Entry entry) { stateStack.element().trackSubmittedChild(entry); } private void tryToStealWorkFromSubmittedChildren() { var currentState = stateStack.element(); var currentSubmittedChildren = currentState.submittedChildren; if (currentSubmittedChildren == null || currentSubmittedChildren.isEmpty()) { return; } currentSubmittedChildren.sort(WorkQueue.Entry.CHILD_COMPARATOR); var iterator = currentSubmittedChildren.iterator(); while (iterator.hasNext()) { WorkQueue.Entry entry = iterator.next(); var result = tryToStealWork(entry, BlockingMode.NON_BLOCKING); if (result.isExecuted()) { iterator.remove(); } } currentState.clearIfEmpty(); } private static class State { private int nextChildIndex = 0; @Nullable private List submittedChildren; private void trackSubmittedChild(WorkQueue.Entry entry) { if (submittedChildren == null) { submittedChildren = new ArrayList<>(); } submittedChildren.add(entry); } private void clearIfEmpty() { if (submittedChildren != null && submittedChildren.isEmpty()) { submittedChildren = null; } } private int nextChildIndex() { return nextChildIndex++; } } private enum WorkStealResult { EXECUTED_BY_DIFFERENT_WORKER, RESOURCE_LOCK_UNAVAILABLE, EXECUTED_BY_THIS_WORKER; private boolean isExecuted() { return this != RESOURCE_LOCK_UNAVAILABLE; } } private interface BlockingAction { T run() throws InterruptedException; } } private static class WorkStealingFuture extends BlockingAwareFuture<@Nullable Void> { private final WorkQueue.Entry entry; WorkStealingFuture(WorkQueue.Entry entry) { super(entry.future); this.entry = entry; } @Override protected @Nullable Void handle(Callable<@Nullable Void> callable) throws Exception { var workerThread = WorkerThread.get(); if (workerThread == null || entry.future.isDone()) { return callable.call(); } workerThread.tryToStealWork(entry, BlockingMode.BLOCKING); if (entry.future.isDone()) { return callable.call(); } workerThread.tryToStealWorkFromSubmittedChildren(); if (entry.future.isDone()) { return callable.call(); } logger.trace(() -> "blocking for child task: " + entry.task); return workerThread.runBlocking(entry.future::isDone, () -> { try { return callable.call(); } catch (Exception ex) { throw throwAsUncheckedException(ex); } }); } } private enum BlockingMode { NON_BLOCKING, BLOCKING } private static class WorkQueue implements Iterable { private final Set queue = new ConcurrentSkipListSet<>(Entry.QUEUE_COMPARATOR); Entry add(TestTask task, int index) { Entry entry = new Entry(task, index); logger.trace(() -> "forking: " + entry.task); return doAdd(entry); } void addAll(Collection entries) { entries.forEach(this::doAdd); } void reAdd(Entry entry) { logger.trace(() -> "re-enqueuing: " + entry.task); doAdd(entry); } private Entry doAdd(Entry entry) { var added = queue.add(entry); if (!added) { throw new IllegalStateException("Could not add entry to the queue for task: " + entry.task); } return entry; } boolean remove(Entry entry) { return queue.remove(entry); } boolean isEmpty() { return queue.isEmpty(); } @Override public Iterator iterator() { return queue.iterator(); } private static final class Entry { private static final Comparator QUEUE_COMPARATOR = comparing(Entry::level).reversed() // .thenComparing(Entry::isContainer) // tests before containers .thenComparing(Entry::index) // .thenComparing(Entry::uniqueId, new SameLengthUniqueIdComparator()); private static final Comparator CHILD_COMPARATOR = comparing(Entry::isContainer).reversed() // containers before tests .thenComparing(Entry::index); private final TestTask task; private final CompletableFuture<@Nullable Void> future; private final int index; @SuppressWarnings("FutureReturnValueIgnored") Entry(TestTask task, int index) { this.future = new CompletableFuture<>(); this.future.whenComplete((__, t) -> { if (t == null) { logger.trace(() -> "completed normally: " + task); } else { logger.trace(t, () -> "completed exceptionally: " + task); } }); this.task = task; this.index = index; } private int index() { return this.index; } private int level() { return uniqueId().getSegments().size(); } private boolean isContainer() { return task.getTestDescriptor().isContainer(); } private UniqueId uniqueId() { return task.getTestDescriptor().getUniqueId(); } CompletableFuture<@Nullable Void> future() { return future; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj == null || obj.getClass() != this.getClass()) { return false; } var that = (Entry) obj; return Objects.equals(this.uniqueId(), that.uniqueId()) && this.index == that.index; } @Override public int hashCode() { return Objects.hash(uniqueId(), index); } @Override public String toString() { return new ToStringBuilder(this) // .append("task", task) // .append("index", index) // .toString(); } private static class SameLengthUniqueIdComparator implements Comparator { @Override public int compare(UniqueId a, UniqueId b) { var aIterator = a.getSegments().iterator(); var bIterator = b.getSegments().iterator(); // ids have the same length while (aIterator.hasNext()) { var aCurrent = aIterator.next(); var bCurrent = bIterator.next(); int result = compareBy(aCurrent, bCurrent); if (result != 0) { return result; } } return 0; } private static int compareBy(UniqueId.Segment a, UniqueId.Segment b) { int result = a.getType().compareTo(b.getType()); if (result != 0) { return result; } return a.getValue().compareTo(b.getValue()); } } } } static class WorkerLeaseManager { private final int parallelism; private final Semaphore semaphore; private final Consumer compensation; WorkerLeaseManager(int parallelism, Consumer onRelease) { this.parallelism = parallelism; this.semaphore = new Semaphore(parallelism); this.compensation = onRelease; } @Nullable WorkerLease tryAcquire() { boolean acquired = semaphore.tryAcquire(); if (acquired) { logger.trace(() -> "acquired worker lease for new worker (available: %d)".formatted( semaphore.availablePermits())); return new WorkerLease(this::release); } return null; } private ReacquisitionToken release(BooleanSupplier doneCondition) { semaphore.release(); logger.trace(() -> "release worker lease (available: %d)".formatted(semaphore.availablePermits())); compensation.accept(doneCondition); return new ReacquisitionToken(); } private class ReacquisitionToken { private boolean used = false; void reacquire() throws InterruptedException { Preconditions.condition(!used, "Lease was already reacquired"); used = true; semaphore.acquire(); logger.trace(() -> "reacquired worker lease (available: %d)".formatted(semaphore.availablePermits())); } } @Override public String toString() { return new ToStringBuilder(this) // .append("parallelism", parallelism) // .append("semaphore", semaphore) // .toString(); } } static class WorkerLease { private final Function releaseAction; private WorkerLeaseManager.@Nullable ReacquisitionToken reacquisitionToken; WorkerLease(Function releaseAction) { this.releaseAction = releaseAction; } public void release() { release(() -> true); } public void release(BooleanSupplier doneCondition) { if (reacquisitionToken == null) { reacquisitionToken = releaseAction.apply(doneCondition); } } void reacquire() throws InterruptedException { Preconditions.notNull(reacquisitionToken, "Cannot reacquire an unreleased WorkerLease"); reacquisitionToken.reacquire(); reacquisitionToken = null; } } private record LeaseAwareRejectedExecutionHandler(WorkerLeaseManager workerLeaseManager) implements RejectedExecutionHandler { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { if (r instanceof RunLeaseAwareWorker worker) { worker.workerLease.release(); } } } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Support classes and base implementation for any * {@link org.junit.platform.engine.TestEngine} that wishes to organize test suites * hierarchically based on the * {@link org.junit.platform.engine.support.hierarchical.Node} abstraction. */ @NullMarked package org.junit.platform.engine.support.hierarchical; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/Namespace.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.store; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.apiguardian.api.API; import org.junit.platform.commons.util.Preconditions; /** * A {@code Namespace} is used to provide a scope for data saved by * extensions within a {@link NamespacedHierarchicalStore}. * *

Storing data in custom namespaces allows extensions to avoid accidentally * mixing data between extensions or across different invocations within the * lifecycle of a single extension. */ @API(status = EXPERIMENTAL, since = "6.0") public final class Namespace { /** * The default, global namespace which allows access to stored data from * all extensions. */ public static final Namespace GLOBAL = Namespace.create(new Object()); /** * Create a namespace which restricts access to data to all extensions * which use the same sequence of {@code parts} for creating a namespace. * *

The order of the {@code parts} is significant. * *

Internally the {@code parts} are compared using {@link Object#equals(Object)}. */ public static Namespace create(Object... parts) { Preconditions.notEmpty(parts, "parts array must not be null or empty"); Preconditions.containsNoNullElements(parts, "individual parts must not be null"); return new Namespace(List.of(parts)); } /** * Create a namespace which restricts access to data to all extensions * which use the same sequence of {@code objects} for creating a namespace. * *

The order of the {@code objects} is significant. * *

Internally the {@code objects} are compared using {@link Object#equals(Object)}. */ public static Namespace create(List objects) { Preconditions.notEmpty(objects, "objects list must not be null or empty"); Preconditions.containsNoNullElements(objects, "individual objects must not be null"); return new Namespace(objects); } private final List parts; private Namespace(List parts) { this.parts = List.copyOf(parts); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Namespace that = (Namespace) o; return this.parts.equals(that.parts); } @Override public int hashCode() { return this.parts.hashCode(); } /** * Create a new namespace by appending the supplied {@code parts} to the * existing sequence of parts in this namespace. * * @return new namespace; never {@code null} */ public Namespace append(Object... parts) { Preconditions.notEmpty(parts, "parts array must not be null or empty"); Preconditions.containsNoNullElements(parts, "individual parts must not be null"); ArrayList newParts = new ArrayList<>(this.parts.size() + parts.length); newParts.addAll(this.parts); Collections.addAll(newParts, parts); return new Namespace(newParts); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStore.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.store; import static java.util.Comparator.comparing; import static java.util.Objects.requireNonNull; import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.junit.platform.commons.util.ExceptionUtils.throwAsUncheckedException; import static org.junit.platform.commons.util.ReflectionUtils.getWrapperType; import static org.junit.platform.commons.util.ReflectionUtils.isAssignableTo; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.UnrecoverableExceptions; /** * {@code NamespacedHierarchicalStore} is a hierarchical, namespaced key-value store. * *

Its {@linkplain #close() closing} behavior can be customized by passing a * {@link CloseAction} to the * {@link #NamespacedHierarchicalStore(NamespacedHierarchicalStore, CloseAction)} * constructor. * *

This class is thread-safe. Please note, however, that thread safety is * not guaranteed while the {@link #close()} method is being invoked. * * @param Namespace type * @since 1.10 */ @API(status = MAINTAINED, since = "1.13.3") public final class NamespacedHierarchicalStore implements AutoCloseable { private final AtomicInteger insertOrderSequence = new AtomicInteger(); private final ConcurrentMap, StoredValue> storedValues = new ConcurrentHashMap<>(4); private final @Nullable NamespacedHierarchicalStore parentStore; private final @Nullable CloseAction closeAction; private volatile boolean closed = false; /** * Create a new store with the supplied parent. * * @param parentStore the parent store to use for lookups; may be {@code null} */ public NamespacedHierarchicalStore(@Nullable NamespacedHierarchicalStore parentStore) { this(parentStore, null); } /** * Create a new store with the supplied parent and close action. * * @param parentStore the parent store to use for lookups; may be {@code null} * @param closeAction the action to be called for each stored value when this * store is closed; may be {@code null} */ public NamespacedHierarchicalStore(@Nullable NamespacedHierarchicalStore parentStore, @Nullable CloseAction closeAction) { this.parentStore = parentStore; this.closeAction = closeAction; } /** * Create a child store with this store as its parent and this store's close * action. */ public NamespacedHierarchicalStore newChild() { return new NamespacedHierarchicalStore<>(this, this.closeAction); } /** * Returns the parent store of this {@code NamespacedHierarchicalStore}. * *

If this store does not have a parent, an empty {@code Optional} is returned. * * @return an {@code Optional} containing the parent store, or an empty {@code Optional} if there is no parent * @since 1.13 */ @API(status = EXPERIMENTAL, since = "6.0") public Optional> getParent() { return Optional.ofNullable(this.parentStore); } /** * Determine if this store has been {@linkplain #close() closed}. * * @return {@code true} if this store has been closed * @since 1.11 * @see #close() */ @API(status = MAINTAINED, since = "1.13.3") public boolean isClosed() { return this.closed; } /** * If a {@link CloseAction} is configured, it will be called with all successfully * stored values in reverse insertion order. * *

Closing a store does not close its parent or any of its children. * *

Invocations of this method after the store has already been closed will * be ignored. * * @see #isClosed() */ @Override public void close() { if (!this.closed) { try { if (this.closeAction != null) { List failures = new ArrayList<>(); this.storedValues.entrySet().stream() // .map(e -> EvaluatedValue.createSafely(e.getKey(), e.getValue())) // .filter(Objects::nonNull) // .sorted(EvaluatedValue.REVERSE_INSERT_ORDER) // .forEach(it -> { try { it.close(this.closeAction); } catch (Throwable t) { UnrecoverableExceptions.rethrowIfUnrecoverable(t); failures.add(t); } }); if (!failures.isEmpty()) { var iterator = failures.iterator(); var throwable = iterator.next(); iterator.forEachRemaining(throwable::addSuppressed); throw throwAsUncheckedException(throwable); } } } finally { this.closed = true; } } } /** * Get the value stored for the supplied namespace and key in this store or * the parent store, if present. * * @param namespace the namespace; never {@code null} * @param key the key; never {@code null} * @return the stored value; may be {@code null} * @throws NamespacedHierarchicalStoreException if this store has already been * closed */ public @Nullable Object get(N namespace, Object key) { StoredValue storedValue = getStoredValue(new CompositeKey<>(namespace, key)); return StoredValue.evaluateIfNotNull(storedValue); } /** * Get the value stored for the supplied namespace and key in this store or * the parent store, if present, and cast it to the supplied required type. * * @param namespace the namespace; never {@code null} * @param key the key; never {@code null} * @param requiredType the required type of the value; never {@code null} * @return the stored value; may be {@code null} * @throws NamespacedHierarchicalStoreException if the stored value cannot * be cast to the required type, or if this store has already been closed */ public @Nullable T get(N namespace, Object key, Class requiredType) throws NamespacedHierarchicalStoreException { Object value = get(namespace, key); return castToRequiredType(key, value, requiredType); } /** * Get the value stored for the supplied namespace and key in this store or * the parent store, if present, or call the supplied function to compute it. * * @param namespace the namespace; never {@code null} * @param key the key; never {@code null} * @param defaultCreator the function called with the supplied {@code key} * to create a new value; never {@code null} but may return {@code null} * @return the stored value; may be {@code null} * @throws NamespacedHierarchicalStoreException if this store has already been * closed * @deprecated Please use {@link #computeIfAbsent(Object, Object, Function)} instead. */ @Deprecated(since = "6.0") @API(status = DEPRECATED, since = "6.0") public @Nullable Object getOrComputeIfAbsent(N namespace, K key, Function defaultCreator) { Preconditions.notNull(defaultCreator, "defaultCreator must not be null"); var compositeKey = new CompositeKey<>(namespace, key); var currentStoredValue = getStoredValue(compositeKey); if (currentStoredValue != null) { return currentStoredValue.evaluate(); } var candidateStoredValue = newStoredSuppliedNullableValue(() -> { rejectIfClosed(); return defaultCreator.apply(key); }); for (;;) { var storedValue = storedValues.compute(compositeKey, // (__, oldStoredValue) -> { // The old stored value remains if a) there is an old stored value and // b) the old stored value has not yet been evaluated or c) the old // stored value was evaluated to a present value. // // Condition b ensures that we do not evaluate or await the evaluation // inside `compute`, this would lead to recursive updates or deadlocks // respectively. // Condition c guards against race conditions (repeated from // getStoredValue) this filters out failures inserted by // computeIfAbsent. if (oldStoredValue != null && (!oldStoredValue.isDone() || oldStoredValue.isPresent())) { return oldStoredValue; } rejectIfClosed(); return candidateStoredValue; }); // Only the caller that created the candidateStoredValue may run it if (candidateStoredValue.equals(storedValue)) { return candidateStoredValue.execute(); } // Implicitly awaits evaluation and uses the result if it was present. // this filters out failures inserted by computeIfAbsent // Otherwise, another thread won the race but failed to insert a // present value so we try again. if (storedValue.isPresent()) { return storedValue.evaluate(); } } } /** * Return the value stored for the supplied namespace and key in this store * or the parent store, if present and not {@code null}, or call the * supplied function to compute it. * * @param namespace the namespace; never {@code null} * @param key the key; never {@code null} * @param defaultCreator the function called with the supplied {@code key} * to create a new value; never {@code null} and must not return * {@code null} * @return the stored value; never {@code null} * @throws NamespacedHierarchicalStoreException if this store has already been * closed * @since 6.0 */ @API(status = MAINTAINED, since = "6.0") public Object computeIfAbsent(N namespace, K key, Function defaultCreator) { Preconditions.notNull(defaultCreator, "defaultCreator must not be null"); var compositeKey = new CompositeKey<>(namespace, key); var currentStoredValue = getStoredValue(compositeKey); var result = StoredValue.evaluateIfNotNull(currentStoredValue); if (result != null) { return result; } var candidateStoredValue = newStoredSuppliedValue(() -> { rejectIfClosed(); return Preconditions.notNull(defaultCreator.apply(key), "defaultCreator must not return null"); }); for (;;) { var storedValue = storedValues.compute(compositeKey, (__, oldStoredValue) -> { // The old stored value remains if a) there is an old stored value and // b) the old stored value has not yet been evaluated or c) the old // stored value evaluated to null. // // Condition b ensures that we do not evaluate or await the evaluation // inside `compute`, this would lead to recursive updates or deadlocks // respectively. // Condition c ensures we replace both null and absent values. if (oldStoredValue != null && (!oldStoredValue.isDone() || oldStoredValue.evaluate() != null)) { return oldStoredValue; } rejectIfClosed(); return candidateStoredValue; }); // Only the caller that created the candidateStoredValue may run it // and see the exception. if (candidateStoredValue.equals(storedValue)) { Object newResult = candidateStoredValue.execute(); // DeferredOptionalValue is quite heavy, replace with lighter container if (candidateStoredValue.isPresent()) { storedValues.computeIfPresent(compositeKey, compareAndPut(storedValue, newStoredValue(newResult))); } return newResult; } // Awaits evaluation and uses the stored value if it was not null // this ensures we replace both null and absent values. // Otherwise, another thread won the race but failed to insert a // non-null value so we try again. Object storedResult = storedValue.evaluate(); if (storedResult != null) { return storedResult; } } } private static BiFunction, StoredValue, StoredValue> compareAndPut(StoredValue expectedValue, StoredValue newValue) { return (__, storedValue) -> { if (expectedValue.equals(storedValue)) { return newValue; } return storedValue; }; } /** * Get the value stored for the supplied namespace and key in this store or * the parent store, if present, or call the supplied function to compute it * and, finally, cast it to the supplied required type. * * @param namespace the namespace; never {@code null} * @param key the key; never {@code null} * @param defaultCreator the function called with the supplied {@code key} * to create a new value; never {@code null} but may return {@code null} * @param requiredType the required type of the value; never {@code null} * @return the stored value; may be {@code null} * @throws NamespacedHierarchicalStoreException if the stored value cannot * be cast to the required type, or if this store has already been closed * @deprecated Please use {@link #computeIfAbsent(Object, Object, Function, Class)} instead. */ @Deprecated(since = "6.0") @API(status = DEPRECATED, since = "6.0") public @Nullable V getOrComputeIfAbsent(N namespace, K key, Function defaultCreator, Class requiredType) throws NamespacedHierarchicalStoreException { Object value = getOrComputeIfAbsent(namespace, key, defaultCreator); return castToRequiredType(key, value, requiredType); } /** * Return the value stored for the supplied namespace and key in this store * or the parent store, if present and not {@code null}, or call the * supplied function to compute it and, finally, cast it to the supplied * required type. * * @param namespace the namespace; never {@code null} * @param key the key; never {@code null} * @param defaultCreator the function called with the supplied {@code key} * to create a new value; never {@code null} and must not return * {@code null} * @param requiredType the required type of the value; never {@code null} * @return the stored value; never {@code null} * @throws NamespacedHierarchicalStoreException if the stored value cannot * be cast to the required type, or if this store has already been closed * @since 6.0 */ @API(status = MAINTAINED, since = "6.0") public V computeIfAbsent(N namespace, K key, Function defaultCreator, Class requiredType) throws NamespacedHierarchicalStoreException { Object value = computeIfAbsent(namespace, key, defaultCreator); return castNonNullToRequiredType(key, value, requiredType); } /** * Put the supplied value for the supplied namespace and key into this * store and return the previously associated value in this store. * *

The {@link CloseAction} will not be called for the previously * stored value, if any. * * @param namespace the namespace; never {@code null} * @param key the key; never {@code null} * @param value the value to store; may be {@code null} * @return the previously stored value; may be {@code null} * @throws NamespacedHierarchicalStoreException if an error occurs while * storing the value, or if this store has already been closed */ public @Nullable Object put(N namespace, Object key, @Nullable Object value) throws NamespacedHierarchicalStoreException { rejectIfClosed(); StoredValue oldValue = this.storedValues.put(new CompositeKey<>(namespace, key), newStoredValue(value)); return StoredValue.evaluateIfNotNull(oldValue); } /** * Remove the value stored for the supplied namespace and key from this * store. * *

The {@link CloseAction} will not be called for the removed * value. * * @param namespace the namespace; never {@code null} * @param key the key; never {@code null} * @return the previously stored value; may be {@code null} * @throws NamespacedHierarchicalStoreException if this store has already been * closed */ public @Nullable Object remove(N namespace, Object key) { rejectIfClosed(); StoredValue previous = this.storedValues.remove(new CompositeKey<>(namespace, key)); return StoredValue.evaluateIfNotNull(previous); } /** * Remove the value stored for the supplied namespace and key from this * store and cast it to the supplied required type. * *

The {@link CloseAction} will not be called for the removed * value. * * @param namespace the namespace; never {@code null} * @param key the key; never {@code null} * @param requiredType the required type of the value; never {@code null} * @return the previously stored value; may be {@code null} * @throws NamespacedHierarchicalStoreException if the stored value cannot * be cast to the required type, or if this store has already been closed */ public @Nullable T remove(N namespace, Object key, Class requiredType) throws NamespacedHierarchicalStoreException { rejectIfClosed(); Object value = remove(namespace, key); return castToRequiredType(key, value, requiredType); } private StoredValue.Value newStoredValue(@Nullable Object value) { var sequenceNumber = insertOrderSequence.getAndIncrement(); return new StoredValue.Value(sequenceNumber, value); } private StoredValue.DeferredValue newStoredSuppliedNullableValue(Supplier<@Nullable Object> supplier) { var sequenceNumber = insertOrderSequence.getAndIncrement(); return new StoredValue.DeferredValue(sequenceNumber, supplier); } private StoredValue.DeferredOptionalValue newStoredSuppliedValue(Supplier supplier) { var sequenceNumber = insertOrderSequence.getAndIncrement(); return new StoredValue.DeferredOptionalValue(sequenceNumber, supplier); } private @Nullable StoredValue getStoredValue(CompositeKey compositeKey) { StoredValue storedValue = this.storedValues.get(compositeKey); if (StoredValue.isNonNullAndPresent(storedValue)) { return storedValue; } if (this.parentStore != null) { return this.parentStore.getStoredValue(compositeKey); } return null; } private @Nullable T castToRequiredType(Object key, @Nullable Object value, Class requiredType) { Preconditions.notNull(requiredType, "requiredType must not be null"); if (value == null) { return null; } return castNonNullToRequiredType(key, value, requiredType); } @SuppressWarnings("unchecked") private T castNonNullToRequiredType(Object key, V value, Class requiredType) { if (isAssignableTo(value, requiredType)) { if (requiredType.isPrimitive()) { return (T) requireNonNull(getWrapperType(requiredType)).cast(value); } return requiredType.cast(value); } // else throw new NamespacedHierarchicalStoreException( "Object stored under key [%s] is not of required type [%s], but was [%s]: %s".formatted(key, requiredType.getName(), value.getClass().getName(), value)); } private void rejectIfClosed() { if (this.closed) { throw new NamespacedHierarchicalStoreException( "A NamespacedHierarchicalStore cannot be modified or queried after it has been closed"); } } private record CompositeKey(N namespace, Object key) { CompositeKey { Preconditions.notNull(namespace, "namespace must not be null"); Preconditions.notNull(key, "key must not be null"); } } private interface StoredValue { int order(); @Nullable Object evaluate(); boolean isPresent(); boolean isDone(); static @Nullable Object evaluateIfNotNull(@Nullable StoredValue value) { return value != null ? value.evaluate() : null; } static boolean isNonNullAndPresent(@Nullable StoredValue value) { return value != null && value.isPresent(); } /** * May contain {@code null} or a value, never an exception. */ final class Value implements StoredValue { private final int order; private final @Nullable Object value; Value(int order, @Nullable Object value) { this.order = order; this.value = value; } @Override public @Nullable Object evaluate() { return value; } @Override public boolean isPresent() { return true; } @Override public boolean isDone() { return true; } @Override public int order() { return order; } } /** * May eventually contain {@code null} or a value or an exception. */ final class DeferredValue implements StoredValue { private final int order; private final DeferredSupplier delegate; DeferredValue(int order, Supplier<@Nullable Object> delegate) { this.order = order; this.delegate = new DeferredSupplier(delegate); } @Override public @Nullable Object evaluate() { return delegate.getOrThrow(); } @Override public boolean isPresent() { return true; } @Override public boolean isDone() { return delegate.isDone(); } @Nullable Object execute() { delegate.run(); return delegate.getOrThrow(); } @Override public int order() { return order; } } /** * May eventually contain a value or an exception, never {@code null}. */ final class DeferredOptionalValue implements StoredValue { private final int order; private final DeferredSupplier delegate; DeferredOptionalValue(int order, Supplier delegate) { this.order = order; this.delegate = new DeferredSupplier(delegate); } @Override public @Nullable Object evaluate() { return delegate.get(); } @Override public boolean isPresent() { return evaluate() != null; } @Override public boolean isDone() { return delegate.isDone(); } Object execute() { delegate.run(); // Delegate does not produce null return requireNonNull(delegate.getOrThrow()); } @Override public int order() { return order; } } } private record EvaluatedValue(CompositeKey compositeKey, int order, Object value) { private static @Nullable EvaluatedValue createSafely(CompositeKey compositeKey, StoredValue value) { try { var evaluatedValue = value.evaluate(); if (evaluatedValue == null) { return null; } return new EvaluatedValue<>(compositeKey, value.order(), evaluatedValue); } catch (Throwable t) { UnrecoverableExceptions.rethrowIfUnrecoverable(t); return null; } } private static final Comparator> REVERSE_INSERT_ORDER = comparing( (EvaluatedValue it) -> it.order).reversed(); private void close(CloseAction closeAction) throws Throwable { closeAction.close(this.compositeKey.namespace, this.compositeKey.key, this.value); } } /** * Deferred computation that can be added to the store. *

* This allows values to be computed outside the * {@link ConcurrentHashMap#compute(Object, BiFunction)} calls and * prevents recursive updates. */ static final class DeferredSupplier { private final FutureTask<@Nullable Object> task; DeferredSupplier(Supplier delegate) { this.task = new FutureTask<>(delegate::get); } void run() { task.run(); } @Nullable Object get() { try { return task.get(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw throwAsUncheckedException(e); } catch (ExecutionException e) { // non-null guaranteed by FutureTask var cause = requireNonNull(e.getCause()); UnrecoverableExceptions.rethrowIfUnrecoverable(cause); return null; } } @Nullable Object getOrThrow() { try { return task.get(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw throwAsUncheckedException(e); } catch (ExecutionException e) { // non-null guaranteed by FutureTask var cause = requireNonNull(e.getCause()); UnrecoverableExceptions.rethrowIfUnrecoverable(cause); throw throwAsUncheckedException(cause); } } boolean isDone() { return task.isDone(); } } /** * Called for each successfully stored non-null value in the store when a * {@link NamespacedHierarchicalStore} is * {@linkplain NamespacedHierarchicalStore#close() closed}. */ @FunctionalInterface public interface CloseAction { /** * Static factory method for creating a {@link CloseAction} which * {@linkplain #close(Object, Object, Object) closes} any value that * implements {@link AutoCloseable}. * * @since 6.0 */ @API(status = EXPERIMENTAL, since = "6.0") static CloseAction closeAutoCloseables() { return (__, ___, value) -> { if (value instanceof AutoCloseable closeable) { closeable.close(); } }; } /** * Close the supplied {@code value}. * * @param namespace the namespace; never {@code null} * @param key the key; never {@code null} * @param value the value; never {@code null} */ void close(N namespace, Object key, Object value) throws Throwable; } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStoreException.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.store; import static org.apiguardian.api.API.Status.MAINTAINED; import java.io.Serial; import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; /** * Exception thrown by failed {@link NamespacedHierarchicalStore} operations. * * @since 1.10 */ @API(status = MAINTAINED, since = "1.13.3") public class NamespacedHierarchicalStoreException extends JUnitException { @Serial private static final long serialVersionUID = 1L; public NamespacedHierarchicalStoreException(String message) { super(message); } public NamespacedHierarchicalStoreException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Reusable data structures for test engines and their extensions. */ @NullMarked package org.junit.platform.engine.support.store; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-engine/src/main/resources/META-INF/services/org.junit.platform.engine.discovery.DiscoverySelectorIdentifierParser ================================================ org.junit.platform.engine.discovery.ClasspathResourceSelector$IdentifierParser org.junit.platform.engine.discovery.ClasspathRootSelector$IdentifierParser org.junit.platform.engine.discovery.ClassSelector$IdentifierParser org.junit.platform.engine.discovery.DirectorySelector$IdentifierParser org.junit.platform.engine.discovery.FileSelector$IdentifierParser org.junit.platform.engine.discovery.IterationSelector$IdentifierParser org.junit.platform.engine.discovery.MethodSelector$IdentifierParser org.junit.platform.engine.discovery.ModuleSelector$IdentifierParser org.junit.platform.engine.discovery.NestedClassSelector$IdentifierParser org.junit.platform.engine.discovery.NestedMethodSelector$IdentifierParser org.junit.platform.engine.discovery.PackageSelector$IdentifierParser org.junit.platform.engine.discovery.UniqueIdSelector$IdentifierParser org.junit.platform.engine.discovery.UriSelector$IdentifierParser ================================================ FILE: junit-platform-engine/src/test/README.md ================================================ For compatibility with the Eclipse IDE, the test for this module are in the `platform-tests` project. ================================================ FILE: junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoEngineExecutionContext.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import org.junit.platform.engine.ExecutionRequest; /** * @since 1.0 */ public record DemoEngineExecutionContext(ExecutionRequest request) implements EngineExecutionContext { } ================================================ FILE: junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalContainerDescriptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static org.junit.platform.engine.support.hierarchical.Node.SkipResult.doNotSkip; import org.jspecify.annotations.Nullable; import org.junit.platform.engine.TestSource; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; /** * @since 1.0 */ public class DemoHierarchicalContainerDescriptor extends AbstractTestDescriptor implements Node { private final @Nullable Runnable beforeBlock; public DemoHierarchicalContainerDescriptor(UniqueId uniqueId, String displayName, @Nullable TestSource source, @Nullable Runnable beforeBlock) { super(uniqueId, displayName, source); this.beforeBlock = beforeBlock; } @Override public Type getType() { return Type.CONTAINER; } @Override public boolean mayRegisterTests() { return true; } @Override public SkipResult shouldBeSkipped(DemoEngineExecutionContext context) { return doNotSkip(); } @Override public DemoEngineExecutionContext before(DemoEngineExecutionContext context) { if (this.beforeBlock != null) { this.beforeBlock.run(); } return context; } } ================================================ FILE: junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalEngineDescriptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static org.junit.platform.engine.support.hierarchical.Node.SkipResult.doNotSkip; import static org.junit.platform.engine.support.hierarchical.Node.SkipResult.skip; import org.jspecify.annotations.Nullable; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.EngineDescriptor; /** * @since 1.0 */ public class DemoHierarchicalEngineDescriptor extends EngineDescriptor implements Node { private @Nullable String skippedReason; private boolean skipped; private Runnable beforeAllBehavior = () -> { }; public DemoHierarchicalEngineDescriptor(UniqueId uniqueId) { super(uniqueId, uniqueId.getEngineId().orElseThrow()); } public void markSkipped(String reason) { this.skipped = true; this.skippedReason = reason; } public void setBeforeAllBehavior(Runnable beforeAllBehavior) { this.beforeAllBehavior = beforeAllBehavior; } @Override public SkipResult shouldBeSkipped(DemoEngineExecutionContext context) { return skipped ? skip(skippedReason) : doNotSkip(); } @Override public DemoEngineExecutionContext before(DemoEngineExecutionContext context) { beforeAllBehavior.run(); return context; } } ================================================ FILE: junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalTestDescriptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static org.junit.platform.engine.support.hierarchical.Node.SkipResult.doNotSkip; import static org.junit.platform.engine.support.hierarchical.Node.SkipResult.skip; import java.util.function.BiConsumer; import org.jspecify.annotations.Nullable; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestSource; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; /** * @since 1.0 */ public class DemoHierarchicalTestDescriptor extends AbstractTestDescriptor implements Node { private final @Nullable BiConsumer executeBlock; private @Nullable String skippedReason; private boolean skipped; public DemoHierarchicalTestDescriptor(UniqueId uniqueId, String displayName, @Nullable BiConsumer executeBlock) { this(uniqueId, displayName, null, executeBlock); } public DemoHierarchicalTestDescriptor(UniqueId uniqueId, String displayName, @Nullable TestSource source, @Nullable BiConsumer executeBlock) { super(uniqueId, displayName, source); this.executeBlock = executeBlock; } @Override public Type getType() { return this.executeBlock != null ? Type.TEST : Type.CONTAINER; } public void markSkipped(String reason) { this.skipped = true; this.skippedReason = reason; } @Override public SkipResult shouldBeSkipped(DemoEngineExecutionContext context) { return skipped ? skip(skippedReason) : doNotSkip(); } @Override public DemoEngineExecutionContext execute(DemoEngineExecutionContext context, DynamicTestExecutor dynamicTestExecutor) { if (this.executeBlock != null) { this.executeBlock.accept(context, this); } return context; } } ================================================ FILE: junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalTestEngine.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import java.util.function.BiConsumer; import java.util.function.Function; import org.jspecify.annotations.Nullable; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestSource; import org.junit.platform.engine.UniqueId; /** * @since 1.0 */ public final class DemoHierarchicalTestEngine extends HierarchicalTestEngine { private final String engineId; private final DemoHierarchicalEngineDescriptor engineDescriptor; public DemoHierarchicalTestEngine() { this("dummy"); } public DemoHierarchicalTestEngine(String engineId) { this.engineId = engineId; this.engineDescriptor = new DemoHierarchicalEngineDescriptor(UniqueId.forEngine(getId())); } @Override public String getId() { return engineId; } public DemoHierarchicalEngineDescriptor getEngineDescriptor() { return engineDescriptor; } public DemoHierarchicalTestDescriptor addTest(String uniqueName, Runnable executeBlock) { return addTest(uniqueName, uniqueName, executeBlock); } public DemoHierarchicalTestDescriptor addTest(String uniqueName, String displayName, Runnable executeBlock) { return addChild(uniqueName, uniqueId -> new DemoHierarchicalTestDescriptor(uniqueId, displayName, (c, t) -> executeBlock.run()), "test"); } public DemoHierarchicalTestDescriptor addTest(String uniqueName, String displayName, BiConsumer executeBlock) { return addChild(uniqueName, uniqueId -> new DemoHierarchicalTestDescriptor(uniqueId, displayName, executeBlock), "test"); } public DemoHierarchicalContainerDescriptor addContainer(String uniqueName, String displayName, @Nullable TestSource source) { return addContainer(uniqueName, displayName, source, null); } public DemoHierarchicalContainerDescriptor addContainer(String uniqueName, Runnable beforeBlock) { return addContainer(uniqueName, uniqueName, null, beforeBlock); } public DemoHierarchicalContainerDescriptor addContainer(String uniqueName, String displayName, @Nullable TestSource source, @Nullable Runnable beforeBlock) { return addChild(uniqueName, uniqueId -> new DemoHierarchicalContainerDescriptor(uniqueId, displayName, source, beforeBlock), "container"); } public > T addChild(String uniqueName, Function creator, String segmentType) { var uniqueId = engineDescriptor.getUniqueId().append(segmentType, uniqueName); var child = creator.apply(uniqueId); engineDescriptor.addChild(child); return child; } @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { return engineDescriptor; } @Override protected DemoEngineExecutionContext createExecutionContext(ExecutionRequest request) { return new DemoEngineExecutionContext(request); } } ================================================ FILE: junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/FaultyTestEngines.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.fakes; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.SelectorResolutionResult; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.EngineDescriptor; public class FaultyTestEngines { public static TestEngineStub createEngineThatCannotResolveAnything(String engineId) { return new TestEngineStub(engineId) { @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { discoveryRequest.getSelectorsByType(DiscoverySelector.class) // .forEach(selector -> discoveryRequest.getDiscoveryListener().selectorProcessed(uniqueId, selector, SelectorResolutionResult.unresolved())); return new EngineDescriptor(uniqueId, "Some Engine"); } @Override public void execute(ExecutionRequest request) { var listener = request.getEngineExecutionListener(); var rootTestDescriptor = request.getRootTestDescriptor(); listener.executionStarted(rootTestDescriptor); listener.executionFinished(rootTestDescriptor, TestExecutionResult.successful()); } }; } public static TestEngineStub createEngineThatFailsToResolveAnything(String engineId, Throwable rootCause) { return new TestEngineStub(engineId) { @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { discoveryRequest.getSelectorsByType(DiscoverySelector.class) // .forEach(selector -> discoveryRequest.getDiscoveryListener().selectorProcessed(uniqueId, selector, SelectorResolutionResult.failed(rootCause))); return new EngineDescriptor(uniqueId, "Some Engine"); } @Override public void execute(ExecutionRequest request) { var listener = request.getEngineExecutionListener(); var rootTestDescriptor = request.getRootTestDescriptor(); listener.executionStarted(rootTestDescriptor); listener.executionFinished(rootTestDescriptor, TestExecutionResult.successful()); } }; } private FaultyTestEngines() { } } ================================================ FILE: junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestDescriptorStub.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.fakes; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; /** * @since 1.4 */ public class TestDescriptorStub extends AbstractTestDescriptor { public TestDescriptorStub(UniqueId uniqueId, String displayName) { super(uniqueId, displayName); } @Override public Type getType() { return getChildren().isEmpty() ? Type.TEST : Type.CONTAINER; } } ================================================ FILE: junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestEngineSpy.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.fakes; import org.jspecify.annotations.Nullable; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.UniqueId; /** * @since 1.4 */ public class TestEngineSpy implements TestEngine { private final String id; public @Nullable ExecutionRequest requestForExecution; public TestEngineSpy() { this(TestEngineSpy.class.getSimpleName()); } public TestEngineSpy(String id) { this.id = id; } @Override public String getId() { return id; } @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { var engineUniqueId = UniqueId.forEngine(id); var engineDescriptor = new TestDescriptorStub(engineUniqueId, id); var testDescriptor = new TestDescriptorStub(engineUniqueId.append("test", "test"), "test"); engineDescriptor.addChild(testDescriptor); return engineDescriptor; } @Override public void execute(ExecutionRequest request) { this.requestForExecution = request; } } ================================================ FILE: junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestEngineStub.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.fakes; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.UniqueId; /** * @since 1.4 */ public class TestEngineStub implements TestEngine { private final String id; public TestEngineStub() { this(TestEngineStub.class.getSimpleName()); } public TestEngineStub(String id) { this.id = id; } @Override public String getId() { return this.id; } @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { return new TestDescriptorStub(uniqueId.appendEngine(getId()), getId()); } @Override public void execute(ExecutionRequest request) { } } ================================================ FILE: junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/package-info.java ================================================ @NullMarked package org.junit.platform.fakes; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-launcher/junit-platform-launcher.gradle.kts ================================================ plugins { id("junitbuild.java-library-conventions") `java-test-fixtures` } description = "JUnit Platform Launcher" dependencies { api(platform(projects.junitBom)) api(projects.junitPlatformEngine) compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) osgiVerification(projects.junitJupiterEngine) } javadocConventions { addExtraModuleReferences(projects.junitPlatformReporting) } tasks { jar { bundle { val importAPIGuardian: String by extra val importJSpecify: String by extra val importCommonsLogging: String by extra val version = project.version bnd(""" Import-Package: \ ${importAPIGuardian},\ ${importJSpecify},\ ${importCommonsLogging},\ jdk.jfr;resolution:="optional",\ * Provide-Capability:\ org.junit.platform.launcher;\ org.junit.platform.launcher='junit-platform-launcher';\ version:Version="${'$'}{version_cleanup;${version}}" """) } } } ================================================ FILE: junit-platform-launcher/src/main/java/module-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Public API for configuring and launching test plans. * *

This API is typically used by IDEs and build tools. * * @since 1.0 * @uses org.junit.platform.engine.TestEngine * @uses org.junit.platform.launcher.LauncherDiscoveryListener * @uses org.junit.platform.launcher.LauncherInterceptor * @uses org.junit.platform.launcher.LauncherSessionListener * @uses org.junit.platform.launcher.PostDiscoveryFilter * @uses org.junit.platform.launcher.TestExecutionListener */ module org.junit.platform.launcher { requires static transitive org.apiguardian.api; requires static transitive org.jspecify; requires static jdk.jfr; requires transitive java.logging; requires transitive org.junit.platform.commons; requires transitive org.junit.platform.engine; exports org.junit.platform.launcher; exports org.junit.platform.launcher.core; exports org.junit.platform.launcher.listeners; exports org.junit.platform.launcher.listeners.discovery; uses org.junit.platform.engine.TestEngine; uses org.junit.platform.launcher.LauncherDiscoveryListener; uses org.junit.platform.launcher.LauncherInterceptor; uses org.junit.platform.launcher.LauncherSessionListener; uses org.junit.platform.launcher.PostDiscoveryFilter; uses org.junit.platform.launcher.TestExecutionListener; provides org.junit.platform.launcher.TestExecutionListener with org.junit.platform.launcher.listeners.UniqueIdTrackingListener; } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/AbstractMethodFilter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher; import static java.util.stream.Collectors.joining; import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.regex.Pattern; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.support.descriptor.MethodSource; /** * Abstract {@link MethodFilter} that servers as a superclass * for filters including or excluding fully qualified method names * without parameters based on pattern-matching. * * @since 1.12 */ abstract class AbstractMethodFilter implements MethodFilter { protected final List patterns; protected final String patternDescription; AbstractMethodFilter(String... patterns) { Preconditions.notEmpty(patterns, "patterns array must not be null or empty"); Preconditions.containsNoNullElements(patterns, "patterns array must not contain null elements"); this.patterns = Arrays.stream(patterns).map(Pattern::compile).toList(); this.patternDescription = Arrays.stream(patterns).collect(joining("' OR '", "'", "'")); } protected Optional findMatchingPattern(@Nullable String methodName) { if (methodName == null) { return Optional.empty(); } return this.patterns.stream().filter(pattern -> pattern.matcher(methodName).matches()).findAny(); } protected @Nullable String getFullyQualifiedMethodNameFromDescriptor(TestDescriptor descriptor) { return descriptor.getSource() // .filter(MethodSource.class::isInstance) // .map(methodSource -> getFullyQualifiedMethodNameWithoutParameters(((MethodSource) methodSource))) // .orElse(null); } private String getFullyQualifiedMethodNameWithoutParameters(MethodSource methodSource) { String methodNameWithParentheses = ReflectionUtils.getFullyQualifiedMethodName(methodSource.getJavaClass(), methodSource.getMethodName(), (Class[]) null); return methodNameWithParentheses.substring(0, methodNameWithParentheses.length() - 2); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/EngineDiscoveryResult.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher; import static org.apiguardian.api.API.Status.STABLE; import java.util.Optional; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.ToStringBuilder; /** * {@code EngineDiscoveryResult} encapsulates the result of test discovery by a * {@link org.junit.platform.engine.TestEngine}. * *

A {@code EngineDiscoveryResult} consists of a mandatory * {@link #getStatus() Status} and an optional {@link #getThrowable() Throwable}. * * @since 1.6 */ @API(status = STABLE, since = "1.10") public class EngineDiscoveryResult { /** * Status of test discovery by a * {@link org.junit.platform.engine.TestEngine}. */ public enum Status { /** * Indicates that test discovery was successful. */ SUCCESSFUL, /** * Indicates that test discovery has failed. */ FAILED } private static final EngineDiscoveryResult SUCCESSFUL_RESULT = new EngineDiscoveryResult(Status.SUCCESSFUL, null); /** * Create a {@code EngineDiscoveryResult} for a successful test * discovery. * @return the {@code EngineDiscoveryResult}; never {@code null} */ public static EngineDiscoveryResult successful() { return SUCCESSFUL_RESULT; } /** * Create a {@code EngineDiscoveryResult} for a failed test * discovery. * * @param throwable the throwable that caused the failed discovery; may be * {@code null} * @return the {@code EngineDiscoveryResult}; never {@code null} */ public static EngineDiscoveryResult failed(@Nullable Throwable throwable) { return new EngineDiscoveryResult(Status.FAILED, throwable); } private final Status status; private final @Nullable Throwable throwable; private EngineDiscoveryResult(Status status, @Nullable Throwable throwable) { this.status = status; this.throwable = throwable; } /** * Get the {@linkplain Status status} of this result. * * @return the status; never {@code null} */ public Status getStatus() { return status; } /** * Get the throwable that caused this result, if available. * * @return an {@code Optional} containing the throwable; never {@code null} * but potentially empty */ public Optional getThrowable() { return Optional.ofNullable(throwable); } @Override public String toString() { // @formatter:off return new ToStringBuilder(this) .append("status", status) .append("throwable", throwable) .toString(); // @formatter:on } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/EngineFilter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; import static org.junit.platform.engine.FilterResult.includedIf; import java.util.Arrays; import java.util.List; import org.apiguardian.api.API; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.Filter; import org.junit.platform.engine.FilterResult; import org.junit.platform.engine.TestEngine; /** * An {@code EngineFilter} is applied to all {@link TestEngine TestEngines} * before they are used. * *

Warning: be cautious when registering multiple competing * {@link #includeEngines include} {@code EngineFilters} or multiple competing * {@link #excludeEngines exclude} {@code EngineFilters} for the same discovery * request since doing so will likely lead to undesirable results (i.e., zero * engines being active). * * @since 1.0 * @see #includeEngines(String...) * @see #excludeEngines(String...) * @see LauncherDiscoveryRequest */ @API(status = STABLE, since = "1.0") public class EngineFilter implements Filter { /** * Create a new include {@code EngineFilter} based on the * supplied engine IDs. * *

Only {@code TestEngines} with matching engine IDs will be * included within the test discovery and execution. * * @param engineIds the list of engine IDs to match against; never {@code null} * or empty; individual IDs must also not be null or blank * @see #includeEngines(String...) */ public static EngineFilter includeEngines(String... engineIds) { return includeEngines(Arrays.asList(engineIds)); } /** * Create a new include {@code EngineFilter} based on the * supplied engine IDs. * *

Only {@code TestEngines} with matching engine IDs will be * included within the test discovery and execution. * * @param engineIds the list of engine IDs to match against; never {@code null} * or empty; individual IDs must also not be null or blank * @see #includeEngines(String...) */ public static EngineFilter includeEngines(List engineIds) { return new EngineFilter(engineIds, Type.INCLUDE); } /** * Create a new exclude {@code EngineFilter} based on the * supplied engine IDs. * *

{@code TestEngines} with matching engine IDs will be * excluded from test discovery and execution. * * @param engineIds the list of engine IDs to match against; never {@code null} * or empty; individual IDs must also not be null or blank * @see #excludeEngines(List) */ public static EngineFilter excludeEngines(String... engineIds) { return excludeEngines(Arrays.asList(engineIds)); } /** * Create a new exclude {@code EngineFilter} based on the * supplied engine IDs. * *

{@code TestEngines} with matching engine IDs will be * excluded from test discovery and execution. * * @param engineIds the list of engine IDs to match against; never {@code null} * or empty; individual IDs must also not be null or blank * @see #includeEngines(String...) */ public static EngineFilter excludeEngines(List engineIds) { return new EngineFilter(engineIds, Type.EXCLUDE); } private final List engineIds; private final Type type; private EngineFilter(List engineIds, Type type) { this.engineIds = validateAndTrim(engineIds); this.type = type; } @API(status = INTERNAL, since = "1.9") public List getEngineIds() { return engineIds; } @API(status = INTERNAL, since = "1.9") public boolean isIncludeFilter() { return type == Type.INCLUDE; } @Override public FilterResult apply(TestEngine testEngine) { Preconditions.notNull(testEngine, "TestEngine must not be null"); String engineId = testEngine.getId(); Preconditions.notBlank(engineId, "TestEngine ID must not be null or blank"); if (this.type == Type.INCLUDE) { return includedIf(this.engineIds.stream().anyMatch(engineId::equals), // () -> "Engine ID [%s] is in included list [%s]".formatted(engineId, this.engineIds), // () -> "Engine ID [%s] is not in included list [%s]".formatted(engineId, this.engineIds)); } else { return includedIf(this.engineIds.stream().noneMatch(engineId::equals), // () -> "Engine ID [%s] is not in excluded list [%s]".formatted(engineId, this.engineIds), // () -> "Engine ID [%s] is in excluded list [%s]".formatted(engineId, this.engineIds)); } } @Override public String toString() { return "%s that %s engines with IDs %s".formatted(getClass().getSimpleName(), this.type.verb, this.engineIds); } private static List validateAndTrim(List engineIds) { Preconditions.notEmpty(engineIds, "engine ID list must not be null or empty"); // @formatter:off return engineIds.stream() .map(id -> Preconditions.notBlank(id, "engine ID must not be null or blank").strip()) .distinct() .toList(); // @formatter:on } private enum Type { INCLUDE("includes"), EXCLUDE("excludes"); private final String verb; Type(String verb) { this.verb = verb; } } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/ExcludeMethodFilter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher; import static org.junit.platform.engine.FilterResult.excluded; import static org.junit.platform.engine.FilterResult.included; import java.util.regex.Pattern; import org.jspecify.annotations.Nullable; import org.junit.platform.engine.FilterResult; import org.junit.platform.engine.TestDescriptor; /** * {@link MethodFilter} that matches fully qualified method names against * patterns in the form of regular expressions. * *

If the fully qualified name of a method matches against at least one * pattern, the class will be excluded. * * @since 1.12 */ class ExcludeMethodFilter extends AbstractMethodFilter { ExcludeMethodFilter(String... patterns) { super(patterns); } @Override public FilterResult apply(TestDescriptor descriptor) { String methodName = getFullyQualifiedMethodNameFromDescriptor(descriptor); return findMatchingPattern(methodName) // .map(pattern -> excluded(formatExclusionReason(methodName, pattern))) // .orElseGet(() -> included(formatInclusionReason(methodName))); } private String formatInclusionReason(@Nullable String methodName) { return "Method name [%s] does not match any excluded pattern: %s".formatted(methodName, patternDescription); } private String formatExclusionReason(@Nullable String methodName, Pattern pattern) { return "Method name [%s] matches excluded pattern: '%s'".formatted(methodName, pattern); } @Override public String toString() { return "%s that excludes method names that match one of the following regular expressions: %s".formatted( getClass().getSimpleName(), patternDescription); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/IncludeMethodFilter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher; import static org.junit.platform.engine.FilterResult.excluded; import static org.junit.platform.engine.FilterResult.included; import java.util.regex.Pattern; import org.jspecify.annotations.Nullable; import org.junit.platform.engine.FilterResult; import org.junit.platform.engine.TestDescriptor; /** * {@link MethodFilter} that matches fully qualified method names against * patterns in the form of regular expressions. * *

If the fully qualified name of a method matches against at least one * pattern, the method will be included. * * @since 1.12 */ class IncludeMethodFilter extends AbstractMethodFilter { IncludeMethodFilter(String... patterns) { super(patterns); } @Override public FilterResult apply(TestDescriptor descriptor) { String methodName = getFullyQualifiedMethodNameFromDescriptor(descriptor); return findMatchingPattern(methodName) // .map(pattern -> included(formatInclusionReason(methodName, pattern))) // .orElseGet(() -> excluded(formatExclusionReason(methodName))); } private String formatInclusionReason(@Nullable String methodName, Pattern pattern) { return "Method name [%s] matches included pattern: '%s'".formatted(methodName, pattern); } private String formatExclusionReason(@Nullable String methodName) { return "Method name [%s] does not match any included pattern: %s".formatted(methodName, patternDescription); } @Override public String toString() { return "%s that includes method names that match one of the following regular expressions: %s".formatted( getClass().getSimpleName(), patternDescription); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; /** * The {@code Launcher} API is the main entry point for client code that * wishes to discover and execute tests using one or more * {@linkplain org.junit.platform.engine.TestEngine test engines}. * *

Implementations of this interface are responsible for determining * the set of test engines to delegate to at runtime and for ensuring that * each test engine has an * {@linkplain org.junit.platform.engine.TestEngine#getId ID} that is unique * among the registered test engines. For example, the default implementation * returned by {@link org.junit.platform.launcher.core.LauncherFactory#create} * dynamically discovers test engines via Java's {@link java.util.ServiceLoader * ServiceLoader} mechanism. * *

Test discovery and execution require a {@link LauncherDiscoveryRequest} * that is passed to all registered engines. Each engine decides which tests it * can discover and execute according to the supplied request. * *

Prior to executing tests, clients of this interface should * {@linkplain #registerTestExecutionListeners register} one or more * {@link TestExecutionListener} instances in order to get feedback about the * progress and results of test execution. Listeners will be notified of events * in the order in which they were registered. The default implementation * returned by {@link org.junit.platform.launcher.core.LauncherFactory#create} * dynamically discovers test execution listeners via Java's * {@link java.util.ServiceLoader ServiceLoader} mechanism. * * @since 1.0 * @see LauncherDiscoveryRequest * @see TestPlan * @see TestExecutionListener * @see org.junit.platform.launcher.core.LauncherFactory * @see org.junit.platform.engine.TestEngine */ @API(status = STABLE, since = "1.0") public interface Launcher { /** * Register one or more listeners for test discovery. * * @param listeners the listeners to be notified of test discovery events; * never {@code null} or empty */ @API(status = STABLE, since = "1.10") void registerLauncherDiscoveryListeners(LauncherDiscoveryListener... listeners); /** * Register one or more listeners for test execution. * * @param listeners the listeners to be notified of test execution events; * never {@code null} or empty */ void registerTestExecutionListeners(TestExecutionListener... listeners); /** * Discover tests and build a {@link TestPlan} according to the supplied * {@link LauncherDiscoveryRequest} by querying all registered engines and * collecting their results. * * @apiNote This method may be called to generate a preview of the test * tree. The resulting {@link TestPlan} is unmodifiable and may be passed to * {@link #execute(TestPlan, TestExecutionListener...)} for execution at * most once. * * @param discoveryRequest the launcher discovery request; never {@code null} * @return an unmodifiable {@code TestPlan} that contains all resolved * {@linkplain TestIdentifier identifiers} from all registered engines */ TestPlan discover(LauncherDiscoveryRequest discoveryRequest); /** * Execute a {@link TestPlan} which is built according to the supplied * {@link LauncherDiscoveryRequest} by querying all registered engines and * collecting their results, and notify * {@linkplain #registerTestExecutionListeners registered listeners} about * the progress and results of the execution. * *

Supplied test execution listeners are registered in addition to already * registered listeners but only for the supplied launcher discovery request. * * @apiNote Calling this method will cause test discovery to be executed for * all registered engines. If the same {@link LauncherDiscoveryRequest} was * previously passed to {@link #discover(LauncherDiscoveryRequest)}, you * should instead call {@link #execute(TestPlan, TestExecutionListener...)} * and pass the already acquired {@link TestPlan} to avoid the potential * performance degradation (e.g., classpath scanning) of running test * discovery twice. * * @param discoveryRequest the launcher discovery request; never {@code null} * @param listeners additional test execution listeners; never {@code null} * @see #execute(TestPlan, TestExecutionListener...) * @see #execute(LauncherExecutionRequest) */ void execute(LauncherDiscoveryRequest discoveryRequest, TestExecutionListener... listeners); /** * Execute the supplied {@link TestPlan} and notify * {@linkplain #registerTestExecutionListeners registered listeners} about * the progress and results of the execution. * *

Supplied test execution listeners are registered in addition to * already registered listeners but only for the execution of the supplied * test plan. * * @apiNote The supplied {@link TestPlan} must not have been executed * previously. * * @param testPlan the test plan to execute; never {@code null} * @param listeners additional test execution listeners; never {@code null} * @since 1.4 * @see #execute(LauncherDiscoveryRequest, TestExecutionListener...) * @see #execute(LauncherExecutionRequest) */ @API(status = STABLE, since = "1.4") void execute(TestPlan testPlan, TestExecutionListener... listeners); /** * Execute tests according to the supplied {@link LauncherExecutionRequest} and * notify {@linkplain #registerTestExecutionListeners registered listeners} about * the progress and results of the execution. * *

Test execution listeners supplied * {@linkplain LauncherExecutionRequest#getAdditionalTestExecutionListeners() * as part of the request} are registered in addition to already registered * listeners but only for the supplied execution request. * * @apiNote If the execution request contains a {@link TestPlan} rather than * a {@link LauncherDiscoveryRequest}, it must not have been executed * previously. * *

If the execution request contains a {@link LauncherDiscoveryRequest}, * calling this method will cause test discovery to be executed for all * registered engines. If the same {@link LauncherDiscoveryRequest} was * previously passed to {@link #discover(LauncherDiscoveryRequest)}, you * should instead provide the resulting {@link TestPlan} as part of the * supplied execution request to avoid the potential performance degradation * (e.g., classpath scanning) of running test discovery twice. * * @param executionRequest the launcher execution request; never {@code null} * @since 6.0 * @see #execute(LauncherDiscoveryRequest, TestExecutionListener...) * @see #execute(TestPlan, TestExecutionListener...) */ @API(status = MAINTAINED, since = "6.0") void execute(LauncherExecutionRequest executionRequest); } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; import org.junit.platform.commons.util.ClassNamePatternFilterUtils; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.reporting.ReportEntry; /** * Collection of constants related to {@link Launcher}. * * @since 1.3 * @see org.junit.platform.engine.ConfigurationParameters */ @API(status = STABLE, since = "1.7") public class LauncherConstants { /** * Property name used to enable capturing output to {@link System#out}: * {@value} * *

By default, output to {@link System#out} is not captured. * *

If enabled, the JUnit Platform captures the corresponding output and * publishes it as a {@link ReportEntry} using the * {@value #STDOUT_REPORT_ENTRY_KEY} key immediately before reporting the * test identifier as finished. * * @see #STDOUT_REPORT_ENTRY_KEY * @see ReportEntry * @see TestExecutionListener#reportingEntryPublished(TestIdentifier, ReportEntry) */ public static final String CAPTURE_STDOUT_PROPERTY_NAME = "junit.platform.output.capture.stdout"; /** * Property name used to enable capturing output to {@link System#err}: * {@value} * *

By default, output to {@link System#err} is not captured. * *

If enabled, the JUnit Platform captures the corresponding output and * publishes it as a {@link ReportEntry} using the * {@value #STDERR_REPORT_ENTRY_KEY} key immediately before reporting the * test identifier as finished. * * @see #STDERR_REPORT_ENTRY_KEY * @see ReportEntry * @see TestExecutionListener#reportingEntryPublished(TestIdentifier, ReportEntry) */ public static final String CAPTURE_STDERR_PROPERTY_NAME = "junit.platform.output.capture.stderr"; /** * Property name used to configure the maximum number of bytes for buffering * to use per thread and output type if output capturing is enabled: * {@value} * *

Value must be an integer; defaults to {@value #CAPTURE_MAX_BUFFER_DEFAULT}. * * @see #CAPTURE_MAX_BUFFER_DEFAULT */ public static final String CAPTURE_MAX_BUFFER_PROPERTY_NAME = "junit.platform.output.capture.maxBuffer"; /** * Default maximum number of bytes for buffering to use per thread and * output type if output capturing is enabled. * * @see #CAPTURE_MAX_BUFFER_PROPERTY_NAME */ public static final int CAPTURE_MAX_BUFFER_DEFAULT = 4 * 1024 * 1024; /** * Key used to publish captured output to {@link System#out} as part of a * {@link ReportEntry}: {@value} */ public static final String STDOUT_REPORT_ENTRY_KEY = "stdout"; /** * Key used to publish captured output to {@link System#err} as part of a * {@link ReportEntry}: {@value} */ public static final String STDERR_REPORT_ENTRY_KEY = "stderr"; /** * Property name used to provide patterns for deactivating * {@linkplain TestExecutionListener listeners} registered via the * {@link java.util.ServiceLoader ServiceLoader} mechanism: {@value} * *

Pattern Matching Syntax

* *

If the property value consists solely of an asterisk ({@code *}), all * listeners will be deactivated. Otherwise, the property value will be treated * as a comma-separated list of patterns where each individual pattern will be * matched against the fully qualified class name (FQCN) of each registered * listener. Any dot ({@code .}) in a pattern will match against a dot ({@code .}) * or a dollar sign ({@code $}) in a FQCN. Any asterisk ({@code *}) will match * against one or more characters in a FQCN. All other characters in a pattern * will be matched one-to-one against a FQCN. * *

Examples

* *
    *
  • {@code *}: deactivates all listeners. *
  • {@code org.junit.*}: deactivates every listener under the {@code org.junit} * base package and any of its subpackages. *
  • {@code *.MyListener}: deactivates every listener whose simple class name is * exactly {@code MyListener}. *
  • {@code *System*, *Dev*}: deactivates every listener whose FQCN contains * {@code System} or {@code Dev}. *
  • {@code org.example.MyListener, org.example.TheirListener}: deactivates * listeners whose FQCN is exactly {@code org.example.MyListener} or * {@code org.example.TheirListener}. *
* *

Only listeners registered via the {@code ServiceLoader} mechanism can * be deactivated. In other words, any listener registered explicitly via the * {@link LauncherDiscoveryRequest} cannot be deactivated via this * configuration parameter. * *

In addition, since execution listeners are registered before the test * run starts, this configuration parameter can only be supplied as a JVM * system property or via the JUnit Platform configuration file but cannot * be supplied in the {@link LauncherDiscoveryRequest}} that is passed to * the {@link Launcher}. * * @see #DEACTIVATE_ALL_LISTENERS_PATTERN * @see org.junit.platform.launcher.TestExecutionListener */ public static final String DEACTIVATE_LISTENERS_PATTERN_PROPERTY_NAME = "junit.platform.execution.listeners.deactivate"; /** * Wildcard pattern which signals that all listeners registered via the * {@link java.util.ServiceLoader ServiceLoader} mechanism should be deactivated: * {@value} * * @see #DEACTIVATE_LISTENERS_PATTERN_PROPERTY_NAME * @see org.junit.platform.launcher.TestExecutionListener */ public static final String DEACTIVATE_ALL_LISTENERS_PATTERN = ClassNamePatternFilterUtils.ALL_PATTERN; /** * Property name used to enable support for * {@link LauncherInterceptor} instances to be registered via the * {@link java.util.ServiceLoader ServiceLoader} mechanism: {@value} * *

By default, interceptor registration is disabled. * *

Since interceptors are registered before the test run starts, this * configuration parameter can only be supplied as a JVM system property or * via the JUnit Platform configuration file but cannot be supplied in the * {@link LauncherDiscoveryRequest}} that is passed to the {@link Launcher}. * * @see LauncherInterceptor */ @API(status = MAINTAINED, since = "1.13.3") public static final String ENABLE_LAUNCHER_INTERCEPTORS = "junit.platform.launcher.interceptors.enabled"; /** * Property name used to enable dry-run mode for test execution. * *

When dry-run mode is enabled, no tests will be executed. Instead, all * registered {@link TestExecutionListener TestExecutionListeners} will * receive events for all test descriptors that are part of the discovered * {@link TestPlan}. All containers will be reported as successful and all * tests as skipped. This can be useful to test changes in the configuration * of a build or to verify a listener is called as expected without having * to wait for all tests to be executed. * *

Value must be either {@code true} or {@code false}; defaults to {@code false}. */ @API(status = MAINTAINED, since = "1.13.3") public static final String DRY_RUN_PROPERTY_NAME = "junit.platform.execution.dryRun.enabled"; /** * Property name used to enable or disable stack trace pruning. * *

By default, stack trace pruning is enabled. * * @see org.junit.platform.launcher.core.EngineExecutionOrchestrator */ @API(status = MAINTAINED, since = "1.13.3") public static final String STACKTRACE_PRUNING_ENABLED_PROPERTY_NAME = "junit.platform.stacktrace.pruning.enabled"; /** * Property name used to configure the output directory for reporting. * *

If set, value must be a valid path that will be created if it doesn't * exist. If not set, the default output directory will be determined by the * reporting engine based on the current working directory. * * @since 1.12 * @see #OUTPUT_DIR_UNIQUE_NUMBER_PLACEHOLDER * @see org.junit.platform.engine.OutputDirectoryCreator * @see EngineDiscoveryRequest#getOutputDirectoryCreator() * @see TestPlan#getOutputDirectoryCreator() */ @API(status = MAINTAINED, since = "1.13.3") public static final String OUTPUT_DIR_PROPERTY_NAME = "junit.platform.reporting.output.dir"; /** * Placeholder for use in {@link #OUTPUT_DIR_PROPERTY_NAME} that will be * replaced with a unique number. * *

This can be used to create a unique output directory for each test * run. For example, if multiple forks are used, each fork can be configured * to write its output to a separate directory. * * @since 1.12 * @see #OUTPUT_DIR_PROPERTY_NAME */ @API(status = MAINTAINED, since = "1.13.3") public static final String OUTPUT_DIR_UNIQUE_NUMBER_PLACEHOLDER = "{uniqueNumber}"; /** * Property name used to configure the critical severity of issues * encountered during test discovery. * *

If an engine reports an issue with a severity equal to or higher than * the configured critical severity, its tests will not be executed. * Depending on {@link #DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME}, a * {@link org.junit.platform.launcher.core.DiscoveryIssueException} listing * all critical issues will be thrown during discovery or be reported as * engine-level failure during execution. * *

Supported Values

* *

Supported values include names of enum constants defined in * {@link org.junit.platform.engine.DiscoveryIssue.Severity Severity}, * ignoring case. * *

If not specified, the default is "error" which corresponds to * {@code Severity.ERROR)}. * * @since 1.13 * @see org.junit.platform.engine.DiscoveryIssue.Severity */ @API(status = EXPERIMENTAL, since = "6.0") public static final String CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME = "junit.platform.discovery.issue.severity.critical"; /** * Property name used to configure the phase that critical discovery issues * should cause a failure * *

Supported Values

* *

Supported values are "discovery" or "execution". * *

If not specified, the {@code Launcher} will report discovery issues * during the discovery phase if * {@link Launcher#discover(LauncherDiscoveryRequest)} is called, and during * the execution phase if {@link Launcher#execute(LauncherExecutionRequest)} * is called. * * @since 1.13 * @see #CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME */ @API(status = EXPERIMENTAL, since = "6.0") public static final String DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME = "junit.platform.discovery.issue.failure.phase"; /** * Property name used to enable the experimental memory cleanup * mode. * *

Supported values are {@code true} or {@code false}; defaults to * {@code false}. * *

If enabled, the {@link Launcher} removes finished or skipped tests and * their children from the {@link TestPlan} and engine-internal data * structures right away to reduce memory consumption, particularly in the * presence of many dynamically reported tests. * *

This is an experimental feature since it breaks some existing * {@link TestExecutionListener} implementations, in particular if they rely * on the test plan to provide information about all executed tests * and containers after they have been executed. Not all * {@link org.junit.platform.engine.TestEngine TestEngine} implementations * may be compatible, either. In both cases, please report issues to the * maintainers of the affected listeners or engines. * * @since 6.1 */ @API(status = EXPERIMENTAL, since = "6.1") public static final String MEMORY_CLEANUP_ENABLED_PROPERTY_NAME = "junit.platform.execution.memory.cleanup.enabled"; private LauncherConstants() { /* no-op */ } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; import org.junit.platform.engine.EngineDiscoveryListener; import org.junit.platform.engine.UniqueId; /** * Register a concrete implementation of this interface with a * {@link org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder} or * {@link Launcher} to be notified of events that occur during test discovery. * *

All methods in this interface have empty default implementations. * Concrete implementations may therefore override one or more of these methods * to be notified of the selected events. * *

JUnit provides default implementations that are created via the factory * methods in * {@link org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners}. * *

The methods declared in this interface are called by the {@link Launcher} * created via the {@link org.junit.platform.launcher.core.LauncherFactory} * during test discovery. * * @since 1.6 * @see org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners * @see LauncherDiscoveryRequest#getDiscoveryListener() * @see org.junit.platform.launcher.core.LauncherConfig.Builder#addLauncherDiscoveryListeners */ @API(status = STABLE, since = "1.11") public interface LauncherDiscoveryListener extends EngineDiscoveryListener { /** * No-op implementation of {@code LauncherDiscoveryListener} */ LauncherDiscoveryListener NOOP = new LauncherDiscoveryListener() { }; /** * Called when test discovery is about to be started. * * @param request the request for which discovery is being started * @since 1.8 */ @API(status = STABLE, since = "1.10") default void launcherDiscoveryStarted(LauncherDiscoveryRequest request) { } /** * Called when test discovery has finished. * * @param request the request for which discovery has finished * @since 1.8 */ @API(status = STABLE, since = "1.10") default void launcherDiscoveryFinished(LauncherDiscoveryRequest request) { } /** * Called when test discovery is about to be started for an engine. * * @param engineId the unique ID of the engine descriptor */ default void engineDiscoveryStarted(UniqueId engineId) { } /** * Called when test discovery has finished for an engine. * *

Exceptions thrown by implementations of this method will cause the * complete test discovery to be aborted. * * @param engineId the unique ID of the engine descriptor * @param result the discovery result of the supplied engine * @see EngineDiscoveryResult */ default void engineDiscoveryFinished(UniqueId engineId, EngineDiscoveryResult result) { } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryRequest.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher; import static org.apiguardian.api.API.Status.STABLE; import java.util.List; import org.apiguardian.api.API; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.DiscoveryFilter; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.EngineDiscoveryRequest; /** * {@code LauncherDiscoveryRequest} extends the {@link EngineDiscoveryRequest} API * with additional filters that are applied by the {@link Launcher} itself. * *

Specifically, a {@code LauncherDiscoveryRequest} contains the following. * *

    *
  • {@linkplain EngineFilter Engine Filters}: filters that are applied before * each {@code TestEngine} is executed. All of them have to include an engine for it * to contribute to the test plan.
  • *
  • {@linkplain ConfigurationParameters Configuration Parameters}: configuration * parameters that can be used to influence the discovery process
  • *
  • {@linkplain DiscoverySelector Discovery Selectors}: components that select * resources that a {@code TestEngine} can use to discover tests
  • *
  • {@linkplain DiscoveryFilter Discovery Filters}: filters that should be applied * by {@code TestEngines} during test discovery. All of them have to include a * resource for it to end up in the test plan.
  • *
  • {@linkplain PostDiscoveryFilter Post-Discovery Filters}: filters that will be * applied by the {@code Launcher} after {@code TestEngines} have performed test * discovery. All of them have to include a {@code TestDescriptor} for it to end up * in the test plan.
  • *
* *

This interface is not intended to be implemented by clients. * * @since 1.0 * @see org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder * @see EngineDiscoveryRequest * @see EngineFilter * @see ConfigurationParameters * @see DiscoverySelector * @see DiscoveryFilter * @see PostDiscoveryFilter * @see #getEngineFilters() * @see #getPostDiscoveryFilters() */ @API(status = STABLE, since = "1.0") public interface LauncherDiscoveryRequest extends EngineDiscoveryRequest { /** * Get the {@code EngineFilters} for this request. * *

The returned filters are to be combined using AND semantics, i.e. all * of them have to include an engine for it to contribute to the test plan. * * @return the list of {@code EngineFilters} for this request; never * {@code null} but potentially empty */ List getEngineFilters(); /** * Get the {@code PostDiscoveryFilters} for this request. * *

The returned filters are to be combined using AND semantics, i.e. all * of them have to include a {@code TestDescriptor} for it to end up in the * test plan. * * @return the list of {@code PostDiscoveryFilters} for this request; never * {@code null} but potentially empty */ List getPostDiscoveryFilters(); /** * Get the {@link LauncherDiscoveryListener} for this request. * *

The default implementation returns a no-op listener that ignores all * calls so that engines that call this methods can be used with an earlier * version of the JUnit Platform that did not yet include it. * * @return the discovery listener; never {@code null} * @since 1.6 */ @API(status = STABLE, since = "1.10") @Override default LauncherDiscoveryListener getDiscoveryListener() { return LauncherDiscoveryListener.NOOP; } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherExecutionRequest.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher; import static org.apiguardian.api.API.Status.MAINTAINED; import java.util.Collection; import java.util.Optional; import org.apiguardian.api.API; import org.junit.platform.engine.CancellationToken; /** * {@code LauncherExecutionRequest} encapsulates a request for test execution * passed to the {@link Launcher}. * *

Most importantly, a {@code LauncherExecutionRequest} contains either a * {@link LauncherDiscoveryRequest} for on-the-fly test discovery or a * {@link TestPlan} that has previously been discovered. * *

Moreover, a {@code LauncherExecutionRequest} may contain the following: * *

    *
  • Additional {@linkplain TestExecutionListener Test Execution Listeners} * that should be notified of events pertaining to this execution request.
  • *
* *

This interface is not intended to be implemented by clients. * * @since 6.0 * @see org.junit.platform.launcher.core.LauncherExecutionRequestBuilder * @see Launcher#execute(LauncherExecutionRequest) */ @API(status = MAINTAINED, since = "6.0") public interface LauncherExecutionRequest { /** * {@return the test plan for this execution request} * *

If absent, a {@link TestPlan} will be present. */ Optional getTestPlan(); /** * {@return the discovery request for this execution request} * *

If absent, a {@link TestPlan} will be present. */ Optional getDiscoveryRequest(); /** * {@return the collection of additional test execution listeners that * should be notified about events pertaining to this execution request} */ Collection getAdditionalTestExecutionListeners(); /** * {@return the cancellation token for this execution request} */ CancellationToken getCancellationToken(); } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherInterceptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher; import static org.apiguardian.api.API.Status.MAINTAINED; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; /** * Interceptor for test discovery and execution by a {@link Launcher} in the * context of a {@link LauncherSession}. * *

Interceptors are instantiated once per {@link LauncherSession} and closed * after the session is closed. They can * {@linkplain #intercept(Invocation) intercept} the following invocations: *

    *
  • * creation of {@link LauncherSessionListener} instances registered via the * {@link java.util.ServiceLoader ServiceLoader} mechanism *
  • *
  • * creation of {@link Launcher} instances *
  • *
  • * calls to {@link Launcher#discover(LauncherDiscoveryRequest)}, * {@link Launcher#execute(TestPlan, TestExecutionListener...)}, * {@link Launcher#execute(LauncherDiscoveryRequest, TestExecutionListener...)}, * and {@link Launcher#execute(LauncherExecutionRequest)}, *
  • *
* *

Implementations of this interface can be registered via the * {@link java.util.ServiceLoader ServiceLoader} mechanism by additionally * setting the {@value LauncherConstants#ENABLE_LAUNCHER_INTERCEPTORS} * configuration parameter to {@code true}. * *

A typical use case is to create a custom {@link ClassLoader} in the * constructor of the implementing class, replace the * {@link Thread#setContextClassLoader(ClassLoader) contextClassLoader} of the * current thread while {@link #intercept(Invocation) intercepting} invocations, * and close the custom {@code ClassLoader} in {@link #close()} * * @since 1.10 * @see Launcher * @see LauncherSession * @see LauncherConstants#ENABLE_LAUNCHER_INTERCEPTORS */ @API(status = MAINTAINED, since = "1.13.3") public interface LauncherInterceptor { /** * Intercept the supplied invocation. * *

Implementations must call {@link Invocation#proceed()} exactly once. * * @param invocation the intercepted invocation; never {@code null} * @return the result of the invocation */ T intercept(Invocation invocation); /** * Closes this interceptor. * *

Any resources held by this interceptor should be released by this * method. */ void close(); /** * An invocation that can be intercepted. * *

This interface is not intended to be implemented by clients. */ interface Invocation { T proceed(); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; import org.junit.platform.engine.support.store.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.core.LauncherFactory; /** * The {@code LauncherSession} API is the main entry point for client code that * wishes to repeatedly discover and execute tests using one * or more {@linkplain org.junit.platform.engine.TestEngine test engines}. * * @since 1.8 * @see Launcher * @see LauncherSessionListener * @see LauncherFactory */ @API(status = STABLE, since = "1.10") public interface LauncherSession extends AutoCloseable { /** * Get the {@link Launcher} associated with this session. * *

Any call to the launcher returned by this method after the session has * been closed will throw an exception. */ Launcher getLauncher(); /** * Close this session and notify all registered * {@link LauncherSessionListener LauncherSessionListeners}. * * @apiNote The behavior of calling this method concurrently with any call * to the {@link Launcher} returned by {@link #getLauncher()} is currently * undefined. */ @Override void close(); /** * Get the {@link NamespacedHierarchicalStore} associated with this session. * *

All stored values that implement {@link AutoCloseable} are notified by * invoking their {@code close()} methods when this session is closed. * *

Any call to the store returned by this method after the session has * been closed will throw an exception. * * @since 1.13 * @see NamespacedHierarchicalStore */ @API(status = MAINTAINED, since = "1.13.3") NamespacedHierarchicalStore getStore(); } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSessionListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; import org.junit.platform.launcher.core.LauncherConfig; import org.junit.platform.launcher.core.LauncherFactory; /** * Register an implementation of this interface to be notified when a * {@link LauncherSession} is opened and closed. * *

A {@code LauncherSessionListener} can be registered programmatically with * the {@link LauncherConfig.Builder#addLauncherSessionListeners LauncherConfig} * passed to the * {@link LauncherFactory#openSession(LauncherConfig) LauncherFactory} or * automatically via Java's {@link java.util.ServiceLoader ServiceLoader} * mechanism. * *

All methods in this class have empty default implementations. * Subclasses may therefore override one or more of these methods to be notified * of the selected events. * *

The methods declared in this interface are called by the {@link Launcher} * or {@link LauncherSession} created via the {@link LauncherFactory}. * * @since 1.8 * @see LauncherSession * @see LauncherConfig.Builder#addLauncherSessionListeners * @see LauncherFactory */ @API(status = STABLE, since = "1.10") public interface LauncherSessionListener { /** * No-op implementation of {@code LauncherSessionListener} */ LauncherSessionListener NOOP = new LauncherSessionListener() { }; /** * Called when a launcher session was opened. * * @param session the opened session */ default void launcherSessionOpened(LauncherSession session) { } /** * Called when a launcher session was closed. * * @param session the closed session */ default void launcherSessionClosed(LauncherSession session) { } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/MethodFilter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher; import static org.apiguardian.api.API.Status.MAINTAINED; import java.lang.reflect.Method; import java.util.List; import org.apiguardian.api.API; /** * {@link PostDiscoveryFilter} that is applied to the fully qualified * {@link Method} name without parameters. * * @since 1.12 * @see #includeMethodNamePatterns(String...) * @see #excludeMethodNamePatterns(String...) */ @API(status = MAINTAINED, since = "1.13.3") public interface MethodFilter extends PostDiscoveryFilter { /** * Create a new include {@link MethodFilter} based on the * supplied patterns. * *

The patterns are combined using OR semantics, i.e. if the fully * qualified name of a method matches against at least one of the patterns, * the method will be included in the result set. * * @param patterns regular expressions to match against fully qualified * method names; never {@code null}, empty, or containing {@code null} * @see Class#getName() * @see Method#getName() * @see #includeMethodNamePatterns(List) * @see #excludeMethodNamePatterns(String...) */ static MethodFilter includeMethodNamePatterns(String... patterns) { return new IncludeMethodFilter(patterns); } /** * Create a new include {@link MethodFilter} based on the * supplied patterns. * *

The patterns are combined using OR semantics, i.e. if the fully * qualified name of a method matches against at least one of the patterns, * the method will be included in the result set. * * @param patterns regular expressions to match against fully qualified * method names; never {@code null}, empty, or containing {@code null} * @see Class#getName() * @see Method#getName() * @see #includeMethodNamePatterns(String...) * @see #excludeMethodNamePatterns(String...) */ static MethodFilter includeMethodNamePatterns(List patterns) { return includeMethodNamePatterns(patterns.toArray(new String[0])); } /** * Create a new exclude {@link MethodFilter} based on the * supplied patterns. * *

The patterns are combined using OR semantics, i.e. if the fully * qualified name of a method matches against at least one of the patterns, * the method will be excluded from the result set. * * @param patterns regular expressions to match against fully qualified * method names; never {@code null}, empty, or containing {@code null} * @see Class#getName() * @see Method#getName() * @see #excludeMethodNamePatterns(List) * @see #includeMethodNamePatterns(String...) */ static MethodFilter excludeMethodNamePatterns(String... patterns) { return new ExcludeMethodFilter(patterns); } /** * Create a new exclude {@link MethodFilter} based on the * supplied patterns. * *

The patterns are combined using OR semantics, i.e. if the fully * qualified name of a method matches against at least one of the patterns, * the method will be excluded from the result set. * * @param patterns regular expressions to match against fully qualified * method names; never {@code null}, empty, or containing {@code null} * @see Class#getName() * @see Method#getName() * @see #excludeMethodNamePatterns(String...) * @see #includeMethodNamePatterns(String...) */ static MethodFilter excludeMethodNamePatterns(List patterns) { return excludeMethodNamePatterns(patterns.toArray(new String[0])); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/PostDiscoveryFilter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; import org.junit.platform.engine.Filter; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestEngine; /** * A {@code PostDiscoveryFilter} is applied to {@link TestDescriptor TestDescriptors} * after test discovery. * *

A {@code PostDiscoveryFilter} must not modify the * {@link TestDescriptor TestDescriptors} it is applied to in any way. * *

{@link TestEngine TestEngines} must not apply * {@code PostDiscoveryFilters} during the test discovery phase. * * @since 1.0 * @see LauncherDiscoveryRequest * @see TestEngine */ @API(status = STABLE, since = "1.0") public interface PostDiscoveryFilter extends Filter { } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/TagFilter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher; import static java.util.Arrays.asList; import static org.apiguardian.api.API.Status.STABLE; import java.util.List; import java.util.Set; import java.util.function.Supplier; import java.util.stream.Collectors; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.FilterResult; import org.junit.platform.engine.TestTag; import org.junit.platform.launcher.tagexpression.TagExpression; /** * Factory methods for creating {@link PostDiscoveryFilter PostDiscoveryFilters} * based on included and excluded tags or tag expressions. * *

Tag expressions are boolean expressions with the following allowed * operators: {@code !} (not), {@code &} (and), and {@code |} (or). Parentheses * can be used to adjust for operator precedence. Please refer to the * JUnit User Guide * for usage examples. * *

Please note that a tag name is a valid tag expression. Thus, wherever a tag * expression can be used, a single tag name can also be used. * * @since 1.0 * @see #includeTags(String...) * @see #excludeTags(String...) * @see TestTag */ @API(status = STABLE, since = "1.0") public final class TagFilter { private TagFilter() { /* no-op */ } /** * Create an include filter based on the supplied tag expressions. * *

Containers and tests will only be executed if their tags match at * least one of the supplied included tag expressions. * * @param tagExpressions the included tag expressions; never {@code null} or * empty * @throws PreconditionViolationException if the supplied tag expressions * array is {@code null} or empty, or if any individual tag expression is * not syntactically valid * @see #includeTags(List) * @see TestTag#isValid(String) */ public static PostDiscoveryFilter includeTags(String... tagExpressions) throws PreconditionViolationException { Preconditions.notNull(tagExpressions, "array of tag expressions must not be null"); return includeTags(asList(tagExpressions)); } /** * Create an include filter based on the supplied tag expressions. * *

Containers and tests will only be executed if their tags match at * least one of the supplied included tag expressions. * * @param tagExpressions the included tag expressions; never {@code null} or * empty * @throws PreconditionViolationException if the supplied tag expressions * array is {@code null} or empty, or if any individual tag expression is * not syntactically valid * @see #includeTags(String...) * @see TestTag#isValid(String) */ public static PostDiscoveryFilter includeTags(List tagExpressions) throws PreconditionViolationException { Preconditions.notEmpty(tagExpressions, "list of tag expressions must not be null or empty"); return includeMatching(tagExpressions); } /** * Create an exclude filter based on the supplied tag expressions. * *

Containers and tests will only be executed if their tags do * not match any of the supplied excluded tag expressions. * * @param tagExpressions the excluded tag expressions; never {@code null} or * empty * @throws PreconditionViolationException if the supplied tag expressions * array is {@code null} or empty, or if any individual tag expression is * not syntactically valid * @see #excludeTags(List) * @see TestTag#isValid(String) */ public static PostDiscoveryFilter excludeTags(String... tagExpressions) throws PreconditionViolationException { Preconditions.notNull(tagExpressions, "array of tag expressions must not be null"); return excludeTags(asList(tagExpressions)); } /** * Create an exclude filter based on the supplied tag expressions. * *

Containers and tests will only be executed if their tags do * not match any of the supplied excluded tag expressions. * * @param tagExpressions the excluded tag expressions; never {@code null} or * empty * @throws PreconditionViolationException if the supplied tag expressions * array is {@code null} or empty, or if any individual tag expression is * not syntactically valid * @see #excludeTags(String...) * @see TestTag#isValid(String) */ public static PostDiscoveryFilter excludeTags(List tagExpressions) throws PreconditionViolationException { Preconditions.notEmpty(tagExpressions, "list of tag expressions must not be null or empty"); return excludeMatching(tagExpressions); } private static PostDiscoveryFilter includeMatching(List tagExpressions) { Supplier<@Nullable String> inclusionReason = () -> inclusionReasonExpressionSatisfy(tagExpressions); Supplier<@Nullable String> exclusionReason = () -> exclusionReasonExpressionNotSatisfy(tagExpressions); List parsedTagExpressions = parseAll(tagExpressions); return descriptor -> { Set tags = descriptor.getTags(); boolean included = parsedTagExpressions.stream().anyMatch(expression -> expression.evaluate(tags)); return FilterResult.includedIf(included, inclusionReason, exclusionReason); }; } private static String inclusionReasonExpressionSatisfy(List tagExpressions) { return "included because tags match expression(s): [%s]".formatted(formatToString(tagExpressions)); } private static String exclusionReasonExpressionNotSatisfy(List tagExpressions) { return "excluded because tags do not match tag expression(s): [%s]".formatted(formatToString(tagExpressions)); } private static PostDiscoveryFilter excludeMatching(List tagExpressions) { Supplier<@Nullable String> inclusionReason = () -> inclusionReasonExpressionNotSatisfy(tagExpressions); Supplier<@Nullable String> exclusionReason = () -> exclusionReasonExpressionSatisfy(tagExpressions); List parsedTagExpressions = parseAll(tagExpressions); return descriptor -> { Set tags = descriptor.getTags(); boolean included = parsedTagExpressions.stream().noneMatch(expression -> expression.evaluate(tags)); return FilterResult.includedIf(included, inclusionReason, exclusionReason); }; } private static String inclusionReasonExpressionNotSatisfy(List tagExpressions) { return "included because tags do not match expression(s): [%s]".formatted(formatToString(tagExpressions)); } private static String exclusionReasonExpressionSatisfy(List tagExpressions) { return "excluded because tags match tag expression(s): [%s]".formatted(formatToString(tagExpressions)); } private static String formatToString(List tagExpressions) { return tagExpressions.stream().map(String::strip).sorted().collect(Collectors.joining(",")); } private static List parseAll(List tagExpressions) { return tagExpressions.stream().map(TagFilter::parse).toList(); } private static TagExpression parse(@Nullable String tagExpression) { return TagExpression.parseFrom(tagExpression).tagExpressionOrThrow( message -> new PreconditionViolationException( "Unable to parse tag expression \"" + tagExpression + "\": " + message)); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestExecutionListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; import org.junit.platform.commons.util.UnrecoverableExceptions; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.TestExecutionResult.Status; import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; /** * Register a concrete implementation of this interface with a {@link Launcher} * or {@link LauncherExecutionRequest} to be notified of events that occur * during test execution. * *

All methods in this interface have empty default implementations. * Concrete implementations may therefore override one or more of these methods * to be notified of the selected events. * *

All {@code TestExecutionListener} methods are called sequentially. Methods * for start events are called in registration order while methods for finish * events are called in reverse order. Test case execution won't start before * all {@link #executionStarted(TestIdentifier)} calls have returned. * *

If an exception is thrown by an implementation of a method of this * interface, the exception will be caught and logged unless it is deemed * {@linkplain UnrecoverableExceptions unrecoverable}. In consequence, a * {@code TestExecutionListener} cannot cause test execution to fail or abort it * early by throwing an exception. * *

JUnit provides two example implementations. * *

    *
  • {@link org.junit.platform.launcher.listeners.LoggingListener}
  • *
  • {@link org.junit.platform.launcher.listeners.SummaryGeneratingListener}
  • *
* *

Contrary to JUnit 4, {@linkplain org.junit.platform.engine.TestEngine test engines} * are supposed to report events not only for {@linkplain TestIdentifier identifiers} * that represent executable leaves in the {@linkplain TestPlan test plan} but also * for all intermediate containers. However, while both the JUnit Vintage and JUnit * Jupiter engines comply with this contract, there is no way to guarantee this for * third-party engines. * *

A {@code TestExecutionListener} can access * {@linkplain org.junit.platform.engine.ConfigurationParameters configuration * parameters} via the {@link TestPlan#getConfigurationParameters() * getConfigurationParameters()} method in the {@code TestPlan} supplied to * {@link #testPlanExecutionStarted(TestPlan)} and * {@link #testPlanExecutionFinished(TestPlan)}. * *

Note on concurrency: {@link #testPlanExecutionStarted(TestPlan)} and * {@link #testPlanExecutionFinished(TestPlan)} are always called from the same * thread. It is safe to assume that there is at most one {@code TestPlan} * instance at a time. All other methods could be called from different threads * concurrently in case one or multiple test engines execute tests in parallel. * * @since 1.0 * @see Launcher * @see TestPlan * @see TestIdentifier */ @API(status = STABLE, since = "1.0") public interface TestExecutionListener { /** * Called when the execution of the {@link TestPlan} has started, * before any test has been executed. * *

Called from the same thread as {@link #testPlanExecutionFinished(TestPlan)}. * * @param testPlan describes the tree of tests about to be executed */ default void testPlanExecutionStarted(TestPlan testPlan) { } /** * Called when the execution of the {@link TestPlan} has finished, * after all tests have been executed. * *

Called from the same thread as {@link #testPlanExecutionStarted(TestPlan)}. * * @param testPlan describes the tree of tests that have been executed */ default void testPlanExecutionFinished(TestPlan testPlan) { } /** * Called when a new, dynamic {@link TestIdentifier} has been registered. * *

A dynamic test is a test that is not known a-priori and * therefore not contained in the original {@link TestPlan}. * * @param testIdentifier the identifier of the newly registered test * or container */ default void dynamicTestRegistered(TestIdentifier testIdentifier) { } /** * Called when the execution of a leaf or subtree of the {@link TestPlan} * has been skipped. * *

The {@link TestIdentifier} may represent a test or a container. In * the case of a container, no listener methods will be called for any of * its descendants. * *

A skipped test or subtree of tests will never be reported as * {@linkplain #executionStarted started} or * {@linkplain #executionFinished finished}. * * @param testIdentifier the identifier of the skipped test or container * @param reason a human-readable message describing why the execution * has been skipped */ default void executionSkipped(TestIdentifier testIdentifier, String reason) { } /** * Called when the execution of a leaf or subtree of the {@link TestPlan} * is about to be started. * *

The {@link TestIdentifier} may represent a test or a container. * *

This method will only be called if the test or container has not * been {@linkplain #executionSkipped skipped}. * *

This method will be called for a container {@code TestIdentifier} * before {@linkplain #executionStarted starting} or * {@linkplain #executionSkipped skipping} any of its children. * * @param testIdentifier the identifier of the started test or container */ default void executionStarted(TestIdentifier testIdentifier) { } /** * Called when the execution of a leaf or subtree of the {@link TestPlan} * has finished, regardless of the outcome. * *

The {@link TestIdentifier} may represent a test or a container. * *

This method will only be called if the test or container has not * been {@linkplain #executionSkipped skipped}. * *

This method will be called for a container {@code TestIdentifier} * after all of its children have been * {@linkplain #executionSkipped skipped} or have * {@linkplain #executionFinished finished}. * *

The {@link TestExecutionResult} describes the result of the execution * for the supplied {@code TestIdentifier}. The result does not include or * aggregate the results of its children. For example, a container with a * failing test will be reported as {@link Status#SUCCESSFUL SUCCESSFUL} even * if one or more of its children are reported as {@link Status#FAILED FAILED}. * * @param testIdentifier the identifier of the finished test or container * @param testExecutionResult the (unaggregated) result of the execution for * the supplied {@code TestIdentifier} * * @see TestExecutionResult */ default void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { } /** * Called when additional test reporting data has been published for * the supplied {@link TestIdentifier}. * *

Can be called at any time during the execution of a test plan. * * @param testIdentifier describes the test or container to which the entry pertains * @param entry the published {@code ReportEntry} */ default void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry entry) { } /** * Called when a file or directory has been published for the supplied * {@link TestIdentifier}. * *

Can be called at any time during the execution of a test plan. * * @param testIdentifier describes the test or container to which the entry pertains * @param file the published {@code FileEntry} * @since 1.12 */ @API(status = MAINTAINED, since = "1.13.3") default void fileEntryPublished(TestIdentifier testIdentifier, FileEntry file) { } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestIdentifier.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher; import static java.util.Collections.emptySet; import static java.util.Collections.unmodifiableSet; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import java.io.Serial; import java.io.Serializable; import java.util.LinkedHashSet; import java.util.Objects; import java.util.Optional; import java.util.Set; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestDescriptor.Type; import org.junit.platform.engine.TestSource; import org.junit.platform.engine.TestTag; import org.junit.platform.engine.UniqueId; /** * Immutable data transfer object that represents a test or container which is * usually part of a {@link TestPlan}. * * @since 1.0 * @see TestPlan */ @API(status = STABLE, since = "1.0") public final class TestIdentifier implements Serializable { @Serial private static final long serialVersionUID = 2L; private final UniqueId uniqueId; private final @Nullable UniqueId parentId; private final String displayName; private final String legacyReportingName; private final @Nullable TestSource source; @SuppressWarnings("serial") // Declared type is Set (not Serializable); actual instances are Serializable. private final Set tags; private final Type type; /** * Factory for creating a new {@link TestIdentifier} from a {@link TestDescriptor}. */ @API(status = INTERNAL, since = "1.0") public static TestIdentifier from(TestDescriptor testDescriptor) { Preconditions.notNull(testDescriptor, "TestDescriptor must not be null"); UniqueId uniqueId = testDescriptor.getUniqueId(); String displayName = testDescriptor.getDisplayName(); TestSource source = testDescriptor.getSource().orElse(null); Set tags = testDescriptor.getTags(); Type type = testDescriptor.getType(); UniqueId parentId = testDescriptor.getParent().map(TestDescriptor::getUniqueId).orElse(null); String legacyReportingName = testDescriptor.getLegacyReportingName(); return new TestIdentifier(uniqueId, displayName, source, tags, type, parentId, legacyReportingName); } private TestIdentifier(UniqueId uniqueId, String displayName, @Nullable TestSource source, Set tags, Type type, @Nullable UniqueId parentId, String legacyReportingName) { Preconditions.notNull(type, "TestDescriptor.Type must not be null"); this.uniqueId = uniqueId; this.parentId = parentId; this.displayName = displayName; this.source = source; this.tags = copyOf(tags); this.type = type; this.legacyReportingName = legacyReportingName; } private Set copyOf(Set tags) { return switch (tags.size()) { case 0 -> emptySet(); case 1 -> Set.of(getOnlyElement(tags)); default -> new LinkedHashSet<>(tags); }; } /** * Get the unique ID of the represented test or container as a * {@code String}. * *

Uniqueness must be guaranteed across an entire * {@linkplain TestPlan test plan}, regardless of how many engines are used * behind the scenes. * * @return the unique ID for this identifier; never {@code null} */ public String getUniqueId() { return this.uniqueId.toString(); } /** * Get the unique ID of the represented test or container as a * {@code UniqueId}. * *

Uniqueness must be guaranteed across an entire * {@linkplain TestPlan test plan}, regardless of how many engines are used * behind the scenes. * * @return the unique ID for this identifier; never {@code null} * @since 1.8 */ @API(status = STABLE, since = "1.8") public UniqueId getUniqueIdObject() { return this.uniqueId; } /** * Get the unique ID of this identifier's parent as a {@code String}, if * available. * *

An identifier without a parent is called a root. * * @return a container for the unique ID for this identifier's parent; * never {@code null} though potentially empty */ public Optional getParentId() { return getParentIdObject().map(UniqueId::toString); } /** * Get the unique ID of this identifier's parent as a {@code UniqueId}, if * available. * *

An identifier without a parent is called a root. * * @return a container for the unique ID for this identifier's parent; * never {@code null} though potentially empty * @since 1.8 */ @API(status = STABLE, since = "1.8") public Optional getParentIdObject() { return Optional.ofNullable(this.parentId); } /** * Get the display name of the represented test or container. * *

A display name is a human-readable name for a test or * container that is typically used for test reporting in IDEs and build * tools. Display names may contain spaces, special characters, and emoji, * and the format may be customized by {@link org.junit.platform.engine.TestEngine * TestEngines} or potentially by end users as well. Consequently, display * names should never be parsed; rather, they should be used for display * purposes only. * * @return the display name for this identifier; never {@code null} or blank * @see #getSource() * @see org.junit.platform.engine.TestDescriptor#getDisplayName() */ public String getDisplayName() { return this.displayName; } /** * Get the name of this identifier in a format that is suitable for legacy * reporting infrastructure — for example, for reporting systems built * on the Ant-based XML reporting format for JUnit 4. * *

The default implementation delegates to {@link #getDisplayName()}. * * @return the legacy reporting name; never {@code null} or blank * @see org.junit.platform.engine.TestDescriptor#getLegacyReportingName() * @see org.junit.platform.reporting.legacy.LegacyReportingUtils */ @SuppressWarnings("JavadocReference") public String getLegacyReportingName() { return this.legacyReportingName; } /** * Get the underlying descriptor type. * * @return the underlying descriptor type; never {@code null} */ public Type getType() { return type; } /** * Determine if this identifier represents a test. * * @return {@code true} if the underlying descriptor type represents a test, * {@code false} otherwise * @see Type#isTest() */ public boolean isTest() { return getType().isTest(); } /** * Determine if this identifier represents a container. * * @return {@code true} if the underlying descriptor type represents a container, * {@code false} otherwise * @see Type#isContainer() */ public boolean isContainer() { return getType().isContainer(); } /** * Get the {@linkplain TestSource source} of the represented test * or container, if available. * * @see TestSource */ public Optional getSource() { return Optional.ofNullable(this.source); } /** * Get the set of {@linkplain TestTag tags} associated with the represented * test or container. * * @see TestTag */ public Set getTags() { return unmodifiableSet(this.tags); } @Override public boolean equals(Object obj) { return (obj instanceof TestIdentifier that && Objects.equals(this.uniqueId, that.uniqueId)); } @Override public int hashCode() { return this.uniqueId.hashCode(); } @Override public String toString() { // @formatter:off return new ToStringBuilder(this) .append("uniqueId", this.uniqueId) .append("parentId", this.parentId) .append("displayName", this.displayName) .append("legacyReportingName", this.legacyReportingName) .append("source", this.source) .append("tags", this.tags) .append("type", this.type) .toString(); // @formatter:on } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestPlan.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher; import static java.util.Collections.emptySet; import static java.util.Collections.synchronizedSet; import static java.util.Collections.unmodifiableSet; import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import java.util.Collection; import java.util.LinkedHashSet; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.OutputDirectoryCreator; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; /** * {@code TestPlan} describes the tree of tests and containers as discovered * by a {@link Launcher}. * *

Tests and containers are represented by {@link TestIdentifier} instances. * The complete set of identifiers comprises a tree-like structure. However, * each identifier only stores the unique ID of its parent. This class provides * a number of helpful methods to retrieve the * {@linkplain #getParent(TestIdentifier) parent}, * {@linkplain #getChildren(TestIdentifier) children}, and * {@linkplain #getDescendants(TestIdentifier) descendants} of an identifier. * *

While the contained instances of {@link TestIdentifier} are immutable, * instances of this class contain mutable state. For example, when a dynamic * test is registered at runtime, it is added to the original test plan and * reported to {@link TestExecutionListener} implementations. * *

This class is not intended to be extended by clients. * * @since 1.0 * @see Launcher * @see TestExecutionListener */ @API(status = STABLE, since = "1.0") public class TestPlan { private final Set roots = synchronizedSet(new LinkedHashSet<>(4)); private final Map> children = new ConcurrentHashMap<>(32); private final Map allIdentifiers = new ConcurrentHashMap<>(32); private final boolean containsTests; private final ConfigurationParameters configurationParameters; private final OutputDirectoryCreator outputDirectoryCreator; /** * Construct a new {@code TestPlan} from the supplied collection of * {@link TestDescriptor TestDescriptors}. * *

Each supplied {@code TestDescriptor} is expected to be a descriptor * for a {@link org.junit.platform.engine.TestEngine TestEngine}. * * @param containsTests whether the test plan contains tests * @param engineDescriptors the engine test descriptors from which the test * plan should be created; never {@code null} * @param configurationParameters the {@code ConfigurationParameters} for * this test plan; never {@code null} * @param outputDirectoryCreator the {@code OutputDirectoryProvider} for * this test plan; never {@code null} * @return a new test plan */ @API(status = INTERNAL, since = "1.14") public static TestPlan from(boolean containsTests, Collection engineDescriptors, ConfigurationParameters configurationParameters, OutputDirectoryCreator outputDirectoryCreator) { Preconditions.notNull(engineDescriptors, "Cannot create TestPlan from a null collection of TestDescriptors"); Preconditions.notNull(configurationParameters, "Cannot create TestPlan from null ConfigurationParameters"); TestPlan testPlan = new TestPlan(containsTests, configurationParameters, outputDirectoryCreator); TestDescriptor.Visitor visitor = descriptor -> testPlan.addInternal(TestIdentifier.from(descriptor)); engineDescriptors.forEach(engineDescriptor -> engineDescriptor.accept(visitor)); return testPlan; } @API(status = INTERNAL, since = "1.4") protected TestPlan(boolean containsTests, ConfigurationParameters configurationParameters, OutputDirectoryCreator outputDirectoryCreator) { this.containsTests = containsTests; this.configurationParameters = configurationParameters; this.outputDirectoryCreator = outputDirectoryCreator; } @API(status = INTERNAL, since = "1.8") public void addInternal(TestIdentifier testIdentifier) { Preconditions.notNull(testIdentifier, "testIdentifier must not be null"); allIdentifiers.put(testIdentifier.getUniqueIdObject(), testIdentifier); // Root identifiers. Typically, a test engine. if (testIdentifier.getParentIdObject().isEmpty()) { roots.add(testIdentifier); return; } // Identifiers without a parent in this test plan. Could be a test // engine that is used in a suite. UniqueId parentId = testIdentifier.getParentIdObject().get(); if (!allIdentifiers.containsKey(parentId)) { roots.add(testIdentifier); return; } Set directChildren = children.computeIfAbsent(parentId, key -> synchronizedSet(new LinkedHashSet<>(16))); directChildren.add(testIdentifier); } @API(status = INTERNAL, since = "6.1") public void removeInternal(UniqueId uniqueId) { Preconditions.notNull(uniqueId, "uniqueId must not be null"); var removedTestIdentifier = removeSubtree(uniqueId); if (removedTestIdentifier != null) { roots.removeIf(root -> root.getUniqueIdObject().equals(uniqueId)); removedTestIdentifier.getParentIdObject().ifPresent( parentId -> children.getOrDefault(parentId, Set.of()).remove(removedTestIdentifier)); } } private @Nullable TestIdentifier removeSubtree(UniqueId uniqueId) { var testIdentifier = allIdentifiers.remove(uniqueId); var removedChildren = children.remove(uniqueId); if (removedChildren != null && !removedChildren.isEmpty()) { for (var child : removedChildren) { removeSubtree(child.getUniqueIdObject()); } } return testIdentifier; } /** * Get the root {@link TestIdentifier TestIdentifiers} for this test plan. * * @return an unmodifiable set of the root identifiers */ public Set getRoots() { return unmodifiableSet(roots); } /** * Get the parent of the supplied {@link TestIdentifier}. * * @param child the identifier to look up the parent for; never {@code null} * @return an {@code Optional} containing the parent, if present */ public Optional getParent(TestIdentifier child) { Preconditions.notNull(child, "child must not be null"); return child.getParentIdObject().map(this::getTestIdentifier); } /** * Get the children of the supplied {@link TestIdentifier}. * * @param parent the identifier to look up the children for; never {@code null} * @return an unmodifiable set of the parent's children, potentially empty * @see #getChildren(UniqueId) */ public Set getChildren(TestIdentifier parent) { Preconditions.notNull(parent, "parent must not be null"); return getChildren(parent.getUniqueIdObject()); } /** * Get the children of the supplied unique ID. * * @param parentId the unique ID to look up the children for; never * {@code null} * @return an unmodifiable set of the parent's children, potentially empty * @see #getChildren(TestIdentifier) */ @API(status = MAINTAINED, since = "1.10") public Set getChildren(UniqueId parentId) { return children.containsKey(parentId) ? unmodifiableSet(children.get(parentId)) : emptySet(); } /** * Get the {@link TestIdentifier} with the supplied unique ID. * * @param uniqueId the unique ID to look up the identifier for; never * {@code null} or blank * @return the identifier with the supplied unique ID; never {@code null} * @throws PreconditionViolationException if no {@code TestIdentifier} * with the supplied unique ID is present in this test plan * @deprecated Use {@link #getTestIdentifier(UniqueId)} instead. */ @API(status = DEPRECATED, since = "1.10", consumers = "Gradle") @Deprecated(since = "1.10") public TestIdentifier getTestIdentifier(String uniqueId) throws PreconditionViolationException { Preconditions.notBlank(uniqueId, "unique ID must not be null or blank"); return getTestIdentifier(UniqueId.parse(uniqueId)); } /** * Get the {@link TestIdentifier} with the supplied unique ID. * * @param uniqueId the unique ID to look up the identifier for; never * {@code null} * @return the identifier with the supplied unique ID; never {@code null} * @throws PreconditionViolationException if no {@code TestIdentifier} * with the supplied unique ID is present in this test plan */ @API(status = MAINTAINED, since = "1.10") public TestIdentifier getTestIdentifier(UniqueId uniqueId) { Preconditions.notNull(uniqueId, () -> "uniqueId must not be null"); return Preconditions.notNull(allIdentifiers.get(uniqueId), () -> "No TestIdentifier with unique ID [" + uniqueId + "] has been added to this TestPlan."); } /** * Count all {@link TestIdentifier TestIdentifiers} that satisfy the * given {@linkplain Predicate predicate}. * * @param predicate a predicate which returns {@code true} for identifiers * to be counted; never {@code null} * @return the number of identifiers that satisfy the supplied predicate */ public long countTestIdentifiers(Predicate predicate) { Preconditions.notNull(predicate, "Predicate must not be null"); return allIdentifiers.values().stream().filter(predicate).count(); } /** * Get all descendants of the supplied {@link TestIdentifier} (i.e., * all of its children and their children, recursively). * * @param parent the identifier to look up the descendants for; never {@code null} * @return an unmodifiable set of the parent's descendants, potentially empty */ public Set getDescendants(TestIdentifier parent) { Preconditions.notNull(parent, "parent must not be null"); Set result = new LinkedHashSet<>(16); Set children = getChildren(parent); result.addAll(children); for (TestIdentifier child : children) { result.addAll(getDescendants(child)); } return unmodifiableSet(result); } /** * Return whether this test plan contains any tests. * *

A test plan contains tests, if at least one of the contained engine * descriptors {@linkplain TestDescriptor#containsTests(TestDescriptor) * contains tests}. * * @return {@code true} if this test plan contains tests * @see TestDescriptor#containsTests(TestDescriptor) */ public boolean containsTests() { return containsTests; } /** * Get the {@link ConfigurationParameters} for this test plan. * * @return the configuration parameters; never {@code null} * @since 1.8 */ @API(status = MAINTAINED, since = "1.8") public ConfigurationParameters getConfigurationParameters() { return this.configurationParameters; } /** * Get the * {@link org.junit.platform.engine.reporting.OutputDirectoryProvider} for * this test plan. * * @return the output directory provider; never {@code null} * @since 1.12 * @deprecated Please use {@link #getOutputDirectoryCreator()} instead */ @SuppressWarnings("removal") @API(status = DEPRECATED, since = "1.14") @Deprecated(since = "1.14", forRemoval = true) public org.junit.platform.engine.reporting.OutputDirectoryProvider getOutputDirectoryProvider() { return org.junit.platform.engine.reporting.OutputDirectoryProvider.castOrAdapt(getOutputDirectoryCreator()); } /** * Get the {@link OutputDirectoryCreator} for this test plan. * * @return the output directory creator; never {@code null} * @since 1.14 */ @API(status = MAINTAINED, since = "1.14") public OutputDirectoryCreator getOutputDirectoryCreator() { return outputDirectoryCreator; } /** * Accept the supplied {@link Visitor} for a depth-first traversal of the * test plan. * * @param visitor the visitor to accept; never {@code null} * @since 1.10 */ @API(status = MAINTAINED, since = "1.13.3") public void accept(Visitor visitor) { getRoots().forEach(it -> accept(visitor, it)); } private void accept(Visitor visitor, TestIdentifier testIdentifier) { if (testIdentifier.isContainer()) { visitor.preVisitContainer(testIdentifier); } visitor.visit(testIdentifier); getChildren(testIdentifier).forEach(it -> accept(visitor, it)); if (testIdentifier.isContainer()) { visitor.postVisitContainer(testIdentifier); } } /** * Visitor for {@link TestIdentifier TestIdentifiers} in a {@link TestPlan}. * * @since 1.10 */ @API(status = MAINTAINED, since = "1.13.3") public interface Visitor { /** * Called before visiting a container. * * @see TestIdentifier#isContainer() */ default void preVisitContainer(TestIdentifier testIdentifier) { } /** * Called for all test identifiers regardless of their type. */ default void visit(TestIdentifier testIdentifier) { } /** * Called after visiting a container. * * @see TestIdentifier#isContainer() */ default void postVisitContainer(TestIdentifier testIdentifier) { } } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ClasspathAlignmentChecker.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static java.util.Comparator.comparing; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.function.Function; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.ClassLoaderUtils; /** * @since 1.12 */ class ClasspathAlignmentChecker { // VisibleForTesting static final List WELL_KNOWN_PACKAGES = List.of( // "org.junit.jupiter.api", // "org.junit.jupiter.engine", // "org.junit.jupiter.migrationsupport", // "org.junit.jupiter.params", // "org.junit.platform.commons", // "org.junit.platform.console", // "org.junit.platform.engine", // "org.junit.platform.launcher", // "org.junit.platform.reporting", // "org.junit.platform.suite.api", // "org.junit.platform.suite.engine", // "org.junit.platform.testkit", // "org.junit.vintage.engine" // ); static Optional check(LinkageError error) { ClassLoader classLoader = ClassLoaderUtils.getClassLoader(ClasspathAlignmentChecker.class); return check(error, classLoader::getDefinedPackage); } // VisibleForTesting static Optional check(LinkageError error, Function packageLookup) { Map> packagesByVersions = new HashMap<>(); WELL_KNOWN_PACKAGES.stream() // .map(packageLookup) // .filter(Objects::nonNull) // .forEach(pkg -> { String version = pkg.getImplementationVersion(); if (version != null) { packagesByVersions.computeIfAbsent(version, __ -> new ArrayList<>()).add(pkg); } }); if (packagesByVersions.size() > 1) { StringBuilder message = new StringBuilder(); String lineBreak = System.lineSeparator(); message.append("The wrapped ").append(error.getClass().getSimpleName()) // .append(" is likely caused by the versions of JUnit jars on the classpath/module path ") // .append("not being properly aligned. ") // .append(lineBreak) // .append("Please ensure consistent versions are used (see https://docs.junit.org/") // .append(ClasspathAlignmentChecker.class.getPackage().getImplementationVersion()) // .append("/appendix.html#dependency-metadata).") // .append(lineBreak) // .append("The following conflicting versions were detected:").append(lineBreak); packagesByVersions.values().stream() // .flatMap(List::stream) // .sorted(comparing(Package::getName)) // .map(pkg -> "- %s: %s%n".formatted(pkg.getName(), pkg.getImplementationVersion())) // .forEach(message::append); return Optional.of(new JUnitException(message.toString(), error)); } return Optional.empty(); } private ClasspathAlignmentChecker() { } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ClasspathAlignmentCheckingLauncherInterceptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import java.util.Optional; import org.junit.platform.commons.JUnitException; import org.junit.platform.launcher.LauncherInterceptor; class ClasspathAlignmentCheckingLauncherInterceptor implements LauncherInterceptor { static final LauncherInterceptor INSTANCE = new ClasspathAlignmentCheckingLauncherInterceptor(); @Override public T intercept(Invocation invocation) { try { return invocation.proceed(); } catch (LinkageError e) { Optional exception = ClasspathAlignmentChecker.check(e); if (exception.isPresent()) { throw exception.get(); } throw e; } } @Override public void close() { // do nothing } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/CompositeEngineExecutionListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; import java.util.function.Supplier; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.UnrecoverableExceptions; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; class CompositeEngineExecutionListener implements EngineExecutionListener { private static final Logger logger = LoggerFactory.getLogger(CompositeEngineExecutionListener.class); private final List engineExecutionListeners; CompositeEngineExecutionListener(List engineExecutionListeners) { this.engineExecutionListeners = new ArrayList<>(engineExecutionListeners); } @Override public void dynamicTestRegistered(TestDescriptor testDescriptor) { notifyEach(engineExecutionListeners, IterationOrder.ORIGINAL, listener -> listener.dynamicTestRegistered(testDescriptor), () -> "dynamicTestRegistered(" + testDescriptor + ")"); } @Override public void executionSkipped(TestDescriptor testDescriptor, String reason) { notifyEach(engineExecutionListeners, IterationOrder.ORIGINAL, listener -> listener.executionSkipped(testDescriptor, reason), () -> "executionSkipped(" + testDescriptor + ", " + reason + ")"); } @Override public void executionStarted(TestDescriptor testDescriptor) { notifyEach(engineExecutionListeners, IterationOrder.ORIGINAL, listener -> listener.executionStarted(testDescriptor), () -> "executionStarted(" + testDescriptor + ")"); } @Override public void executionFinished(TestDescriptor testDescriptor, TestExecutionResult testExecutionResult) { notifyEach(engineExecutionListeners, IterationOrder.REVERSED, listener -> listener.executionFinished(testDescriptor, testExecutionResult), () -> "executionFinished(" + testDescriptor + ", " + testExecutionResult + ")"); } @Override public void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry entry) { notifyEach(engineExecutionListeners, IterationOrder.ORIGINAL, listener -> listener.reportingEntryPublished(testDescriptor, entry), () -> "reportingEntryPublished(" + testDescriptor + ", " + entry + ")"); } @Override public void fileEntryPublished(TestDescriptor testDescriptor, FileEntry file) { notifyEach(engineExecutionListeners, IterationOrder.ORIGINAL, listener -> listener.fileEntryPublished(testDescriptor, file), () -> "fileEntryPublished(" + testDescriptor + ", " + file + ")"); } private static void notifyEach(List listeners, IterationOrder iterationOrder, Consumer consumer, Supplier description) { iterationOrder.forEach(listeners, listener -> { try { consumer.accept(listener); } catch (Throwable throwable) { UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); logger.warn(throwable, () -> "EngineExecutionListener [%s] threw exception for method: %s".formatted( listener.getClass().getName(), description.get())); } }); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/CompositeTestExecutionListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; import java.util.function.Supplier; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.UnrecoverableExceptions; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; class CompositeTestExecutionListener implements TestExecutionListener { private static final Logger logger = LoggerFactory.getLogger(CompositeTestExecutionListener.class); private final List testExecutionListeners; private final List eagerTestExecutionListeners; CompositeTestExecutionListener(List testExecutionListeners) { this.testExecutionListeners = new ArrayList<>(testExecutionListeners); this.eagerTestExecutionListeners = this.testExecutionListeners.stream() // .filter(EagerTestExecutionListener.class::isInstance) // .map(EagerTestExecutionListener.class::cast) // .toList(); } @Override public void dynamicTestRegistered(TestIdentifier testIdentifier) { notifyEach(testExecutionListeners, IterationOrder.ORIGINAL, listener -> listener.dynamicTestRegistered(testIdentifier), () -> "dynamicTestRegistered(" + testIdentifier + ")"); } @Override public void executionSkipped(TestIdentifier testIdentifier, String reason) { notifyEach(testExecutionListeners, IterationOrder.ORIGINAL, listener -> listener.executionSkipped(testIdentifier, reason), () -> "executionSkipped(" + testIdentifier + ", " + reason + ")"); } @Override public void executionStarted(TestIdentifier testIdentifier) { notifyEach(eagerTestExecutionListeners, IterationOrder.ORIGINAL, listener -> listener.executionJustStarted(testIdentifier), () -> "executionJustStarted(" + testIdentifier + ")"); notifyEach(testExecutionListeners, IterationOrder.ORIGINAL, listener -> listener.executionStarted(testIdentifier), () -> "executionStarted(" + testIdentifier + ")"); } @Override public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { notifyEach(eagerTestExecutionListeners, IterationOrder.REVERSED, listener -> listener.executionJustFinished(testIdentifier, testExecutionResult), () -> "executionJustFinished(" + testIdentifier + ", " + testExecutionResult + ")"); notifyEach(testExecutionListeners, IterationOrder.REVERSED, listener -> listener.executionFinished(testIdentifier, testExecutionResult), () -> "executionFinished(" + testIdentifier + ", " + testExecutionResult + ")"); } @Override public void testPlanExecutionStarted(TestPlan testPlan) { notifyEach(testExecutionListeners, IterationOrder.ORIGINAL, listener -> listener.testPlanExecutionStarted(testPlan), () -> "testPlanExecutionStarted(" + testPlan + ")"); } @Override public void testPlanExecutionFinished(TestPlan testPlan) { notifyEach(testExecutionListeners, IterationOrder.REVERSED, listener -> listener.testPlanExecutionFinished(testPlan), () -> "testPlanExecutionFinished(" + testPlan + ")"); } @Override public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry entry) { notifyEach(testExecutionListeners, IterationOrder.ORIGINAL, listener -> listener.reportingEntryPublished(testIdentifier, entry), () -> "reportingEntryPublished(" + testIdentifier + ", " + entry + ")"); } @Override public void fileEntryPublished(TestIdentifier testIdentifier, FileEntry file) { notifyEach(testExecutionListeners, IterationOrder.ORIGINAL, listener -> listener.fileEntryPublished(testIdentifier, file), () -> "fileEntryPublished(" + testIdentifier + ", " + file + ")"); } private static void notifyEach(List listeners, IterationOrder iterationOrder, Consumer consumer, Supplier description) { iterationOrder.forEach(listeners, listener -> { try { consumer.accept(listener); } catch (Throwable throwable) { UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); logger.warn(throwable, () -> "TestExecutionListener [%s] threw exception for method: %s".formatted( listener.getClass().getName(), description.get())); } }); } interface EagerTestExecutionListener extends TestExecutionListener { default void executionJustStarted(TestIdentifier testIdentifier) { } default void executionJustFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { } } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultDiscoveryRequest.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import java.util.List; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.DiscoveryFilter; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.OutputDirectoryCreator; import org.junit.platform.launcher.EngineFilter; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.PostDiscoveryFilter; /** * {@code DefaultDiscoveryRequest} is the default implementation of the * {@link EngineDiscoveryRequest} and {@link LauncherDiscoveryRequest} APIs. * * @since 1.0 */ final class DefaultDiscoveryRequest implements LauncherDiscoveryRequest { // Selectors provided to the engines to be used for discovering tests private final List selectors; // Filters based on engines private final List engineFilters; // Discovery filters are handed through to all engines to be applied during discovery. private final List> discoveryFilters; // Descriptor filters are applied by the launcher itself after engines have performed discovery. private final List postDiscoveryFilters; // Configuration parameters can be used to provide custom configuration to engines, e.g. for extensions private final LauncherConfigurationParameters configurationParameters; // Listener for test discovery that may abort on errors. private final LauncherDiscoveryListener discoveryListener; private final OutputDirectoryCreator outputDirectoryCreator; DefaultDiscoveryRequest(List selectors, List engineFilters, List> discoveryFilters, List postDiscoveryFilters, LauncherConfigurationParameters configurationParameters, LauncherDiscoveryListener discoveryListener, OutputDirectoryCreator outputDirectoryCreator) { this.selectors = List.copyOf(selectors); this.engineFilters = List.copyOf(engineFilters); this.discoveryFilters = List.copyOf(discoveryFilters); this.postDiscoveryFilters = List.copyOf(postDiscoveryFilters); this.configurationParameters = configurationParameters; this.discoveryListener = discoveryListener; this.outputDirectoryCreator = outputDirectoryCreator; } @Override public List getSelectorsByType(Class selectorType) { Preconditions.notNull(selectorType, "selectorType must not be null"); return this.selectors.stream().filter(selectorType::isInstance).map(selectorType::cast).toList(); } @Override public List getEngineFilters() { return this.engineFilters; } @Override public > List getFiltersByType(Class filterType) { Preconditions.notNull(filterType, "filterType must not be null"); return this.discoveryFilters.stream().filter(filterType::isInstance).map(filterType::cast).toList(); } @Override public List getPostDiscoveryFilters() { return this.postDiscoveryFilters; } @Override public ConfigurationParameters getConfigurationParameters() { return this.configurationParameters; } @Override public LauncherDiscoveryListener getDiscoveryListener() { return this.discoveryListener; } @Override public OutputDirectoryCreator getOutputDirectoryCreator() { return this.outputDirectoryCreator; } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static java.util.Collections.unmodifiableCollection; import static org.junit.platform.engine.support.store.NamespacedHierarchicalStore.CloseAction.closeAutoCloseables; import static org.junit.platform.launcher.core.LauncherPhase.DISCOVERY; import static org.junit.platform.launcher.core.LauncherPhase.EXECUTION; import java.util.Collection; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.support.store.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.LauncherExecutionRequest; import org.junit.platform.launcher.PostDiscoveryFilter; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestPlan; /** * Default implementation of the {@link Launcher} API. * *

External clients can obtain an instance by invoking * {@link LauncherFactory#create()}. * * @since 1.0 * @see Launcher * @see LauncherFactory */ class DefaultLauncher implements Launcher { private final LauncherListenerRegistry listenerRegistry = new LauncherListenerRegistry(); private final EngineExecutionOrchestrator executionOrchestrator = new EngineExecutionOrchestrator( listenerRegistry.testExecutionListeners); private final EngineDiscoveryOrchestrator discoveryOrchestrator; private final NamespacedHierarchicalStore sessionLevelStore; /** * Construct a new {@code DefaultLauncher} with the supplied test engines. * * @param testEngines the test engines to delegate to; never {@code null} or * empty * @param postDiscoveryFilters the additional post discovery filters for * discovery requests; never {@code null} */ DefaultLauncher(Iterable testEngines, Collection postDiscoveryFilters, NamespacedHierarchicalStore sessionLevelStore) { Preconditions.condition(testEngines.iterator().hasNext(), () -> "Cannot create Launcher without at least one TestEngine; " + "consider adding an engine implementation JAR to the classpath"); Preconditions.notNull(postDiscoveryFilters, "postDiscoveryFilter array must not be null"); Preconditions.containsNoNullElements(postDiscoveryFilters, "postDiscoveryFilter array must not contain null elements"); this.discoveryOrchestrator = new EngineDiscoveryOrchestrator(testEngines, unmodifiableCollection(postDiscoveryFilters), listenerRegistry.launcherDiscoveryListeners); this.sessionLevelStore = sessionLevelStore; } @Override public void registerLauncherDiscoveryListeners(LauncherDiscoveryListener... listeners) { Preconditions.notNull(listeners, "listeners must not be null"); Preconditions.containsNoNullElements(listeners, "listener array must not contain null elements"); this.listenerRegistry.launcherDiscoveryListeners.addAll(listeners); } @Override public void registerTestExecutionListeners(TestExecutionListener... listeners) { Preconditions.notNull(listeners, "listeners must not be null"); Preconditions.containsNoNullElements(listeners, "listener array must not contain null elements"); this.listenerRegistry.testExecutionListeners.addAll(listeners); } @Override public TestPlan discover(LauncherDiscoveryRequest discoveryRequest) { Preconditions.notNull(discoveryRequest, "discoveryRequest must not be null"); return InternalTestPlan.from(discover(discoveryRequest, DISCOVERY)); } @Override public void execute(LauncherDiscoveryRequest discoveryRequest, TestExecutionListener... listeners) { Preconditions.notNull(discoveryRequest, "discoveryRequest must not be null"); Preconditions.notNull(listeners, "listeners must not be null"); Preconditions.containsNoNullElements(listeners, "listener array must not contain null elements"); var executionRequest = LauncherExecutionRequestBuilder.request(discoveryRequest) // .listeners(listeners) // .build(); execute(executionRequest); } @Override public void execute(TestPlan testPlan, TestExecutionListener... listeners) { Preconditions.notNull(testPlan, "testPlan must not be null"); Preconditions.notNull(listeners, "listeners must not be null"); Preconditions.containsNoNullElements(listeners, "listener array must not contain null elements"); var executionRequest = LauncherExecutionRequestBuilder.request(testPlan) // .listeners(listeners) // .build(); execute(executionRequest); } @Override public void execute(LauncherExecutionRequest executionRequest) { Preconditions.notNull(executionRequest, "executionRequest must not be null"); var testPlan = executionRequest.getTestPlan().map(it -> { Preconditions.condition(it instanceof InternalTestPlan, "The TestPlan in executionRequest was not created by this Launcher"); return ((InternalTestPlan) it); }).orElseGet(() -> { Preconditions.condition(executionRequest.getDiscoveryRequest().isPresent(), "Either a TestPlan or LauncherDiscoveryRequest must be present in the LauncherExecutionRequest"); return InternalTestPlan.from(discover(executionRequest.getDiscoveryRequest().get(), EXECUTION)); }); execute(testPlan, executionRequest.getAdditionalTestExecutionListeners(), executionRequest.getCancellationToken()); } private LauncherDiscoveryResult discover(LauncherDiscoveryRequest discoveryRequest, LauncherPhase phase) { return discoveryOrchestrator.discover(discoveryRequest, phase); } private void execute(InternalTestPlan internalTestPlan, Collection listeners, CancellationToken cancellationToken) { try (NamespacedHierarchicalStore requestLevelStore = createRequestLevelStore()) { executionOrchestrator.execute(internalTestPlan, requestLevelStore, listeners, cancellationToken); } } private NamespacedHierarchicalStore createRequestLevelStore() { return new NamespacedHierarchicalStore<>(sessionLevelStore, closeAutoCloseables()); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherConfig.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static java.util.Collections.unmodifiableCollection; import java.util.Collection; import org.junit.platform.engine.TestEngine; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherSessionListener; import org.junit.platform.launcher.PostDiscoveryFilter; import org.junit.platform.launcher.TestExecutionListener; /** * Default implementation of the {@link LauncherConfig} API. * * @since 1.3 */ class DefaultLauncherConfig implements LauncherConfig { private final boolean testEngineAutoRegistrationEnabled; private final boolean launcherSessionListenerAutoRegistrationEnabled; private final boolean launcherDiscoveryListenerAutoRegistrationEnabled; private final boolean testExecutionListenerAutoRegistrationEnabled; private final boolean postDiscoveryFilterAutoRegistrationEnabled; private final Collection additionalTestEngines; private final Collection additionalLauncherSessionListeners; private final Collection additionalLauncherDiscoveryListeners; private final Collection additionalTestExecutionListeners; private final Collection additionalPostDiscoveryFilters; DefaultLauncherConfig(boolean testEngineAutoRegistrationEnabled, boolean launcherSessionListenerAutoRegistrationEnabled, boolean launcherDiscoveryListenerAutoRegistrationEnabled, boolean testExecutionListenerAutoRegistrationEnabled, boolean postDiscoveryFilterAutoRegistrationEnabled, Collection additionalTestEngines, Collection additionalLauncherSessionListeners, Collection additionalLauncherDiscoveryListeners, Collection additionalTestExecutionListeners, Collection additionalPostDiscoveryFilters) { this.launcherSessionListenerAutoRegistrationEnabled = launcherSessionListenerAutoRegistrationEnabled; this.launcherDiscoveryListenerAutoRegistrationEnabled = launcherDiscoveryListenerAutoRegistrationEnabled; this.testExecutionListenerAutoRegistrationEnabled = testExecutionListenerAutoRegistrationEnabled; this.testEngineAutoRegistrationEnabled = testEngineAutoRegistrationEnabled; this.postDiscoveryFilterAutoRegistrationEnabled = postDiscoveryFilterAutoRegistrationEnabled; this.additionalTestEngines = unmodifiableCollection(additionalTestEngines); this.additionalLauncherSessionListeners = unmodifiableCollection(additionalLauncherSessionListeners); this.additionalLauncherDiscoveryListeners = unmodifiableCollection(additionalLauncherDiscoveryListeners); this.additionalTestExecutionListeners = unmodifiableCollection(additionalTestExecutionListeners); this.additionalPostDiscoveryFilters = unmodifiableCollection(additionalPostDiscoveryFilters); } @Override public boolean isTestEngineAutoRegistrationEnabled() { return this.testEngineAutoRegistrationEnabled; } @Override public boolean isLauncherSessionListenerAutoRegistrationEnabled() { return launcherSessionListenerAutoRegistrationEnabled; } @Override public boolean isLauncherDiscoveryListenerAutoRegistrationEnabled() { return launcherDiscoveryListenerAutoRegistrationEnabled; } @Override public boolean isTestExecutionListenerAutoRegistrationEnabled() { return this.testExecutionListenerAutoRegistrationEnabled; } @Override public boolean isPostDiscoveryFilterAutoRegistrationEnabled() { return this.postDiscoveryFilterAutoRegistrationEnabled; } @Override public Collection getAdditionalTestEngines() { return this.additionalTestEngines; } @Override public Collection getAdditionalLauncherSessionListeners() { return additionalLauncherSessionListeners; } @Override public Collection getAdditionalLauncherDiscoveryListeners() { return additionalLauncherDiscoveryListeners; } @Override public Collection getAdditionalTestExecutionListeners() { return this.additionalTestExecutionListeners; } @Override public Collection getAdditionalPostDiscoveryFilters() { return this.additionalPostDiscoveryFilters; } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherExecutionRequest.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import java.util.Collection; import java.util.List; import java.util.Optional; import org.jspecify.annotations.Nullable; import org.junit.platform.engine.CancellationToken; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.LauncherExecutionRequest; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestPlan; /** * @since 6.0 */ final class DefaultLauncherExecutionRequest implements LauncherExecutionRequest { private final @Nullable LauncherDiscoveryRequest discoveryRequest; private final @Nullable TestPlan testPlan; private final List executionListeners; private final CancellationToken cancellationToken; DefaultLauncherExecutionRequest(@Nullable LauncherDiscoveryRequest discoveryRequest, @Nullable TestPlan testPlan, Collection executionListeners, CancellationToken cancellationToken) { this.discoveryRequest = discoveryRequest; this.testPlan = testPlan; this.executionListeners = List.copyOf(executionListeners); this.cancellationToken = cancellationToken; } @Override public Optional getDiscoveryRequest() { return Optional.ofNullable(discoveryRequest); } @Override public Optional getTestPlan() { return Optional.ofNullable(testPlan); } @Override public Collection getAdditionalTestExecutionListeners() { return executionListeners; } @Override public CancellationToken getCancellationToken() { return cancellationToken; } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static org.junit.platform.engine.support.store.NamespacedHierarchicalStore.CloseAction.closeAutoCloseables; import java.util.List; import java.util.function.Function; import java.util.function.Supplier; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.engine.support.store.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.LauncherExecutionRequest; import org.junit.platform.launcher.LauncherInterceptor; import org.junit.platform.launcher.LauncherSession; import org.junit.platform.launcher.LauncherSessionListener; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestPlan; /** * @since 1.8 */ class DefaultLauncherSession implements LauncherSession { private static final LauncherInterceptor NOOP_INTERCEPTOR = new LauncherInterceptor() { @Override public T intercept(Invocation invocation) { return invocation.proceed(); } @Override public void close() { // do nothing } }; private final NamespacedHierarchicalStore store = new NamespacedHierarchicalStore<>(null, closeAutoCloseables()); private final LauncherInterceptor interceptor; private final LauncherSessionListener listener; private final DelegatingLauncher launcher; DefaultLauncherSession(List interceptors, // Supplier listenerSupplier, // Function, Launcher> launcherFactory // ) { interceptor = composite(interceptors); Launcher launcher; if (interceptor == NOOP_INTERCEPTOR) { this.listener = listenerSupplier.get(); launcher = launcherFactory.apply(this.store); } else { this.listener = interceptor.intercept(listenerSupplier::get); launcher = new InterceptingLauncher(interceptor.intercept(() -> launcherFactory.apply(this.store)), interceptor); } this.launcher = new DelegatingLauncher(launcher); listener.launcherSessionOpened(this); } @Override public Launcher getLauncher() { return launcher; } LauncherSessionListener getListener() { return listener; } @Override public void close() { if (launcher.delegate != ClosedLauncher.INSTANCE) { launcher.delegate = ClosedLauncher.INSTANCE; listener.launcherSessionClosed(this); store.close(); interceptor.close(); } } @Override public NamespacedHierarchicalStore getStore() { return store; } private static class ClosedLauncher implements Launcher { static final ClosedLauncher INSTANCE = new ClosedLauncher(); private ClosedLauncher() { } @Override public void registerLauncherDiscoveryListeners(LauncherDiscoveryListener... listeners) { throw new PreconditionViolationException("Launcher session has already been closed"); } @Override public void registerTestExecutionListeners(TestExecutionListener... listeners) { throw new PreconditionViolationException("Launcher session has already been closed"); } @Override public TestPlan discover(LauncherDiscoveryRequest launcherDiscoveryRequest) { throw new PreconditionViolationException("Launcher session has already been closed"); } @Override public void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, TestExecutionListener... listeners) { throw new PreconditionViolationException("Launcher session has already been closed"); } @Override public void execute(TestPlan testPlan, TestExecutionListener... listeners) { throw new PreconditionViolationException("Launcher session has already been closed"); } @Override public void execute(LauncherExecutionRequest launcherExecutionRequest) { throw new PreconditionViolationException("Launcher session has already been closed"); } } private static LauncherInterceptor composite(List interceptors) { if (interceptors.isEmpty()) { return NOOP_INTERCEPTOR; } return interceptors.stream() // .skip(1) // .reduce(interceptors.get(0), (a, b) -> new LauncherInterceptor() { @Override public void close() { try { a.close(); } finally { b.close(); } } @Override public T intercept(Invocation invocation) { return a.intercept(() -> b.intercept(invocation)); } }); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingEngineExecutionListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; /** * @since 1.6 */ class DelegatingEngineExecutionListener implements EngineExecutionListener { private final EngineExecutionListener delegate; DelegatingEngineExecutionListener(EngineExecutionListener delegate) { this.delegate = delegate; } @Override public void dynamicTestRegistered(TestDescriptor testDescriptor) { delegate.dynamicTestRegistered(testDescriptor); } @Override public void executionSkipped(TestDescriptor testDescriptor, String reason) { delegate.executionSkipped(testDescriptor, reason); } @Override public void executionStarted(TestDescriptor testDescriptor) { delegate.executionStarted(testDescriptor); } @Override public void executionFinished(TestDescriptor testDescriptor, TestExecutionResult testExecutionResult) { delegate.executionFinished(testDescriptor, testExecutionResult); } @Override public void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry entry) { delegate.reportingEntryPublished(testDescriptor, entry); } @Override public void fileEntryPublished(TestDescriptor testDescriptor, FileEntry file) { delegate.fileEntryPublished(testDescriptor, file); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncher.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.LauncherExecutionRequest; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestPlan; /** * @since 1.10 */ class DelegatingLauncher implements Launcher { protected Launcher delegate; DelegatingLauncher(Launcher delegate) { this.delegate = delegate; } @Override public void registerLauncherDiscoveryListeners(LauncherDiscoveryListener... listeners) { Preconditions.notNull(listeners, "listeners must not be null"); Preconditions.containsNoNullElements(listeners, "listener array must not contain null elements"); delegate.registerLauncherDiscoveryListeners(listeners); } @Override public void registerTestExecutionListeners(TestExecutionListener... listeners) { Preconditions.notNull(listeners, "listeners must not be null"); Preconditions.containsNoNullElements(listeners, "listener array must not contain null elements"); delegate.registerTestExecutionListeners(listeners); } @Override public TestPlan discover(LauncherDiscoveryRequest discoveryRequest) { Preconditions.notNull(discoveryRequest, "discoveryRequest must not be null"); return delegate.discover(discoveryRequest); } @Override public void execute(LauncherDiscoveryRequest discoveryRequest, TestExecutionListener... listeners) { Preconditions.notNull(discoveryRequest, "discoveryRequest must not be null"); Preconditions.notNull(listeners, "listeners must not be null"); Preconditions.containsNoNullElements(listeners, "listener array must not contain null elements"); delegate.execute(discoveryRequest, listeners); } @Override public void execute(TestPlan testPlan, TestExecutionListener... listeners) { Preconditions.notNull(testPlan, "testPlan must not be null"); Preconditions.notNull(listeners, "listeners must not be null"); Preconditions.containsNoNullElements(listeners, "listener array must not contain null elements"); delegate.execute(testPlan, listeners); } @Override public void execute(LauncherExecutionRequest executionRequest) { Preconditions.notNull(executionRequest, "executionRequest must not be null"); delegate.execute(executionRequest); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncherDiscoveryListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.SelectorResolutionResult; import org.junit.platform.engine.UniqueId; import org.junit.platform.launcher.EngineDiscoveryResult; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; /** * @since 6.1 */ class DelegatingLauncherDiscoveryListener implements LauncherDiscoveryListener { private final LauncherDiscoveryListener delegate; DelegatingLauncherDiscoveryListener(LauncherDiscoveryListener delegate) { this.delegate = delegate; } @Override public void launcherDiscoveryStarted(LauncherDiscoveryRequest request) { delegate.launcherDiscoveryStarted(request); } @Override public void launcherDiscoveryFinished(LauncherDiscoveryRequest request) { delegate.launcherDiscoveryFinished(request); } @Override public void engineDiscoveryStarted(UniqueId engineId) { delegate.engineDiscoveryStarted(engineId); } @Override public void engineDiscoveryFinished(UniqueId engineId, EngineDiscoveryResult result) { delegate.engineDiscoveryFinished(engineId, result); } @Override public void selectorProcessed(UniqueId engineId, DiscoverySelector selector, SelectorResolutionResult result) { delegate.selectorProcessed(engineId, selector, result); } @Override public void issueEncountered(UniqueId engineId, DiscoveryIssue issue) { delegate.issueEncountered(engineId, issue); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncherDiscoveryRequest.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import java.util.List; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.DiscoveryFilter; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.OutputDirectoryCreator; import org.junit.platform.launcher.EngineFilter; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.PostDiscoveryFilter; /** * @since 1.13 */ class DelegatingLauncherDiscoveryRequest implements LauncherDiscoveryRequest { private final LauncherDiscoveryRequest request; DelegatingLauncherDiscoveryRequest(LauncherDiscoveryRequest request) { this.request = request; } @Override public List getEngineFilters() { return this.request.getEngineFilters(); } @Override public List getPostDiscoveryFilters() { return this.request.getPostDiscoveryFilters(); } @Override public LauncherDiscoveryListener getDiscoveryListener() { return this.request.getDiscoveryListener(); } @Override public List getSelectorsByType(Class selectorType) { return this.request.getSelectorsByType(selectorType); } @Override public > List getFiltersByType(Class filterType) { return this.request.getFiltersByType(filterType); } @Override public ConfigurationParameters getConfigurationParameters() { return this.request.getConfigurationParameters(); } @SuppressWarnings("removal") @Override public org.junit.platform.engine.reporting.OutputDirectoryProvider getOutputDirectoryProvider() { return this.request.getOutputDirectoryProvider(); } @Override public OutputDirectoryCreator getOutputDirectoryCreator() { return this.request.getOutputDirectoryCreator(); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DiscoveryIssueCollector.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import java.util.ArrayList; import java.util.List; import java.util.Locale; import org.junit.platform.commons.JUnitException; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.UniqueId; import org.junit.platform.launcher.LauncherConstants; import org.junit.platform.launcher.LauncherDiscoveryListener; class DiscoveryIssueCollector implements LauncherDiscoveryListener { final List issues = new ArrayList<>(); private final Severity criticalSeverity; DiscoveryIssueCollector(ConfigurationParameters configurationParameters) { this.criticalSeverity = getCriticalSeverity(configurationParameters); } @Override public void engineDiscoveryStarted(UniqueId engineId) { this.issues.clear(); } @Override public void issueEncountered(UniqueId engineId, DiscoveryIssue issue) { this.issues.add(issue); } DiscoveryIssueNotifier toNotifier() { if (this.issues.isEmpty()) { return DiscoveryIssueNotifier.NO_ISSUES; } return DiscoveryIssueNotifier.from(criticalSeverity, this.issues); } private static Severity getCriticalSeverity(ConfigurationParameters configurationParameters) { return configurationParameters // .get(LauncherConstants.CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME, value -> { try { return Severity.valueOf(value.toUpperCase(Locale.ROOT)); } catch (Exception e) { throw new JUnitException( "Invalid DiscoveryIssue.Severity '%s' set via the '%s' configuration parameter.".formatted( value, LauncherConstants.CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME)); } }) // .orElse(Severity.ERROR); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DiscoveryIssueException.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import java.io.Serial; import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; /** * {@code DiscoveryIssueException} is an exception that is thrown if an engine * reports critical issues during test discovery. * * @since 1.13 */ @API(status = EXPERIMENTAL, since = "6.0") public class DiscoveryIssueException extends JUnitException { @Serial private static final long serialVersionUID = 1L; DiscoveryIssueException(String message) { super(message, null, false, false); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DiscoveryIssueNotifier.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static java.util.Comparator.comparing; import static java.util.stream.Collectors.partitioningBy; import static java.util.stream.Collectors.toUnmodifiableList; import static org.junit.platform.commons.util.ExceptionUtils.readStackTrace; import java.util.List; import java.util.Map; import java.util.function.Consumer; import java.util.function.Supplier; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.platform.engine.support.descriptor.MethodSource; /** * @since 1.13 */ class DiscoveryIssueNotifier { static final DiscoveryIssueNotifier NO_ISSUES = new DiscoveryIssueNotifier(List.of(), List.of(), List.of()); private static final Logger logger = LoggerFactory.getLogger(DiscoveryIssueNotifier.class); private final List allIssues; private final List criticalIssues; private final List nonCriticalIssues; @SuppressWarnings("NullAway") static DiscoveryIssueNotifier from(Severity criticalSeverity, List issues) { var issuesByCriticality = partitionByCriticality(criticalSeverity, issues); List criticalIssues = issuesByCriticality.get(true); List nonCriticalIssues = issuesByCriticality.get(false); return new DiscoveryIssueNotifier(issues, criticalIssues, nonCriticalIssues); } private static Map> partitionByCriticality(Severity criticalSeverity, List issues) { return issues.stream() // .sorted(comparing(DiscoveryIssue::severity).reversed()) // .collect( partitioningBy(issue -> issue.severity().compareTo(criticalSeverity) >= 0, toUnmodifiableList())); } private DiscoveryIssueNotifier(List allIssues, List criticalIssues, List nonCriticalIssues) { this.allIssues = List.copyOf(allIssues); this.criticalIssues = List.copyOf(criticalIssues); this.nonCriticalIssues = List.copyOf(nonCriticalIssues); } List getAllIssues() { return allIssues; } boolean hasCriticalIssues() { return !criticalIssues.isEmpty(); } void logCriticalIssues(TestEngine testEngine) { logIssues(testEngine, criticalIssues, "critical"); } void logNonCriticalIssues(TestEngine testEngine) { logIssues(testEngine, nonCriticalIssues, "non-critical"); } @Nullable DiscoveryIssueException createExceptionForCriticalIssues(TestEngine testEngine) { if (criticalIssues.isEmpty()) { return null; } String message = formatMessage(testEngine, criticalIssues, "critical"); return new DiscoveryIssueException(message); } private void logIssues(TestEngine testEngine, List issues, String adjective) { if (!issues.isEmpty()) { Severity maxSeverity = issues.get(0).severity(); logger(maxSeverity).accept(() -> formatMessage(testEngine, issues, adjective)); } } private static Consumer> logger(Severity severity) { return switch (severity) { case INFO -> logger::info; case WARNING -> logger::warn; case ERROR -> logger::error; }; } private static String formatMessage(TestEngine testEngine, List issues, String adjective) { Preconditions.notNull(testEngine, "testEngine must not be null"); Preconditions.notNull(issues, "issues must not be null"); Preconditions.notEmpty(issues, "issues must not be empty"); String engineId = testEngine.getId(); StringBuilder message = new StringBuilder(); message.append("TestEngine with ID '").append(engineId).append("' encountered "); if (issues.size() == 1) { message.append("a ").append(adjective).append(" issue"); } else { message.append(issues.size()).append(' ').append(adjective).append(" issues"); } message.append(" during test discovery:"); for (int i = 0; i < issues.size(); i++) { DiscoveryIssue issue = issues.get(i); message.append("\n\n(").append(i + 1).append(") [").append(issue.severity()).append("] ").append( issue.message()); issue.source().ifPresent(source -> { message.append("\n Source: ").append(source); if (source instanceof MethodSource methodSource) { appendIdeCompatibleLink(message, methodSource.getClassName(), methodSource.getMethodName()); } else if (source instanceof ClassSource classSource) { appendIdeCompatibleLink(message, classSource.getClassName(), ""); } }); issue.cause().ifPresent(t -> message.append("\n Cause: ").append(readStackTrace(t))); } return message.toString(); } private static void appendIdeCompatibleLink(StringBuilder message, String className, String methodName) { message.append("\n at ").append(className).append(".").append(methodName).append("(SourceFile:0)"); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DiscoveryIssueReportingDiscoveryListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ 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; import java.util.Optional; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.SelectorResolutionResult; import org.junit.platform.engine.TestSource; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.discovery.ClassSelector; import org.junit.platform.engine.discovery.ClasspathResourceSelector; import org.junit.platform.engine.discovery.DirectorySelector; import org.junit.platform.engine.discovery.FileSelector; import org.junit.platform.engine.discovery.MethodSelector; import org.junit.platform.engine.discovery.PackageSelector; import org.junit.platform.engine.discovery.UniqueIdSelector; import org.junit.platform.engine.discovery.UriSelector; import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.platform.engine.support.descriptor.ClasspathResourceSource; import org.junit.platform.engine.support.descriptor.DirectorySource; import org.junit.platform.engine.support.descriptor.FilePosition; import org.junit.platform.engine.support.descriptor.FileSource; import org.junit.platform.engine.support.descriptor.MethodSource; import org.junit.platform.engine.support.descriptor.PackageSource; import org.junit.platform.engine.support.descriptor.UriSource; import org.junit.platform.launcher.LauncherDiscoveryListener; /** * @since 6.1 */ class DiscoveryIssueReportingDiscoveryListener extends DelegatingLauncherDiscoveryListener { private static final Logger logger = LoggerFactory.getLogger(DiscoveryIssueReportingDiscoveryListener.class); DiscoveryIssueReportingDiscoveryListener(LauncherDiscoveryListener delegate) { super(delegate); } @Override public void selectorProcessed(UniqueId engineId, DiscoverySelector selector, SelectorResolutionResult result) { super.selectorProcessed(engineId, selector, result); reportIssue(engineId, selector, result) // .ifPresent(issue -> issueEncountered(engineId, issue)); } private Optional reportIssue(UniqueId engineId, DiscoverySelector selector, SelectorResolutionResult result) { if (result.getStatus() == FAILED) { DiscoveryIssue issue = DiscoveryIssue.builder(Severity.ERROR, selector + " resolution failed") // .cause(result.getThrowable()) // .source(toSource(selector)) // .build(); return Optional.of(issue); } else if (result.getStatus() == UNRESOLVED && selector instanceof UniqueIdSelector uniqueIdSelector) { UniqueId uniqueId = uniqueIdSelector.getUniqueId(); if (uniqueId.hasPrefix(engineId)) { DiscoveryIssue issue = DiscoveryIssue.create(Severity.ERROR, selector + " could not be resolved"); return Optional.of(issue); } } return Optional.empty(); } private static @Nullable TestSource toSource(DiscoverySelector selector) { if (selector instanceof ClassSelector classSelector) { return ClassSource.from(classSelector.getClassName()); } if (selector instanceof MethodSelector methodSelector) { return MethodSource.from(methodSelector.getClassName(), methodSelector.getMethodName(), methodSelector.getParameterTypeNames()); } if (selector instanceof ClasspathResourceSelector resourceSelector) { String resourceName = resourceSelector.getClasspathResourceName(); return resourceSelector.getPosition() // .map(DiscoveryIssueReportingDiscoveryListener::convert) // .map(position -> ClasspathResourceSource.from(resourceName, position)) // .orElseGet(() -> ClasspathResourceSource.from(resourceName)); } if (selector instanceof PackageSelector packageSelector) { return PackageSource.from(packageSelector.getPackageName()); } 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) { return fileSelector.getPosition() // .map(DiscoveryIssueReportingDiscoveryListener::convert) // .map(position -> FileSource.from(fileSelector.getFile(), position)) // .orElseGet(() -> FileSource.from(fileSelector.getFile())); } if (selector instanceof DirectorySelector directorySelector) { return DirectorySource.from(directorySelector.getDirectory()); } if (selector instanceof UriSelector uriSelector) { return UriSource.from(uriSelector.getUri()); } } catch (Exception ex) { rethrowIfUnrecoverable(ex); logger.warn(ex, () -> "Failed to convert DiscoverySelector [%s] into TestSource".formatted(selector)); } return null; } private static FilePosition convert(org.junit.platform.engine.discovery.FilePosition position) { return position.getColumn() // .map(column -> FilePosition.from(position.getLine(), column)) // .orElseGet(() -> FilePosition.from(position.getLine())); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static java.util.stream.Collectors.joining; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.engine.Filter.composeFilters; import static org.junit.platform.launcher.core.LauncherPhase.getDiscoveryIssueFailurePhase; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.Function; import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.UnrecoverableExceptions; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.Filter; import org.junit.platform.engine.FilterResult; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.EngineDescriptor; import org.junit.platform.launcher.EngineDiscoveryResult; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.PostDiscoveryFilter; import org.junit.platform.launcher.core.LauncherDiscoveryResult.EngineResultInfo; /** * Orchestrates test discovery using the configured test engines. * * @since 1.7 */ @API(status = INTERNAL, since = "1.7", consumers = { "org.junit.platform.testkit", "org.junit.platform.suite.engine" }) public class EngineDiscoveryOrchestrator { private static final Logger logger = LoggerFactory.getLogger(EngineDiscoveryOrchestrator.class); private final EngineDiscoveryResultValidator discoveryResultValidator = new EngineDiscoveryResultValidator(); private final Iterable testEngines; private final Collection postDiscoveryFilters; private final ListenerRegistry launcherDiscoveryListenerRegistry; public EngineDiscoveryOrchestrator(Iterable testEngines, Collection postDiscoveryFilters) { this(testEngines, postDiscoveryFilters, ListenerRegistry.forLauncherDiscoveryListeners()); } EngineDiscoveryOrchestrator(Iterable testEngines, Collection postDiscoveryFilters, ListenerRegistry launcherDiscoveryListenerRegistry) { this.testEngines = EngineIdValidator.validate(testEngines); this.postDiscoveryFilters = postDiscoveryFilters; this.launcherDiscoveryListenerRegistry = launcherDiscoveryListenerRegistry; } /** * Discovers tests for the supplied request using the configured test * engines. * *

Applies {@linkplain org.junit.platform.launcher.EngineFilter engine * filters} and {@linkplain PostDiscoveryFilter post-discovery filters} and * {@linkplain TestDescriptor#prune() prunes} the resulting test tree. */ public LauncherDiscoveryResult discover(LauncherDiscoveryRequest request) { return discover(request, Optional.empty(), UniqueId::forEngine); } LauncherDiscoveryResult discover(LauncherDiscoveryRequest request, LauncherPhase phase) { return discover(request, Optional.of(phase), UniqueId::forEngine); } /** * Discovers tests for the supplied request in the supplied phase using the * configured test engines to be used by the suite engine. * *

Applies {@linkplain org.junit.platform.launcher.EngineFilter engine * filters} and {@linkplain PostDiscoveryFilter post-discovery filters} and * {@linkplain TestDescriptor#prune() prunes} the resulting test tree. * *

Note: The test descriptors in the discovery result can safely be used * as non-root descriptors. Engine-test descriptor entries are pruned from * the returned result. As such execution by * {@link EngineExecutionOrchestrator} will not emit start or emit events * for engines without tests. */ public LauncherDiscoveryResult discover(LauncherDiscoveryRequest request, UniqueId parentId) { LauncherDiscoveryResult result = discover(request, Optional.empty(), parentId::appendEngine); return result.withRetainedEngines(TestDescriptor::containsTests); } private LauncherDiscoveryResult discover(LauncherDiscoveryRequest request, Optional phase, Function uniqueIdCreator) { DiscoveryIssueCollector issueCollector = new DiscoveryIssueCollector(request.getConfigurationParameters()); LauncherDiscoveryListener listener = getLauncherDiscoveryListener(request, issueCollector); LauncherDiscoveryRequest delegatingRequest = new DelegatingLauncherDiscoveryRequest(request) { @Override public LauncherDiscoveryListener getDiscoveryListener() { return listener; } }; listener.launcherDiscoveryStarted(request); LauncherDiscoveryResult discoveryResult; try { Map testEngineResults = discoverSafely(delegatingRequest, phase, issueCollector, uniqueIdCreator); discoveryResult = new LauncherDiscoveryResult(testEngineResults, request.getConfigurationParameters(), request.getOutputDirectoryCreator()); } finally { listener.launcherDiscoveryFinished(request); } if (shouldReportDiscoveryIssues(request, phase)) { reportDiscoveryIssues(discoveryResult); } return discoveryResult; } private static boolean shouldReportDiscoveryIssues(LauncherDiscoveryRequest request, Optional phase) { ConfigurationParameters configurationParameters = request.getConfigurationParameters(); return getDiscoveryIssueFailurePhase(configurationParameters) // .orElse(phase.orElse(null)) == LauncherPhase.DISCOVERY; } private static void reportDiscoveryIssues(LauncherDiscoveryResult discoveryResult) { DiscoveryIssueException exception = null; for (TestEngine testEngine : discoveryResult.getTestEngines()) { EngineResultInfo engineResult = discoveryResult.getEngineResult(testEngine); DiscoveryIssueNotifier discoveryIssueNotifier = engineResult.getDiscoveryIssueNotifier(); discoveryIssueNotifier.logCriticalIssues(testEngine); discoveryIssueNotifier.logNonCriticalIssues(testEngine); if (exception == null) { exception = discoveryIssueNotifier.createExceptionForCriticalIssues(testEngine); } } if (exception != null) { throw exception; } } private Map discoverSafely(LauncherDiscoveryRequest request, Optional phase, DiscoveryIssueCollector issueCollector, Function uniqueIdCreator) { Map testEngineDescriptors = new LinkedHashMap<>(); EngineFilterer engineFilterer = new EngineFilterer(request.getEngineFilters()); for (TestEngine testEngine : this.testEngines) { boolean engineIsExcluded = engineFilterer.isExcluded(testEngine); if (engineIsExcluded) { logger.debug(() -> "Test discovery for engine '%s' was skipped due to an EngineFilter%s.".formatted( testEngine.getId(), phase.map(" in %s phase"::formatted).orElse(""))); continue; } logger.debug(() -> "Discovering tests%s in engine '%s'.".formatted( phase.map(" during Launcher %s phase"::formatted).orElse(""), testEngine.getId())); EngineResultInfo engineResult = discoverEngineRoot(testEngine, request, issueCollector, uniqueIdCreator); testEngineDescriptors.put(testEngine, engineResult); } engineFilterer.performSanityChecks(); List filters = new ArrayList<>(postDiscoveryFilters); filters.addAll(request.getPostDiscoveryFilters()); applyPostDiscoveryFilters(testEngineDescriptors, filters); prune(testEngineDescriptors); return testEngineDescriptors; } private EngineResultInfo discoverEngineRoot(TestEngine testEngine, LauncherDiscoveryRequest request, DiscoveryIssueCollector issueCollector, Function uniqueIdCreator) { UniqueId uniqueEngineId = uniqueIdCreator.apply(testEngine.getId()); LauncherDiscoveryListener listener = request.getDiscoveryListener(); try { listener.engineDiscoveryStarted(uniqueEngineId); TestDescriptor engineRoot = testEngine.discover(request, uniqueEngineId); discoveryResultValidator.validate(testEngine, engineRoot); listener.engineDiscoveryFinished(uniqueEngineId, EngineDiscoveryResult.successful()); return EngineResultInfo.completed(engineRoot, issueCollector.toNotifier()); } catch (Throwable throwable) { UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); JUnitException cause = null; if (throwable instanceof LinkageError error) { cause = ClasspathAlignmentChecker.check(error).orElse(null); } if (cause == null) { String message = "TestEngine with ID '%s' failed to discover tests".formatted(testEngine.getId()); cause = new JUnitException(message, throwable); } listener.engineDiscoveryFinished(uniqueEngineId, EngineDiscoveryResult.failed(cause)); return EngineResultInfo.errored(new EngineDescriptor(uniqueEngineId, testEngine.getId()), issueCollector.toNotifier(), cause); } } LauncherDiscoveryListener getLauncherDiscoveryListener(LauncherDiscoveryRequest discoveryRequest, DiscoveryIssueCollector issueCollector) { var delegate = ListenerRegistry.copyOf(launcherDiscoveryListenerRegistry) // .add(discoveryRequest.getDiscoveryListener()) // .add(issueCollector) // .getCompositeListener(); return new DiscoveryIssueReportingDiscoveryListener(delegate); } private void applyPostDiscoveryFilters(Map testEngineDescriptors, List filters) { Filter postDiscoveryFilter = composeFilters(filters); Map> excludedTestDescriptorsByReason = new LinkedHashMap<>(); TestDescriptor.Visitor removeExcludedTestDescriptors = descriptor -> { FilterResult filterResult = postDiscoveryFilter.apply(descriptor); if (!descriptor.isRoot() && isExcluded(descriptor, filterResult)) { populateExclusionReasonInMap(filterResult.getReason(), descriptor, excludedTestDescriptorsByReason); descriptor.removeFromHierarchy(); } }; acceptInAllTestEngines(testEngineDescriptors, removeExcludedTestDescriptors); logTestDescriptorExclusionReasons(excludedTestDescriptorsByReason); } private void populateExclusionReasonInMap(Optional reason, TestDescriptor testDescriptor, Map> excludedTestDescriptorsByReason) { excludedTestDescriptorsByReason.computeIfAbsent(reason.orElse("Unknown"), __ -> new ArrayList<>()).add( testDescriptor); } private void logTestDescriptorExclusionReasons(Map> excludedTestDescriptorsByReason) { excludedTestDescriptorsByReason.forEach((exclusionReason, testDescriptors) -> { String displayNames = testDescriptors.stream().map(TestDescriptor::getDisplayName).collect(joining(", ")); long containerCount = testDescriptors.stream().filter(TestDescriptor::isContainer).count(); long methodCount = testDescriptors.stream().filter(TestDescriptor::isTest).count(); logger.config( () -> "%d containers and %d tests were %s".formatted(containerCount, methodCount, exclusionReason)); logger.debug( () -> "The following containers and tests were %s: %s".formatted(exclusionReason, displayNames)); }); } /** * Prune all branches in the tree of {@link TestDescriptor TestDescriptors} * that do not have executable tests. * *

If a {@link TestEngine} ends up with no {@code TestDescriptors} after * pruning, it will not be removed. */ private void prune(Map testEngineResults) { acceptInAllTestEngines(testEngineResults, TestDescriptor::prune); } private boolean isExcluded(TestDescriptor descriptor, FilterResult filterResult) { return descriptor.getChildren().isEmpty() && filterResult.excluded(); } private void acceptInAllTestEngines(Map testEngineResults, TestDescriptor.Visitor visitor) { testEngineResults.values().forEach(result -> result.getRootDescriptor().accept(visitor)); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryResultValidator.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static java.util.stream.Collectors.joining; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Queue; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.UniqueId; /** * Perform common validation checks on the result from the `discover()` method. * * @since 1.3 */ class EngineDiscoveryResultValidator { /** * Perform common validation checks. * * @throws org.junit.platform.commons.PreconditionViolationException if any check fails */ void validate(TestEngine testEngine, TestDescriptor root) { Preconditions.notNull(root, () -> "The discover() method for TestEngine with ID '%s' must return a non-null root TestDescriptor.".formatted( testEngine.getId())); Optional cyclicGraphInfo = getCyclicGraphInfo(root); Preconditions.condition(cyclicGraphInfo.isEmpty(), () -> "The discover() method for TestEngine with ID '%s' returned a cyclic graph; %s".formatted( testEngine.getId(), cyclicGraphInfo.get())); } /** * @return non-empty {@link Optional} if the tree contains a cycle */ private Optional getCyclicGraphInfo(TestDescriptor root) { Map> visited = new HashMap<>(); visited.put(root.getUniqueId(), Optional.empty()); Queue queue = new ArrayDeque<>(); queue.add(root); while (!queue.isEmpty()) { TestDescriptor parent = queue.remove(); for (TestDescriptor child : parent.getChildren()) { UniqueId uid = child.getUniqueId(); if (visited.containsKey(uid)) { // id already known: cycle detected! List path1 = findPath(visited, uid); List path2 = findPath(visited, parent.getUniqueId()); path2.add(uid); return Optional.of("%s exists in at least two paths:\n(1) %s\n(2) %s".formatted(uid, formatted(path1), formatted(path2))); } else { visited.put(uid, Optional.of(parent.getUniqueId())); } if (child.isContainer()) { queue.add(child); } } } return Optional.empty(); } private String formatted(List path) { return path.stream().map(UniqueId::toString).collect(joining(" -> ")); } private static List findPath(Map> visited, UniqueId target) { List path = new ArrayList<>(); path.add(target); UniqueId current = target; while (visited.containsKey(current)) { Optional backTraced = visited.get(current); if (backTraced.isPresent()) { path.add(0, backTraced.get()); current = backTraced.get(); } else { break; } } return path; } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.launcher.LauncherConstants.DRY_RUN_PROPERTY_NAME; import static org.junit.platform.launcher.LauncherConstants.STACKTRACE_PRUNING_ENABLED_PROPERTY_NAME; import static org.junit.platform.launcher.core.LauncherPhase.getDiscoveryIssueFailurePhase; import java.util.Collection; import java.util.Optional; import java.util.function.Consumer; import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.UnrecoverableExceptions; import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.OutputDirectoryCreator; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.support.store.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.LauncherConstants; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; import org.junit.platform.launcher.core.LauncherDiscoveryResult.EngineResultInfo; /** * Orchestrates test execution using the configured test engines. * * @since 1.7 */ @API(status = INTERNAL, since = "1.7", consumers = { "org.junit.platform.testkit", "org.junit.platform.suite.engine" }) public class EngineExecutionOrchestrator { private final ListenerRegistry listenerRegistry; public EngineExecutionOrchestrator() { this(ListenerRegistry.forTestExecutionListeners()); } EngineExecutionOrchestrator(ListenerRegistry listenerRegistry) { this.listenerRegistry = listenerRegistry; } void execute(InternalTestPlan internalTestPlan, NamespacedHierarchicalStore requestLevelStore, Collection listeners, CancellationToken cancellationToken) { ConfigurationParameters configurationParameters = internalTestPlan.getConfigurationParameters(); ListenerRegistry testExecutionListenerListeners = buildListenerRegistryForExecution( listeners); withInterceptedStreams(configurationParameters, testExecutionListenerListeners, testExecutionListener -> execute(internalTestPlan, EngineExecutionListener.NOOP, testExecutionListener, requestLevelStore, cancellationToken)); } /** * Executes tests for the supplied {@linkplain LauncherDiscoveryResult * discoveryResult} and notifies the supplied {@linkplain * EngineExecutionListener engineExecutionListener} and * {@linkplain TestExecutionListener testExecutionListener} of execution * events. */ @API(status = INTERNAL, since = "1.9", consumers = { "org.junit.platform.testkit", "org.junit.platform.suite.engine" }) public void execute(LauncherDiscoveryResult discoveryResult, EngineExecutionListener engineExecutionListener, TestExecutionListener testExecutionListener, NamespacedHierarchicalStore requestLevelStore, CancellationToken cancellationToken) { Preconditions.notNull(discoveryResult, "discoveryResult must not be null"); Preconditions.notNull(engineExecutionListener, "engineExecutionListener must not be null"); Preconditions.notNull(testExecutionListener, "testExecutionListener must not be null"); Preconditions.notNull(requestLevelStore, "requestLevelStore must not be null"); Preconditions.notNull(cancellationToken, "cancellationToken must not be null"); InternalTestPlan internalTestPlan = InternalTestPlan.from(discoveryResult); execute(internalTestPlan, engineExecutionListener, testExecutionListener, requestLevelStore, cancellationToken); } private void execute(InternalTestPlan internalTestPlan, EngineExecutionListener parentEngineExecutionListener, TestExecutionListener testExecutionListener, NamespacedHierarchicalStore requestLevelStore, CancellationToken cancellationToken) { internalTestPlan.markStarted(); // Do not directly pass the internal test plan to test execution listeners. // Hyrum's Law indicates that someone will eventually come to depend on it. TestPlan testPlan = internalTestPlan.getDelegate(); LauncherDiscoveryResult discoveryResult = internalTestPlan.getDiscoveryResult(); testExecutionListener.testPlanExecutionStarted(testPlan); if (isDryRun(internalTestPlan)) { dryRun(testPlan, testExecutionListener); } else { execute(discoveryResult, buildEngineExecutionListener(parentEngineExecutionListener, testExecutionListener, testPlan), requestLevelStore, cancellationToken); } testExecutionListener.testPlanExecutionFinished(testPlan); } private Boolean isDryRun(InternalTestPlan internalTestPlan) { return internalTestPlan.getConfigurationParameters().getBoolean(DRY_RUN_PROPERTY_NAME).orElse(false); } private void dryRun(TestPlan testPlan, TestExecutionListener listener) { testPlan.accept(new TestPlan.Visitor() { @Override public void preVisitContainer(TestIdentifier testIdentifier) { listener.executionStarted(testIdentifier); } @Override public void visit(TestIdentifier testIdentifier) { if (!testIdentifier.isContainer()) { listener.executionSkipped(testIdentifier, "JUnit Platform dry-run mode is enabled"); } } @Override public void postVisitContainer(TestIdentifier testIdentifier) { listener.executionFinished(testIdentifier, TestExecutionResult.successful()); } }); } private static EngineExecutionListener buildEngineExecutionListener( EngineExecutionListener parentEngineExecutionListener, TestExecutionListener testExecutionListener, TestPlan testPlan) { var registry = ListenerRegistry.forEngineExecutionListeners(); registry.add(new ExecutionListenerAdapter(testPlan, testExecutionListener)); registry.add(parentEngineExecutionListener); var listener = registry.getCompositeListener(); if (isMemoryCleanupEnabled(testPlan)) { listener = new MemoryCleanupListener(listener, testPlan); } return listener; } private static Boolean isMemoryCleanupEnabled(TestPlan testPlan) { return testPlan.getConfigurationParameters() // .getBoolean(LauncherConstants.MEMORY_CLEANUP_ENABLED_PROPERTY_NAME) // .orElse(false); } private void withInterceptedStreams(ConfigurationParameters configurationParameters, ListenerRegistry listenerRegistry, Consumer action) { TestExecutionListener testExecutionListener = listenerRegistry.getCompositeListener(); Optional streamInterceptingTestExecutionListener = StreamInterceptingTestExecutionListener.create( configurationParameters, testExecutionListener::reportingEntryPublished); streamInterceptingTestExecutionListener.ifPresent(listenerRegistry::add); try { action.accept(listenerRegistry.getCompositeListener()); } finally { streamInterceptingTestExecutionListener.ifPresent(StreamInterceptingTestExecutionListener::unregister); } } /** * Executes tests for the supplied {@linkplain LauncherDiscoveryResult * discovery results} and notifies the supplied {@linkplain * EngineExecutionListener listener} of execution events. */ private void execute(LauncherDiscoveryResult discoveryResult, EngineExecutionListener engineExecutionListener, NamespacedHierarchicalStore requestLevelStore, CancellationToken cancellationToken) { Preconditions.notNull(discoveryResult, "discoveryResult must not be null"); Preconditions.notNull(engineExecutionListener, "engineExecutionListener must not be null"); ConfigurationParameters configurationParameters = discoveryResult.getConfigurationParameters(); EngineExecutionListener listener = selectExecutionListener(engineExecutionListener, configurationParameters); for (TestEngine testEngine : discoveryResult.getTestEngines()) { failOrExecuteEngine(discoveryResult, listener, testEngine, requestLevelStore, cancellationToken); } } private static EngineExecutionListener selectExecutionListener(EngineExecutionListener engineExecutionListener, ConfigurationParameters configurationParameters) { boolean stackTracePruningEnabled = configurationParameters.getBoolean(STACKTRACE_PRUNING_ENABLED_PROPERTY_NAME) // .orElse(true); if (stackTracePruningEnabled) { return new StackTracePruningEngineExecutionListener(engineExecutionListener); } return engineExecutionListener; } private void failOrExecuteEngine(LauncherDiscoveryResult discoveryResult, EngineExecutionListener listener, TestEngine testEngine, NamespacedHierarchicalStore requestLevelStore, CancellationToken cancellationToken) { EngineResultInfo engineDiscoveryResult = discoveryResult.getEngineResult(testEngine); DiscoveryIssueNotifier discoveryIssueNotifier = shouldReportDiscoveryIssues(discoveryResult) // ? engineDiscoveryResult.getDiscoveryIssueNotifier() // : DiscoveryIssueNotifier.NO_ISSUES; TestDescriptor engineDescriptor = engineDiscoveryResult.getRootDescriptor(); Throwable failure = engineDiscoveryResult.getCause() // .orElseGet(() -> discoveryIssueNotifier.createExceptionForCriticalIssues(testEngine)); if (failure != null) { listener.executionStarted(engineDescriptor); if (engineDiscoveryResult.getCause().isPresent()) { discoveryIssueNotifier.logCriticalIssues(testEngine); } discoveryIssueNotifier.logNonCriticalIssues(testEngine); listener.executionFinished(engineDescriptor, TestExecutionResult.failed(failure)); } else if (cancellationToken.isCancellationRequested()) { listener.executionStarted(engineDescriptor); engineDescriptor.getChildren().forEach(child -> listener.executionSkipped(child, "Execution cancelled")); listener.executionFinished(engineDescriptor, TestExecutionResult.aborted(null)); } else { executeEngine(engineDescriptor, listener, discoveryResult.getConfigurationParameters(), testEngine, discoveryResult.getOutputDirectoryCreator(), discoveryIssueNotifier, requestLevelStore, cancellationToken); } } private static boolean shouldReportDiscoveryIssues(LauncherDiscoveryResult discoveryResult) { ConfigurationParameters configurationParameters = discoveryResult.getConfigurationParameters(); return getDiscoveryIssueFailurePhase(configurationParameters) // .orElse(LauncherPhase.EXECUTION) == LauncherPhase.EXECUTION; } private ListenerRegistry buildListenerRegistryForExecution( Collection listeners) { if (listeners.isEmpty()) { return this.listenerRegistry; } return ListenerRegistry.copyOf(this.listenerRegistry).addAll(listeners); } private void executeEngine(TestDescriptor engineDescriptor, EngineExecutionListener listener, ConfigurationParameters configurationParameters, TestEngine testEngine, OutputDirectoryCreator outputDirectoryCreator, DiscoveryIssueNotifier discoveryIssueNotifier, NamespacedHierarchicalStore requestLevelStore, CancellationToken cancellationToken) { OutcomeDelayingEngineExecutionListener delayingListener = new OutcomeDelayingEngineExecutionListener(listener, engineDescriptor); try { testEngine.execute(ExecutionRequest.create(engineDescriptor, delayingListener, configurationParameters, outputDirectoryCreator, requestLevelStore, cancellationToken)); discoveryIssueNotifier.logNonCriticalIssues(testEngine); delayingListener.reportEngineOutcome(); } catch (Throwable throwable) { UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); JUnitException cause = null; if (throwable instanceof LinkageError error) { cause = ClasspathAlignmentChecker.check(error).orElse(null); } if (cause == null) { String message = "TestEngine with ID '%s' failed to execute tests".formatted(testEngine.getId()); cause = new JUnitException(message, throwable); } delayingListener.reportEngineStartIfNecessary(); discoveryIssueNotifier.logNonCriticalIssues(testEngine); delayingListener.reportEngineFailure(cause); } } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineFilterer.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toCollection; import static java.util.stream.Collectors.toSet; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.engine.FilterResult; import org.junit.platform.engine.TestEngine; import org.junit.platform.launcher.EngineFilter; class EngineFilterer { private static final Logger logger = LoggerFactory.getLogger(EngineFilterer.class); private final List engineFilters; private final Map checkedTestEngines = new HashMap<>(); EngineFilterer(List engineFilters) { this.engineFilters = engineFilters; } boolean isExcluded(TestEngine testEngine) { boolean excluded = engineFilters.stream() // .map(engineFilter -> engineFilter.apply(testEngine)) // .anyMatch(FilterResult::excluded); checkedTestEngines.put(testEngine, excluded); return excluded; } void performSanityChecks() { checkNoUnmatchedIncludeFilter(); warnIfAllEnginesExcluded(); } private void warnIfAllEnginesExcluded() { if (!checkedTestEngines.isEmpty() && checkedTestEngines.values().stream().allMatch(excluded -> excluded)) { logger.warn(() -> "All TestEngines were excluded by EngineFilters.\n" + getStateDescription()); } } private void checkNoUnmatchedIncludeFilter() { SortedSet unmatchedEngineIdsOfIncludeFilters = getUnmatchedEngineIdsOfIncludeFilters(); if (!unmatchedEngineIdsOfIncludeFilters.isEmpty()) { throw new JUnitException("No TestEngine ID matched the following include EngineFilters: " + unmatchedEngineIdsOfIncludeFilters + ".\n" // + "Please fix/remove the filter or add the engine.\n" // + getStateDescription()); } } private SortedSet getUnmatchedEngineIdsOfIncludeFilters() { Set checkedTestEngineIds = checkedTestEngines.keySet().stream() // .map(TestEngine::getId) // .collect(toSet()); return engineFilters.stream() // .filter(EngineFilter::isIncludeFilter) // .map(EngineFilter::getEngineIds) // .flatMap(Collection::stream) // .filter(id -> !checkedTestEngineIds.contains(id)) // .collect(toCollection(TreeSet::new)); } private String getStateDescription() { return getRegisteredEnginesDescription() + "\n" + getRegisteredFiltersDescription(); } private String getRegisteredEnginesDescription() { return TestEngineFormatter.format("Registered TestEngines", checkedTestEngines.keySet()); } private String getRegisteredFiltersDescription() { return "Registered EngineFilters:" + engineFilters.stream() // .map(Objects::toString) // .collect(joining("\n- ", "\n- ", "")); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineIdValidator.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import java.util.HashSet; import java.util.Set; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.TestEngine; /** * @since 1.7 */ class EngineIdValidator { private EngineIdValidator() { } static Iterable validate(Iterable testEngines) { Set ids = new HashSet<>(); for (TestEngine testEngine : testEngines) { // check usage of reserved ID prefix if (!validateReservedIds(testEngine)) { getLogger().warn( () -> "Third-party TestEngine implementations are forbidden to use the reserved 'junit-' prefix for their ID: '%s'".formatted( testEngine.getId())); } // check uniqueness if (!ids.add(testEngine.getId())) { throw new JUnitException( "Cannot create Launcher for multiple engines with the same ID '%s'.".formatted(testEngine.getId())); } } return testEngines; } private static Logger getLogger() { // Not a constant to avoid problems with building GraalVM native images return LoggerFactory.getLogger(EngineIdValidator.class); } // https://github.com/junit-team/junit-framework/issues/1557 private static boolean validateReservedIds(TestEngine testEngine) { String engineId = Preconditions.notBlank(testEngine.getId(), () -> "ID for TestEngine [%s] must not be null or blank".formatted(testEngine.getClass().getName())); if (!engineId.startsWith("junit-")) { return true; } return switch (engineId) { case "junit-jupiter" -> { validateWellKnownClassName(testEngine, "org.junit.jupiter.engine.JupiterTestEngine"); yield true; } case "junit-vintage" -> { validateWellKnownClassName(testEngine, "org.junit.vintage.engine.VintageTestEngine"); yield true; } case "junit-platform-suite" -> { validateWellKnownClassName(testEngine, "org.junit.platform.suite.engine.SuiteTestEngine"); yield true; } default -> false; }; } private static void validateWellKnownClassName(TestEngine testEngine, String expectedClassName) { String actualClassName = testEngine.getClass().getName(); if (actualClassName.equals(expectedClassName)) { return; } throw new JUnitException( "Third-party TestEngine '%s' is forbidden to use the reserved '%s' TestEngine ID.".formatted( actualClassName, testEngine.getId())); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ExecutionListenerAdapter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; /** * An {@code ExecutionListenerAdapter} adapts a {@link TestPlan} and a corresponding * {@link TestExecutionListener} to the {@link EngineExecutionListener} API. * * @since 1.0 */ class ExecutionListenerAdapter implements EngineExecutionListener { private final TestPlan testPlan; private final TestExecutionListener testExecutionListener; ExecutionListenerAdapter(TestPlan testPlan, TestExecutionListener testExecutionListener) { this.testPlan = testPlan; this.testExecutionListener = testExecutionListener; } @Override public void dynamicTestRegistered(TestDescriptor testDescriptor) { TestIdentifier testIdentifier = TestIdentifier.from(testDescriptor); this.testPlan.addInternal(testIdentifier); this.testExecutionListener.dynamicTestRegistered(testIdentifier); } @Override public void executionStarted(TestDescriptor testDescriptor) { this.testExecutionListener.executionStarted(getTestIdentifier(testDescriptor)); } @Override public void executionSkipped(TestDescriptor testDescriptor, String reason) { this.testExecutionListener.executionSkipped(getTestIdentifier(testDescriptor), reason); } @Override public void executionFinished(TestDescriptor testDescriptor, TestExecutionResult testExecutionResult) { this.testExecutionListener.executionFinished(getTestIdentifier(testDescriptor), testExecutionResult); } @Override public void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry entry) { this.testExecutionListener.reportingEntryPublished(getTestIdentifier(testDescriptor), entry); } @Override public void fileEntryPublished(TestDescriptor testDescriptor, FileEntry file) { this.testExecutionListener.fileEntryPublished(getTestIdentifier(testDescriptor), file); } private TestIdentifier getTestIdentifier(TestDescriptor testDescriptor) { return this.testPlan.getTestIdentifier(testDescriptor.getUniqueId()); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/HierarchicalOutputDirectoryCreator.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.function.Supplier; import java.util.regex.Pattern; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.OutputDirectoryCreator; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId.Segment; /** * Hierarchical {@link OutputDirectoryCreator} that creates directories based on * the unique ID segments of a {@link TestDescriptor}. * * @since 1.12 */ class HierarchicalOutputDirectoryCreator implements OutputDirectoryCreator { private static final Pattern FORBIDDEN_CHARS = Pattern.compile("[^a-z0-9.,_\\-() ]", Pattern.CASE_INSENSITIVE); private static final String REPLACEMENT = "_"; private final Supplier rootDirSupplier; private volatile @Nullable Path rootDir; HierarchicalOutputDirectoryCreator(Supplier rootDirSupplier) { this.rootDirSupplier = rootDirSupplier; } @Override public Path createOutputDirectory(TestDescriptor testDescriptor) throws IOException { Preconditions.notNull(testDescriptor, "testDescriptor must not be null"); List segments = testDescriptor.getUniqueId().getSegments(); Path relativePath = segments.stream() // .skip(1) // .map(HierarchicalOutputDirectoryCreator::toSanitizedPath) // .reduce(toSanitizedPath(segments.get(0)), Path::resolve); return Files.createDirectories(getRootDirectory().resolve(relativePath)); } @Override public synchronized Path getRootDirectory() { var currentRootDir = rootDir; if (currentRootDir == null) { currentRootDir = rootDirSupplier.get(); rootDir = currentRootDir; } return currentRootDir; } private static Path toSanitizedPath(Segment segment) { return Path.of(sanitizeName(segment.getValue())); } private static String sanitizeName(String value) { return FORBIDDEN_CHARS.matcher(value).replaceAll(REPLACEMENT); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InterceptingLauncher.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.LauncherExecutionRequest; import org.junit.platform.launcher.LauncherInterceptor; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestPlan; /** * @since 1.10 */ class InterceptingLauncher extends DelegatingLauncher { private final LauncherInterceptor interceptor; InterceptingLauncher(Launcher delegate, LauncherInterceptor interceptor) { super(delegate); this.interceptor = interceptor; } @Override public TestPlan discover(LauncherDiscoveryRequest discoveryRequest) { Preconditions.notNull(discoveryRequest, "discoveryRequest must not be null"); return interceptor.intercept(() -> super.discover(discoveryRequest)); } @Override public void execute(LauncherDiscoveryRequest discoveryRequest, TestExecutionListener... listeners) { Preconditions.notNull(discoveryRequest, "discoveryRequest must not be null"); Preconditions.notNull(listeners, "listeners must not be null"); Preconditions.containsNoNullElements(listeners, "listener array must not contain null elements"); interceptor.<@Nullable Object> intercept(() -> { super.execute(discoveryRequest, listeners); return null; }); } @Override public void execute(TestPlan testPlan, TestExecutionListener... listeners) { Preconditions.notNull(testPlan, "testPlan must not be null"); Preconditions.notNull(listeners, "listeners must not be null"); Preconditions.containsNoNullElements(listeners, "listener array must not contain null elements"); interceptor.<@Nullable Object> intercept(() -> { super.execute(testPlan, listeners); return null; }); } @Override public void execute(LauncherExecutionRequest executionRequest) { Preconditions.notNull(executionRequest, "executionRequest must not be null"); interceptor.<@Nullable Object> intercept(() -> { super.execute(executionRequest); return null; }); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InternalTestPlan.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import java.util.Optional; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Predicate; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.engine.UniqueId; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; /** * @since 1.4 */ class InternalTestPlan extends TestPlan { private final AtomicBoolean executionStarted = new AtomicBoolean(false); private final LauncherDiscoveryResult discoveryResult; private final TestPlan delegate; static InternalTestPlan from(LauncherDiscoveryResult discoveryResult) { TestPlan delegate = TestPlan.from(discoveryResult.containsCriticalIssuesOrContainsTests(), discoveryResult.getEngineTestDescriptors(), discoveryResult.getConfigurationParameters(), discoveryResult.getOutputDirectoryCreator()); return new InternalTestPlan(discoveryResult, delegate); } private InternalTestPlan(LauncherDiscoveryResult discoveryResult, TestPlan delegate) { super(delegate.containsTests(), delegate.getConfigurationParameters(), delegate.getOutputDirectoryCreator()); this.discoveryResult = discoveryResult; this.delegate = delegate; } void markStarted() { if (!executionStarted.compareAndSet(false, true)) { throw new PreconditionViolationException("TestPlan must only be executed once"); } } LauncherDiscoveryResult getDiscoveryResult() { return discoveryResult; } public TestPlan getDelegate() { return delegate; } @Override public void addInternal(TestIdentifier testIdentifier) { delegate.addInternal(testIdentifier); } @Override public void removeInternal(UniqueId uniqueId) { delegate.removeInternal(uniqueId); } @Override public Set getRoots() { return delegate.getRoots(); } @Override public Optional getParent(TestIdentifier child) { return delegate.getParent(child); } @Override public Set getChildren(TestIdentifier parent) { return delegate.getChildren(parent); } @Override public Set getChildren(UniqueId parentId) { return delegate.getChildren(parentId); } @Override public TestIdentifier getTestIdentifier(UniqueId uniqueId) { return delegate.getTestIdentifier(uniqueId); } @Override public long countTestIdentifiers(Predicate predicate) { return delegate.countTestIdentifiers(predicate); } @Override public Set getDescendants(TestIdentifier parent) { return delegate.getDescendants(parent); } @Override public boolean containsTests() { return delegate.containsTests(); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/IterationOrder.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static org.junit.platform.commons.util.CollectionUtils.forEachInReverseOrder; import java.util.List; import java.util.function.Consumer; enum IterationOrder { ORIGINAL { @Override void forEach(List listeners, Consumer action) { listeners.forEach(action); } }, REVERSED { @Override void forEach(List listeners, Consumer action) { forEachInReverseOrder(listeners, action); } }; abstract void forEach(List listeners, Consumer action); } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfig.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static org.apiguardian.api.API.Status.STABLE; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; import org.apiguardian.api.API; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.TestEngine; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherSessionListener; import org.junit.platform.launcher.PostDiscoveryFilter; import org.junit.platform.launcher.TestExecutionListener; /** * {@code LauncherConfig} defines the configuration API for creating * {@link Launcher} instances via the {@link LauncherFactory}. * *

Example

* *
 * LauncherConfig launcherConfig = LauncherConfig.builder()
 *   .enableTestEngineAutoRegistration(false)
 *   .enableTestExecutionListenerAutoRegistration(false)
 *   .addTestEngines(new CustomTestEngine())
 *   .addTestExecutionListeners(new CustomTestExecutionListener())
 *   .build();
 *
 * Launcher launcher = LauncherFactory.create(launcherConfig);
 * LauncherDiscoveryRequest discoveryRequest = ...
 * launcher.execute(discoveryRequest);
 * 
* * @since 1.3 * @see #builder() * @see Launcher * @see LauncherFactory */ @API(status = STABLE, since = "1.7") public interface LauncherConfig { /** * The default {@code LauncherConfig} which uses automatic registration for * test engines, supported listeners, and post-discovery filters. */ LauncherConfig DEFAULT = builder().build(); /** * Determine if test engines should be discovered at runtime using the * {@link java.util.ServiceLoader ServiceLoader} mechanism and * automatically registered. * * @return {@code true} if test engines should be automatically registered */ boolean isTestEngineAutoRegistrationEnabled(); /** * Determine if launcher session listeners should be discovered at runtime * using the {@link java.util.ServiceLoader ServiceLoader} mechanism and * automatically registered. * * @return {@code true} if launcher session listeners should be * automatically registered * @since 1.8 */ @API(status = STABLE, since = "1.10") boolean isLauncherSessionListenerAutoRegistrationEnabled(); /** * Determine if launcher discovery listeners should be discovered at runtime * using the {@link java.util.ServiceLoader ServiceLoader} mechanism and * automatically registered. * * @return {@code true} if launcher discovery listeners should be * automatically registered * @since 1.8 */ @API(status = STABLE, since = "1.10") boolean isLauncherDiscoveryListenerAutoRegistrationEnabled(); /** * Determine if test execution listeners should be discovered at runtime * using the {@link java.util.ServiceLoader ServiceLoader} mechanism and * automatically registered. * * @return {@code true} if test execution listeners should be automatically * registered */ boolean isTestExecutionListenerAutoRegistrationEnabled(); /** * Determine if post discovery filters should be discovered at runtime * using the {@link java.util.ServiceLoader ServiceLoader} mechanism and * automatically registered. * * @return {@code true} if post discovery filters should be automatically * registered * @since 1.7 */ @API(status = STABLE, since = "1.10") boolean isPostDiscoveryFilterAutoRegistrationEnabled(); /** * Get the collection of additional test engines that should be added to * the {@link Launcher}. * * @return the collection of additional test engines; never {@code null} but * potentially empty */ Collection getAdditionalTestEngines(); /** * Get the collection of additional launcher session listeners that should * be added to the {@link Launcher}. * * @return the collection of additional launcher session listeners; never * {@code null} but potentially empty * @since 1.8 */ @API(status = STABLE, since = "1.10") Collection getAdditionalLauncherSessionListeners(); /** * Get the collection of additional launcher discovery listeners that should * be added to the {@link Launcher}. * * @return the collection of additional launcher discovery listeners; never * {@code null} but potentially empty * @since 1.8 */ @API(status = STABLE, since = "1.10") Collection getAdditionalLauncherDiscoveryListeners(); /** * Get the collection of additional test execution listeners that should be * added to the {@link Launcher}. * * @return the collection of additional test execution listeners; never * {@code null} but potentially empty */ Collection getAdditionalTestExecutionListeners(); /** * Get the collection of additional post discovery filters that should be * added to the {@link Launcher}. * * @return the collection of additional post discovery filters; never * {@code null} but potentially empty * @since 1.7 */ @API(status = STABLE, since = "1.10") Collection getAdditionalPostDiscoveryFilters(); /** * Create a new {@link LauncherConfig.Builder}. * * @return a new builder; never {@code null} */ static Builder builder() { return new Builder(); } /** * Builder API for {@link LauncherConfig}. */ class Builder { private boolean engineAutoRegistrationEnabled = true; private boolean launcherSessionListenerAutoRegistrationEnabled = true; private boolean launcherDiscoveryListenerAutoRegistrationEnabled = true; private boolean testExecutionListenerAutoRegistrationEnabled = true; private boolean postDiscoveryFilterAutoRegistrationEnabled = true; private final Collection engines = new LinkedHashSet<>(); private final Collection sessionListeners = new LinkedHashSet<>(); private final Collection discoveryListeners = new LinkedHashSet<>(); private final Collection executionListeners = new LinkedHashSet<>(); private final Collection postDiscoveryFilters = new LinkedHashSet<>(); private Builder() { /* no-op */ } /** * Configure the auto-registration flag for launcher session * listeners. * *

Defaults to {@code true}. * * @param enabled {@code true} if launcher session listeners should be * automatically registered * @return this builder for method chaining * @since 1.8 */ @API(status = STABLE, since = "1.10") public Builder enableLauncherSessionListenerAutoRegistration(boolean enabled) { this.launcherSessionListenerAutoRegistrationEnabled = enabled; return this; } /** * Configure the auto-registration flag for launcher discovery * listeners. * *

Defaults to {@code true}. * * @param enabled {@code true} if launcher discovery listeners should be * automatically registered * @return this builder for method chaining * @since 1.8 */ @API(status = STABLE, since = "1.10") public Builder enableLauncherDiscoveryListenerAutoRegistration(boolean enabled) { this.launcherDiscoveryListenerAutoRegistrationEnabled = enabled; return this; } /** * Configure the auto-registration flag for test execution listeners. * *

Defaults to {@code true}. * * @param enabled {@code true} if test execution listeners should be * automatically registered * @return this builder for method chaining */ public Builder enableTestExecutionListenerAutoRegistration(boolean enabled) { this.testExecutionListenerAutoRegistrationEnabled = enabled; return this; } /** * Configure the auto-registration flag for test engines. * *

Defaults to {@code true}. * * @param enabled {@code true} if test engines should be automatically * registered * @return this builder for method chaining */ public Builder enableTestEngineAutoRegistration(boolean enabled) { this.engineAutoRegistrationEnabled = enabled; return this; } /** * Configure the auto-registration flag for post discovery filters. * *

Defaults to {@code true}. * * @param enabled {@code true} if post discovery filters should be automatically * registered * @return this builder for method chaining * @since 1.7 */ @API(status = STABLE, since = "1.10") public Builder enablePostDiscoveryFilterAutoRegistration(boolean enabled) { this.postDiscoveryFilterAutoRegistrationEnabled = enabled; return this; } /** * Add all of the supplied test engines to the configuration. * * @param engines additional test engines to register; never {@code null} * or containing {@code null} * @return this builder for method chaining */ public Builder addTestEngines(TestEngine... engines) { Preconditions.notNull(engines, "TestEngine array must not be null"); Preconditions.containsNoNullElements(engines, "TestEngine array must not contain null elements"); Collections.addAll(this.engines, engines); return this; } /** * Add all of the supplied launcher session listeners to the configuration. * * @param listeners additional launcher session listeners to register; * never {@code null} or containing {@code null} * @return this builder for method chaining */ public Builder addLauncherSessionListeners(LauncherSessionListener... listeners) { Preconditions.notNull(listeners, "LauncherSessionListener array must not be null"); Preconditions.containsNoNullElements(listeners, "LauncherSessionListener array must not contain null elements"); Collections.addAll(this.sessionListeners, listeners); return this; } /** * Add all of the supplied launcher discovery listeners to the configuration. * * @param listeners additional launcher discovery listeners to register; * never {@code null} or containing {@code null} * @return this builder for method chaining */ public Builder addLauncherDiscoveryListeners(LauncherDiscoveryListener... listeners) { Preconditions.notNull(listeners, "LauncherDiscoveryListener array must not be null"); Preconditions.containsNoNullElements(listeners, "LauncherDiscoveryListener array must not contain null elements"); Collections.addAll(this.discoveryListeners, listeners); return this; } /** * Add all of the supplied test execution listeners to the configuration. * * @param listeners additional test execution listeners to register; * never {@code null} or containing {@code null} * @return this builder for method chaining */ public Builder addTestExecutionListeners(TestExecutionListener... listeners) { Preconditions.notNull(listeners, "TestExecutionListener array must not be null"); Preconditions.containsNoNullElements(listeners, "TestExecutionListener array must not contain null elements"); Collections.addAll(this.executionListeners, listeners); return this; } /** * Add all of the supplied {@code filters} to the configuration. * * @param filters additional post discovery filters to register; * never {@code null} or containing {@code null} * @return this builder for method chaining * @since 1.7 */ @API(status = STABLE, since = "1.10") public Builder addPostDiscoveryFilters(PostDiscoveryFilter... filters) { Preconditions.notNull(filters, "PostDiscoveryFilter array must not be null"); Preconditions.containsNoNullElements(filters, "PostDiscoveryFilter array must not contain null elements"); Collections.addAll(this.postDiscoveryFilters, filters); return this; } /** * Build the {@link LauncherConfig} that has been configured via this * builder. */ public LauncherConfig build() { return new DefaultLauncherConfig(this.engineAutoRegistrationEnabled, this.launcherSessionListenerAutoRegistrationEnabled, this.launcherDiscoveryListenerAutoRegistrationEnabled, this.testExecutionListenerAutoRegistrationEnabled, this.postDiscoveryFilterAutoRegistrationEnabled, this.engines, this.sessionListeners, this.discoveryListeners, this.executionListeners, this.postDiscoveryFilters); } } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfigurationParameters.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static java.util.stream.Collectors.joining; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Properties; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.ClassLoaderUtils; import org.junit.platform.commons.util.CollectionUtils; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.ConfigurationParameters; /** * @since 1.0 */ class LauncherConfigurationParameters implements ConfigurationParameters { private static final Logger logger = LoggerFactory.getLogger(LauncherConfigurationParameters.class); static Builder builder() { return new Builder(); } private final List providers; private LauncherConfigurationParameters(List providers) { this.providers = providers; } @Override public Optional get(String key) { return Optional.ofNullable(getProperty(key)); } @Override public Optional getBoolean(String key) { return get(key).map(Boolean::parseBoolean); } @Override public Set keySet() { return providers.stream().map(ParameterProvider::keySet).flatMap(Collection::stream).collect( Collectors.toSet()); } private @Nullable String getProperty(String key) { Preconditions.notBlank(key, "key must not be null or blank"); return providers.stream() // .map(parameterProvider -> parameterProvider.getValue(key)) // .filter(Objects::nonNull) // .findFirst() // .orElse(null); } @Override public String toString() { return new ToStringBuilder(this) // .append("lookups", providers) // .toString(); } static final class Builder { private final Map explicitParameters = new HashMap<>(); private final List configResources = new ArrayList<>(); private boolean implicitProvidersEnabled = true; private String configFileName = ConfigurationParameters.CONFIG_FILE_NAME; @Nullable private ConfigurationParameters parentConfigurationParameters; private Builder() { } Builder explicitParameters(Map parameters) { Preconditions.notNull(parameters, "configuration parameters must not be null"); explicitParameters.putAll(parameters); return this; } Builder configurationResources(List configResources) { Preconditions.notNull(configResources, "configResources must not be null"); this.configResources.addAll(configResources); return this; } Builder enableImplicitProviders(boolean enabled) { this.implicitProvidersEnabled = enabled; return this; } Builder configFileName(String configFileName) { Preconditions.notBlank(configFileName, "configFileName must not be null or blank"); this.configFileName = configFileName; return this; } Builder parentConfigurationParameters(ConfigurationParameters parameters) { Preconditions.notNull(parameters, "parent configuration parameters must not be null"); this.parentConfigurationParameters = parameters; return this; } LauncherConfigurationParameters build() { List parameterProviders = new ArrayList<>(); if (!explicitParameters.isEmpty()) { parameterProviders.add(ParameterProvider.explicit(explicitParameters)); } CollectionUtils.forEachInReverseOrder(configResources, configResource -> parameterProviders.add(ParameterProvider.propertiesFile(configResource))); if (parentConfigurationParameters != null) { parameterProviders.add(ParameterProvider.inherited(parentConfigurationParameters)); } if (implicitProvidersEnabled) { parameterProviders.add(ParameterProvider.systemProperties()); parameterProviders.add(ParameterProvider.propertiesFile(configFileName)); } return new LauncherConfigurationParameters(parameterProviders); } } private interface ParameterProvider { @Nullable String getValue(String key); Set keySet(); static ParameterProvider explicit(Map configParams) { return new ParameterProvider() { @Override public @Nullable String getValue(String key) { return configParams.get(key); } @Override public Set keySet() { return configParams.keySet(); } @Override public String toString() { ToStringBuilder builder = new ToStringBuilder("explicit"); configParams.forEach(builder::append); return builder.toString(); } }; } static ParameterProvider systemProperties() { return new ParameterProvider() { @Override public @Nullable String getValue(String key) { try { return System.getProperty(key); } catch (Exception ignore) { return null; } } @Override public Set keySet() { return System.getProperties().stringPropertyNames(); } @Override public String toString() { return "systemProperties [...]"; } }; } static ParameterProvider propertiesFile(String configFileName) { Preconditions.notBlank(configFileName, "configFileName must not be null or blank"); Properties properties = loadClasspathResource(configFileName.strip()); return new ParameterProvider() { @Override public String getValue(String key) { return properties.getProperty(key); } @Override public Set keySet() { return properties.stringPropertyNames(); } @Override public String toString() { ToStringBuilder builder = new ToStringBuilder("propertiesFile"); properties.stringPropertyNames().forEach(key -> builder.append(key, getValue(key))); return builder.toString(); } }; } static ParameterProvider inherited(ConfigurationParameters configParams) { return new ParameterProvider() { @Override public @Nullable String getValue(String key) { return configParams.get(key).orElse(null); } @Override public Set keySet() { return configParams.keySet(); } @Override public String toString() { ToStringBuilder builder = new ToStringBuilder("inherited"); builder.append("parent", configParams); return builder.toString(); } }; } } private static Properties loadClasspathResource(String configFileName) { Properties props = new Properties(); try { URL configFileUrl = findConfigFile(configFileName); if (configFileUrl != null) { loadClasspathResource(configFileUrl, props); } } catch (Exception ex) { logger.warn(ex, () -> "Failed to load JUnit Platform configuration parameters from classpath resource [%s].".formatted( configFileName)); } return props; } private static @Nullable URL findConfigFile(String configFileName) throws IOException { ClassLoader classLoader = ClassLoaderUtils.getDefaultClassLoader(); List urls = Collections.list(classLoader.getResources(configFileName)); if (urls.size() == 1) { return urls.get(0); } if (urls.size() > 1) { List resources = urls.stream() // .map(LauncherConfigurationParameters::toURI) // .distinct() // .toList(); URL configFileUrl = resources.get(0).toURL(); if (resources.size() > 1) { logger.warn(() -> { String formattedResourceList = Stream.concat( // Stream.of(configFileUrl + " (*)"), // resources.stream().skip(1).map(URI::toString) // ).collect(joining("\n- ", "\n- ", "")); return "Discovered %d '%s' configuration files on the classpath (see below); only the first (*) will be used.%s".formatted( resources.size(), configFileName, formattedResourceList); }); } return configFileUrl; } return null; } private static void loadClasspathResource(URL configFileUrl, Properties props) throws IOException { logger.config(() -> "Loading JUnit Platform configuration parameters from classpath resource [%s].".formatted( configFileUrl)); URLConnection urlConnection = configFileUrl.openConnection(); urlConnection.setUseCaches(false); try (InputStream inputStream = urlConnection.getInputStream()) { props.load(inputStream); } } private static URI toURI(URL url) { try { return url.toURI(); } catch (URISyntaxException e) { throw ExceptionUtils.throwAsUncheckedException(e); } } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import static org.junit.platform.launcher.LauncherConstants.OUTPUT_DIR_PROPERTY_NAME; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.DiscoveryFilter; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.Filter; import org.junit.platform.engine.OutputDirectoryCreator; import org.junit.platform.launcher.EngineFilter; import org.junit.platform.launcher.LauncherConstants; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.PostDiscoveryFilter; import org.junit.platform.launcher.core.LauncherConfigurationParameters.Builder; import org.junit.platform.launcher.listeners.OutputDir; import org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners; /** * The {@code LauncherDiscoveryRequestBuilder} provides a light-weight DSL for * generating a {@link LauncherDiscoveryRequest}. * *

Example

* *
 * import static org.junit.platform.engine.discovery.DiscoverySelectors.*;
 * import static org.junit.platform.engine.discovery.ClassNameFilter.*;
 * import static org.junit.platform.launcher.EngineFilter.*;
 * import static org.junit.platform.launcher.TagFilter.*;
 * import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.discoveryRequest;
 *
 * // ...
 *
 * LauncherDiscoveryRequest discoveryRequest = discoveryRequest()
 *    .selectors(
 *        selectPackage("org.example.user"),
 *        selectClass("org.example.payment.PaymentTests"),
 *        selectClass(ShippingTests.class),
 *        selectMethod("org.example.order.OrderTests#test1"),
 *        selectMethod("org.example.order.OrderTests#test2()"),
 *        selectMethod("org.example.order.OrderTests#test3(java.lang.String)"),
 *        selectMethod("org.example.order.OrderTests", "test4"),
 *        selectMethod(OrderTests.class, "test5"),
 *        selectMethod(OrderTests.class, testMethod),
 *        selectUniqueId("unique-id-1"),
 *        selectUniqueId("unique-id-2")
 *    )
 *    .selectors(
 *        selectClasspathRoots(Set.of(Path.of("/my/local/path1")))
 *    )
 *   .filters(
 *        includeEngines("junit-jupiter", "spek"),
 *        // excludeEngines("junit-vintage"),
 *        includeTags("fast"),
 *        // excludeTags("slow"),
 *        includeClassNamePatterns(".*Test[s]?")
 *        // includeClassNamePatterns("org\.example\.tests.*")
 *    )
 *    .configurationParameter("key1", "value1")
 *    .configurationParameters(configParameterMap)
 *    .build();
* * @since 1.0 * @see org.junit.platform.engine.discovery.DiscoverySelectors * @see org.junit.platform.engine.discovery.ClassNameFilter * @see org.junit.platform.launcher.EngineFilter * @see org.junit.platform.launcher.TagFilter * @see LauncherExecutionRequestBuilder */ @API(status = STABLE, since = "1.0") public final class LauncherDiscoveryRequestBuilder { /** * Property name used to set the default discovery listener that is added to all : {@value} * *

Supported Values

* *

Supported values are {@code "logging"} and {@code "abortOnFailure"}. * *

If not specified, the default is {@value #DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_VALUE}. */ public static final String DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME = "junit.platform.discovery.listener.default"; private static final String DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_VALUE = "abortOnFailure"; private final List selectors = new ArrayList<>(); private final List engineFilters = new ArrayList<>(); private final List> discoveryFilters = new ArrayList<>(); private final List postDiscoveryFilters = new ArrayList<>(); private final Map configurationParameters = new HashMap<>(); private final List configurationParametersResources = new ArrayList<>(); private final List discoveryListeners = new ArrayList<>(); private boolean implicitConfigurationParametersEnabled = true; private @Nullable ConfigurationParameters parentConfigurationParameters; private @Nullable OutputDirectoryCreator outputDirectoryCreator; /** * Create a new {@code LauncherDiscoveryRequestBuilder}. * * @return a new builder * @see #discoveryRequest() */ public static LauncherDiscoveryRequestBuilder request() { return new LauncherDiscoveryRequestBuilder(); } /** * Create a new {@code LauncherDiscoveryRequestBuilder}. * *

This method is an alias for {@link #request()} and is intended * to be used when statically imported — for example, via: * {@code import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.discoveryRequest;} * * @return a new builder * @since 6.0 * @see #request() */ @API(status = STABLE, since = "6.0") public static LauncherDiscoveryRequestBuilder discoveryRequest() { return request(); } private LauncherDiscoveryRequestBuilder() { } /** * Add all supplied {@code selectors} to the request. * * @param selectors the {@code DiscoverySelectors} to add; never {@code null} * @return this builder for method chaining */ public LauncherDiscoveryRequestBuilder selectors(DiscoverySelector... selectors) { Preconditions.notNull(selectors, "selectors array must not be null"); selectors(Arrays.asList(selectors)); return this; } /** * Add all supplied {@code selectors} to the request. * * @param selectors the {@code DiscoverySelectors} to add; never {@code null} * @return this builder for method chaining */ public LauncherDiscoveryRequestBuilder selectors(List selectors) { Preconditions.notNull(selectors, "selectors list must not be null"); Preconditions.containsNoNullElements(selectors, "individual selectors must not be null"); this.selectors.addAll(selectors); return this; } /** * Add all supplied {@code filters} to the request. * *

The {@code filters} are combined using AND semantics, i.e. all of them * have to include a resource for it to end up in the test plan. * *

Warning: be cautious when registering multiple competing * {@link EngineFilter#includeEngines include} {@code EngineFilters} or multiple * competing {@link EngineFilter#excludeEngines exclude} {@code EngineFilters} * for the same discovery request since doing so will likely lead to * undesirable results (i.e., zero engines being active). * * @param filters the {@code Filter}s to add; never {@code null} * @return this builder for method chaining */ public LauncherDiscoveryRequestBuilder filters(Filter... filters) { Preconditions.notNull(filters, "filters array must not be null"); Preconditions.containsNoNullElements(filters, "individual filters must not be null"); Arrays.stream(filters).forEach(this::storeFilter); return this; } /** * Add the supplied configuration parameter to the request. * * @param key the configuration parameter key under which to store the * value; never {@code null} or blank * @param value the value to store * @return this builder for method chaining */ public LauncherDiscoveryRequestBuilder configurationParameter(String key, String value) { Preconditions.notBlank(key, "configuration parameter key must not be null or blank"); this.configurationParameters.put(key, value); return this; } /** * Add all supplied configuration parameters to the request. * * @param configurationParameters the map of configuration parameters to add; * never {@code null} * @return this builder for method chaining * @see #configurationParameter(String, String) */ public LauncherDiscoveryRequestBuilder configurationParameters(Map configurationParameters) { Preconditions.notNull(configurationParameters, "configuration parameters map must not be null"); configurationParameters.forEach(this::configurationParameter); return this; } /** * Add all of the supplied configuration parameters resource files to the request. * @param paths the classpath locations of the properties files * never {@code null} * @return this builder for method chaining */ public LauncherDiscoveryRequestBuilder configurationParametersResources(String... paths) { Preconditions.notNull(paths, "property file paths must not be null"); Collections.addAll(configurationParametersResources, paths); return this; } /** * Set the parent configuration parameters to use for the request. * *

Any explicit configuration parameters configured via * {@link #configurationParameter(String, String)} or * {@link #configurationParameters(Map)} takes precedence over the supplied * configuration parameters. * * @param parentConfigurationParameters the parent instance to use for looking * up configuration parameters that have not been explicitly configured; * never {@code null} * @return this builder for method chaining * @since 1.8 * @see #configurationParameter(String, String) * @see #configurationParameters(Map) */ @API(status = STABLE, since = "1.10") public LauncherDiscoveryRequestBuilder parentConfigurationParameters( ConfigurationParameters parentConfigurationParameters) { Preconditions.notNull(parentConfigurationParameters, "parent configuration parameters must not be null"); this.parentConfigurationParameters = parentConfigurationParameters; return this; } /** * Configure whether implicit configuration parameters should be considered. * *

By default, in addition to those parameters that are passed explicitly * to this builder, configuration parameters are read from system properties * and from the {@code junit-platform.properties} classpath resource. * Passing {@code false} to this method, disables the latter two sources so * that only explicit configuration parameters are taken into account. * * @param enabled {@code true} if implicit configuration parameters should be * considered * @return this builder for method chaining * @since 1.7 * @see #configurationParameter(String, String) * @see #configurationParameters(Map) */ @API(status = STABLE, since = "1.10") public LauncherDiscoveryRequestBuilder enableImplicitConfigurationParameters(boolean enabled) { this.implicitConfigurationParametersEnabled = enabled; return this; } /** * Add all supplied discovery listeners to the request. * *

In addition to the {@linkplain LauncherDiscoveryListener listeners} * registered using this method, this builder will add a default listener * to this request that can be specified using the * {@value #DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME} * configuration parameter. * * @param listeners the {@code LauncherDiscoveryListeners} to add; never * {@code null} * @return this builder for method chaining * @since 1.6 * @see LauncherDiscoveryListener * @see LauncherDiscoveryListeners * @see LauncherDiscoveryRequestBuilder#DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME */ @API(status = STABLE, since = "1.10") public LauncherDiscoveryRequestBuilder listeners(LauncherDiscoveryListener... listeners) { Preconditions.notNull(listeners, "discovery listener array must not be null"); Preconditions.containsNoNullElements(listeners, "individual discovery listeners must not be null"); Collections.addAll(this.discoveryListeners, listeners); return this; } /** * Set the * {@link org.junit.platform.engine.reporting.OutputDirectoryProvider} to use for the request. * *

If not specified, a default provider will be used that can be * configured via the {@value LauncherConstants#OUTPUT_DIR_PROPERTY_NAME} * configuration parameter. * * @param outputDirectoryProvider the output directory provider to use; * never {@code null} * @return this builder for method chaining * @since 1.12 * @see org.junit.platform.engine.reporting.OutputDirectoryProvider * @see LauncherConstants#OUTPUT_DIR_PROPERTY_NAME * @deprecated Please use * {@link #outputDirectoryCreator(OutputDirectoryCreator)} instead */ @SuppressWarnings("removal") @API(status = DEPRECATED, since = "1.14") @Deprecated(since = "1.14", forRemoval = true) public LauncherDiscoveryRequestBuilder outputDirectoryProvider( org.junit.platform.engine.reporting.OutputDirectoryProvider outputDirectoryProvider) { return outputDirectoryCreator(outputDirectoryProvider); } /** * Set the {@link OutputDirectoryCreator} to use for the request. * *

If not specified, a default implementation will be used that can be * configured via the {@value LauncherConstants#OUTPUT_DIR_PROPERTY_NAME} * configuration parameter. * * @param outputDirectoryCreator the output directory creator to use; * never {@code null} * @return this builder for method chaining * @since 1.14 * @see OutputDirectoryCreator * @see LauncherConstants#OUTPUT_DIR_PROPERTY_NAME */ @SuppressWarnings("removal") @API(status = MAINTAINED, since = "1.14") public LauncherDiscoveryRequestBuilder outputDirectoryCreator(OutputDirectoryCreator outputDirectoryCreator) { this.outputDirectoryCreator = Preconditions.notNull(outputDirectoryCreator, "outputDirectoryCreator must not be null"); return this; } private void storeFilter(Filter filter) { if (filter instanceof EngineFilter engineFilter) { this.engineFilters.add(engineFilter); } else if (filter instanceof PostDiscoveryFilter postDiscoveryFilter) { this.postDiscoveryFilters.add(postDiscoveryFilter); } else if (filter instanceof DiscoveryFilter discoveryFilter) { this.discoveryFilters.add(discoveryFilter); } else { throw new PreconditionViolationException( "Filter [%s] must implement %s, %s, or %s.".formatted(filter, EngineFilter.class.getSimpleName(), PostDiscoveryFilter.class.getSimpleName(), DiscoveryFilter.class.getSimpleName())); } } /** * Builds this discovery request and returns a new builder for creating a * {@link org.junit.platform.launcher.LauncherExecutionRequest} that is * initialized to contain the resulting discovery request. * * @return a new {@link LauncherExecutionRequestBuilder} * @since 6.0 */ @API(status = EXPERIMENTAL, since = "6.0") public LauncherExecutionRequestBuilder forExecution() { return LauncherExecutionRequestBuilder.request(build()); } /** * Build the {@link LauncherDiscoveryRequest} that has been configured via * this builder. */ public LauncherDiscoveryRequest build() { LauncherConfigurationParameters launcherConfigurationParameters = buildLauncherConfigurationParameters(); LauncherDiscoveryListener discoveryListener = getLauncherDiscoveryListener(launcherConfigurationParameters); OutputDirectoryCreator outputDirectoryCreator = getOutputDirectoryCreator(launcherConfigurationParameters); return new DefaultDiscoveryRequest(this.selectors, this.engineFilters, this.discoveryFilters, this.postDiscoveryFilters, launcherConfigurationParameters, discoveryListener, outputDirectoryCreator); } private OutputDirectoryCreator getOutputDirectoryCreator(LauncherConfigurationParameters configurationParameters) { if (this.outputDirectoryCreator != null) { return this.outputDirectoryCreator; } return new HierarchicalOutputDirectoryCreator( () -> OutputDir.create(configurationParameters.get(OUTPUT_DIR_PROPERTY_NAME)).toPath()); } private LauncherConfigurationParameters buildLauncherConfigurationParameters() { Builder builder = LauncherConfigurationParameters.builder() // .explicitParameters(this.configurationParameters) // .configurationResources(this.configurationParametersResources) // .enableImplicitProviders(this.implicitConfigurationParametersEnabled); if (this.parentConfigurationParameters != null) { builder.parentConfigurationParameters(this.parentConfigurationParameters); } return builder.build(); } private LauncherDiscoveryListener getLauncherDiscoveryListener(ConfigurationParameters configurationParameters) { LauncherDiscoveryListener defaultDiscoveryListener = getDefaultLauncherDiscoveryListener( configurationParameters); if (this.discoveryListeners.isEmpty()) { return defaultDiscoveryListener; } if (this.discoveryListeners.contains(defaultDiscoveryListener)) { return LauncherDiscoveryListeners.composite(this.discoveryListeners); } List allDiscoveryListeners = new ArrayList<>(this.discoveryListeners.size() + 1); allDiscoveryListeners.addAll(this.discoveryListeners); allDiscoveryListeners.add(defaultDiscoveryListener); return LauncherDiscoveryListeners.composite(allDiscoveryListeners); } private LauncherDiscoveryListener getDefaultLauncherDiscoveryListener( ConfigurationParameters configurationParameters) { String value = configurationParameters.get(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME) // .orElse(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_VALUE); return LauncherDiscoveryListeners.fromConfigurationParameter( DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, value); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryResult.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static java.util.Collections.unmodifiableMap; import static java.util.Objects.requireNonNull; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.Predicate; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.OutputDirectoryCreator; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestEngine; /** * Represents the result of test discovery of the configured * {@linkplain TestEngine test engines}. * * @since 1.7 */ @API(status = INTERNAL, since = "1.7", consumers = { "org.junit.platform.testkit", "org.junit.platform.suite.engine" }) public class LauncherDiscoveryResult { private final Map testEngineResults; private final ConfigurationParameters configurationParameters; private final OutputDirectoryCreator outputDirectoryCreator; LauncherDiscoveryResult(Map testEngineResults, ConfigurationParameters configurationParameters, OutputDirectoryCreator outputDirectoryCreator) { this.testEngineResults = unmodifiableMap(new LinkedHashMap<>(testEngineResults)); this.configurationParameters = configurationParameters; this.outputDirectoryCreator = outputDirectoryCreator; } public TestDescriptor getEngineTestDescriptor(TestEngine testEngine) { return getEngineResult(testEngine).getRootDescriptor(); } @API(status = INTERNAL, since = "1.13") public List getDiscoveryIssues(TestEngine testEngine) { return getEngineResult(testEngine).getDiscoveryIssueNotifier().getAllIssues(); } EngineResultInfo getEngineResult(TestEngine testEngine) { return requireNonNull(this.testEngineResults.get(testEngine)); } ConfigurationParameters getConfigurationParameters() { return this.configurationParameters; } OutputDirectoryCreator getOutputDirectoryCreator() { return this.outputDirectoryCreator; } public Collection getTestEngines() { return this.testEngineResults.keySet(); } boolean containsCriticalIssuesOrContainsTests() { return this.testEngineResults.values().stream() // .anyMatch(EngineResultInfo::containsCriticalIssuesOrContainsTests); } Collection getEngineTestDescriptors() { return this.testEngineResults.values().stream() // .map(EngineResultInfo::getRootDescriptor) // .toList(); } public LauncherDiscoveryResult withRetainedEngines(Predicate predicate) { Map prunedTestEngineResults = retainEngines(predicate); if (prunedTestEngineResults.size() < this.testEngineResults.size()) { return new LauncherDiscoveryResult(prunedTestEngineResults, this.configurationParameters, this.outputDirectoryCreator); } return this; } private Map retainEngines(Predicate predicate) { var retainedEngines = new LinkedHashMap<>(this.testEngineResults); retainedEngines.entrySet().removeIf(entry -> !predicate.test(entry.getValue().getRootDescriptor())); return retainedEngines; } static class EngineResultInfo { static EngineResultInfo completed(TestDescriptor rootDescriptor, DiscoveryIssueNotifier discoveryIssueNotifier) { return new EngineResultInfo(rootDescriptor, discoveryIssueNotifier, null); } static EngineResultInfo errored(TestDescriptor rootDescriptor, DiscoveryIssueNotifier discoveryIssueNotifier, Throwable cause) { return new EngineResultInfo(rootDescriptor, discoveryIssueNotifier, cause); } private final TestDescriptor rootDescriptor; @Nullable private final Throwable cause; private final DiscoveryIssueNotifier discoveryIssueNotifier; EngineResultInfo(TestDescriptor rootDescriptor, DiscoveryIssueNotifier discoveryIssueNotifier, @Nullable Throwable cause) { this.rootDescriptor = rootDescriptor; this.discoveryIssueNotifier = discoveryIssueNotifier; this.cause = cause; } TestDescriptor getRootDescriptor() { return this.rootDescriptor; } DiscoveryIssueNotifier getDiscoveryIssueNotifier() { return discoveryIssueNotifier; } Optional getCause() { return Optional.ofNullable(this.cause); } boolean containsCriticalIssuesOrContainsTests() { return cause != null // || discoveryIssueNotifier.hasCriticalIssues() // || TestDescriptor.containsTests(rootDescriptor); } } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherExecutionRequestBuilder.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static java.util.Objects.requireNonNullElseGet; import static org.apiguardian.api.API.Status.MAINTAINED; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.CancellationToken; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.LauncherExecutionRequest; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestPlan; /** * The {@code LauncherExecutionRequestBuilder} provides a light-weight DSL for * generating a {@link LauncherExecutionRequest}. * *

Example

* *
 * import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage;
 * import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.discoveryRequest;
 * import static org.junit.platform.launcher.core.LauncherExecutionRequestBuilder.executionRequest;
 *
 * import org.junit.platform.engine.CancellationToken;
 * import org.junit.platform.launcher.LauncherDiscoveryRequest;
 * import org.junit.platform.launcher.LauncherExecutionRequest;
 * import org.junit.platform.launcher.TestExecutionListener;
 *
 * TestExecutionListener listener = ...
 * CancellationToken cancellationToken = CancellationToken.create();
 *
 * LauncherDiscoveryRequest discoveryRequest = discoveryRequest()
 *    .selectors(selectPackage("org.example.user"))
 *    .build();
 *
 * LauncherExecutionRequest executionRequest = executionRequest(discoveryRequest)
 *    .listeners(listener)
 *    .cancellationToken(cancellationToken)
 *    .build();
* * @since 6.0 * @see LauncherExecutionRequest * @see LauncherDiscoveryRequestBuilder */ @API(status = MAINTAINED, since = "6.0") public final class LauncherExecutionRequestBuilder { /** * Create a new {@code LauncherExecutionRequestBuilder} from the supplied * {@link LauncherDiscoveryRequest}. * * @return a new builder * @see #executionRequest(LauncherDiscoveryRequest) */ public static LauncherExecutionRequestBuilder request(LauncherDiscoveryRequest discoveryRequest) { Preconditions.notNull(discoveryRequest, "LauncherDiscoveryRequest must not be null"); return new LauncherExecutionRequestBuilder(discoveryRequest, null); } /** * Create a new {@code LauncherExecutionRequestBuilder} from the supplied * {@link LauncherDiscoveryRequest}. * *

This method is an alias for {@link #request(LauncherDiscoveryRequest)} * and is intended to be used when statically imported — for example, via: * {@code import static org.junit.platform.launcher.core.LauncherExecutionRequestBuilder.executionRequest;} * * @return a new builder * @see #request(LauncherDiscoveryRequest) */ public static LauncherExecutionRequestBuilder executionRequest(LauncherDiscoveryRequest discoveryRequest) { return request(discoveryRequest); } /** * Create a new {@code LauncherExecutionRequestBuilder} from the supplied * {@link TestPlan}. * * @return a new builder * @see #executionRequest(TestPlan) */ public static LauncherExecutionRequestBuilder request(TestPlan testPlan) { Preconditions.notNull(testPlan, "TestPlan must not be null"); return new LauncherExecutionRequestBuilder(null, testPlan); } /** * Create a new {@code LauncherExecutionRequestBuilder} from the supplied * {@link TestPlan}. * *

This method is an alias for {@link #request(TestPlan)} * and is intended to be used when statically imported — for example, via: * {@code import static org.junit.platform.launcher.core.LauncherExecutionRequestBuilder.executionRequest;} * * @return a new builder * @see #request(TestPlan) */ public static LauncherExecutionRequestBuilder executionRequest(TestPlan testPlan) { return request(testPlan); } private final @Nullable LauncherDiscoveryRequest discoveryRequest; private final @Nullable TestPlan testPlan; private final Collection executionListeners = new ArrayList<>(); private @Nullable CancellationToken cancellationToken; private LauncherExecutionRequestBuilder(@Nullable LauncherDiscoveryRequest discoveryRequest, @Nullable TestPlan testPlan) { this.discoveryRequest = discoveryRequest; this.testPlan = testPlan; } /** * Add all supplied execution listeners to the request. * * @param listeners the {@code TestExecutionListener} to add; never * {@code null} * @return this builder for method chaining * @see TestExecutionListener */ public LauncherExecutionRequestBuilder listeners(TestExecutionListener... listeners) { Preconditions.notNull(listeners, "TestExecutionListener array must not be null"); Preconditions.containsNoNullElements(listeners, "individual listeners must not be null"); Collections.addAll(this.executionListeners, listeners); return this; } /** * Set the cancellation token for the request. * * @param cancellationToken the {@code CancellationToken} to use; never * {@code null}. * @return this builder for method chaining * @see CancellationToken */ public LauncherExecutionRequestBuilder cancellationToken(CancellationToken cancellationToken) { this.cancellationToken = Preconditions.notNull(cancellationToken, "CancellationToken must not be null"); return this; } /** * Build the {@link LauncherExecutionRequest} that has been configured via * this builder. */ public LauncherExecutionRequest build() { return new DefaultLauncherExecutionRequest(this.discoveryRequest, this.testPlan, this.executionListeners, requireNonNullElseGet(this.cancellationToken, CancellationToken::disabled)); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static org.apiguardian.api.API.Status.STABLE; import static org.junit.platform.launcher.LauncherConstants.DEACTIVATE_LISTENERS_PATTERN_PROPERTY_NAME; import static org.junit.platform.launcher.LauncherConstants.ENABLE_LAUNCHER_INTERCEPTORS; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.function.Predicate; import org.apiguardian.api.API; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.ClassNamePatternFilterUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.support.store.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherInterceptor; import org.junit.platform.launcher.LauncherSession; import org.junit.platform.launcher.LauncherSessionListener; import org.junit.platform.launcher.PostDiscoveryFilter; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.jfr.JfrUtils; /** * Factory for creating {@link Launcher} instances by invoking {@link #create()} * or {@link #create(LauncherConfig)}. * *

By default, test engines are discovered at runtime using the * {@link java.util.ServiceLoader ServiceLoader} mechanism. For that purpose, a * text file named {@code META-INF/services/org.junit.platform.engine.TestEngine} * has to be added to the engine's JAR file in which the fully qualified name * of the implementation class of the {@link org.junit.platform.engine.TestEngine} * interface is declared. * *

By default, test execution listeners are discovered at runtime via the * {@link java.util.ServiceLoader ServiceLoader} mechanism and are * automatically registered with the {@link Launcher} created by this factory. * Users may register additional listeners using the * {@link Launcher#registerTestExecutionListeners(TestExecutionListener...)} * method on the created launcher instance. * *

For full control over automatic registration and programmatic registration * of test engines and listeners, supply an instance of {@link LauncherConfig} * to {@link #create(LauncherConfig)}. * * @since 1.0 * @see Launcher * @see LauncherConfig */ @API(status = STABLE, since = "1.0") public class LauncherFactory { private LauncherFactory() { /* no-op */ } /** * Factory method for opening a new {@link LauncherSession} using the * {@linkplain LauncherConfig#DEFAULT default} {@link LauncherConfig}. * * @throws PreconditionViolationException if no test engines are detected * @since 1.8 * @see #openSession(LauncherConfig) */ @API(status = STABLE, since = "1.10") public static LauncherSession openSession() throws PreconditionViolationException { return openSession(LauncherConfig.DEFAULT); } /** * Factory method for opening a new {@link LauncherSession} using the * supplied {@link LauncherConfig}. * * @param config the configuration for the session and the launcher; never * {@code null} * @throws PreconditionViolationException if the supplied configuration is * {@code null}, or if no test engines are detected * @since 1.8 * @see #openSession() */ @API(status = STABLE, since = "1.10") public static LauncherSession openSession(LauncherConfig config) throws PreconditionViolationException { Preconditions.notNull(config, "LauncherConfig must not be null"); LauncherConfigurationParameters configurationParameters = LauncherConfigurationParameters.builder().build(); return new DefaultLauncherSession(collectLauncherInterceptors(configurationParameters), () -> createLauncherSessionListener(config), sessionLevelStore -> createDefaultLauncher(config, configurationParameters, sessionLevelStore)); } /** * Factory method for creating a new {@link Launcher} using the * {@linkplain LauncherConfig#DEFAULT default} {@link LauncherConfig}. * * @throws PreconditionViolationException if no test engines are detected * @see #create(LauncherConfig) */ public static Launcher create() throws PreconditionViolationException { return create(LauncherConfig.DEFAULT); } /** * Factory method for creating a new {@link Launcher} using the supplied * {@link LauncherConfig}. * * @param config the configuration for the launcher; never {@code null} * @throws PreconditionViolationException if the supplied configuration is * {@code null}, or if no test engines are detected * registered * @since 1.3 * @see #create() */ @API(status = STABLE, since = "1.10") public static Launcher create(LauncherConfig config) throws PreconditionViolationException { Preconditions.notNull(config, "LauncherConfig must not be null"); LauncherConfigurationParameters configurationParameters = LauncherConfigurationParameters.builder().build(); return new SessionPerRequestLauncher( sessionLevelStore -> createDefaultLauncher(config, configurationParameters, sessionLevelStore), () -> createLauncherSessionListener(config), () -> collectLauncherInterceptors(configurationParameters)); } private static DefaultLauncher createDefaultLauncher(LauncherConfig config, LauncherConfigurationParameters configurationParameters, NamespacedHierarchicalStore sessionLevelStore) { Set engines = collectTestEngines(config); List filters = collectPostDiscoveryFilters(config); DefaultLauncher launcher = new DefaultLauncher(engines, filters, sessionLevelStore); JfrUtils.registerListeners(launcher); registerLauncherDiscoveryListeners(config, launcher); registerTestExecutionListeners(config, launcher, configurationParameters); return launcher; } private static List collectLauncherInterceptors( LauncherConfigurationParameters configurationParameters) { List interceptors = new ArrayList<>(); if (configurationParameters.getBoolean(ENABLE_LAUNCHER_INTERCEPTORS).orElse(false)) { ServiceLoaderRegistry.load(LauncherInterceptor.class).forEach(interceptors::add); } interceptors.add(ClasspathAlignmentCheckingLauncherInterceptor.INSTANCE); return interceptors; } private static Set collectTestEngines(LauncherConfig config) { Set engines = new LinkedHashSet<>(); if (config.isTestEngineAutoRegistrationEnabled()) { new ServiceLoaderTestEngineRegistry().loadTestEngines().forEach(engines::add); } engines.addAll(config.getAdditionalTestEngines()); return engines; } private static LauncherSessionListener createLauncherSessionListener(LauncherConfig config) { ListenerRegistry listenerRegistry = ListenerRegistry.forLauncherSessionListeners(); if (config.isLauncherSessionListenerAutoRegistrationEnabled()) { ServiceLoaderRegistry.load(LauncherSessionListener.class).forEach(listenerRegistry::add); } config.getAdditionalLauncherSessionListeners().forEach(listenerRegistry::add); return listenerRegistry.getCompositeListener(); } private static List collectPostDiscoveryFilters(LauncherConfig config) { List filters = new ArrayList<>(); if (config.isPostDiscoveryFilterAutoRegistrationEnabled()) { ServiceLoaderRegistry.load(PostDiscoveryFilter.class).forEach(filters::add); } filters.addAll(config.getAdditionalPostDiscoveryFilters()); return filters; } private static void registerLauncherDiscoveryListeners(LauncherConfig config, Launcher launcher) { if (config.isLauncherDiscoveryListenerAutoRegistrationEnabled()) { ServiceLoaderRegistry.load(LauncherDiscoveryListener.class).forEach( launcher::registerLauncherDiscoveryListeners); } config.getAdditionalLauncherDiscoveryListeners().forEach(launcher::registerLauncherDiscoveryListeners); } private static void registerTestExecutionListeners(LauncherConfig config, Launcher launcher, LauncherConfigurationParameters configurationParameters) { if (config.isTestExecutionListenerAutoRegistrationEnabled()) { loadAndFilterTestExecutionListeners(configurationParameters).forEach( launcher::registerTestExecutionListeners); } config.getAdditionalTestExecutionListeners().forEach(launcher::registerTestExecutionListeners); } private static Iterable loadAndFilterTestExecutionListeners( ConfigurationParameters configurationParameters) { Predicate classNameFilter = configurationParameters.get(DEACTIVATE_LISTENERS_PATTERN_PROPERTY_NAME) // .map(ClassNamePatternFilterUtils::excludeMatchingClassNames) // .orElse(__ -> true); return ServiceLoaderRegistry.load(TestExecutionListener.class, classNameFilter); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherListenerRegistry.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.TestExecutionListener; class LauncherListenerRegistry { final ListenerRegistry launcherDiscoveryListeners = ListenerRegistry.forLauncherDiscoveryListeners(); final ListenerRegistry testExecutionListeners = ListenerRegistry.forTestExecutionListeners(); } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherPhase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static org.junit.platform.launcher.LauncherConstants.DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME; import java.util.Locale; import java.util.Optional; import java.util.function.Function; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.engine.ConfigurationParameters; /** * The phase the {@link org.junit.platform.launcher.Launcher} is in. * * @since 1.13 */ enum LauncherPhase { DISCOVERY, EXECUTION; static Optional getDiscoveryIssueFailurePhase(ConfigurationParameters configurationParameters) { Function stringLauncherPhaseFunction = value -> { try { return LauncherPhase.valueOf(value.toUpperCase(Locale.ROOT)); } catch (Exception e) { throw new JUnitException( "Invalid LauncherPhase '%s' set via the '%s' configuration parameter.".formatted(value, DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME)); } }; return configurationParameters.get(DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME, stringLauncherPhaseFunction); } @Override public String toString() { return name().toLowerCase(Locale.ENGLISH); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ListenerRegistry.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.function.Function; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherSessionListener; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners; import org.junit.platform.launcher.listeners.session.LauncherSessionListeners; class ListenerRegistry { private final Function, T> compositeListenerFactory; static ListenerRegistry forLauncherSessionListeners() { return create(LauncherSessionListeners::composite); } static ListenerRegistry forLauncherDiscoveryListeners() { return create(LauncherDiscoveryListeners::composite); } static ListenerRegistry forTestExecutionListeners() { return create(CompositeTestExecutionListener::new); } static ListenerRegistry forEngineExecutionListeners() { return create(CompositeEngineExecutionListener::new); } static ListenerRegistry create(Function, T> compositeListenerFactory) { return new ListenerRegistry<>(compositeListenerFactory); } static ListenerRegistry copyOf(ListenerRegistry source) { ListenerRegistry registry = new ListenerRegistry<>(source.compositeListenerFactory); if (!source.listeners.isEmpty()) { registry.addAll(source.listeners); } return registry; } private final ArrayList listeners = new ArrayList<>(); private ListenerRegistry(Function, T> compositeListenerFactory) { this.compositeListenerFactory = compositeListenerFactory; } ListenerRegistry add(T listener) { Preconditions.notNull(listener, "listener must not be null"); this.listeners.add(listener); return this; } @SafeVarargs @SuppressWarnings("varargs") final ListenerRegistry addAll(T... listeners) { Preconditions.notEmpty(listeners, "listeners array must not be null or empty"); Preconditions.containsNoNullElements(listeners, "individual listeners must not be null"); Collections.addAll(this.listeners, listeners); return this; } ListenerRegistry addAll(Collection listeners) { Preconditions.notEmpty(listeners, "listeners collection must not be null or empty"); Preconditions.containsNoNullElements(listeners, "individual listeners must not be null"); this.listeners.addAll(listeners); return this; } T getCompositeListener() { this.listeners.trimToSize(); return compositeListenerFactory.apply(this.listeners); } List getListeners() { return Collections.unmodifiableList(this.listeners); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/MemoryCleanupListener.java ================================================ /* * Copyright 2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.launcher.TestPlan; /** * @since 6.1 */ class MemoryCleanupListener extends DelegatingEngineExecutionListener { private final TestPlan testPlan; MemoryCleanupListener(EngineExecutionListener delegate, TestPlan testPlan) { super(delegate); this.testPlan = testPlan; } @Override public void executionSkipped(TestDescriptor testDescriptor, String reason) { super.executionSkipped(testDescriptor, reason); cleanUp(testDescriptor); } @Override public void executionFinished(TestDescriptor testDescriptor, TestExecutionResult testExecutionResult) { super.executionFinished(testDescriptor, testExecutionResult); cleanUp(testDescriptor); } private void cleanUp(TestDescriptor testDescriptor) { testPlan.removeInternal(testDescriptor.getUniqueId()); if (!testDescriptor.isRoot()) { testDescriptor.removeFromHierarchy(); } } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/OutcomeDelayingEngineExecutionListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static java.util.Objects.requireNonNull; import java.util.Optional; import org.jspecify.annotations.Nullable; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; /** * Delays reporting of engine skipped/finished events so that exceptions thrown * by engines can be reported to listeners. * * @since 1.6 */ class OutcomeDelayingEngineExecutionListener extends DelegatingEngineExecutionListener { private final TestDescriptor engineDescriptor; private volatile boolean engineStarted; private volatile @Nullable Outcome outcome; private volatile @Nullable String skipReason; private volatile @Nullable TestExecutionResult executionResult; OutcomeDelayingEngineExecutionListener(EngineExecutionListener delegate, TestDescriptor engineDescriptor) { super(delegate); this.engineDescriptor = engineDescriptor; } @Override public void executionSkipped(TestDescriptor testDescriptor, String reason) { if (testDescriptor == engineDescriptor) { outcome = Outcome.SKIPPED; skipReason = reason; } else { super.executionSkipped(testDescriptor, reason); } } @Override public void executionStarted(TestDescriptor testDescriptor) { if (testDescriptor == engineDescriptor) { engineStarted = true; } super.executionStarted(testDescriptor); } @Override public void executionFinished(TestDescriptor testDescriptor, TestExecutionResult executionResult) { if (testDescriptor == engineDescriptor) { outcome = Outcome.FINISHED; this.executionResult = executionResult; } else { super.executionFinished(testDescriptor, executionResult); } } void reportEngineOutcome() { if (outcome == Outcome.FINISHED) { super.executionFinished(engineDescriptor, requireNonNull(executionResult)); } else if (outcome == Outcome.SKIPPED) { super.executionSkipped(engineDescriptor, requireNonNull(skipReason)); } } void reportEngineStartIfNecessary() { if (!engineStarted) { super.executionStarted(engineDescriptor); } } void reportEngineFailure(Throwable throwable) { Optional.ofNullable(this.executionResult) // .flatMap(TestExecutionResult::getThrowable) // .ifPresent(throwable::addSuppressed); super.executionFinished(engineDescriptor, TestExecutionResult.failed(throwable)); } private enum Outcome { SKIPPED, FINISHED } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderRegistry.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import java.util.ArrayList; import java.util.List; import java.util.ServiceLoader; import java.util.function.Function; import java.util.function.Predicate; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.ClassLoaderUtils; import org.junit.platform.commons.util.ServiceLoaderUtils; /** * @since 1.8 */ class ServiceLoaderRegistry { static Iterable load(Class type) { return load(type, __ -> true, instances -> logLoadedInstances(type, instances, null)); } static Iterable load(@SuppressWarnings("SameParameterValue") Class type, Predicate classNameFilter) { List exclusions = new ArrayList<>(); Predicate collectingClassNameFilter = className -> { boolean included = classNameFilter.test(className); if (!included) { exclusions.add(className); } return included; }; return load(type, collectingClassNameFilter, instances -> logLoadedInstances(type, instances, exclusions)); } private static String logLoadedInstances(Class type, List instances, @Nullable List exclusions) { String typeName = type.getSimpleName(); if (exclusions == null) { return "Loaded %s instances: %s".formatted(typeName, instances); } return "Loaded %s instances: %s (excluded classes: %s)".formatted(typeName, instances, exclusions); } private static List load(Class type, Predicate classNameFilter, Function, String> logMessageSupplier) { ServiceLoader serviceLoader = ServiceLoader.load(type, ClassLoaderUtils.getDefaultClassLoader()); Predicate> providerPredicate = clazz -> classNameFilter.test(clazz.getName()); List instances = ServiceLoaderUtils.filter(serviceLoader, providerPredicate).toList(); getLogger().config(() -> logMessageSupplier.apply(instances)); return instances; } private static Logger getLogger() { // Not a constant to avoid problems with building GraalVM native images return LoggerFactory.getLogger(ServiceLoaderRegistry.class); } private ServiceLoaderRegistry() { } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderTestEngineRegistry.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.ServiceLoader; import org.apiguardian.api.API; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.ClassLoaderUtils; import org.junit.platform.engine.TestEngine; /** * @since 1.0 */ @API(status = INTERNAL, since = "1.0", consumers = "org.junit.platform.suite.engine") public final class ServiceLoaderTestEngineRegistry { public ServiceLoaderTestEngineRegistry() { } public Iterable loadTestEngines() { Iterable testEngines = ServiceLoader.load(TestEngine.class, ClassLoaderUtils.getDefaultClassLoader()); getLogger().config(() -> TestEngineFormatter.format("Discovered TestEngines", testEngines)); return testEngines; } private static Logger getLogger() { // Not a constant to avoid problems with building GraalVM native images return LoggerFactory.getLogger(ServiceLoaderTestEngineRegistry.class); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import java.util.List; import java.util.function.Function; import java.util.function.Supplier; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.support.store.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.LauncherExecutionRequest; import org.junit.platform.launcher.LauncherInterceptor; import org.junit.platform.launcher.LauncherSession; import org.junit.platform.launcher.LauncherSessionListener; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestPlan; /** * @since 1.8 */ class SessionPerRequestLauncher implements Launcher { private final LauncherListenerRegistry listenerRegistry = new LauncherListenerRegistry(); private final Function, Launcher> launcherFactory; private final Supplier sessionListenerSupplier; private final Supplier> interceptorFactory; SessionPerRequestLauncher(Function, Launcher> launcherFactory, Supplier sessionListenerSupplier, Supplier> interceptorFactory) { this.launcherFactory = launcherFactory; this.sessionListenerSupplier = sessionListenerSupplier; this.interceptorFactory = interceptorFactory; } @Override public void registerLauncherDiscoveryListeners(LauncherDiscoveryListener... listeners) { Preconditions.notNull(listeners, "listeners must not be null"); Preconditions.containsNoNullElements(listeners, "listener array must not contain null elements"); listenerRegistry.launcherDiscoveryListeners.addAll(listeners); } @Override public void registerTestExecutionListeners(TestExecutionListener... listeners) { Preconditions.notNull(listeners, "listeners must not be null"); Preconditions.containsNoNullElements(listeners, "listener array must not contain null elements"); listenerRegistry.testExecutionListeners.addAll(listeners); } @Override public TestPlan discover(LauncherDiscoveryRequest discoveryRequest) { Preconditions.notNull(discoveryRequest, "discoveryRequest must not be null"); try (LauncherSession session = createSession()) { return session.getLauncher().discover(discoveryRequest); } } @Override public void execute(LauncherDiscoveryRequest discoveryRequest, TestExecutionListener... listeners) { Preconditions.notNull(discoveryRequest, "discoveryRequest must not be null"); Preconditions.notNull(listeners, "listeners must not be null"); Preconditions.containsNoNullElements(listeners, "listener array must not contain null elements"); try (LauncherSession session = createSession()) { session.getLauncher().execute(discoveryRequest, listeners); } } @Override public void execute(TestPlan testPlan, TestExecutionListener... listeners) { Preconditions.notNull(testPlan, "testPlan must not be null"); Preconditions.notNull(listeners, "listeners must not be null"); Preconditions.containsNoNullElements(listeners, "listener array must not contain null elements"); try (LauncherSession session = createSession()) { session.getLauncher().execute(testPlan, listeners); } } @Override public void execute(LauncherExecutionRequest executionRequest) { Preconditions.notNull(executionRequest, "executionRequest must not be null"); try (LauncherSession session = createSession()) { session.getLauncher().execute(executionRequest); } } private LauncherSession createSession() { LauncherSession session = new DefaultLauncherSession(interceptorFactory.get(), sessionListenerSupplier, this.launcherFactory); Launcher launcher = session.getLauncher(); listenerRegistry.launcherDiscoveryListeners.getListeners().forEach( launcher::registerLauncherDiscoveryListeners); listenerRegistry.testExecutionListeners.getListeners().forEach(launcher::registerTestExecutionListeners); return session; } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StackTracePruningEngineExecutionListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.stream.Stream; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.platform.engine.support.descriptor.MethodSource; /** * Prunes the stack trace in case of a failed event. * * @since 1.10 * @see org.junit.platform.commons.util.ExceptionUtils#pruneStackTrace(Throwable, List) */ class StackTracePruningEngineExecutionListener extends DelegatingEngineExecutionListener { StackTracePruningEngineExecutionListener(EngineExecutionListener delegate) { super(delegate); } @Override public void executionFinished(TestDescriptor testDescriptor, TestExecutionResult testExecutionResult) { List testClassNames = getTestClassNames(testDescriptor); if (testExecutionResult.getThrowable().isPresent()) { Throwable throwable = testExecutionResult.getThrowable().get(); ExceptionUtils.findNestedThrowables(throwable).forEach( t -> ExceptionUtils.pruneStackTrace(t, testClassNames)); } super.executionFinished(testDescriptor, testExecutionResult); } private static List getTestClassNames(TestDescriptor testDescriptor) { Stream self = Stream.of(testDescriptor); Stream ancestors = testDescriptor.getAncestors().stream(); return Stream.concat(self, ancestors) // .map(TestDescriptor::getSource) // .flatMap(Optional::stream) // .map(source -> { if (source instanceof ClassSource classSource) { return classSource.getClassName(); } else if (source instanceof MethodSource methodSource) { return methodSource.getClassName(); } else { return null; } }) // .filter(Objects::nonNull) // .toList(); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static org.junit.platform.launcher.LauncherConstants.CAPTURE_MAX_BUFFER_DEFAULT; import static org.junit.platform.launcher.LauncherConstants.CAPTURE_MAX_BUFFER_PROPERTY_NAME; import static org.junit.platform.launcher.LauncherConstants.CAPTURE_STDERR_PROPERTY_NAME; import static org.junit.platform.launcher.LauncherConstants.CAPTURE_STDOUT_PROPERTY_NAME; import static org.junit.platform.launcher.LauncherConstants.STDERR_REPORT_ENTRY_KEY; import static org.junit.platform.launcher.LauncherConstants.STDOUT_REPORT_ENTRY_KEY; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.function.BiConsumer; import org.junit.platform.commons.util.StringUtils; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.core.CompositeTestExecutionListener.EagerTestExecutionListener; /** * @since 1.3 */ class StreamInterceptingTestExecutionListener implements EagerTestExecutionListener { private final Optional stdoutInterceptor; private final Optional stderrInterceptor; private final BiConsumer reporter; static Optional create(ConfigurationParameters configurationParameters, BiConsumer reporter) { boolean captureStdout = configurationParameters.getBoolean(CAPTURE_STDOUT_PROPERTY_NAME).orElse(false); boolean captureStderr = configurationParameters.getBoolean(CAPTURE_STDERR_PROPERTY_NAME).orElse(false); if (!captureStdout && !captureStderr) { return Optional.empty(); } int maxSize = configurationParameters.get(CAPTURE_MAX_BUFFER_PROPERTY_NAME, Integer::valueOf) // .orElse(CAPTURE_MAX_BUFFER_DEFAULT); Optional stdoutInterceptor = captureStdout ? StreamInterceptor.registerStdout(maxSize) : Optional.empty(); Optional stderrInterceptor = captureStderr ? StreamInterceptor.registerStderr(maxSize) : Optional.empty(); if ((stdoutInterceptor.isEmpty() && captureStdout) || (stderrInterceptor.isEmpty() && captureStderr)) { stdoutInterceptor.ifPresent(StreamInterceptor::unregister); stderrInterceptor.ifPresent(StreamInterceptor::unregister); return Optional.empty(); } return Optional.of(new StreamInterceptingTestExecutionListener(stdoutInterceptor, stderrInterceptor, reporter)); } private StreamInterceptingTestExecutionListener(Optional stdoutInterceptor, Optional stderrInterceptor, BiConsumer reporter) { this.stdoutInterceptor = stdoutInterceptor; this.stderrInterceptor = stderrInterceptor; this.reporter = reporter; } void unregister() { stdoutInterceptor.ifPresent(StreamInterceptor::unregister); stderrInterceptor.ifPresent(StreamInterceptor::unregister); } @Override public void executionJustStarted(TestIdentifier testIdentifier) { stdoutInterceptor.ifPresent(StreamInterceptor::capture); stderrInterceptor.ifPresent(StreamInterceptor::capture); } @Override public void executionJustFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { Map map = new HashMap<>(); String out = stdoutInterceptor.map(StreamInterceptor::consume).orElse(""); if (StringUtils.isNotBlank(out)) { map.put(STDOUT_REPORT_ENTRY_KEY, out); } String err = stderrInterceptor.map(StreamInterceptor::consume).orElse(""); if (StringUtils.isNotBlank(err)) { map.put(STDERR_REPORT_ENTRY_KEY, err); } if (!map.isEmpty()) { reporter.accept(testIdentifier, ReportEntry.from(map)); } } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.nio.charset.Charset; import java.util.ArrayDeque; import java.util.Deque; import java.util.Optional; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.function.Consumer; import org.jspecify.annotations.Nullable; /** * @since 1.3 */ class StreamInterceptor extends PrintStream { private final Deque mostRecentOutputs = new ConcurrentLinkedDeque<>(); private final PrintStream originalStream; private final Consumer unregisterAction; private final int maxNumberOfBytesPerThread; private final ThreadLocal output = ThreadLocal.withInitial( RewindableByteArrayOutputStream::new); static Optional registerStdout(int maxNumberOfBytesPerThread) { return register(System.out, System::setOut, maxNumberOfBytesPerThread); } static Optional registerStderr(int maxNumberOfBytesPerThread) { return register(System.err, System::setErr, maxNumberOfBytesPerThread); } static Optional register(PrintStream originalStream, Consumer streamSetter, int maxNumberOfBytesPerThread) { if (originalStream instanceof StreamInterceptor) { return Optional.empty(); } StreamInterceptor interceptor = new StreamInterceptor(originalStream, streamSetter, maxNumberOfBytesPerThread); streamSetter.accept(interceptor); return Optional.of(interceptor); } private StreamInterceptor(PrintStream originalStream, Consumer unregisterAction, int maxNumberOfBytesPerThread) { super(originalStream); this.originalStream = originalStream; this.unregisterAction = unregisterAction; this.maxNumberOfBytesPerThread = maxNumberOfBytesPerThread; } void capture() { RewindableByteArrayOutputStream out = output.get(); out.mark(); pushToTop(out); } String consume() { RewindableByteArrayOutputStream out = output.get(); String result = out.rewind(); if (!out.isMarked()) { mostRecentOutputs.remove(out); } return result; } void unregister() { unregisterAction.accept(originalStream); } @Override public void write(int b) { RewindableByteArrayOutputStream out = getOutput(); if (out != null && out.size() < maxNumberOfBytesPerThread) { pushToTop(out); out.write(b); } super.write(b); } @Override public void write(byte[] b) { write(b, 0, b.length); } @Override public void write(byte[] buf, int off, int len) { RewindableByteArrayOutputStream out = getOutput(); if (out != null) { int actualLength = Math.max(0, Math.min(len, maxNumberOfBytesPerThread - out.size())); if (actualLength > 0) { pushToTop(out); out.write(buf, off, actualLength); } } super.write(buf, off, len); } private void pushToTop(RewindableByteArrayOutputStream out) { if (!out.equals(mostRecentOutputs.peek())) { mostRecentOutputs.remove(out); mostRecentOutputs.push(out); } } private @Nullable RewindableByteArrayOutputStream getOutput() { RewindableByteArrayOutputStream out = output.get(); return out.isMarked() ? out : mostRecentOutputs.peek(); } static class RewindableByteArrayOutputStream extends ByteArrayOutputStream { private final Deque markedPositions = new ArrayDeque<>(); boolean isMarked() { return !markedPositions.isEmpty(); } void mark() { markedPositions.addFirst(count); } String rewind() { Integer position = markedPositions.pollFirst(); if (position == null || position == count) { return ""; } int length = count - position; count -= length; return new String(buf, position, length, Charset.defaultCharset()); } } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/TestEngineFormatter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static java.lang.String.join; import static java.util.stream.Collectors.joining; import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; import org.junit.platform.commons.util.ClassLoaderUtils; import org.junit.platform.commons.util.CollectionUtils; import org.junit.platform.engine.TestEngine; class TestEngineFormatter { private TestEngineFormatter() { } @SuppressWarnings("unchecked") static String format(String title, Iterable testEngines) { return format(title, (Stream) CollectionUtils.toStream(testEngines)); } private static String format(String title, Stream testEngines) { String details = testEngines // .map(engine -> "- %s (%s)".formatted(engine.getId(), join(", ", computeAttributes(engine)))) // .collect(joining("\n")); return title + ":" + (details.isEmpty() ? " " : "\n" + details); } private static List computeAttributes(TestEngine engine) { List attributes = new ArrayList<>(4); engine.getGroupId().ifPresent(groupId -> attributes.add("group ID: " + groupId)); engine.getArtifactId().ifPresent(artifactId -> attributes.add("artifact ID: " + artifactId)); engine.getVersion().ifPresent(version -> attributes.add("version: " + version)); ClassLoaderUtils.getLocation(engine).ifPresent(location -> attributes.add("location: " + location)); return attributes; } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Core support classes for the {@link org.junit.platform.launcher.Launcher Launcher} * including the {@link org.junit.platform.launcher.core.LauncherFactory LauncherFactory} * and the {@link org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder * LauncherDiscoveryRequestBuilder}. */ @NullMarked package org.junit.platform.launcher.core; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/jfr/FlightRecordingDiscoveryListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.jfr; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.HashMap; import java.util.Map; import jdk.jfr.Category; import jdk.jfr.Event; import jdk.jfr.Label; import jdk.jfr.Name; import jdk.jfr.StackTrace; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.engine.DiscoveryFilter; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.launcher.EngineDiscoveryResult; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; /** * A {@link LauncherDiscoveryListener} that generates Java Flight Recorder * events. * * @since 1.8 * @see JEP 328: Flight Recorder */ @API(status = INTERNAL, since = "6.0") class FlightRecordingDiscoveryListener implements LauncherDiscoveryListener { private final Map engineDiscoveryEvents = new HashMap<>(); private @Nullable LauncherDiscoveryEvent launcherDiscoveryEvent; @Override public void launcherDiscoveryStarted(LauncherDiscoveryRequest request) { var event = new LauncherDiscoveryEvent(); if (event.isEnabled()) { event.begin(); this.launcherDiscoveryEvent = event; } } @Override public void launcherDiscoveryFinished(LauncherDiscoveryRequest request) { LauncherDiscoveryEvent event = this.launcherDiscoveryEvent; this.launcherDiscoveryEvent = null; if (event != null && event.shouldCommit()) { event.selectors = request.getSelectorsByType(DiscoverySelector.class).size(); event.filters = request.getFiltersByType(DiscoveryFilter.class).size(); event.commit(); } } @Override public void engineDiscoveryStarted(org.junit.platform.engine.UniqueId engineId) { var event = new EngineDiscoveryEvent(); if (event.isEnabled()) { event.begin(); this.engineDiscoveryEvents.put(engineId, event); } } @Override public void engineDiscoveryFinished(org.junit.platform.engine.UniqueId engineId, EngineDiscoveryResult result) { EngineDiscoveryEvent event = this.engineDiscoveryEvents.remove(engineId); if (event != null && event.shouldCommit()) { event.uniqueId = engineId.toString(); event.result = result.getStatus().toString(); event.commit(); } } @Override public void issueEncountered(org.junit.platform.engine.UniqueId engineId, DiscoveryIssue issue) { var event = new DiscoveryIssueEvent(); if (event.shouldCommit()) { event.engineId = engineId.toString(); event.severity = issue.severity().name(); event.message = issue.message(); event.source = issue.source().map(Object::toString).orElse(null); event.cause = issue.cause().map(ExceptionUtils::readStackTrace).orElse(null); event.commit(); } } @Category({ "JUnit", "Discovery" }) @StackTrace(false) abstract static class DiscoveryEvent extends Event { } @Label("Test Discovery") @Name("org.junit.LauncherDiscovery") static class LauncherDiscoveryEvent extends DiscoveryEvent { @Label("Number of selectors") int selectors; @Label("Number of filters") int filters; } @Label("Engine Discovery") @Name("org.junit.EngineDiscovery") static class EngineDiscoveryEvent extends DiscoveryEvent { @UniqueId @Label("Unique Id") @Nullable String uniqueId; @Label("Result") @Nullable String result; } @Label("Discovery Issue") @Name("org.junit.DiscoveryIssue") static class DiscoveryIssueEvent extends DiscoveryEvent { @Label("Engine Id") @Nullable String engineId; @Label("Severity") @Nullable String severity; @Label("Message") @Nullable String message; @Label("Source") @Nullable String source; @Label("Cause") @Nullable String cause; } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/jfr/FlightRecordingExecutionListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.jfr; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import jdk.jfr.Category; import jdk.jfr.Event; import jdk.jfr.Label; import jdk.jfr.Name; import jdk.jfr.StackTrace; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; /** * A {@link TestExecutionListener} that generates Java Flight Recorder * events. * * @since 1.8 * @see JEP 328: Flight Recorder */ @API(status = INTERNAL, since = "6.0") class FlightRecordingExecutionListener implements TestExecutionListener { private final Map testExecutionEvents = new ConcurrentHashMap<>(); private @Nullable TestPlanExecutionEvent testPlanExecutionEvent; @Override public void testPlanExecutionStarted(TestPlan plan) { var event = new TestPlanExecutionEvent(); if (event.isEnabled()) { event.begin(); this.testPlanExecutionEvent = event; } } @Override public void testPlanExecutionFinished(TestPlan plan) { var event = this.testPlanExecutionEvent; this.testPlanExecutionEvent = null; if (event != null && event.shouldCommit()) { event.containsTests = plan.containsTests(); event.engineNames = plan.getRoots().stream().map(TestIdentifier::getDisplayName).collect( Collectors.joining(", ")); event.commit(); } } @Override public void executionSkipped(TestIdentifier test, String reason) { var event = new SkippedTestEvent(); if (event.shouldCommit()) { event.initialize(test); event.reason = reason; event.commit(); } } @Override public void executionStarted(TestIdentifier test) { var event = new TestExecutionEvent(); if (event.isEnabled()) { event.begin(); this.testExecutionEvents.put(test.getUniqueIdObject(), event); } } @Override public void executionFinished(TestIdentifier test, TestExecutionResult result) { TestExecutionEvent event = this.testExecutionEvents.remove(test.getUniqueIdObject()); if (event != null && event.shouldCommit()) { event.end(); event.initialize(test); event.result = result.getStatus().toString(); Optional throwable = result.getThrowable(); event.exceptionClass = throwable.map(Throwable::getClass).orElse(null); event.exceptionMessage = throwable.map(Throwable::getMessage).orElse(null); event.commit(); } } @Override public void reportingEntryPublished(TestIdentifier test, ReportEntry reportEntry) { for (var entry : reportEntry.getKeyValuePairs().entrySet()) { var event = new ReportEntryEvent(); if (event.shouldCommit()) { event.uniqueId = test.getUniqueId(); event.key = entry.getKey(); event.value = entry.getValue(); event.commit(); } } } @Override public void fileEntryPublished(TestIdentifier testIdentifier, FileEntry file) { var event = new FileEntryEvent(); if (event.shouldCommit()) { event.uniqueId = testIdentifier.getUniqueId(); event.path = file.getPath().toAbsolutePath().toString(); event.commit(); } } @Category({ "JUnit", "Execution" }) @StackTrace(false) abstract static class ExecutionEvent extends Event { } @Label("Test Execution") @Name("org.junit.TestPlanExecution") static class TestPlanExecutionEvent extends ExecutionEvent { @Label("Contains Tests") boolean containsTests; @Label("Engine Names") @Nullable String engineNames; } abstract static class TestEvent extends ExecutionEvent { @UniqueId @Label("Unique Id") @Nullable String uniqueId; @Label("Display Name") @Nullable String displayName; @Label("Tags") @Nullable String tags; @Label("Type") @Nullable String type; void initialize(TestIdentifier test) { this.uniqueId = test.getUniqueId(); this.displayName = test.getDisplayName(); this.tags = test.getTags().isEmpty() ? null : test.getTags().toString(); this.type = test.getType().name(); } } @Label("Skipped Test") @Name("org.junit.SkippedTest") static class SkippedTestEvent extends TestEvent { @Label("Reason") @Nullable String reason; } @Label("Test") @Name("org.junit.TestExecution") static class TestExecutionEvent extends TestEvent { @Label("Result") @Nullable String result; @Label("Exception Class") @Nullable Class exceptionClass; @Label("Exception Message") @Nullable String exceptionMessage; } @Label("Report Entry") @Name("org.junit.ReportEntry") static class ReportEntryEvent extends ExecutionEvent { @UniqueId @Label("Unique Id") @Nullable String uniqueId; @Label("Key") @Nullable String key; @Label("Value") @Nullable String value; } @Label("File Entry") @Name("org.junit.FileEntry") static class FileEntryEvent extends ExecutionEvent { @UniqueId @Label("Unique Id") @Nullable String uniqueId; @Label("Path") @Nullable String path; } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/jfr/JfrUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.jfr; import static org.apiguardian.api.API.Status.INTERNAL; import org.apiguardian.api.API; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.launcher.Launcher; /** * Internal utility for Java Flight Recorder (JFR) support. * * @since 6.0 */ @API(status = INTERNAL, since = "6.0") public class JfrUtils { public static void registerListeners(Launcher launcher) { if (isJfrAvailable()) { launcher.registerLauncherDiscoveryListeners(new FlightRecordingDiscoveryListener()); launcher.registerTestExecutionListeners(new FlightRecordingExecutionListener()); } } private static boolean isJfrAvailable() { return System.getProperty("org.graalvm.nativeimage.imagecode") == null // && ReflectionSupport.tryToLoadClass("jdk.jfr.FlightRecorder").toOptional().isPresent(); } private JfrUtils() { } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/jfr/UniqueId.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.jfr; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import jdk.jfr.MetadataDefinition; import jdk.jfr.Name; import jdk.jfr.Relational; @MetadataDefinition @Relational @Name("org.junit.UniqueId") @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @interface UniqueId { } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/jfr/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Java Flight Recorder (JFR) support package. */ @NullMarked package org.junit.platform.launcher.jfr; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/LoggingListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.listeners; import static org.apiguardian.api.API.Status.MAINTAINED; import java.util.function.BiConsumer; import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; /** * Simple {@link TestExecutionListener} for logging informational messages * for all events via a {@link BiConsumer} that consumes {@code Throwable} * and {@code Supplier}. * * @since 1.0 * @see #forJavaUtilLogging() * @see #forJavaUtilLogging(Level) * @see LoggingListener#LoggingListener(BiConsumer) */ @API(status = MAINTAINED, since = "1.0") public class LoggingListener implements TestExecutionListener { /** * Create a {@code LoggingListener} which delegates to a * {@link java.util.logging.Logger} using a log level of * {@link Level#FINE FINE}. * * @see #forJavaUtilLogging(Level) * @see #forBiConsumer(BiConsumer) */ public static LoggingListener forJavaUtilLogging() { return forJavaUtilLogging(Level.FINE); } /** * Create a {@code LoggingListener} which delegates to a * {@link java.util.logging.Logger} using the supplied * {@linkplain Level log level}. * * @param logLevel the log level to use; never {@code null} * @see #forJavaUtilLogging() * @see #forBiConsumer(BiConsumer) */ public static LoggingListener forJavaUtilLogging(Level logLevel) { Preconditions.notNull(logLevel, "logLevel must not be null"); Logger logger = Logger.getLogger(LoggingListener.class.getName()); return new LoggingListener((t, messageSupplier) -> logger.log(logLevel, t, messageSupplier)); } /** * Create a {@code LoggingListener} which delegates to the supplied * {@link BiConsumer} for consumption of logging messages. * *

The {@code BiConsumer's} arguments are a {@link Throwable} (potentially * {@code null}) and a {@link Supplier} (never {@code null}) for the log * message. * * @param logger a logger implemented as a {@code BiConsumer}; * never {@code null} * * @see #forJavaUtilLogging() * @see #forJavaUtilLogging(Level) */ public static LoggingListener forBiConsumer(BiConsumer<@Nullable Throwable, Supplier> logger) { return new LoggingListener(logger); } private final BiConsumer<@Nullable Throwable, Supplier> logger; private LoggingListener(BiConsumer<@Nullable Throwable, Supplier> logger) { Preconditions.notNull(logger, "logger must not be null"); this.logger = logger; } @Override public void testPlanExecutionStarted(TestPlan testPlan) { log("TestPlan Execution Started: %s", testPlan); } @Override public void testPlanExecutionFinished(TestPlan testPlan) { log("TestPlan Execution Finished: %s", testPlan); } @Override public void dynamicTestRegistered(TestIdentifier testIdentifier) { log("Dynamic Test Registered: %s - %s", testIdentifier.getDisplayName(), testIdentifier.getUniqueId()); } @Override public void executionStarted(TestIdentifier testIdentifier) { log("Execution Started: %s - %s", testIdentifier.getDisplayName(), testIdentifier.getUniqueId()); } @Override public void executionSkipped(TestIdentifier testIdentifier, String reason) { log("Execution Skipped: %s - %s - %s", testIdentifier.getDisplayName(), testIdentifier.getUniqueId(), reason); } @Override public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { logWithThrowable("Execution Finished: %s - %s - %s", testExecutionResult.getThrowable().orElse(null), testIdentifier.getDisplayName(), testIdentifier.getUniqueId(), testExecutionResult); } private void log(String message, Object... args) { logWithThrowable(message, null, args); } private void logWithThrowable(String message, @Nullable Throwable t, Object... args) { this.logger.accept(t, () -> message.formatted(args)); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/MutableTestExecutionSummary.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.listeners; import static java.lang.String.join; import static java.util.Collections.synchronizedList; import java.io.PrintWriter; import java.io.Serial; import java.time.Duration; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.UniqueId; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; /** * Mutable, internal implementation of the {@link TestExecutionSummary} API. * * @since 1.0 */ class MutableTestExecutionSummary implements TestExecutionSummary { private static final String TAB = " "; private static final String DOUBLE_TAB = TAB + TAB; private static final int DEFAULT_MAX_STACKTRACE_LINES = 10; private static final String CAUSED_BY = "Caused by: "; private static final String SUPPRESSED = "Suppressed: "; private static final String CIRCULAR = "Circular reference: "; final AtomicLong containersFound = new AtomicLong(); final AtomicLong containersStarted = new AtomicLong(); final AtomicLong containersSkipped = new AtomicLong(); final AtomicLong containersAborted = new AtomicLong(); final AtomicLong containersSucceeded = new AtomicLong(); final AtomicLong containersFailed = new AtomicLong(); final AtomicLong testsFound = new AtomicLong(); final AtomicLong testsStarted = new AtomicLong(); final AtomicLong testsSkipped = new AtomicLong(); final AtomicLong testsAborted = new AtomicLong(); final AtomicLong testsSucceeded = new AtomicLong(); final AtomicLong testsFailed = new AtomicLong(); private final TestPlan testPlan; private final List failures = synchronizedList(new ArrayList<>()); private final Map descriptions = new ConcurrentHashMap<>(); private final long timeStarted; private final long timeStartedNanos; long timeFinished; long timeFinishedNanos; MutableTestExecutionSummary(TestPlan testPlan) { this.testPlan = testPlan; this.containersFound.set(testPlan.countTestIdentifiers(TestIdentifier::isContainer)); this.testsFound.set(testPlan.countTestIdentifiers(TestIdentifier::isTest)); this.timeStarted = System.currentTimeMillis(); this.timeStartedNanos = System.nanoTime(); } void addFailure(TestIdentifier testIdentifier, Throwable throwable) { this.failures.add(new DefaultFailure(testIdentifier, throwable)); this.descriptions.put(testIdentifier.getUniqueIdObject(), describeTest(testIdentifier)); } @Override public long getTimeStarted() { return this.timeStarted; } @Override public long getTimeFinished() { return this.timeFinished; } @Override public long getTotalFailureCount() { return getTestsFailedCount() + getContainersFailedCount(); } @Override public long getContainersFoundCount() { return this.containersFound.get(); } @Override public long getContainersStartedCount() { return this.containersStarted.get(); } @Override public long getContainersSkippedCount() { return this.containersSkipped.get(); } @Override public long getContainersAbortedCount() { return this.containersAborted.get(); } @Override public long getContainersSucceededCount() { return this.containersSucceeded.get(); } @Override public long getContainersFailedCount() { return this.containersFailed.get(); } @Override public long getTestsFoundCount() { return this.testsFound.get(); } @Override public long getTestsStartedCount() { return this.testsStarted.get(); } @Override public long getTestsSkippedCount() { return this.testsSkipped.get(); } @Override public long getTestsAbortedCount() { return this.testsAborted.get(); } @Override public long getTestsSucceededCount() { return this.testsSucceeded.get(); } @Override public long getTestsFailedCount() { return this.testsFailed.get(); } @Override public void printTo(PrintWriter writer) { // @formatter:off writer.printf("%nTest run finished after %d ms%n" + "[%10d containers found ]%n" + "[%10d containers skipped ]%n" + "[%10d containers started ]%n" + "[%10d containers aborted ]%n" + "[%10d containers successful ]%n" + "[%10d containers failed ]%n" + "[%10d tests found ]%n" + "[%10d tests skipped ]%n" + "[%10d tests started ]%n" + "[%10d tests aborted ]%n" + "[%10d tests successful ]%n" + "[%10d tests failed ]%n" + "%n", Duration.ofNanos(this.timeFinishedNanos - this.timeStartedNanos).toMillis(), getContainersFoundCount(), getContainersSkippedCount(), getContainersStartedCount(), getContainersAbortedCount(), getContainersSucceededCount(), getContainersFailedCount(), getTestsFoundCount(), getTestsSkippedCount(), getTestsStartedCount(), getTestsAbortedCount(), getTestsSucceededCount(), getTestsFailedCount() ); // @formatter:on writer.flush(); } @Override public void printFailuresTo(PrintWriter writer) { printFailuresTo(writer, DEFAULT_MAX_STACKTRACE_LINES); } @Override public void printFailuresTo(PrintWriter writer, int maxStackTraceLines) { Preconditions.notNull(writer, "PrintWriter must not be null"); Preconditions.condition(maxStackTraceLines >= 0, "maxStackTraceLines must be a positive number"); if (getTotalFailureCount() > 0) { writer.printf("%nFailures (%d):%n", getTotalFailureCount()); this.failures.forEach(failure -> { var testIdentifier = failure.getTestIdentifier(); writer.printf("%s%s%n", TAB, descriptions.get(testIdentifier.getUniqueIdObject())); printSource(writer, testIdentifier); writer.printf("%s=> %s%n", DOUBLE_TAB, failure.getException()); printStackTrace(writer, failure.getException(), maxStackTraceLines); }); writer.flush(); } } @Override public List getFailures() { return Collections.unmodifiableList(failures); } private String describeTest(TestIdentifier testIdentifier) { List descriptionParts = new ArrayList<>(); collectTestDescription(testIdentifier, descriptionParts); return join(":", descriptionParts); } private void collectTestDescription(TestIdentifier identifier, List descriptionParts) { descriptionParts.add(0, identifier.getDisplayName()); this.testPlan.getParent(identifier).ifPresent(parent -> collectTestDescription(parent, descriptionParts)); } private static void printSource(PrintWriter writer, TestIdentifier testIdentifier) { testIdentifier.getSource().ifPresent(source -> writer.printf("%s%s%n", DOUBLE_TAB, source)); } private static void printStackTrace(PrintWriter writer, Throwable throwable, int max) { if (throwable.getCause() != null || (throwable.getSuppressed() != null && throwable.getSuppressed().length > 0)) { max = max / 2; } printStackTrace(writer, new StackTraceElement[] {}, throwable, "", DOUBLE_TAB + " ", new HashSet<>(), max); writer.flush(); } private static void printStackTrace(PrintWriter writer, StackTraceElement[] parentTrace, Throwable throwable, String caption, String indentation, Set seenThrowables, int max) { if (seenThrowables.contains(throwable)) { writer.printf("%s%s[%s%s]%n", indentation, TAB, CIRCULAR, throwable); return; } seenThrowables.add(throwable); StackTraceElement[] trace = throwable.getStackTrace(); if (parentTrace.length > 0) { writer.printf("%s%s%s%n", indentation, caption, throwable); } int duplicates = numberOfCommonFrames(trace, parentTrace); int numDistinctFrames = trace.length - duplicates; int numDisplayLines = Math.min(numDistinctFrames, max); for (int i = 0; i < numDisplayLines; i++) { writer.printf("%s%s%s%n", indentation, TAB, trace[i]); } if (trace.length > max || duplicates != 0) { writer.printf("%s%s%s%n", indentation, TAB, "[...]"); } for (Throwable suppressed : throwable.getSuppressed()) { printStackTrace(writer, trace, suppressed, SUPPRESSED, indentation + TAB, seenThrowables, max); } if (throwable.getCause() != null) { printStackTrace(writer, trace, throwable.getCause(), CAUSED_BY, indentation, seenThrowables, max); } } private static int numberOfCommonFrames(StackTraceElement[] currentTrace, StackTraceElement[] parentTrace) { int currentIndex = currentTrace.length - 1; for (int parentIndex = parentTrace.length - 1; currentIndex >= 0 && parentIndex >= 0; currentIndex--, parentIndex--) { if (!currentTrace[currentIndex].equals(parentTrace[parentIndex])) { break; } } return currentTrace.length - 1 - currentIndex; } private record DefaultFailure(TestIdentifier testIdentifier, Throwable exception) implements Failure { @Serial private static final long serialVersionUID = 1L; @Override public TestIdentifier getTestIdentifier() { return testIdentifier; } @Override public Throwable getException() { return exception; } } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/OutputDir.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.listeners; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.launcher.LauncherConstants.OUTPUT_DIR_UNIQUE_NUMBER_PLACEHOLDER; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.security.SecureRandom; import java.util.Optional; import java.util.function.BiPredicate; import java.util.function.Supplier; import java.util.regex.Pattern; import java.util.stream.Stream; import org.apiguardian.api.API; import org.junit.platform.commons.util.StringUtils; @API(status = INTERNAL, since = "1.9") public class OutputDir { private static final Pattern OUTPUT_DIR_UNIQUE_NUMBER_PLACEHOLDER_PATTERN = Pattern.compile( Pattern.quote(OUTPUT_DIR_UNIQUE_NUMBER_PLACEHOLDER)); public static OutputDir create(Optional customDir) { return create(customDir, () -> Path.of(".")); } static OutputDir create(Optional customDir, Supplier currentWorkingDir) { try { return createSafely(customDir, currentWorkingDir); } catch (IOException e) { throw new UncheckedIOException("Failed to create output dir", e); } } /** * Package private for testing purposes. */ static OutputDir createSafely(Optional customDir, Supplier currentWorkingDir) throws IOException { return createSafely(customDir, currentWorkingDir, new SecureRandom()); } private static OutputDir createSafely(Optional customDir, Supplier currentWorkingDir, SecureRandom random) throws IOException { Path cwd = currentWorkingDir.get().toAbsolutePath(); Path outputDir; if (customDir.isPresent() && StringUtils.isNotBlank(customDir.get())) { outputDir = cwd.resolve(expandPlaceholders(customDir.get(), random)); } else if (Files.exists(cwd.resolve("pom.xml"))) { outputDir = cwd.resolve("target"); } else if (containsFilesWithExtensions(cwd, ".gradle", ".gradle.kts")) { outputDir = cwd.resolve("build"); } else { outputDir = cwd; } if (!Files.exists(outputDir)) { Files.createDirectories(outputDir); } return new OutputDir(outputDir.normalize(), random); } private static String expandPlaceholders(String customDir, SecureRandom random) { String customPath = customDir; while (customPath.contains(OUTPUT_DIR_UNIQUE_NUMBER_PLACEHOLDER)) { customPath = OUTPUT_DIR_UNIQUE_NUMBER_PLACEHOLDER_PATTERN.matcher(customPath) // .replaceFirst(String.valueOf(positiveLong(random))); } return customPath; } private final Path path; private final SecureRandom random; private OutputDir(Path path, SecureRandom random) { this.path = path; this.random = random; } public Path toPath() { return path; } public Path createFile(String prefix, String extension) throws UncheckedIOException { String filename = "%s-%d.%s".formatted(prefix, positiveLong(random), extension); Path outputFile = path.resolve(filename); try { if (Files.exists(outputFile)) { Files.delete(outputFile); } return Files.createFile(outputFile); } catch (IOException e) { throw new UncheckedIOException("Failed to create output file: " + outputFile, e); } } private static long positiveLong(SecureRandom random) { var value = random.nextLong(); if (value == Long.MIN_VALUE) { // ensure Math.abs returns positive value value++; } return Math.abs(value); } /** * Determine if the supplied directory contains files with any of the * supplied extensions. */ private static boolean containsFilesWithExtensions(Path dir, String... extensions) throws IOException { BiPredicate matcher = (path, basicFileAttributes) -> { if (basicFileAttributes.isRegularFile()) { for (String extension : extensions) { if (path.getFileName().toString().endsWith(extension)) { return true; } } } return false; }; try (Stream pathStream = Files.find(dir, 1, matcher)) { return pathStream.findFirst().isPresent(); } } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/SummaryGeneratingListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.listeners; import static java.util.Objects.requireNonNull; import static java.util.stream.Stream.concat; import static org.apiguardian.api.API.Status.MAINTAINED; import java.util.stream.Stream; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; /** * Simple {@link TestExecutionListener} that generates a * {@linkplain TestExecutionSummary summary} of the test execution. * * @since 1.0 * @see #getSummary() */ @API(status = MAINTAINED, since = "1.0") public class SummaryGeneratingListener implements TestExecutionListener { private @Nullable TestPlan testPlan; private @Nullable MutableTestExecutionSummary summary; public SummaryGeneratingListener() { } /** * Get the summary generated by this listener. */ public TestExecutionSummary getSummary() { return getMutableSummary(); } private MutableTestExecutionSummary getMutableSummary() { return Preconditions.notNull(this.summary, "No tests have yet been executed"); } @Override public void testPlanExecutionStarted(TestPlan testPlan) { this.testPlan = testPlan; this.summary = new MutableTestExecutionSummary(testPlan); } @Override public void testPlanExecutionFinished(TestPlan testPlan) { var summary = getMutableSummary(); summary.timeFinished = System.currentTimeMillis(); summary.timeFinishedNanos = System.nanoTime(); } @Override public void dynamicTestRegistered(TestIdentifier testIdentifier) { var summary = getMutableSummary(); if (testIdentifier.isContainer()) { summary.containersFound.incrementAndGet(); } if (testIdentifier.isTest()) { summary.testsFound.incrementAndGet(); } } @Override public void executionSkipped(TestIdentifier testIdentifier, String reason) { var testPlan = requireNonNull(this.testPlan); // @formatter:off long skippedContainers = concat(Stream.of(testIdentifier), testPlan.getDescendants(testIdentifier).stream()) .filter(TestIdentifier::isContainer) .count(); long skippedTests = concat(Stream.of(testIdentifier), testPlan.getDescendants(testIdentifier).stream()) .filter(TestIdentifier::isTest) .count(); // @formatter:on var summary = getMutableSummary(); summary.containersSkipped.addAndGet(skippedContainers); summary.testsSkipped.addAndGet(skippedTests); } @Override public void executionStarted(TestIdentifier testIdentifier) { var summary = getMutableSummary(); if (testIdentifier.isContainer()) { summary.containersStarted.incrementAndGet(); } if (testIdentifier.isTest()) { summary.testsStarted.incrementAndGet(); } } @Override public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { var summary = getMutableSummary(); switch (testExecutionResult.getStatus()) { case SUCCESSFUL -> { if (testIdentifier.isContainer()) { summary.containersSucceeded.incrementAndGet(); } if (testIdentifier.isTest()) { summary.testsSucceeded.incrementAndGet(); } } case ABORTED -> { if (testIdentifier.isContainer()) { summary.containersAborted.incrementAndGet(); } if (testIdentifier.isTest()) { summary.testsAborted.incrementAndGet(); } } case FAILED -> { if (testIdentifier.isContainer()) { summary.containersFailed.incrementAndGet(); } if (testIdentifier.isTest()) { summary.testsFailed.incrementAndGet(); } testExecutionResult.getThrowable().ifPresent( throwable -> summary.addFailure(testIdentifier, throwable)); } default -> throw new PreconditionViolationException( "Unsupported execution status:" + testExecutionResult.getStatus()); } } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/TestExecutionSummary.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.listeners; import static org.apiguardian.api.API.Status.MAINTAINED; import java.io.PrintWriter; import java.io.Serializable; import java.util.List; import org.apiguardian.api.API; import org.junit.platform.launcher.TestIdentifier; /** * Summary of test plan execution. * * @since 1.0 * @see SummaryGeneratingListener */ @API(status = MAINTAINED, since = "1.0") public interface TestExecutionSummary { /** * Get the timestamp (in milliseconds) when the test plan started. */ long getTimeStarted(); /** * Get the timestamp (in milliseconds) when the test plan finished. */ long getTimeFinished(); /** * Get the total number of {@linkplain #getContainersFailedCount failed * containers} and {@linkplain #getTestsFailedCount failed tests}. * * @see #getTestsFailedCount() * @see #getContainersFailedCount() */ long getTotalFailureCount(); /** * Get the number of containers found. */ long getContainersFoundCount(); /** * Get the number of containers started. */ long getContainersStartedCount(); /** * Get the number of containers skipped. */ long getContainersSkippedCount(); /** * Get the number of containers aborted. */ long getContainersAbortedCount(); /** * Get the number of containers that succeeded. */ long getContainersSucceededCount(); /** * Get the number of containers that failed. * * @see #getTestsFailedCount() * @see #getTotalFailureCount() */ long getContainersFailedCount(); /** * Get the number of tests found. */ long getTestsFoundCount(); /** * Get the number of tests started. */ long getTestsStartedCount(); /** * Get the number of tests skipped. */ long getTestsSkippedCount(); /** * Get the number of tests aborted. */ long getTestsAbortedCount(); /** * Get the number of tests that succeeded. */ long getTestsSucceededCount(); /** * Get the number of tests that failed. * * @see #getContainersFailedCount() * @see #getTotalFailureCount() */ long getTestsFailedCount(); /** * Print this summary to the supplied {@link PrintWriter}. * *

This method does not print failure messages. * * @see #printFailuresTo(PrintWriter) */ void printTo(PrintWriter writer); /** * Print failed containers and tests, including sources and exception * messages, to the supplied {@link PrintWriter}. * * @param writer the {@code PrintWriter} to which to print; never {@code null} * @see #printTo(PrintWriter) * @see #printFailuresTo(PrintWriter, int) */ void printFailuresTo(PrintWriter writer); /** * Print failed containers and tests, including sources and exception * messages, to the supplied {@link PrintWriter}. * *

The maximum number of lines to print for exception stack traces (if any) * can be specified via the {@code maxStackTraceLines} argument. * *

By default, this method delegates to {@link #printFailuresTo(PrintWriter)}, * effectively ignoring the {@code maxStackTraceLines} argument. Concrete * implementations of this interface should therefore override this default * method in order to honor the {@code maxStackTraceLines} argument. * * @param writer the {@code PrintWriter} to which to print; never {@code null} * @param maxStackTraceLines the maximum number of lines to print for exception * stack traces; must be a positive value * @since 1.6 * @see #printTo(PrintWriter) * @see #printFailuresTo(PrintWriter) */ @API(status = MAINTAINED, since = "1.6") default void printFailuresTo(PrintWriter writer, int maxStackTraceLines) { printFailuresTo(writer); } /** * Get an immutable list of the failures of the test plan execution. */ List getFailures(); /** * Failure of a test or container. */ interface Failure extends Serializable { /** * Get the identifier of the failed test or container. * * @return the {@link TestIdentifier} for this failure; never {@code null} */ TestIdentifier getTestIdentifier(); /** * Get the {@link Throwable} causing the failure. * * @return the {@link Throwable} for this failure; never {@code null} */ Throwable getException(); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.listeners; import static java.util.Objects.requireNonNull; import static org.apiguardian.api.API.Status.STABLE; import java.io.IOException; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.function.Supplier; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; /** * {@code UniqueIdTrackingListener} is a {@link TestExecutionListener} that tracks * the {@linkplain TestIdentifier#getUniqueId() unique IDs} of all * {@linkplain TestIdentifier#isTest() tests} that were * {@linkplain #executionFinished executed} during the execution of the * {@link TestPlan} and generates a file containing the unique IDs once execution * of the {@code TestPlan} has {@linkplain #testPlanExecutionFinished(TestPlan) * finished}. * *

Tests are tracked regardless of their {@link TestExecutionResult} or whether * they were skipped, and the unique IDs are written to an output file, one ID * per line, encoding using UTF-8. * *

The output file can be used to execute the same set of tests again without * having to query the user configuration for the test plan and without having to * perform test discovery again. This can be useful for test environments such as * within a native image — for example, a GraalVM native image — in * order to rerun the exact same tests from a standard JVM test run within a * native image. * *

Configuration and Defaults

* *

The {@code OUTPUT_DIR} is the directory in which this listener generates * the output file. The exact path of the generated file is * {@code OUTPUT_DIR/OUTPUT_FILE_PREFIX-.txt}, where * {@code } is a pseudo-random number. The inclusion of a random * number in the file name ensures that multiple concurrently executing test * plans do not overwrite each other's results. * *

The value of the {@code OUTPUT_FILE_PREFIX} defaults to * {@link #DEFAULT_OUTPUT_FILE_PREFIX}, but a custom prefix can be set via the * {@link #OUTPUT_FILE_PREFIX_PROPERTY_NAME} configuration property. * *

The {@code OUTPUT_DIR} can be set to a custom directory via the * {@link #OUTPUT_DIR_PROPERTY_NAME} configuration property. Otherwise the * following algorithm is used to select a default output directory. * *

    *
  • If the current working directory of the Java process contains a file named * {@code pom.xml}, the output directory will be {@code ./target}, following the * conventions of Maven.
  • *
  • If the current working directory of the Java process contains a file with * the extension {@code .gradle} or {@code .gradle.kts}, the output directory * will be {@code ./build}, following the conventions of Gradle.
  • *
  • Otherwise, the current working directory of the Java process will be used * as the output directory.
  • *
* *

For example, in a project using Gradle as the build tool, the file generated * by this listener would be {@code ./build/junit-platform-unique-ids-.txt} * by default. * *

Configuration properties can be set via JVM system properties, via a * {@code junit-platform.properties} file in the root of the classpath, or as * JUnit Platform {@linkplain ConfigurationParameters configuration parameters}. * * @since 1.8 */ @API(status = STABLE, since = "1.11") public class UniqueIdTrackingListener implements TestExecutionListener { /** * Property name used to enable the {@code UniqueIdTrackingListener}: {@value} * *

The {@code UniqueIdTrackingListener} is registered automatically via * Java's {@link java.util.ServiceLoader} mechanism but disabled by default. * *

Set the value of this property to {@code true} to enable this listener. */ public static final String LISTENER_ENABLED_PROPERTY_NAME = "junit.platform.listeners.uid.tracking.enabled"; /** * Property name used to set the path to the output directory for the file * generated by the {@code UniqueIdTrackingListener}: {@value} * *

For details on the default output directory, see the * {@linkplain UniqueIdTrackingListener class-level Javadoc}. */ public static final String OUTPUT_DIR_PROPERTY_NAME = "junit.platform.listeners.uid.tracking.output.dir"; /** * Property name used to set the prefix for the name of the file generated * by the {@code UniqueIdTrackingListener}: {@value} * *

Defaults to {@link #DEFAULT_OUTPUT_FILE_PREFIX}. */ public static final String OUTPUT_FILE_PREFIX_PROPERTY_NAME = "junit.platform.listeners.uid.tracking.output.file.prefix"; /** * The default prefix for the name of the file generated by the * {@code UniqueIdTrackingListener}: {@value} * * @see #OUTPUT_FILE_PREFIX_PROPERTY_NAME */ public static final String DEFAULT_OUTPUT_FILE_PREFIX = "junit-platform-unique-ids"; static final String WORKING_DIR_PROPERTY_NAME = "junit.platform.listeners.uid.tracking.working.dir"; private final Logger logger = LoggerFactory.getLogger(UniqueIdTrackingListener.class); private final List uniqueIds = new ArrayList<>(); private boolean enabled; private @Nullable TestPlan testPlan; public UniqueIdTrackingListener() { // to avoid missing-explicit-ctor warning } @Override public void testPlanExecutionStarted(TestPlan testPlan) { this.enabled = testPlan.getConfigurationParameters().getBoolean(LISTENER_ENABLED_PROPERTY_NAME).orElse(false); this.testPlan = testPlan; } @Override public void executionSkipped(TestIdentifier testIdentifier, String reason) { if (this.enabled) { // When a container is skipped, there are no events for its children. // Therefore, in order to track them, we need to traverse the subtree. trackTestUidRecursively(testIdentifier); } } @Override public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { if (this.enabled) { trackTestUid(testIdentifier); } } private void trackTestUidRecursively(TestIdentifier testIdentifier) { boolean tracked = trackTestUid(testIdentifier); if (!tracked) { requireNonNull(this.testPlan).getChildren(testIdentifier).forEach(this::trackTestUidRecursively); } } private boolean trackTestUid(TestIdentifier testIdentifier) { if (testIdentifier.isTest()) { this.uniqueIds.add(testIdentifier.getUniqueId()); return true; } return false; } @Override public void testPlanExecutionFinished(TestPlan testPlan) { if (this.enabled) { Path outputFile; try { outputFile = createOutputFile(testPlan.getConfigurationParameters()); } catch (Exception ex) { logger.error(ex, () -> "Failed to create output file"); // Abort since we cannot generate the file. return; } logger.debug(() -> "Writing unique IDs to output file " + outputFile.toAbsolutePath()); try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(outputFile, StandardCharsets.UTF_8))) { this.uniqueIds.forEach(writer::println); writer.flush(); } catch (IOException ex) { logger.error(ex, () -> "Failed to write unique IDs to output file " + outputFile.toAbsolutePath()); } } this.testPlan = null; } private Path createOutputFile(ConfigurationParameters configurationParameters) { String prefix = configurationParameters.get(OUTPUT_FILE_PREFIX_PROPERTY_NAME) // .orElse(DEFAULT_OUTPUT_FILE_PREFIX); Supplier workingDirSupplier = () -> configurationParameters.get(WORKING_DIR_PROPERTY_NAME) // .map(Path::of) // .orElseGet(() -> Path.of(".")); return OutputDir.create(configurationParameters.get(OUTPUT_DIR_PROPERTY_NAME), workingDirSupplier) // .createFile(prefix, "txt"); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/AbortOnFailureLauncherDiscoveryListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.listeners.discovery; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.engine.UniqueId; import org.junit.platform.launcher.EngineDiscoveryResult; import org.junit.platform.launcher.LauncherDiscoveryListener; /** * @since 1.6 * @see LauncherDiscoveryListeners#abortOnFailure() */ final class AbortOnFailureLauncherDiscoveryListener implements LauncherDiscoveryListener { @Override public void engineDiscoveryFinished(UniqueId engineId, EngineDiscoveryResult result) { result.getThrowable().ifPresent(ExceptionUtils::throwAsUncheckedException); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } return getClass() == obj.getClass(); } @Override public int hashCode() { return 0; } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/CompositeLauncherDiscoveryListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.listeners.discovery; import static org.junit.platform.commons.util.CollectionUtils.forEachInReverseOrder; import java.util.List; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.SelectorResolutionResult; import org.junit.platform.engine.UniqueId; import org.junit.platform.launcher.EngineDiscoveryResult; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; /** * @since 1.6 * @see LauncherDiscoveryListeners#composite(List) */ class CompositeLauncherDiscoveryListener implements LauncherDiscoveryListener { private final List listeners; CompositeLauncherDiscoveryListener(List listeners) { this.listeners = List.copyOf(listeners); } @Override public void launcherDiscoveryStarted(LauncherDiscoveryRequest request) { listeners.forEach(delegate -> delegate.launcherDiscoveryStarted(request)); } @Override public void launcherDiscoveryFinished(LauncherDiscoveryRequest request) { forEachInReverseOrder(listeners, delegate -> delegate.launcherDiscoveryFinished(request)); } @Override public void engineDiscoveryStarted(UniqueId engineId) { listeners.forEach(delegate -> delegate.engineDiscoveryStarted(engineId)); } @Override public void engineDiscoveryFinished(UniqueId engineId, EngineDiscoveryResult result) { forEachInReverseOrder(listeners, delegate -> delegate.engineDiscoveryFinished(engineId, result)); } @Override public void selectorProcessed(UniqueId engineId, DiscoverySelector selector, SelectorResolutionResult result) { listeners.forEach(delegate -> delegate.selectorProcessed(engineId, selector, result)); } @Override public void issueEncountered(UniqueId engineId, DiscoveryIssue issue) { listeners.forEach(delegate -> delegate.issueEncountered(engineId, issue)); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/LauncherDiscoveryListeners.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.listeners.discovery; import static java.util.stream.Collectors.joining; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; import java.util.Arrays; import java.util.List; import java.util.function.Supplier; import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.TestEngine; import org.junit.platform.launcher.LauncherDiscoveryListener; /** * Collection of {@code static} factory methods for creating * {@link LauncherDiscoveryListener LauncherDiscoveryListeners}. * * @since 1.6 */ @API(status = STABLE, since = "1.10") public class LauncherDiscoveryListeners { private LauncherDiscoveryListeners() { } /** * Create a {@link LauncherDiscoveryListener} that aborts test discovery on * failures. * *

The following events are considered failures: * *

    *
  • * any recoverable {@link Throwable} thrown by * {@link TestEngine#discover}. *
  • *
*/ public static LauncherDiscoveryListener abortOnFailure() { return new AbortOnFailureLauncherDiscoveryListener(); } /** * Create a {@link LauncherDiscoveryListener} that logs test discovery * events based on their severity. * *

For example, failures during test discovery are logged as errors. */ public static LauncherDiscoveryListener logging() { return new LoggingLauncherDiscoveryListener(); } @API(status = INTERNAL, since = "1.6") public static LauncherDiscoveryListener composite(List listeners) { Preconditions.notNull(listeners, "listeners must not be null"); Preconditions.containsNoNullElements(listeners, "listeners must not contain any null elements"); if (listeners.isEmpty()) { return LauncherDiscoveryListener.NOOP; } if (listeners.size() == 1) { return listeners.get(0); } return new CompositeLauncherDiscoveryListener(listeners); } @API(status = INTERNAL, since = "1.6") public static LauncherDiscoveryListener fromConfigurationParameter(String key, String value) { return Arrays.stream(LauncherDiscoveryListenerType.values()) // .filter(type -> type.parameterValue.equalsIgnoreCase(value)) // .findFirst() // .map(type -> type.creator.get()) // .orElseThrow(() -> newInvalidConfigurationParameterException(key, value)); } private static JUnitException newInvalidConfigurationParameterException(String key, String value) { String allowedValues = Arrays.stream(LauncherDiscoveryListenerType.values()) // .map(type -> type.parameterValue) // .collect(joining("', '", "'", "'")); return new JUnitException("Invalid value of configuration parameter '" + key + "': " // + value + " (allowed are " + allowedValues + ")"); } private enum LauncherDiscoveryListenerType { LOGGING("logging", LauncherDiscoveryListeners::logging), ABORT_ON_FAILURE("abortOnFailure", LauncherDiscoveryListeners::abortOnFailure); private final String parameterValue; private final Supplier creator; LauncherDiscoveryListenerType(String parameterValue, Supplier creator) { this.parameterValue = parameterValue; this.creator = creator; } } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/LoggingLauncherDiscoveryListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.listeners.discovery; import static org.junit.platform.launcher.EngineDiscoveryResult.Status.FAILED; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Supplier; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.SelectorResolutionResult; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.discovery.UniqueIdSelector; import org.junit.platform.launcher.EngineDiscoveryResult; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; /** * @since 1.6 * @see LauncherDiscoveryListeners#logging() */ final class LoggingLauncherDiscoveryListener implements LauncherDiscoveryListener { private static final Logger logger = LoggerFactory.getLogger(LoggingLauncherDiscoveryListener.class); @Override public void launcherDiscoveryStarted(LauncherDiscoveryRequest request) { logger.trace(() -> "Test discovery started"); } @Override public void launcherDiscoveryFinished(LauncherDiscoveryRequest request) { logger.trace(() -> "Test discovery finished"); } @Override public void engineDiscoveryStarted(UniqueId engineId) { logger.trace(() -> "Engine " + engineId + " has started discovering tests"); } @Override public void engineDiscoveryFinished(UniqueId engineId, EngineDiscoveryResult result) { if (result.getStatus() == FAILED) { Optional failure = result.getThrowable(); if (failure.isPresent()) { logger.error(failure.get().getCause(), () -> failure.get().getMessage()); } else { logger.error(() -> "Engine " + engineId + " failed to discover tests"); } } else { logger.trace(() -> "Engine " + engineId + " has finished discovering tests"); } } @Override public void selectorProcessed(UniqueId engineId, DiscoverySelector selector, SelectorResolutionResult result) { switch (result.getStatus()) { case RESOLVED -> logger.debug(() -> selector + " was resolved by " + engineId); case FAILED -> logger.error(result.getThrowable().orElse(null), () -> "Resolution of " + selector + " by " + engineId + " failed"); case UNRESOLVED -> { Consumer> loggingConsumer = logger::debug; if (selector instanceof UniqueIdSelector uniqueIdSelector) { UniqueId uniqueId = uniqueIdSelector.getUniqueId(); if (uniqueId.hasPrefix(engineId)) { loggingConsumer = logger::warn; } } loggingConsumer.accept(() -> selector + " could not be resolved by " + engineId); } } } @Override public void issueEncountered(UniqueId engineId, DiscoveryIssue issue) { logger.trace(() -> "Issue encountered during discovery by TestEngine with ID '" + engineId + "': " + issue); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } return getClass() == obj.getClass(); } @Override public int hashCode() { return 1; } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Common {@link org.junit.platform.launcher.LauncherDiscoveryListener} * implementations and factory methods. * * @see org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners * @since 1.6 */ @NullMarked package org.junit.platform.launcher.listeners.discovery; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Common {@link org.junit.platform.launcher.TestExecutionListener * TestExecutionListener} implementations and related support classes for * the {@link org.junit.platform.launcher.Launcher Launcher}. */ @NullMarked package org.junit.platform.launcher.listeners; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/CompositeLauncherSessionListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.listeners.session; import static org.junit.platform.commons.util.CollectionUtils.forEachInReverseOrder; import java.util.List; import org.junit.platform.launcher.LauncherSession; import org.junit.platform.launcher.LauncherSessionListener; /** * @since 1.8 * @see LauncherSessionListeners#composite(List) */ class CompositeLauncherSessionListener implements LauncherSessionListener { private final List listeners; CompositeLauncherSessionListener(List listeners) { this.listeners = List.copyOf(listeners); } @Override public void launcherSessionOpened(LauncherSession session) { listeners.forEach(delegate -> delegate.launcherSessionOpened(session)); } @Override public void launcherSessionClosed(LauncherSession session) { forEachInReverseOrder(listeners, delegate -> delegate.launcherSessionClosed(session)); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/LauncherSessionListeners.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.listeners.session; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.List; import org.apiguardian.api.API; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.launcher.LauncherSessionListener; /** * Collection of {@code static} factory methods for creating * {@link LauncherSessionListener LauncherSessionListeners}. * * @since 1.8 */ @API(status = INTERNAL, since = "1.8") public class LauncherSessionListeners { private LauncherSessionListeners() { } public static LauncherSessionListener composite(List listeners) { Preconditions.notNull(listeners, "listeners must not be null"); Preconditions.containsNoNullElements(listeners, "listeners must not contain any null elements"); if (listeners.isEmpty()) { return LauncherSessionListener.NOOP; } if (listeners.size() == 1) { return listeners.get(0); } return new CompositeLauncherSessionListener(listeners); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Common {@link org.junit.platform.launcher.LauncherSessionListener} * implementations and factory methods. * * @see org.junit.platform.launcher.listeners.session.LauncherSessionListeners * @since 1.8 */ @NullMarked package org.junit.platform.launcher.listeners.session; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Public API for configuring and launching test plans. * *

This API is typically used by IDEs and build tools. */ @NullMarked package org.junit.platform.launcher; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/DequeStack.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.tagexpression; import java.util.ArrayDeque; import java.util.Deque; import org.junit.platform.commons.util.Preconditions; /** * @since 1.1 */ class DequeStack implements Stack { private final Deque deque = new ArrayDeque<>(); @Override public void push(T t) { deque.addFirst(t); } @Override public T peek() { return Preconditions.notNull(deque.peek(), () -> "stack is empty"); } @Override public T pop() { return Preconditions.notNull(deque.pollFirst(), () -> "stack is empty"); } @Override public boolean isEmpty() { return deque.isEmpty(); } @Override public int size() { return deque.size(); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Operator.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.tagexpression; import static java.util.Objects.requireNonNull; import static org.junit.platform.launcher.tagexpression.Operator.Associativity.Left; import static org.junit.platform.launcher.tagexpression.ParseStatus.missingOperatorBetween; import static org.junit.platform.launcher.tagexpression.ParseStatus.missingRhsOperand; import static org.junit.platform.launcher.tagexpression.ParseStatus.problemParsing; import static org.junit.platform.launcher.tagexpression.ParseStatus.success; import java.util.function.BiFunction; import java.util.function.Function; import org.jspecify.annotations.Nullable; /** * @since 1.1 */ class Operator { enum Associativity { Left, Right } interface TagExpressionCreator { ParseStatus createExpressionAndAddTo(Stack> expressions, Token operatorToken); } static Operator nullaryOperator(String representation, int precedence) { return new Operator(representation, precedence, 0, null, (expressions, operatorToken) -> success()); } @SuppressWarnings("SameParameterValue") static Operator unaryOperator(String representation, int precedence, Associativity associativity, Function unaryExpression) { return new Operator(representation, precedence, 1, associativity, (expressions, operatorToken) -> { TokenWith rhs = expressions.pop(); if (operatorToken.isLeftOf(rhs.token())) { Token combinedToken = operatorToken.concatenate(rhs.token()); expressions.push(new TokenWith<>(combinedToken, unaryExpression.apply(rhs.element()))); return success(); } return missingRhsOperand(operatorToken, representation); }); } @SuppressWarnings("SameParameterValue") static Operator binaryOperator(String representation, int precedence, Associativity associativity, BiFunction binaryExpression) { return new Operator(representation, precedence, 2, associativity, (expressions, operatorToken) -> { TokenWith rhs = expressions.pop(); TokenWith lhs = expressions.pop(); Token lhsToken = lhs.token(); if (lhsToken.isLeftOf(operatorToken) && operatorToken.isLeftOf(rhs.token())) { Token combinedToken = lhsToken.concatenate(operatorToken).concatenate(rhs.token()); expressions.push(new TokenWith<>(combinedToken, binaryExpression.apply(lhs.element(), rhs.element()))); return success(); } if (rhs.token().isLeftOf(operatorToken)) { return missingRhsOperand(operatorToken, representation); } if (operatorToken.isLeftOf(lhsToken)) { return missingOperatorBetween(lhs, rhs); } return problemParsing(operatorToken, representation); }); } private final String representation; private final int precedence; private final int arity; private final @Nullable Associativity associativity; private final TagExpressionCreator tagExpressionCreator; private Operator(String representation, int precedence, int arity, @Nullable Associativity associativity, TagExpressionCreator tagExpressionCreator) { this.representation = representation; this.precedence = precedence; this.arity = arity; this.associativity = associativity; this.tagExpressionCreator = tagExpressionCreator; } boolean represents(String token) { return representation.equals(token); } String representation() { return representation; } boolean hasLowerPrecedenceThan(Operator operator) { return this.precedence < operator.precedence; } boolean hasSamePrecedenceAs(Operator operator) { return this.precedence == operator.precedence; } boolean isLeftAssociative() { return Left == associativity; } ParseStatus createAndAddExpressionTo(Stack> expressions, Token operatorToken) { if (expressions.size() < arity) { String message = createMissingOperandMessage(expressions, operatorToken); return ParseStatus.errorAt(operatorToken, representation, message); } return tagExpressionCreator.createExpressionAndAddTo(expressions, operatorToken); } private String createMissingOperandMessage(Stack> expressions, Token operatorToken) { if (1 == arity) { return missingOneOperand(associativity == Left ? "lhs" : "rhs"); } if (2 == arity) { int mismatch = arity - expressions.size(); if (2 == mismatch) { return "missing lhs and rhs operand"; } return missingOneOperand( operatorToken.isLeftOf(requireNonNull(expressions.peek()).token()) ? "lhs" : "rhs"); } return "missing operand"; } private String missingOneOperand(String side) { return "missing " + side + " operand"; } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Operators.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.tagexpression; import static java.util.function.Function.identity; import static java.util.stream.Collectors.toMap; import static org.junit.platform.launcher.tagexpression.Operator.Associativity.Left; import static org.junit.platform.launcher.tagexpression.Operator.Associativity.Right; import java.util.Map; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; /** * @since 1.1 */ class Operators { private static final Operator Not = Operator.unaryOperator("!", 3, Right, TagExpressions::not); private static final Operator And = Operator.binaryOperator("&", 2, Left, TagExpressions::and); private static final Operator Or = Operator.binaryOperator("|", 1, Left, TagExpressions::or); private final Map representationToOperator = Stream.of(Not, And, Or).collect( toMap(Operator::representation, identity())); @Nullable Operator operatorFor(String token) { return representationToOperator.get(token); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseResult.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.tagexpression; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.Optional; import java.util.function.Function; import org.apiguardian.api.API; /** * The result of attempting to parse a {@link TagExpression}. * *

An instance of this type either contains a successfully parsed * {@link TagExpression} or an error message describing the parse * error. * * @since 1.1 * @see TagExpression#parseFrom(String) */ @API(status = INTERNAL, since = "1.1") public interface ParseResult { /** * Return the parsed {@link TagExpression} or throw an exception with the * contained parse error. * * @param exceptionCreator will be called with the error message in case * this parse result contains a parse error; never {@code null}. */ default TagExpression tagExpressionOrThrow(Function exceptionCreator) { if (errorMessage().isPresent()) { throw exceptionCreator.apply(errorMessage().get()); } return tagExpression().orElseThrow(); } /** * Return the contained parse error message, if any. */ default Optional errorMessage() { return Optional.empty(); } /** * Return the contained {@link TagExpression}, if any. */ default Optional tagExpression() { return Optional.empty(); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseResults.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.tagexpression; import java.util.Optional; /** * @since 1.1 */ class ParseResults { private ParseResults() { /* no-op */ } static ParseResult success(TagExpression tagExpression) { return new ParseResult() { @Override public Optional tagExpression() { return Optional.of(tagExpression); } }; } static ParseResult error(String errorMessage) { return new ParseResult() { @Override public Optional errorMessage() { return Optional.of(errorMessage); } }; } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseStatus.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.tagexpression; import java.util.function.Supplier; import org.jspecify.annotations.Nullable; /** * @since 1.1 */ class ParseStatus { static ParseStatus success() { return error(null); } static ParseStatus problemParsing(Token token, String representation) { return errorAt(token, representation, "problem parsing"); } static ParseStatus missingOpeningParenthesis(Token token, String representation) { return errorAt(token, representation, "missing opening parenthesis"); } static ParseStatus missingClosingParenthesis(Token token, String representation) { return errorAt(token, representation, "missing closing parenthesis"); } static ParseStatus missingRhsOperand(Token token, String representation) { return errorAt(token, representation, "missing rhs operand"); } static ParseStatus errorAt(Token token, String operatorRepresentation, String message) { return error( message + " for '" + operatorRepresentation + "' at index " + format(token.strippedTokenStartIndex())); } static ParseStatus missingOperatorBetween(TokenWith lhs, TokenWith rhs) { String lhsString = "'" + lhs.element() + "' at index " + format(lhs.token().lastCharacterIndex()); String rhsString = "'" + rhs.element() + "' at index " + format(rhs.token().strippedTokenStartIndex()); return error("missing operator between " + lhsString + " and " + rhsString); } static ParseStatus emptyTagExpression() { return error("empty tag expression"); } private static String format(int indexInTagExpression) { return "<" + indexInTagExpression + ">"; } private static ParseStatus error(@Nullable String errorMessage) { return new ParseStatus(errorMessage); } final @Nullable String errorMessage; private ParseStatus(@Nullable String errorMessage) { this.errorMessage = errorMessage; } public ParseStatus process(Supplier step) { if (isSuccess()) { return step.get(); } return this; } public boolean isError() { return !isSuccess(); } private boolean isSuccess() { return errorMessage == null; } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Parser.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.tagexpression; import java.util.List; import org.jspecify.annotations.Nullable; /** * @since 1.1 */ class Parser { private final Tokenizer tokenizer = new Tokenizer(); ParseResult parse(@Nullable String infixTagExpression) { return constructExpressionFrom(tokensDerivedFrom(infixTagExpression)); } private List tokensDerivedFrom(@Nullable String infixTagExpression) { return tokenizer.tokenize(infixTagExpression); } private ParseResult constructExpressionFrom(List tokens) { return new ShuntingYard(tokens).execute(); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ShuntingYard.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.tagexpression; import static java.lang.Integer.MIN_VALUE; import static java.util.Objects.requireNonNull; import static org.junit.platform.launcher.tagexpression.Operator.nullaryOperator; import static org.junit.platform.launcher.tagexpression.ParseStatus.emptyTagExpression; import static org.junit.platform.launcher.tagexpression.ParseStatus.missingClosingParenthesis; import static org.junit.platform.launcher.tagexpression.ParseStatus.missingOpeningParenthesis; import static org.junit.platform.launcher.tagexpression.ParseStatus.success; import static org.junit.platform.launcher.tagexpression.TagExpressions.any; import static org.junit.platform.launcher.tagexpression.TagExpressions.none; import static org.junit.platform.launcher.tagexpression.TagExpressions.tag; import java.util.List; /** * This is based on a modified version of the * * Shunting-yard algorithm. * * @since 1.1 */ class ShuntingYard { private static final Operator RightParenthesis = nullaryOperator(")", -1); private static final Operator LeftParenthesis = nullaryOperator("(", -2); private static final Operator Sentinel = nullaryOperator("sentinel", MIN_VALUE); private static final Token SentinelToken = new Token(-1, ""); private final Operators validOperators = new Operators(); private final Stack> expressions = new DequeStack<>(); private final Stack> operators = new DequeStack<>(); private final List tokens; ShuntingYard(List tokens) { this.tokens = tokens; pushOperatorAt(SentinelToken, Sentinel); } public ParseResult execute() { // @formatter:off ParseStatus parseStatus = processTokens() .process(this::consumeRemainingOperators) .process(this::ensureOnlySingleExpressionRemains); // @formatter:on if (parseStatus.isError()) { return ParseResults.error(requireNonNull(parseStatus.errorMessage)); } return ParseResults.success(expressions.pop().element()); } private ParseStatus processTokens() { ParseStatus parseStatus = success(); for (Token token : tokens) { parseStatus = parseStatus.process(() -> process(token)); } return parseStatus; } private ParseStatus process(Token token) { String trimmed = token.string(); if (LeftParenthesis.represents(trimmed)) { pushOperatorAt(token, LeftParenthesis); return success(); } if (RightParenthesis.represents(trimmed)) { return findMatchingLeftParenthesis(token); } var operator = validOperators.operatorFor(trimmed); if (operator != null) { return findOperands(token, operator); } pushExpressionAt(token, convertLeafTokenToExpression(trimmed)); return success(); } private TagExpression convertLeafTokenToExpression(String trimmed) { if ("any()".equalsIgnoreCase(trimmed)) { return any(); } if ("none()".equalsIgnoreCase(trimmed)) { return none(); } return tag(trimmed); } private ParseStatus findMatchingLeftParenthesis(Token token) { while (!operators.isEmpty()) { TokenWith tokenWithWithOperator = operators.pop(); Operator operator = tokenWithWithOperator.element(); if (LeftParenthesis.equals(operator)) { return success(); } ParseStatus parseStatus = operator.createAndAddExpressionTo(expressions, tokenWithWithOperator.token()); if (parseStatus.isError()) { return parseStatus; } } return missingOpeningParenthesis(token, RightParenthesis.representation()); } private ParseStatus findOperands(Token token, Operator currentOperator) { while (currentOperator.hasLowerPrecedenceThan(previousOperator()) || (currentOperator.hasSamePrecedenceAs(previousOperator()) && currentOperator.isLeftAssociative())) { TokenWith tokenWithWithOperator = operators.pop(); ParseStatus parseStatus = tokenWithWithOperator.element().createAndAddExpressionTo(expressions, tokenWithWithOperator.token()); if (parseStatus.isError()) { return parseStatus; } } pushOperatorAt(token, currentOperator); return success(); } private Operator previousOperator() { return operators.peek().element(); } private void pushExpressionAt(Token token, TagExpression tagExpression) { expressions.push(new TokenWith<>(token, tagExpression)); } private void pushOperatorAt(Token token, Operator operator) { operators.push(new TokenWith<>(token, operator)); } private ParseStatus consumeRemainingOperators() { while (!operators.isEmpty()) { TokenWith tokenWithWithOperator = operators.pop(); Operator operator = tokenWithWithOperator.element(); if (LeftParenthesis.equals(operator)) { return missingClosingParenthesis(tokenWithWithOperator.token(), operator.representation()); } ParseStatus parseStatus = operator.createAndAddExpressionTo(expressions, tokenWithWithOperator.token()); if (parseStatus.isError()) { return parseStatus; } } return success(); } private ParseStatus ensureOnlySingleExpressionRemains() { if (expressions.size() == 1) { return success(); } if (expressions.isEmpty()) { return emptyTagExpression(); } TokenWith rhs = expressions.pop(); TokenWith lhs = expressions.pop(); return ParseStatus.missingOperatorBetween(lhs, rhs); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Stack.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.tagexpression; /** * @since 1.1 */ interface Stack { void push(T t); T peek(); T pop(); boolean isEmpty(); int size(); } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TagExpression.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.tagexpression; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.Collection; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.engine.TestTag; /** * A tag expression can be evaluated against a collection of * {@linkplain TestTag tags} to determine if they match the expression. * * @since 1.1 */ @API(status = INTERNAL, since = "1.1") public interface TagExpression { /** * Attempt to parse a {@link TagExpression} from the supplied tag * expression string. * * @param infixTagExpression the tag expression string to parse; may be * {@code null}. * @see ParseResult */ @API(status = INTERNAL, since = "1.1") static ParseResult parseFrom(@Nullable String infixTagExpression) { return new Parser().parse(infixTagExpression); } /** * Evaluate this tag expression against the supplied collection of * {@linkplain TestTag tags}. * * @param tags the tags this tag expression is to be evaluated against * @return {@code true}, if the tags match this tag expression; {@code false}, otherwise */ boolean evaluate(Collection tags); } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TagExpressions.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.tagexpression; import java.util.Collection; import org.junit.platform.engine.TestTag; /** * @since 1.1 */ class TagExpressions { static TagExpression tag(String tag) { TestTag testTag = TestTag.create(tag); return new TagExpression() { @Override public boolean evaluate(Collection tags) { return tags.contains(testTag); } @Override public String toString() { return testTag.getName(); } }; } static TagExpression none() { return new TagExpression() { @Override public boolean evaluate(Collection tags) { return tags.isEmpty(); } @Override public String toString() { return "none()"; } }; } static TagExpression any() { return new TagExpression() { @Override public boolean evaluate(Collection tags) { return !tags.isEmpty(); } @Override public String toString() { return "any()"; } }; } static TagExpression not(TagExpression toNegate) { return new TagExpression() { @Override public boolean evaluate(Collection tags) { return !toNegate.evaluate(tags); } @Override public String toString() { return "!" + toNegate; } }; } static TagExpression and(TagExpression lhs, TagExpression rhs) { return new TagExpression() { @Override public boolean evaluate(Collection tags) { return lhs.evaluate(tags) && rhs.evaluate(tags); } @Override public String toString() { return "(" + lhs + " & " + rhs + ")"; } }; } static TagExpression or(TagExpression lhs, TagExpression rhs) { return new TagExpression() { @Override public boolean evaluate(Collection tags) { return lhs.evaluate(tags) || rhs.evaluate(tags); } @Override public String toString() { return "(" + lhs + " | " + rhs + ")"; } }; } private TagExpressions() { } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Token.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.tagexpression; /** * @since 1.1 */ record Token(int startIndex, String rawString) { String string() { return rawString.strip(); } public int strippedTokenStartIndex() { return startIndex + rawString.indexOf(string()); } public boolean isLeftOf(Token token) { return lastCharacterIndex() < token.startIndex; } public int lastCharacterIndex() { return endIndexExclusive() - 1; } public int endIndexExclusive() { return startIndex + rawString.length(); } public Token concatenate(Token rightOfThis) { String concatenatedRawString = this.rawString + rightOfThis.rawString; return new Token(startIndex, concatenatedRawString); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TokenWith.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.tagexpression; /** * @since 1.1 */ record TokenWith(Token token, T element) { } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Tokenizer.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.tagexpression; import static java.util.regex.Pattern.CASE_INSENSITIVE; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.jspecify.annotations.Nullable; /** * @since 1.1 */ class Tokenizer { private static final Pattern PATTERN = Pattern.compile("\\s*(?:(?:any|none)\\(\\)|[()!|&]|[^\\s()!|&]+)", CASE_INSENSITIVE); List tokenize(@Nullable String infixTagExpression) { if (infixTagExpression == null) { return List.of(); } List parts = new ArrayList<>(); Matcher matcher = PATTERN.matcher(infixTagExpression); while (matcher.find()) { parts.add(new Token(matcher.start(), matcher.group())); } return List.copyOf(parts); } } ================================================ FILE: junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * The tag expression language parser and related support classes. */ @NullMarked package org.junit.platform.launcher.tagexpression; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-launcher/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener ================================================ org.junit.platform.launcher.listeners.UniqueIdTrackingListener ================================================ FILE: junit-platform-launcher/src/test/README.md ================================================ For compatibility with the Eclipse IDE, the test for this module are in the `platform-tests` project. ================================================ FILE: junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/ConfigurationParametersFactoryForTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static java.util.stream.Collectors.toMap; import static org.junit.platform.commons.util.StringUtils.nullSafeToString; import java.util.Map; import org.junit.platform.engine.ConfigurationParameters; public class ConfigurationParametersFactoryForTests { private ConfigurationParametersFactoryForTests() { } public static ConfigurationParameters create(Map configParams) { return LauncherConfigurationParameters.builder() // .explicitParameters(withStringValues(configParams)) // .enableImplicitProviders(false) // .build(); } private static Map withStringValues(Map configParams) { return configParams.entrySet().stream().collect(toMap(Map.Entry::getKey, e -> nullSafeToString(e.getValue()))); } } ================================================ FILE: junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/LauncherFactoryForTestingPurposesOnly.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import org.junit.platform.engine.TestEngine; import org.junit.platform.launcher.Launcher; /** * @since 1.0 */ public class LauncherFactoryForTestingPurposesOnly { public static Launcher createLauncher(TestEngine... engines) { return LauncherFactory.create(createLauncherConfigBuilderWithDisabledServiceLoading() // .addTestEngines(engines) // .build()); } public static LauncherConfig.Builder createLauncherConfigBuilderWithDisabledServiceLoading() { return LauncherConfig.builder() // .enableTestEngineAutoRegistration(false) // .enableLauncherDiscoveryListenerAutoRegistration(false) // .enableTestExecutionListenerAutoRegistration(false) // .enablePostDiscoveryFilterAutoRegistration(false) // .enableLauncherSessionListenerAutoRegistration(false); } private LauncherFactoryForTestingPurposesOnly() { } } ================================================ FILE: junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/NamespacedHierarchicalStoreProviders.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static org.junit.platform.engine.support.store.NamespacedHierarchicalStore.CloseAction.closeAutoCloseables; import org.junit.platform.engine.support.store.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; public class NamespacedHierarchicalStoreProviders { public static NamespacedHierarchicalStore dummyNamespacedHierarchicalStore() { return new NamespacedHierarchicalStore<>(dummyNamespacedHierarchicalStoreWithNoParent(), closeAutoCloseables()); } public static NamespacedHierarchicalStore dummyNamespacedHierarchicalStoreWithNoParent() { return new NamespacedHierarchicalStore<>(null, closeAutoCloseables()); } private NamespacedHierarchicalStoreProviders() { } } ================================================ FILE: junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/OutputDirectoryCreators.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import java.nio.file.Path; import org.junit.platform.commons.JUnitException; import org.junit.platform.engine.OutputDirectoryCreator; public class OutputDirectoryCreators { public static OutputDirectoryCreator dummyOutputDirectoryCreator() { return new HierarchicalOutputDirectoryCreator(() -> { throw new JUnitException("This should not be called; use a real implementation instead"); }); } public static OutputDirectoryCreator hierarchicalOutputDirectoryCreator(Path rootDir) { return new HierarchicalOutputDirectoryCreator(() -> rootDir); } private OutputDirectoryCreators() { } } ================================================ FILE: junit-platform-reporting/LICENSE-open-test-reporting.md ================================================ Apache License ============== _Version 2.0, January 2004_ _<>_ ### Terms and Conditions for use, reproduction, and distribution #### 1. Definitions “License” shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. “Licensor” shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. “Legal Entity” shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, “control” means **(i)** the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the outstanding shares, or **(iii)** beneficial ownership of such entity. “You” (or “Your”) shall mean an individual or Legal Entity exercising permissions granted by this License. “Source” form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. “Object” form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. “Work” shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). “Derivative Works” shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. “Contribution” shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, “submitted” means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as “Not a Contribution.” “Contributor” shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. #### 2. Grant of Copyright License Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. #### 3. Grant of Patent License Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. #### 4. Redistribution You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: * **(a)** You must give any other recipients of the Work or Derivative Works a copy of this License; and * **(b)** You must cause any modified files to carry prominent notices stating that You changed the files; and * **(c)** You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and * **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. #### 5. Submission of Contributions Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. #### 6. Trademarks This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. #### 7. Disclaimer of Warranty Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. #### 8. Limitation of Liability In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. #### 9. Accepting Warranty or Additional Liability While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. _END OF TERMS AND CONDITIONS_ ### APPENDIX: How to apply the Apache License to your work To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets `[]` replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same “printed page” as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: junit-platform-reporting/junit-platform-reporting.gradle.kts ================================================ import junitbuild.extensions.javaModuleName plugins { id("junitbuild.java-library-conventions") id("junitbuild.shadow-conventions") `java-test-fixtures` } description = "JUnit Platform Reporting" dependencies { api(platform(projects.junitBom)) api(projects.junitPlatformLauncher) implementation(libs.openTestReporting.tooling.spi) compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) shadowed(libs.openTestReporting.events) osgiVerification(projects.junitJupiterEngine) osgiVerification(projects.junitPlatformLauncher) osgiVerification(libs.openTestReporting.tooling.spi) testFixturesApi(projects.junitJupiterApi) } tasks { shadowJar { listOf("events", "schema").forEach { name -> val packageName = "org.opentest4j.reporting.${name}" relocate(packageName, "org.junit.platform.reporting.shadow.${packageName}") } from(projectDir) { include("LICENSE-open-test-reporting.md") into("META-INF") } } compileJava { options.compilerArgs.addAll(listOf( "--add-modules", "org.opentest4j.reporting.events", "--add-reads", "${javaModuleName}=org.opentest4j.reporting.events" )) } javadoc { (options as StandardJavadocDocletOptions).apply { addStringOption("-add-modules", "org.opentest4j.reporting.events") addStringOption("-add-reads", "${javaModuleName}=org.opentest4j.reporting.events") } } } ================================================ FILE: junit-platform-reporting/src/main/java/module-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Defines the JUnit Platform Reporting API. * * @since 1.4 */ module org.junit.platform.reporting { requires static transitive org.apiguardian.api; requires static transitive org.jspecify; requires java.xml; requires org.junit.platform.commons; requires transitive org.junit.platform.engine; requires transitive org.junit.platform.launcher; requires org.opentest4j.reporting.tooling.spi; // exports org.junit.platform.reporting; empty package exports org.junit.platform.reporting.legacy; exports org.junit.platform.reporting.legacy.xml; exports org.junit.platform.reporting.open.xml; provides org.junit.platform.launcher.TestExecutionListener with org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener; provides org.opentest4j.reporting.tooling.spi.htmlreport.Contributor with org.junit.platform.reporting.open.xml.JUnitContributor; } ================================================ FILE: junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/LegacyReportingUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.reporting.legacy; import static org.apiguardian.api.API.Status.MAINTAINED; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; /** * Utility methods for dealing with legacy reporting infrastructure, such as * reporting systems built on the Ant-based XML reporting format for JUnit 4. * *

This class was formerly from {@code junit-platform-launcher} in the * {@link org.junit.platform.launcher.listeners} package. * * @since 1.0.3 */ @API(status = MAINTAINED, since = "1.6") public final class LegacyReportingUtils { private LegacyReportingUtils() { /* no-op */ } /** * Get the class name for the supplied {@link TestIdentifier} using the * supplied {@link TestPlan}. * *

This implementation attempts to find the closest test identifier with * a {@link ClassSource} by traversing the hierarchy upwards towards the * root starting with the supplied test identifier. In case no such source * is found, it falls back to using the parent's * {@linkplain TestIdentifier#getLegacyReportingName legacy reporting name}. * * @param testPlan the test plan that contains the {@code TestIdentifier}; * never {@code null} * @param testIdentifier the identifier to determine the class name for; * never {@code null} * @see TestIdentifier#getLegacyReportingName */ public static String getClassName(TestPlan testPlan, TestIdentifier testIdentifier) { Preconditions.notNull(testPlan, "testPlan must not be null"); Preconditions.notNull(testIdentifier, "testIdentifier must not be null"); for (TestIdentifier current = testIdentifier; current != null; current = getParent(testPlan, current)) { ClassSource source = getClassSource(current); if (source != null) { return source.getClassName(); } } return getParentLegacyReportingName(testPlan, testIdentifier); } private static @Nullable TestIdentifier getParent(TestPlan testPlan, TestIdentifier testIdentifier) { return testPlan.getParent(testIdentifier).orElse(null); } private static @Nullable ClassSource getClassSource(TestIdentifier current) { // @formatter:off return current.getSource() .filter(ClassSource.class::isInstance) .map(ClassSource.class::cast) .orElse(null); // @formatter:on } private static String getParentLegacyReportingName(TestPlan testPlan, TestIdentifier testIdentifier) { // @formatter:off return testPlan.getParent(testIdentifier) .map(TestIdentifier::getLegacyReportingName) .orElse(""); // @formatter:on } } ================================================ FILE: junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Support for legacy reporting formats. */ @NullMarked package org.junit.platform.reporting.legacy; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.reporting.legacy.xml; import static java.util.Objects.requireNonNull; import static org.apiguardian.api.API.Status.STABLE; import java.io.IOException; import java.io.PrintWriter; import java.io.Writer; import java.nio.file.Files; import java.nio.file.Path; import java.time.Clock; import java.time.ZoneId; import javax.xml.stream.XMLStreamException; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; /** * {@code LegacyXmlReportGeneratingListener} is a {@link TestExecutionListener} that * generates a separate XML report for each {@linkplain TestPlan#getRoots() root} * in the {@link TestPlan}. * *

Note that the generated XML format is compatible with the legacy * de facto standard for JUnit 4 based test reports that was made popular by the * Ant build system. * * @since 1.4 * @see org.junit.platform.launcher.listeners.LoggingListener * @see org.junit.platform.launcher.listeners.SummaryGeneratingListener */ @API(status = STABLE, since = "1.7") public class LegacyXmlReportGeneratingListener implements TestExecutionListener { private final Path reportsDir; private final PrintWriter out; private final Clock clock; private @Nullable XmlReportData reportData; public LegacyXmlReportGeneratingListener(Path reportsDir, PrintWriter out) { this(reportsDir, out, Clock.system(ZoneId.systemDefault())); } // For tests only LegacyXmlReportGeneratingListener(String reportsDir, PrintWriter out, Clock clock) { this(Path.of(reportsDir), out, clock); } private LegacyXmlReportGeneratingListener(Path reportsDir, PrintWriter out, Clock clock) { this.reportsDir = reportsDir; this.out = out; this.clock = clock; } @Override public void testPlanExecutionStarted(TestPlan testPlan) { this.reportData = new XmlReportData(testPlan, clock); try { Files.createDirectories(this.reportsDir); } catch (IOException e) { printException("Could not create reports directory: " + this.reportsDir, e); } } @Override public void testPlanExecutionFinished(TestPlan testPlan) { this.reportData = null; } @Override public void executionSkipped(TestIdentifier testIdentifier, String reason) { requiredReportData().markSkipped(testIdentifier, reason); writeXmlReportInCaseOfRoot(testIdentifier); } @Override public void executionStarted(TestIdentifier testIdentifier) { requiredReportData().markStarted(testIdentifier); } @Override public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry entry) { requiredReportData().addReportEntry(testIdentifier, entry); } @Override public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult result) { requiredReportData().markFinished(testIdentifier, result); writeXmlReportInCaseOfRoot(testIdentifier); } private void writeXmlReportInCaseOfRoot(TestIdentifier testIdentifier) { if (isRoot(testIdentifier)) { String rootName = testIdentifier.getUniqueIdObject().getSegments().get(0).getValue(); writeXmlReportSafely(testIdentifier, rootName); } } private void writeXmlReportSafely(TestIdentifier testIdentifier, String rootName) { Path xmlFile = this.reportsDir.resolve("TEST-" + rootName + ".xml"); try (Writer fileWriter = Files.newBufferedWriter(xmlFile)) { new XmlReportWriter(requiredReportData()).writeXmlReport(testIdentifier, fileWriter); } catch (XMLStreamException | IOException e) { printException("Could not write XML report: " + xmlFile, e); } } private XmlReportData requiredReportData() { return requireNonNull(this.reportData); } private boolean isRoot(TestIdentifier testIdentifier) { return testIdentifier.getParentIdObject().isEmpty(); } private void printException(String message, Exception exception) { out.println(message); exception.printStackTrace(out); } } ================================================ FILE: junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportData.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.reporting.legacy.xml; import static java.util.Collections.emptyList; import static org.junit.platform.engine.TestExecutionResult.Status.ABORTED; import java.time.Clock; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; /** * @since 1.4 */ class XmlReportData { private static final int MILLIS_PER_SECOND = 1000; private final Map finishedTests = new ConcurrentHashMap<>(); private final Map skippedTests = new ConcurrentHashMap<>(); private final Map startInstants = new ConcurrentHashMap<>(); private final Map endInstants = new ConcurrentHashMap<>(); private final Map> reportEntries = new ConcurrentHashMap<>(); private final TestPlan testPlan; private final Clock clock; XmlReportData(TestPlan testPlan, Clock clock) { this.testPlan = testPlan; this.clock = clock; } TestPlan getTestPlan() { return this.testPlan; } Clock getClock() { return this.clock; } void markSkipped(TestIdentifier testIdentifier, @Nullable String reason) { this.skippedTests.put(testIdentifier, reason == null ? "" : reason); } void markStarted(TestIdentifier testIdentifier) { this.startInstants.put(testIdentifier, this.clock.instant()); } void markFinished(TestIdentifier testIdentifier, TestExecutionResult result) { this.endInstants.put(testIdentifier, this.clock.instant()); if (result.getStatus() == ABORTED) { String reason = result.getThrowable().map(ExceptionUtils::readStackTrace).orElse(""); this.skippedTests.put(testIdentifier, reason); } else { this.finishedTests.put(testIdentifier, result); } } void addReportEntry(TestIdentifier testIdentifier, ReportEntry entry) { List entries = this.reportEntries.computeIfAbsent(testIdentifier, key -> new ArrayList<>()); entries.add(entry); } boolean wasSkipped(TestIdentifier testIdentifier) { return findSkippedAncestor(testIdentifier).isPresent(); } double getDurationInSeconds(TestIdentifier testIdentifier) { Instant startInstant = this.startInstants.getOrDefault(testIdentifier, Instant.EPOCH); Instant endInstant = this.endInstants.getOrDefault(testIdentifier, startInstant); return Duration.between(startInstant, endInstant).toMillis() / (double) MILLIS_PER_SECOND; } @Nullable String getSkipReason(TestIdentifier testIdentifier) { return findSkippedAncestor(testIdentifier).map(skippedTestIdentifier -> { String reason = this.skippedTests.get(skippedTestIdentifier); if (!testIdentifier.equals(skippedTestIdentifier)) { reason = "parent was skipped: " + reason; } return reason; }).orElse(null); } List getResults(TestIdentifier testIdentifier) { return getAncestors(testIdentifier).stream() // .map(this.finishedTests::get) // .filter(Objects::nonNull) // .toList(); } List getReportEntries(TestIdentifier testIdentifier) { return this.reportEntries.getOrDefault(testIdentifier, emptyList()); } private Optional findSkippedAncestor(TestIdentifier testIdentifier) { return findAncestor(testIdentifier, this.skippedTests::containsKey); } private Optional findAncestor(TestIdentifier testIdentifier, Predicate predicate) { Optional current = Optional.of(testIdentifier); while (current.isPresent()) { if (predicate.test(current.get())) { return current; } current = this.testPlan.getParent(current.get()); } return Optional.empty(); } private List getAncestors(TestIdentifier testIdentifier) { TestIdentifier current = testIdentifier; List ancestors = new ArrayList<>(); while (current != null) { ancestors.add(current); current = this.testPlan.getParent(current).orElse(null); } return ancestors; } } ================================================ FILE: junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportWriter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.reporting.legacy.xml; import static java.text.MessageFormat.format; import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME; import static java.util.Collections.emptyList; import static java.util.Comparator.naturalOrder; import static java.util.function.Function.identity; import static java.util.stream.Collectors.counting; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.mapping; import static java.util.stream.Collectors.toCollection; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toMap; import static org.junit.platform.commons.util.ExceptionUtils.readStackTrace; import static org.junit.platform.commons.util.StringUtils.isNotBlank; import static org.junit.platform.engine.TestExecutionResult.Status.FAILED; import static org.junit.platform.launcher.LauncherConstants.STDERR_REPORT_ENTRY_KEY; import static org.junit.platform.launcher.LauncherConstants.STDOUT_REPORT_ENTRY_KEY; import static org.junit.platform.reporting.legacy.xml.XmlReportWriter.AggregatedTestResult.Type.ERROR; import static org.junit.platform.reporting.legacy.xml.XmlReportWriter.AggregatedTestResult.Type.FAILURE; import static org.junit.platform.reporting.legacy.xml.XmlReportWriter.AggregatedTestResult.Type.SKIPPED; import static org.junit.platform.reporting.legacy.xml.XmlReportWriter.AggregatedTestResult.Type.SUCCESS; import java.io.IOException; import java.io.Writer; import java.net.InetAddress; import java.net.UnknownHostException; import java.text.NumberFormat; import java.time.LocalDateTime; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.EnumSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Optional; import java.util.Properties; import java.util.TreeSet; import java.util.regex.Pattern; import java.util.stream.Stream; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; import org.jspecify.annotations.Nullable; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; import org.junit.platform.reporting.legacy.LegacyReportingUtils; import org.junit.platform.reporting.legacy.xml.XmlReportWriter.AggregatedTestResult.Type; /** * {@code XmlReportWriter} writes an XML report whose format is compatible * with the de facto standard for JUnit 4 based test reports that was made * popular by the Ant build system. * * @since 1.4 */ class XmlReportWriter { static final char ILLEGAL_CHARACTER_REPLACEMENT = '\uFFFD'; private static final Map REPLACEMENTS_IN_ATTRIBUTE_VALUES = Map.of( // '\n', " ", // '\r', " ", // '\t', " " // ); // Using zero-width assertions in the split pattern simplifies the splitting process: All split parts // (including the first and last one) can be used directly, without having to re-add separator characters. private static final Pattern CDATA_SPLIT_PATTERN = Pattern.compile("(?<=]])(?=>)"); private final XmlReportData reportData; XmlReportWriter(XmlReportData reportData) { this.reportData = reportData; } void writeXmlReport(TestIdentifier rootDescriptor, Writer out) throws XMLStreamException { TestPlan testPlan = this.reportData.getTestPlan(); Map tests = testPlan.getDescendants(rootDescriptor) // .stream() // .filter(testIdentifier -> shouldInclude(testPlan, testIdentifier)) // .collect(toMap(identity(), this::toAggregatedResult)); // writeXmlReport(rootDescriptor, tests, out); } private AggregatedTestResult toAggregatedResult(TestIdentifier testIdentifier) { if (this.reportData.wasSkipped(testIdentifier)) { return AggregatedTestResult.skipped(); } return AggregatedTestResult.nonSkipped(this.reportData.getResults(testIdentifier)); } private boolean shouldInclude(TestPlan testPlan, TestIdentifier testIdentifier) { return testIdentifier.isTest() || testPlan.getChildren(testIdentifier).isEmpty(); } private void writeXmlReport(TestIdentifier testIdentifier, Map tests, Writer out) throws XMLStreamException { try (var report = new XmlReport(out)) { report.write(testIdentifier, tests); } } private class XmlReport implements AutoCloseable { private final XMLStreamWriter xml; private final ReplacingWriter out; XmlReport(Writer out) throws XMLStreamException { this.out = new ReplacingWriter(out); XMLOutputFactory factory = XMLOutputFactory.newInstance(); this.xml = factory.createXMLStreamWriter(this.out); } void write(TestIdentifier testIdentifier, Map tests) throws XMLStreamException { xml.writeStartDocument("UTF-8", "1.0"); newLine(); writeTestsuite(testIdentifier, tests); xml.writeEndDocument(); } private void writeTestsuite(TestIdentifier testIdentifier, Map tests) throws XMLStreamException { // NumberFormat is not thread-safe. Thus, we instantiate it here and pass it to // writeTestcase instead of using a constant NumberFormat numberFormat = NumberFormat.getInstance(Locale.US); xml.writeStartElement("testsuite"); writeSuiteAttributes(testIdentifier, tests.values(), numberFormat); newLine(); writeSystemProperties(); for (Entry entry : tests.entrySet()) { writeTestcase(entry.getKey(), entry.getValue(), numberFormat); } writeOutputElement("system-out", formatNonStandardAttributesAsString(testIdentifier)); xml.writeEndElement(); newLine(); } private void writeSuiteAttributes(TestIdentifier testIdentifier, Collection testResults, NumberFormat numberFormat) throws XMLStreamException { writeAttributeSafely("name", testIdentifier.getDisplayName()); writeTestCounts(testResults); writeAttributeSafely("time", getTime(testIdentifier, numberFormat)); writeAttributeSafely("hostname", getHostname().orElse("")); writeAttributeSafely("timestamp", ISO_LOCAL_DATE_TIME.format(getCurrentDateTime())); } private void writeTestCounts(Collection testResults) throws XMLStreamException { Map counts = testResults.stream().map(it -> it.type).collect( groupingBy(identity(), counting())); long total = counts.values().stream().mapToLong(Long::longValue).sum(); writeAttributeSafely("tests", String.valueOf(total)); writeAttributeSafely("skipped", counts.getOrDefault(SKIPPED, 0L).toString()); writeAttributeSafely("failures", counts.getOrDefault(FAILURE, 0L).toString()); writeAttributeSafely("errors", counts.getOrDefault(ERROR, 0L).toString()); } private void writeSystemProperties() throws XMLStreamException { xml.writeStartElement("properties"); newLine(); Properties systemProperties = System.getProperties(); for (String propertyName : new TreeSet<>(systemProperties.stringPropertyNames())) { xml.writeEmptyElement("property"); writeAttributeSafely("name", propertyName); writeAttributeSafely("value", systemProperties.getProperty(propertyName)); newLine(); } xml.writeEndElement(); newLine(); } private void writeTestcase(TestIdentifier testIdentifier, AggregatedTestResult testResult, NumberFormat numberFormat) throws XMLStreamException { xml.writeStartElement("testcase"); writeAttributeSafely("name", getName(testIdentifier)); writeAttributeSafely("classname", getClassName(testIdentifier)); writeAttributeSafely("time", getTime(testIdentifier, numberFormat)); newLine(); writeSkippedOrErrorOrFailureElement(testIdentifier, testResult); List systemOutElements = new ArrayList<>(); List systemErrElements = new ArrayList<>(); systemOutElements.add(formatNonStandardAttributesAsString(testIdentifier)); collectReportEntries(testIdentifier, systemOutElements, systemErrElements); writeOutputElements("system-out", systemOutElements); writeOutputElements("system-err", systemErrElements); xml.writeEndElement(); newLine(); } private String getName(TestIdentifier testIdentifier) { return testIdentifier.getLegacyReportingName(); } private String getClassName(TestIdentifier testIdentifier) { return LegacyReportingUtils.getClassName(reportData.getTestPlan(), testIdentifier); } private void writeSkippedOrErrorOrFailureElement(TestIdentifier testIdentifier, AggregatedTestResult testResult) throws XMLStreamException { if (testResult.type == SKIPPED) { writeSkippedElement(reportData.getSkipReason(testIdentifier), xml); } else { Map>> throwablesByType = testResult.getThrowablesByType(); for (Type type : EnumSet.of(FAILURE, ERROR)) { for (Optional throwable : throwablesByType.getOrDefault(type, emptyList())) { writeErrorOrFailureElement(type, throwable.orElse(null), xml); } } } } private void writeSkippedElement(@Nullable String reason, XMLStreamWriter writer) throws XMLStreamException { if (isNotBlank(reason)) { writer.writeStartElement("skipped"); writeCDataSafely(reason); writer.writeEndElement(); } else { writer.writeEmptyElement("skipped"); } newLine(); } private void writeErrorOrFailureElement(Type type, @Nullable Throwable throwable, XMLStreamWriter writer) throws XMLStreamException { String elementName = type == FAILURE ? "failure" : "error"; if (throwable != null) { writer.writeStartElement(elementName); writeFailureAttributesAndContent(throwable); writer.writeEndElement(); } else { writer.writeEmptyElement(elementName); } newLine(); } private void writeFailureAttributesAndContent(Throwable throwable) throws XMLStreamException { if (throwable.getMessage() != null) { writeAttributeSafely("message", throwable.getMessage()); } writeAttributeSafely("type", throwable.getClass().getName()); writeCDataSafely(readStackTrace(throwable)); } private void collectReportEntries(TestIdentifier testIdentifier, List systemOutElements, List systemErrElements) { List entries = reportData.getReportEntries(testIdentifier); if (!entries.isEmpty()) { List systemOutElementsForCapturedOutput = new ArrayList<>(); StringBuilder formattedReportEntries = new StringBuilder(); for (int i = 0; i < entries.size(); i++) { ReportEntry reportEntry = entries.get(i); Map keyValuePairs = new LinkedHashMap<>(reportEntry.getKeyValuePairs()); removeIfPresentAndAddAsSeparateElement(keyValuePairs, STDOUT_REPORT_ENTRY_KEY, systemOutElementsForCapturedOutput); removeIfPresentAndAddAsSeparateElement(keyValuePairs, STDERR_REPORT_ENTRY_KEY, systemErrElements); if (!keyValuePairs.isEmpty()) { buildReportEntryDescription(reportEntry.getTimestamp(), keyValuePairs, i + 1, formattedReportEntries); } } systemOutElements.add(formattedReportEntries.toString().strip()); systemOutElements.addAll(systemOutElementsForCapturedOutput); } } private void removeIfPresentAndAddAsSeparateElement(Map keyValuePairs, String key, List elements) { String value = keyValuePairs.remove(key); if (value != null) { elements.add(value); } } private void buildReportEntryDescription(LocalDateTime timestamp, Map keyValuePairs, int entryNumber, StringBuilder result) { result.append( format("Report Entry #{0} (timestamp: {1})\n", entryNumber, ISO_LOCAL_DATE_TIME.format(timestamp))); keyValuePairs.forEach((key, value) -> result.append(format("\t- {0}: {1}\n", key, value))); } private String getTime(TestIdentifier testIdentifier, NumberFormat numberFormat) { return numberFormat.format(reportData.getDurationInSeconds(testIdentifier)); } private Optional getHostname() { try { return Optional.ofNullable(InetAddress.getLocalHost().getHostName()); } catch (UnknownHostException e) { return Optional.empty(); } } private LocalDateTime getCurrentDateTime() { return LocalDateTime.now(reportData.getClock()).withNano(0); } private String formatNonStandardAttributesAsString(TestIdentifier testIdentifier) { var allDisplayNames = getSelfAndAncestors(testIdentifier) // .map(TestIdentifier::getDisplayName) // .collect(toCollection(ArrayDeque::new)); var fullDisplayName = String.join(" > ", (Iterable) allDisplayNames::descendingIterator); return "unique-id: " + testIdentifier.getUniqueId() // + "\ndisplay-name: " + fullDisplayName; } @SuppressWarnings({ "DataFlowIssue", "ConstantValue" }) private Stream getSelfAndAncestors(TestIdentifier testIdentifier) { var testPlan = reportData.getTestPlan(); return Stream.iterate(testIdentifier, Objects::nonNull, it -> testPlan.getParent(it).orElse(null)); } private void writeOutputElements(String elementName, List elements) throws XMLStreamException { for (String content : elements) { writeOutputElement(elementName, content); } } private void writeOutputElement(String elementName, String content) throws XMLStreamException { xml.writeStartElement(elementName); writeCDataSafely("\n" + content + "\n"); xml.writeEndElement(); newLine(); } private void writeAttributeSafely(String name, String value) throws XMLStreamException { // Workaround for XMLStreamWriter implementations that don't escape // '\n', '\r', and '\t' characters in attribute values xml.flush(); out.setWhitespaceReplacingEnabled(true); xml.writeAttribute(name, replaceIllegalCharacters(value)); xml.flush(); out.setWhitespaceReplacingEnabled(false); } private void writeCDataSafely(String data) throws XMLStreamException { for (String safeDataPart : CDATA_SPLIT_PATTERN.split(replaceIllegalCharacters(data))) { xml.writeCData(safeDataPart); } } private void newLine() throws XMLStreamException { xml.writeCharacters("\n"); } @Override public void close() throws XMLStreamException { xml.flush(); xml.close(); } } static String replaceIllegalCharacters(String text) { if (text.codePoints().allMatch(XmlReportWriter::isAllowedXmlCharacter)) { return text; } StringBuilder result = new StringBuilder(text.length() * 2); text.codePoints().forEach(codePoint -> { if (isAllowedXmlCharacter(codePoint)) { result.appendCodePoint(codePoint); } else { result.append(ILLEGAL_CHARACTER_REPLACEMENT); } }); return result.toString(); } static boolean isAllowedXmlCharacter(int codePoint) { // source: https://www.w3.org/TR/xml/#charsets return codePoint == 0x9 // || codePoint == 0xA // || codePoint == 0xD // || (codePoint >= 0x20 && codePoint <= 0xD7FF) // || (codePoint >= 0xE000 && codePoint <= 0xFFFD) // || (codePoint >= 0x10000 && codePoint <= 0x10FFFF); } static class AggregatedTestResult { private static final AggregatedTestResult SKIPPED_RESULT = new AggregatedTestResult(SKIPPED, emptyList()); public static AggregatedTestResult skipped() { return SKIPPED_RESULT; } public static AggregatedTestResult nonSkipped(List executionResults) { Type type = executionResults.stream() // .map(Type::from) // .max(naturalOrder()) // .orElse(SUCCESS); return new AggregatedTestResult(type, executionResults); } private final Type type; private final List executionResults; private AggregatedTestResult(Type type, List executionResults) { this.type = type; this.executionResults = executionResults; } public Map>> getThrowablesByType() { return executionResults.stream() // .collect(groupingBy(Type::from, mapping(TestExecutionResult::getThrowable, toList()))); } enum Type { SUCCESS, SKIPPED, FAILURE, ERROR; private static Type from(TestExecutionResult executionResult) { if (executionResult.getStatus() == FAILED) { return isFailure(executionResult) ? FAILURE : ERROR; } return SUCCESS; } private static boolean isFailure(TestExecutionResult result) { Optional throwable = result.getThrowable(); return throwable.isPresent() && throwable.get() instanceof AssertionError; } } } private static class ReplacingWriter extends Writer { private final Writer delegate; private boolean whitespaceReplacingEnabled; ReplacingWriter(Writer delegate) { this.delegate = delegate; } void setWhitespaceReplacingEnabled(boolean whitespaceReplacingEnabled) { this.whitespaceReplacingEnabled = whitespaceReplacingEnabled; } @Override public void write(char[] cbuf, int off, int len) throws IOException { if (!whitespaceReplacingEnabled) { delegate.write(cbuf, off, len); return; } StringBuilder stringBuilder = new StringBuilder(len * 2); for (int i = off; i < off + len; i++) { char c = cbuf[i]; String replacement = REPLACEMENTS_IN_ATTRIBUTE_VALUES.get(c); if (replacement != null) { stringBuilder.append(replacement); } else { stringBuilder.append(c); } } delegate.write(stringBuilder.toString()); } @Override public void write(int c) throws IOException { if (whitespaceReplacingEnabled) { super.write(c); } else { delegate.write(c); } } @Override public void write(char[] cbuf) throws IOException { if (whitespaceReplacingEnabled) { super.write(cbuf); } else { delegate.write(cbuf); } } @Override public void write(String str) throws IOException { if (whitespaceReplacingEnabled) { super.write(str); } else { delegate.write(str); } } @Override public void write(String str, int off, int len) throws IOException { if (whitespaceReplacingEnabled) { super.write(str, off, len); } else { delegate.write(str, off, len); } } @Override public Writer append(CharSequence csq) throws IOException { if (whitespaceReplacingEnabled) { return super.append(csq); } else { return delegate.append(csq); } } @Override public Writer append(CharSequence csq, int start, int end) throws IOException { if (whitespaceReplacingEnabled) { return super.append(csq, start, end); } else { return delegate.append(csq, start, end); } } @Override public Writer append(char c) throws IOException { if (whitespaceReplacingEnabled) { return super.append(c); } else { return delegate.append(c); } } @Override public void flush() throws IOException { delegate.flush(); } @Override public void close() throws IOException { delegate.close(); } } } ================================================ FILE: junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Support for generating XML reports using a format which is compatible with * the de facto standard for JUnit 4 based test reports that was made popular * by the Ant build system. */ @NullMarked package org.junit.platform.reporting.legacy.xml; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/GitInfoCollector.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.reporting.open.xml; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.UncheckedIOException; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.Charset; import java.nio.file.Path; import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.commons.util.StringUtils; /** * @since 1.13.2 */ interface GitInfoCollector { static Optional get(Path workingDir) { ProcessExecutor executor = new ProcessExecutor(workingDir); boolean gitInstalled = executor.exec("git", "--version").isPresent(); return gitInstalled ? Optional.of(new CliGitInfoCollector(executor)) : Optional.empty(); } Optional getOriginUrl(); Optional getBranch(); Optional getCommitHash(); Optional getStatus(); class CliGitInfoCollector implements GitInfoCollector { private static final String ALLOWED_USERNAME = "git"; private static final String USER_INFO_REPLACEMENT = "***"; private static final String USER_INFO_SEPARATOR = "@"; private final ProcessExecutor executor; CliGitInfoCollector(ProcessExecutor executor) { this.executor = executor; } @Override public Optional getOriginUrl() { return executor.exec("git", "config", "--get", "remote.origin.url") // .filter(StringUtils::isNotBlank) // .flatMap(this::toGitUrl) // .flatMap(this::obfuscateUserInfo); } @Override public Optional getBranch() { return executor.exec("git", "rev-parse", "--abbrev-ref", "HEAD") // .filter(StringUtils::isNotBlank); } @Override public Optional getCommitHash() { return executor.exec("git", "rev-parse", "--verify", "HEAD") // .filter(StringUtils::isNotBlank); } @Override public Optional getStatus() { return executor.exec("git", "status", "--porcelain"); } private Optional obfuscateUserInfo(GitUrl gitUrl) { try { if (gitUrl.uri.getUserInfo() != null) { URI newUri = new URI(gitUrl.uri.getScheme(), USER_INFO_REPLACEMENT, gitUrl.uri.getHost(), gitUrl.uri.getPort(), gitUrl.uri.getPath(), gitUrl.uri.getQuery(), gitUrl.uri.getFragment()); return Optional.of(newUri.toString().substring(gitUrl.addedPrefix.length())); } if (gitUrl.uri.getAuthority() != null && gitUrl.uri.getAuthority().contains(USER_INFO_SEPARATOR)) { String oldAuthority = gitUrl.uri.getAuthority(); String[] parts = oldAuthority.split(USER_INFO_SEPARATOR, 2); if (!ALLOWED_USERNAME.equals(parts[0])) { String newAuthority = USER_INFO_REPLACEMENT + USER_INFO_SEPARATOR + parts[1]; URI newUri = new URI(gitUrl.uri.getScheme(), newAuthority, gitUrl.uri.getPath(), gitUrl.uri.getQuery(), gitUrl.uri.getFragment()); return Optional.of(newUri.toString().substring(gitUrl.addedPrefix.length())); } } return Optional.of(gitUrl.rawValue); } catch (URISyntaxException e) { return Optional.empty(); } } private Optional toGitUrl(String remoteUrl) { try { return Optional.of(new GitUrl(remoteUrl, new URI(remoteUrl), "")); } catch (URISyntaxException ex) { try { return Optional.of(new GitUrl(remoteUrl, new URI("ssh://" + remoteUrl), "ssh://")); } catch (URISyntaxException ignore) { return Optional.empty(); } } } } class ProcessExecutor { private final Path workingDir; ProcessExecutor(Path workingDir) { this.workingDir = workingDir; } Optional exec(String... args) { Process process = startProcess(args); try (Reader out = newBufferedReader(process.getInputStream()); Reader err = newBufferedReader(process.getErrorStream())) { StringBuilder output = new StringBuilder(); readAllChars(out, (chars, numChars) -> output.append(chars, 0, numChars)); readAllChars(err, (__, ___) -> { // ignore }); boolean terminated = process.waitFor(10, TimeUnit.SECONDS); return terminated && process.exitValue() == 0 ? Optional.of(trimAtEnd(output)) : Optional.empty(); } catch (InterruptedException e) { throw ExceptionUtils.throwAsUncheckedException(e); } catch (IOException ignore) { return Optional.empty(); } finally { process.destroyForcibly(); } } private static BufferedReader newBufferedReader(InputStream stream) { return new BufferedReader(new InputStreamReader(stream, Charset.defaultCharset())); } private Process startProcess(String[] command) { Process process; try { process = new ProcessBuilder().directory(workingDir.toFile()).command(command).start(); } catch (IOException e) { throw new UncheckedIOException("Failed to start process", e); } return process; } private static void readAllChars(Reader reader, BiConsumer consumer) throws IOException { char[] buffer = new char[1024]; int numChars; while ((numChars = reader.read(buffer)) != -1) { consumer.accept(buffer, numChars); } } private static String trimAtEnd(StringBuilder value) { int endIndex = value.length(); for (int i = value.length() - 1; i >= 0; i--) { if (Character.isWhitespace(value.charAt(i))) { endIndex--; break; } } return value.substring(0, endIndex); } } class GitUrl { private final String rawValue; private final URI uri; private final String addedPrefix; GitUrl(String rawValue, URI uri, String addedPrefix) { this.rawValue = rawValue; this.uri = uri; this.addedPrefix = addedPrefix; } } } ================================================ FILE: junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/JUnitContributor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.reporting.open.xml; import static java.util.Collections.emptyList; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; import org.apiguardian.api.API; import org.opentest4j.reporting.schema.Namespace; import org.opentest4j.reporting.tooling.spi.htmlreport.Contributor; import org.opentest4j.reporting.tooling.spi.htmlreport.KeyValuePairs; import org.opentest4j.reporting.tooling.spi.htmlreport.Section; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * Contributes a section containing JUnit-specific metadata for each test node * to the open-test-reporting HTML report. * * @since 1.12 */ @SuppressWarnings("exports") // we don't want to export 'org.opentest4j.reporting.tooling.spi' transitively @API(status = INTERNAL, since = "1.12") public class JUnitContributor implements Contributor { public JUnitContributor() { } @Override public List

contributeSectionsForTestNode(Context context) { return findChild(context.element(), Namespace.REPORTING_CORE, "metadata") // .map(metadata -> { Map table = new LinkedHashMap<>(); findChild(metadata, JUnitFactory.NAMESPACE, "type") // .map(Node::getTextContent) // .ifPresent(value -> table.put("Type", value)); findChild(metadata, JUnitFactory.NAMESPACE, "uniqueId") // .map(Node::getTextContent) // .ifPresent(value -> table.put("Unique ID", value)); findChild(metadata, JUnitFactory.NAMESPACE, "legacyReportingName") // .map(Node::getTextContent) // .ifPresent(value -> table.put("Legacy reporting name", value)); return table; }) // .filter(table -> !table.isEmpty()) // .map(table -> List.of(Section.builder() // .title("JUnit metadata") // .order(15) // .addBlock(KeyValuePairs.builder().content(table).build()) // .build())) // .orElse(emptyList()); } private static Optional findChild(Node parent, Namespace namespace, String localName) { NodeList children = parent.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { Node child = children.item(i); if (localName.equals(child.getLocalName()) && namespace.getUri().equals(child.getNamespaceURI())) { return Optional.of(child); } } return Optional.empty(); } } ================================================ FILE: junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/JUnitFactory.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.reporting.open.xml; import org.junit.platform.engine.TestDescriptor; import org.opentest4j.reporting.events.api.Factory; import org.opentest4j.reporting.schema.Namespace; class JUnitFactory { static Namespace NAMESPACE = Namespace.of("https://schemas.junit.org/open-test-reporting"); private JUnitFactory() { } static Factory uniqueId(String uniqueId) { return context -> new UniqueId(context, uniqueId); } static Factory legacyReportingName(String legacyReportingName) { return context -> new LegacyReportingName(context, legacyReportingName); } static Factory type(TestDescriptor.Type type) { return context -> new Type(context, type); } } ================================================ FILE: junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/LegacyReportingName.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.reporting.open.xml; import org.opentest4j.reporting.events.api.ChildElement; import org.opentest4j.reporting.events.api.Context; import org.opentest4j.reporting.events.core.Metadata; import org.opentest4j.reporting.schema.QualifiedName; class LegacyReportingName extends ChildElement { static final QualifiedName ELEMENT = QualifiedName.of(JUnitFactory.NAMESPACE, "legacyReportingName"); LegacyReportingName(Context context, String value) { super(context, ELEMENT); withContent(value); } } ================================================ FILE: junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.reporting.open.xml; import static java.util.Objects.requireNonNull; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.junit.platform.commons.util.StringUtils.isNotBlank; import static org.junit.platform.launcher.LauncherConstants.STDERR_REPORT_ENTRY_KEY; import static org.junit.platform.launcher.LauncherConstants.STDOUT_REPORT_ENTRY_KEY; import static org.junit.platform.reporting.open.xml.JUnitFactory.legacyReportingName; import static org.junit.platform.reporting.open.xml.JUnitFactory.type; import static org.junit.platform.reporting.open.xml.JUnitFactory.uniqueId; import static org.opentest4j.reporting.events.core.CoreFactory.attachments; import static org.opentest4j.reporting.events.core.CoreFactory.cpuCores; import static org.opentest4j.reporting.events.core.CoreFactory.data; import static org.opentest4j.reporting.events.core.CoreFactory.directorySource; import static org.opentest4j.reporting.events.core.CoreFactory.file; import static org.opentest4j.reporting.events.core.CoreFactory.fileSource; import static org.opentest4j.reporting.events.core.CoreFactory.hostName; import static org.opentest4j.reporting.events.core.CoreFactory.infrastructure; import static org.opentest4j.reporting.events.core.CoreFactory.metadata; import static org.opentest4j.reporting.events.core.CoreFactory.operatingSystem; import static org.opentest4j.reporting.events.core.CoreFactory.output; import static org.opentest4j.reporting.events.core.CoreFactory.reason; import static org.opentest4j.reporting.events.core.CoreFactory.result; import static org.opentest4j.reporting.events.core.CoreFactory.sources; import static org.opentest4j.reporting.events.core.CoreFactory.tag; import static org.opentest4j.reporting.events.core.CoreFactory.tags; import static org.opentest4j.reporting.events.core.CoreFactory.uriSource; import static org.opentest4j.reporting.events.core.CoreFactory.userName; import static org.opentest4j.reporting.events.git.GitFactory.branch; import static org.opentest4j.reporting.events.git.GitFactory.commit; import static org.opentest4j.reporting.events.git.GitFactory.repository; import static org.opentest4j.reporting.events.git.GitFactory.status; import static org.opentest4j.reporting.events.java.JavaFactory.classSource; import static org.opentest4j.reporting.events.java.JavaFactory.classpathResourceSource; import static org.opentest4j.reporting.events.java.JavaFactory.fileEncoding; import static org.opentest4j.reporting.events.java.JavaFactory.heapSize; import static org.opentest4j.reporting.events.java.JavaFactory.javaVersion; import static org.opentest4j.reporting.events.java.JavaFactory.methodSource; import static org.opentest4j.reporting.events.java.JavaFactory.packageSource; import static org.opentest4j.reporting.events.java.JavaFactory.throwable; import static org.opentest4j.reporting.events.root.RootFactory.finished; import static org.opentest4j.reporting.events.root.RootFactory.reported; import static org.opentest4j.reporting.events.root.RootFactory.started; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.UncheckedIOException; import java.io.Writer; import java.net.InetAddress; import java.net.Socket; import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.time.Instant; import java.time.LocalDateTime; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.TestSource; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.platform.engine.support.descriptor.ClasspathResourceSource; import org.junit.platform.engine.support.descriptor.CompositeTestSource; import org.junit.platform.engine.support.descriptor.DirectorySource; import org.junit.platform.engine.support.descriptor.FileSource; import org.junit.platform.engine.support.descriptor.MethodSource; import org.junit.platform.engine.support.descriptor.PackageSource; import org.junit.platform.engine.support.descriptor.UriSource; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; import org.opentest4j.reporting.events.api.DocumentWriter; import org.opentest4j.reporting.events.api.NamespaceRegistry; import org.opentest4j.reporting.events.core.Attachments; import org.opentest4j.reporting.events.core.Infrastructure; import org.opentest4j.reporting.events.core.Result; import org.opentest4j.reporting.events.core.Sources; import org.opentest4j.reporting.events.root.Events; import org.opentest4j.reporting.schema.Namespace; /** * Open Test Reporting events XML generating test execution listener. * * @since 1.9 */ @API(status = MAINTAINED, since = "1.13.3") public class OpenTestReportGeneratingListener implements TestExecutionListener { static final String ENABLED_PROPERTY_NAME = "junit.platform.reporting.open.xml.enabled"; static final String GIT_ENABLED_PROPERTY_NAME = "junit.platform.reporting.open.xml.git.enabled"; static final String SOCKET_PROPERTY_NAME = "junit.platform.reporting.open.xml.socket"; private final AtomicInteger idCounter = new AtomicInteger(); private final Map inProgressIds = new ConcurrentHashMap<>(); private DocumentWriter eventsFileWriter = DocumentWriter.noop(); private final Path workingDir; private @Nullable Path outputDir; @SuppressWarnings("unused") // Used via ServiceLoader public OpenTestReportGeneratingListener() { this(Path.of(".").toAbsolutePath()); } OpenTestReportGeneratingListener(Path workingDir) { this.workingDir = workingDir; } @Override public void testPlanExecutionStarted(TestPlan testPlan) { ConfigurationParameters config = testPlan.getConfigurationParameters(); if (isEnabled(config)) { NamespaceRegistry namespaceRegistry = NamespaceRegistry.builder(Namespace.REPORTING_CORE) // .add("e", Namespace.REPORTING_EVENTS) // .add("git", Namespace.REPORTING_GIT) // .add("java", Namespace.REPORTING_JAVA) // .add("junit", JUnitFactory.NAMESPACE, "https://schemas.junit.org/open-test-reporting/junit-1.9.xsd") // .build(); outputDir = testPlan.getOutputDirectoryCreator().getRootDirectory(); try { eventsFileWriter = createDocumentWriter(config, namespaceRegistry); reportInfrastructure(config); } catch (Exception e) { throw new JUnitException("Failed to initialize XML events writer", e); } } } private DocumentWriter createDocumentWriter(ConfigurationParameters config, NamespaceRegistry namespaceRegistry) throws Exception { return config.get(SOCKET_PROPERTY_NAME, Integer::valueOf) // .map(port -> { try { Socket socket = new Socket(InetAddress.getLoopbackAddress(), port); Writer writer = new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8); return Events.createDocumentWriter(namespaceRegistry, writer); } catch (Exception e) { throw new JUnitException("Failed to connect to socket on port " + port, e); } }) // .orElseGet(() -> { try { Path eventsXml = requireNonNull(outputDir).resolve("open-test-report.xml"); return Events.createDocumentWriter(namespaceRegistry, eventsXml); } catch (Exception e) { throw new JUnitException("Failed to create XML events file", e); } }); } private boolean isEnabled(ConfigurationParameters config) { return config.getBoolean(ENABLED_PROPERTY_NAME).orElse(false); } private boolean isGitEnabled(ConfigurationParameters config) { return config.getBoolean(GIT_ENABLED_PROPERTY_NAME).orElse(false); } @SuppressWarnings("EmptyCatch") private void reportInfrastructure(ConfigurationParameters config) { eventsFileWriter.append(infrastructure(), infrastructure -> { try { String hostName = InetAddress.getLocalHost().getHostName(); infrastructure.append(hostName(hostName)); } catch (UnknownHostException ignored) { } infrastructure // .append(userName(System.getProperty("user.name"))) // .append(operatingSystem(System.getProperty("os.name"))) // .append(cpuCores(Runtime.getRuntime().availableProcessors())) // .append(javaVersion(System.getProperty("java.version"))) // .append(fileEncoding(System.getProperty("file.encoding"))) // .append(heapSize(), heapSize -> heapSize.withMax(Runtime.getRuntime().maxMemory())); if (isGitEnabled(config)) { GitInfoCollector.get(workingDir).ifPresent(git -> addGitInfo(infrastructure, git)); } }); } private void addGitInfo(Infrastructure infrastructure, GitInfoCollector git) { git.getOriginUrl() // .ifPresent( gitUrl -> infrastructure.append(repository(), repository -> repository.withOriginUrl(gitUrl))); git.getBranch() // .ifPresent(branch -> infrastructure.append(branch(branch))); git.getCommitHash() // .ifPresent(gitCommitHash -> infrastructure.append(commit(gitCommitHash))); git.getStatus() // .ifPresent(statusOutput -> infrastructure.append(status(statusOutput), status -> status.withClean(statusOutput.isEmpty()))); } @Override public void testPlanExecutionFinished(TestPlan testPlan) { try { eventsFileWriter.close(); } catch (IOException e) { throw new UncheckedIOException("Failed to close XML events file", e); } finally { eventsFileWriter = DocumentWriter.noop(); } } @Override public void executionSkipped(TestIdentifier testIdentifier, String reason) { String id = String.valueOf(idCounter.incrementAndGet()); reportStarted(testIdentifier, id); eventsFileWriter.append(finished(id, Instant.now()), // finished -> finished.append(result(Result.Status.SKIPPED), result -> { if (isNotBlank(reason)) { result.append(reason(reason)); } })); } @Override public void executionStarted(TestIdentifier testIdentifier) { String id = String.valueOf(idCounter.incrementAndGet()); inProgressIds.put(testIdentifier.getUniqueIdObject(), id); reportStarted(testIdentifier, id); } private void reportStarted(TestIdentifier testIdentifier, String id) { eventsFileWriter.append(started(id, Instant.now(), testIdentifier.getDisplayName()), started -> { testIdentifier.getParentIdObject().ifPresent(parentId -> started.withParentId(inProgressIds.get(parentId))); started.append(metadata(), metadata -> { if (!testIdentifier.getTags().isEmpty()) { metadata.append(tags(), tags -> // testIdentifier.getTags().forEach(tag -> tags.append(tag(tag.getName())))); } metadata.append(uniqueId(testIdentifier.getUniqueId())) // .append(legacyReportingName(testIdentifier.getLegacyReportingName())) // .append(type(testIdentifier.getType())); }); testIdentifier.getSource().ifPresent( source -> started.append(sources(), sources -> addTestSource(source, sources))); }); } private void addTestSource(TestSource source, Sources sources) { if (source instanceof CompositeTestSource compositeSource) { compositeSource.getSources().forEach(it -> addTestSource(it, sources)); } else if (source instanceof ClassSource classSource) { sources.append(classSource(classSource.getClassName()), // element -> classSource.getPosition().ifPresent( filePosition -> element.addFilePosition(filePosition.getLine(), filePosition.getColumn()))); } else if (source instanceof MethodSource methodSource) { sources.append(methodSource(methodSource.getClassName(), methodSource.getMethodName()), element -> { String methodParameterTypes = methodSource.getMethodParameterTypes(); if (methodParameterTypes != null) { element.withMethodParameterTypes(methodParameterTypes); } }); } else if (source instanceof ClasspathResourceSource classpathResourceSource) { sources.append(classpathResourceSource(classpathResourceSource.getClasspathResourceName()), // element -> classpathResourceSource.getPosition().ifPresent( filePosition -> element.addFilePosition(filePosition.getLine(), filePosition.getColumn()))); } else if (source instanceof PackageSource packageSource) { sources.append(packageSource(packageSource.getPackageName())); } else if (source instanceof FileSource fileSource) { sources.append(fileSource(fileSource.getFile()), // element -> fileSource.getPosition().ifPresent( filePosition -> element.addFilePosition(filePosition.getLine(), filePosition.getColumn()))); } else if (source instanceof DirectorySource directorySource) { sources.append(directorySource(directorySource.getFile())); } else if (source instanceof UriSource uriSource) { sources.append(uriSource(uriSource.getUri())); } } @Override public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry entry) { String id = inProgressIds.get(testIdentifier.getUniqueIdObject()); eventsFileWriter.append(reported(id, Instant.now()), // reported -> reported.append(attachments(), // attachments -> { Map keyValuePairs = entry.getKeyValuePairs(); if (keyValuePairs.containsKey(STDOUT_REPORT_ENTRY_KEY) || keyValuePairs.containsKey(STDERR_REPORT_ENTRY_KEY)) { attachOutput(attachments, entry.getTimestamp(), keyValuePairs.get(STDOUT_REPORT_ENTRY_KEY), "stdout"); attachOutput(attachments, entry.getTimestamp(), keyValuePairs.get(STDERR_REPORT_ENTRY_KEY), "stderr"); } else { attachments.append(data(entry.getTimestamp()), // data -> keyValuePairs.forEach(data::addEntry)); } })); } private static void attachOutput(Attachments attachments, LocalDateTime timestamp, @Nullable String content, String source) { if (content != null) { attachments.append(output(timestamp), output -> output.withSource(source).withContent(content)); } } @Override public void fileEntryPublished(TestIdentifier testIdentifier, FileEntry entry) { String id = inProgressIds.get(testIdentifier.getUniqueIdObject()); eventsFileWriter.append(reported(id, Instant.now()), // reported -> reported.append(attachments(), attachments -> attachments.append(file(entry.getTimestamp()), // file -> { file.withPath(requireNonNull(outputDir).relativize(entry.getPath()).toString()); entry.getMediaType().ifPresent(file::withMediaType); }))); } @Override public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { String id = inProgressIds.remove(testIdentifier.getUniqueIdObject()); eventsFileWriter.append(finished(id, Instant.now()), // finished -> finished.append(result(convertStatus(testExecutionResult.getStatus())), // result -> testExecutionResult.getThrowable() // .ifPresent(throwable -> result.append(throwable(throwable))))); } private Result.Status convertStatus(TestExecutionResult.Status status) { return switch (status) { case FAILED -> Result.Status.FAILED; case SUCCESSFUL -> Result.Status.SUCCESSFUL; case ABORTED -> Result.Status.ABORTED; }; } } ================================================ FILE: junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/Type.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.reporting.open.xml; import org.junit.platform.engine.TestDescriptor; import org.opentest4j.reporting.events.api.ChildElement; import org.opentest4j.reporting.events.api.Context; import org.opentest4j.reporting.events.core.Metadata; import org.opentest4j.reporting.schema.QualifiedName; class Type extends ChildElement { static final QualifiedName ELEMENT = QualifiedName.of(JUnitFactory.NAMESPACE, "type"); Type(Context context, TestDescriptor.Type type) { super(context, ELEMENT); withContent(type.toString()); } } ================================================ FILE: junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/UniqueId.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.reporting.open.xml; import org.opentest4j.reporting.events.api.ChildElement; import org.opentest4j.reporting.events.api.Context; import org.opentest4j.reporting.events.core.Metadata; import org.opentest4j.reporting.schema.QualifiedName; class UniqueId extends ChildElement { static final QualifiedName ELEMENT = QualifiedName.of(JUnitFactory.NAMESPACE, "uniqueId"); UniqueId(Context context, String value) { super(context, ELEMENT); withContent(value); } } ================================================ FILE: junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Support for generating Open Test Reporting compatible XML event reports. */ @NullMarked package org.junit.platform.reporting.open.xml; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-reporting/src/main/java/org/junit/platform/reporting/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * JUnit Platform Reporting. */ @NullMarked package org.junit.platform.reporting; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-reporting/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener ================================================ org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener ================================================ FILE: junit-platform-reporting/src/main/resources/META-INF/services/org.opentest4j.reporting.tooling.spi.htmlreport.Contributor ================================================ org.junit.platform.reporting.open.xml.JUnitContributor ================================================ FILE: junit-platform-reporting/src/main/resources/org/junit/platform/reporting/open/xml/catalog.xml ================================================ ================================================ FILE: junit-platform-reporting/src/main/resources/org/junit/platform/reporting/open/xml/junit.xsd ================================================ ================================================ FILE: junit-platform-reporting/src/test/README.md ================================================ For compatibility with the Eclipse IDE, the test for this module are in the `platform-tests` project. ================================================ FILE: junit-platform-reporting/src/testFixtures/java/org/junit/platform/reporting/testutil/FileUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.reporting.testutil; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; public class FileUtils { public static Path findPath(Path rootDir, String syntaxAndPattern) { var matcher = rootDir.getFileSystem().getPathMatcher(syntaxAndPattern); try (var files = Files.walk(rootDir)) { return files.filter(matcher::matches).findFirst() // .orElseThrow(() -> new AssertionError( "Failed to find file matching '%s' in %s".formatted(syntaxAndPattern, rootDir))); } catch (IOException e) { throw new UncheckedIOException(e); } } private FileUtils() { } } ================================================ FILE: junit-platform-suite/junit-platform-suite.gradle.kts ================================================ plugins { id("junitbuild.java-aggregator-conventions") } description = "JUnit Platform Suite (Aggregator)" dependencies { api(platform(projects.junitBom)) api(projects.junitPlatformSuiteApi) implementation(projects.junitPlatformSuiteEngine) osgiVerification(projects.junitJupiterEngine) osgiVerification(projects.junitPlatformLauncher) } ================================================ FILE: junit-platform-suite/src/main/java/module-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Aggregates all JUnit Platform Suite modules. * * @since 1.8 */ module org.junit.platform.suite { requires transitive org.junit.platform.suite.api; requires transitive org.junit.platform.suite.engine; } ================================================ FILE: junit-platform-suite/src/test/README.md ================================================ For compatibility with the Eclipse IDE, the test for this module are in the `platform-tests` project. ================================================ FILE: junit-platform-suite-api/junit-platform-suite-api.gradle.kts ================================================ plugins { id("junitbuild.java-library-conventions") } description = "JUnit Platform Suite API" dependencies { api(platform(projects.junitBom)) api(projects.junitPlatformCommons) compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) osgiVerification(projects.junitJupiterEngine) osgiVerification(projects.junitPlatformLauncher) } javadocConventions { addExtraModuleReferences(projects.junitPlatformEngine, projects.junitPlatformLauncher) } ================================================ FILE: junit-platform-suite-api/src/main/java/module-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Annotations for configuring a test suite on the JUnit Platform. * * @since 1.0 */ module org.junit.platform.suite.api { requires static transitive org.apiguardian.api; requires static transitive org.jspecify; requires transitive org.junit.platform.commons; exports org.junit.platform.suite.api; } ================================================ FILE: junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/AfterSuite.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.api; import static org.apiguardian.api.API.Status.MAINTAINED; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @AfterSuite} is used to signal that the annotated method should be * executed after all tests in the current test suite. * *

Method Signatures

* *

{@code @AfterSuite} methods must have a {@code void} return type, must * be {@code static} and must not be {@code private}. * *

Inheritance and Execution Order

* *

{@code @AfterSuite} methods are inherited from superclasses as long as they * are not overridden according to the visibility rules of the Java * language. Furthermore, {@code @AfterSuite} methods from superclasses will be * executed after {@code @AfterSuite} methods in subclasses. * *

The JUnit Platform Suite Engine does not guarantee the execution order of * multiple {@code @AfterSuite} methods that are declared within a single test * class or test interface. While it may at times appear that these methods are * invoked in alphabetical order, they are in fact sorted using an algorithm * that is deterministic but intentionally non-obvious. * *

In addition, {@code @AfterSuite} methods are in no way linked to * {@code @BeforeSuite} methods. Consequently, there are no guarantees with regard * to their wrapping behavior. For example, given two * {@code @BeforeSuite} methods {@code createA()} and {@code createB()} as well as * two {@code @AfterSuite} methods {@code destroyA()} and {@code destroyB()}, the * order in which the {@code @BeforeSuite} methods are executed (e.g. * {@code createA()} before {@code createB()}) does not imply any order for the * seemingly corresponding {@code @AfterSuite} methods. In other words, * {@code destroyA()} might be called before or after * {@code destroyB()}. The JUnit Team therefore recommends that developers * declare at most one {@code @BeforeSuite} method and at most one * {@code @AfterSuite} method per test class or test interface unless there are no * dependencies between the {@code @BeforeSuite} methods or between the * {@code @AfterSuite} methods. * *

Composition

* *

{@code @AfterSuite} may be used as a meta-annotation in order to create * a custom composed annotation that inherits the semantics of * {@code @AfterSuite}. * * @since 1.11 * @see BeforeSuite * @see Suite */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = MAINTAINED, since = "1.13.3") public @interface AfterSuite { } ================================================ FILE: junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/BeforeSuite.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.api; import static org.apiguardian.api.API.Status.MAINTAINED; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @BeforeSuite} is used to signal that the annotated method should be * executed before all tests in the current test suite. * *

Method Signatures

* *

{@code @BeforeSuite} methods must have a {@code void} return type, must * be {@code static} and must not be {@code private}. * *

Inheritance and Execution Order

* *

{@code @BeforeSuite} methods are inherited from superclasses as long as they * are not overridden according to the visibility rules of the Java * language. Furthermore, {@code @BeforeSuite} methods from superclasses will be * executed before {@code @BeforeSuite} methods in subclasses. * *

The JUnit Platform Suite Engine does not guarantee the execution order of * multiple {@code @BeforeSuite} methods that are declared within a single test * class or test interface. While it may at times appear that these methods are * invoked in alphabetical order, they are in fact sorted using an algorithm * that is deterministic but intentionally non-obvious. * *

In addition, {@code @BeforeSuite} methods are in no way linked to * {@code @AfterSuite} methods. Consequently, there are no guarantees with regard * to their wrapping behavior. For example, given two * {@code @BeforeSuite} methods {@code createA()} and {@code createB()} as well as * two {@code @AfterSuite} methods {@code destroyA()} and {@code destroyB()}, the * order in which the {@code @BeforeSuite} methods are executed (e.g. * {@code createA()} before {@code createB()}) does not imply any order for the * seemingly corresponding {@code @AfterSuite} methods. In other words, * {@code destroyA()} might be called before or after * {@code destroyB()}. The JUnit Team therefore recommends that developers * declare at most one {@code @BeforeSuite} method and at most one * {@code @AfterSuite} method per test class or test interface unless there are no * dependencies between the {@code @BeforeSuite} methods or between the * {@code @AfterSuite} methods. * *

Composition

* *

{@code @BeforeSuite} may be used as a meta-annotation in order to create * a custom composed annotation that inherits the semantics of * {@code @BeforeSuite}. * * @since 1.11 * @see AfterSuite * @see Suite */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = MAINTAINED, since = "1.13.3") public @interface BeforeSuite { } ================================================ FILE: junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParameter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.api; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @ConfigurationParameter} is a {@linkplain Repeatable repeatable} * annotation that specifies a configuration {@link #key key} and * {@link #value value} pair to be added to the discovery request when running * a test suite on the JUnit Platform. * * @since 1.8 * @see ConfigurationParametersResource * @see DisableParentConfigurationParameters * @see Suite * @see org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder#configurationParameter(String, String) */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited @Documented @API(status = STABLE, since = "1.10") @Repeatable(ConfigurationParameters.class) public @interface ConfigurationParameter { /** * The configuration parameter key under which to add the {@link #value() value} * to the discovery request; never {@code null} or blank. */ String key(); /** * The value to add to the discovery request for the specified {@link #key() key}. */ String value(); } ================================================ FILE: junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParameters.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.api; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @ConfigurationParameters} is a container for one or more * {@link ConfigurationParameter @ConfigurationParameter} declarations. * *

Note, however, that use of the {@code @ConfigurationParameters} container * is completely optional since {@code @ConfigurationParameter} is a * {@linkplain java.lang.annotation.Repeatable repeatable} annotation. * * @since 1.8 * @see ConfigurationParameter */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited @Documented @API(status = STABLE, since = "1.10") public @interface ConfigurationParameters { /** * An array of one or more {@link ConfigurationParameter @ConfigurationParameter} * declarations. */ ConfigurationParameter[] value(); } ================================================ FILE: junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParametersResource.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.api; import static org.apiguardian.api.API.Status.MAINTAINED; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @ConfigurationParametersResource} is a * {@linkplain Repeatable repeatable} annotation that specifies a configuration * file in Java's properties format on the classpath to be added to the * discovery request when running a test suite on the JUnit Platform. * * @since 1.11 * @see ConfigurationParameter * @see DisableParentConfigurationParameters * @see Suite * @see org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder#configurationParametersResources(String...) */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited @Documented @API(status = MAINTAINED, since = "1.13.3") @Repeatable(ConfigurationParametersResources.class) public @interface ConfigurationParametersResource { /** * The classpath location for the desired properties file; never {@code null} or blank. */ String value(); } ================================================ FILE: junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParametersResources.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.api; import static org.apiguardian.api.API.Status.MAINTAINED; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @ConfigurationParametersResources} is a container for one or more * {@link ConfigurationParametersResource @ConfigurationParametersResource} declarations. * *

Note, however, that use of the {@code @ConfigurationParametersResources} container * is completely optional since {@code @ConfigurationParametersResource} is a * {@linkplain java.lang.annotation.Repeatable repeatable} annotation. * * @since 1.11 * @see ConfigurationParametersResource */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited @Documented @API(status = MAINTAINED, since = "1.13.3") public @interface ConfigurationParametersResources { /** * An array of one or more {@link ConfigurationParametersResource @ConfigurationParameterResource} * declarations. */ ConfigurationParametersResource[] value(); } ================================================ FILE: junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/DisableParentConfigurationParameters.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.api; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * Disable parent configuration parameters. * *

By default, a suite discovers tests using the configuration parameters * explicitly configured via {@link ConfigurationParameter @ConfigurationParameter} * and {@link ConfigurationParametersResource} as well as the configuration * parameters from the discovery request that was used to discover the suite. * *

Annotating a suite with this annotation disables the latter source so * that only explicit configuration parameters and resources are taken into * account. * * @since 1.8 * @see ConfigurationParameter * @see ConfigurationParametersResource * @see Suite */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited @Documented @API(status = STABLE, since = "1.10") public @interface DisableParentConfigurationParameters { } ================================================ FILE: junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeClassNamePatterns.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.api; import static org.apiguardian.api.API.Status.MAINTAINED; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @ExcludeClassNamePatterns} specifies regular expressions that are used * to match against fully qualified class names when running a test suite on the * JUnit Platform. * *

The patterns are combined using OR semantics: if the fully qualified name * of a class matches against at least one of the patterns, the class will be * excluded from the test plan. * * @since 1.0 * @see Suite * @see org.junit.platform.engine.discovery.ClassNameFilter#excludeClassNamePatterns */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited @Documented @API(status = MAINTAINED, since = "1.0") public @interface ExcludeClassNamePatterns { /** * Regular expressions used to match against fully qualified class names. */ String[] value(); } ================================================ FILE: junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeEngines.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.api; import static org.apiguardian.api.API.Status.MAINTAINED; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @ExcludeEngines} specifies the {@linkplain #value IDs} of * {@link org.junit.platform.engine.TestEngine TestEngines} to be excluded * when running a test suite on the JUnit Platform. * * @since 1.0 * @see Suite * @see org.junit.platform.launcher.EngineFilter#excludeEngines */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited @Documented @API(status = MAINTAINED, since = "1.0") public @interface ExcludeEngines { /** * One or more TestEngine IDs to be excluded from the test plan. */ String[] value(); } ================================================ FILE: junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludePackages.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.api; import static org.apiguardian.api.API.Status.MAINTAINED; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @ExcludePackages} specifies the {@linkplain #value packages} to be * excluded when running a test suite on the JUnit Platform. * @since 1.0 * @see Suite * @see org.junit.platform.engine.discovery.PackageNameFilter#excludePackageNames(String...) */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited @Documented @API(status = MAINTAINED, since = "1.0") public @interface ExcludePackages { /** * One or more packages to exclude. */ String[] value(); } ================================================ FILE: junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeTags.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.api; import static org.apiguardian.api.API.Status.MAINTAINED; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @ExcludeTags} specifies the * {@linkplain #value tags or tag expressions} to be excluded when running a * test suite on the JUnit Platform. * *

Tag Expressions

* *

Tag expressions are boolean expressions with the following allowed * operators: {@code !} (not), {@code &} (and) and {@code |} (or). Parentheses * can be used to adjust for operator precedence. Please refer to the * JUnit User Guide * for usage examples. * *

Syntax Rules for Tags

*
    *
  • A tag must not be blank.
  • *
  • A stripped tag must not contain whitespace.
  • *
  • A stripped tag must not contain ISO control characters.
  • *
  • A stripped tag must not contain reserved characters.
  • *
* *

Reserved characters that are not permissible as part of a tag name. * *

    *
  • {@code ","}
  • *
  • {@code "("}
  • *
  • {@code ")"}
  • *
  • {@code "&"}
  • *
  • {@code "|"}
  • *
  • {@code "!"}
  • *
* * @since 1.0 * @see Suite * @see org.junit.platform.launcher.TagFilter#excludeTags */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited @Documented @API(status = MAINTAINED, since = "1.0") public @interface ExcludeTags { /** * One or more tags to exclude. * *

Note: each tag will be {@linkplain String#strip() stripped} and * validated according to the Syntax Rules for Tags (see * {@linkplain ExcludeTags class-level Javadoc} for details). */ String[] value(); } ================================================ FILE: junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeClassNamePatterns.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.api; import static org.apiguardian.api.API.Status.MAINTAINED; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @IncludeClassNamePatterns} specifies regular expressions that are used * to match against fully qualified class names when running a test suite on the * JUnit Platform. * *

The patterns are combined using OR semantics: if the fully qualified name * of a class matches against at least one of the patterns, the class will be * included in the test plan. * * @since 1.0 * @see Suite * @see org.junit.platform.engine.discovery.ClassNameFilter#STANDARD_INCLUDE_PATTERN * @see org.junit.platform.engine.discovery.ClassNameFilter#includeClassNamePatterns */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited @Documented @API(status = MAINTAINED, since = "1.0") public @interface IncludeClassNamePatterns { /** * Regular expressions used to match against fully qualified class names. * *

The default pattern matches against classes whose names either begin * with {@code Test} or end with {@code Test} or {@code Tests} (in any package). */ // Implementation notes: // - Test.* :: "Test" prefix for classes in default package // - .+[.$]Test.* :: "Test" prefix for top-level and nested classes in a named package // - .*Tests? :: "Test" and "Tests" suffixes in any package String[] value() default "^(Test.*|.+[.$]Test.*|.*Tests?)$"; } ================================================ FILE: junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeEngines.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.api; import static org.apiguardian.api.API.Status.MAINTAINED; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @IncludeEngines} specifies the {@linkplain #value IDs} of * {@link org.junit.platform.engine.TestEngine TestEngines} to be included * when running a test suite on the JUnit Platform. * * @since 1.0 * @see Suite * @see org.junit.platform.launcher.EngineFilter#includeEngines */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited @Documented @API(status = MAINTAINED, since = "1.0") public @interface IncludeEngines { /** * One or more TestEngine IDs to be included in the test plan. */ String[] value(); } ================================================ FILE: junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludePackages.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.api; import static org.apiguardian.api.API.Status.MAINTAINED; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @IncludePackages} specifies the {@linkplain #value packages} to be * included when running a test suite on the JUnit Platform. * * @since 1.0 * @see Suite * @see org.junit.platform.engine.discovery.PackageNameFilter#includePackageNames(String...) */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited @Documented @API(status = MAINTAINED, since = "1.0") public @interface IncludePackages { /** * One or more packages to include. */ String[] value(); } ================================================ FILE: junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeTags.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.api; import static org.apiguardian.api.API.Status.MAINTAINED; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @IncludeTags} specifies the * {@linkplain #value tags or tag expressions} to be included when running a * test suite on the JUnit Platform. * *

Tag Expressions

* *

Tag expressions are boolean expressions with the following allowed * operators: {@code !} (not), {@code &} (and) and {@code |} (or). Parentheses * can be used to adjust for operator precedence. Please refer to the * JUnit User Guide * for usage examples. * *

Syntax Rules for Tags

*
    *
  • A tag must not be blank.
  • *
  • A stripped tag must not contain whitespace.
  • *
  • A stripped tag must not contain ISO control characters.
  • *
  • A stripped tag must not contain reserved characters.
  • *
* *

Reserved characters that are not permissible as part of a tag name. * *

    *
  • {@code ","}
  • *
  • {@code "("}
  • *
  • {@code ")"}
  • *
  • {@code "&"}
  • *
  • {@code "|"}
  • *
  • {@code "!"}
  • *
* * @since 1.0 * @see Suite * @see org.junit.platform.launcher.TagFilter#includeTags */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited @Documented @API(status = MAINTAINED, since = "1.0") public @interface IncludeTags { /** * One or more tags to include. * *

Note: each tag will be {@linkplain String#strip() stripped} and * validated according to the Syntax Rules for Tags (see * {@linkplain IncludeTags class-level Javadoc} for details). */ String[] value(); } ================================================ FILE: junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/Select.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.api; import static org.apiguardian.api.API.Status.MAINTAINED; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @Select} is a {@linkplain Repeatable repeatable} annotation that * specifies which tests to select based on prefixed * {@linkplain org.junit.platform.engine.DiscoverySelectorIdentifier selector identifiers}. * * @since 1.11 * @see Suite * @see org.junit.platform.engine.discovery.DiscoverySelectors#parse(String) */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited @Documented @API(status = MAINTAINED, since = "1.13.3") @Repeatable(Selects.class) public @interface Select { /** * One or more prefixed * {@linkplain org.junit.platform.engine.DiscoverySelectorIdentifier selector identifiers} * to select. */ String[] value(); } ================================================ FILE: junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasses.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.api; import static org.apiguardian.api.API.Status.MAINTAINED; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @SelectClasses} specifies the classes to select when running * a test suite on the JUnit Platform. * * @since 1.0 * @see Suite * @see org.junit.platform.engine.discovery.DiscoverySelectors#selectClass(Class) * @see org.junit.platform.engine.discovery.DiscoverySelectors#selectClass(String) */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited @Documented @API(status = MAINTAINED, since = "1.0") public @interface SelectClasses { /** * One or more classes to select. * *

May be use in conjunction with or instead of {@link #names() names}. */ Class[] value() default {}; /** * One or more classes to select by their fully qualified names. * *

May be use in conjunction with or instead of {@link #value() value}. * *

This attribute is intended to be used when a class cannot be referenced * directly from where this annotation is used — for example, when a * class is not visible due to being private or package-private. * * @since 1.10 */ @API(status = MAINTAINED, since = "1.13.3") String[] names() default {}; } ================================================ FILE: junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasspathResource.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.api; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @SelectClasspathResource} is a {@linkplain Repeatable repeatable} * annotation that specifies a classpath resource to select when running * a test suite on the JUnit Platform. * * @since 1.8 * @see Suite * @see org.junit.platform.engine.discovery.DiscoverySelectors#selectClasspathResource(String, org.junit.platform.engine.discovery.FilePosition) */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited @Documented @API(status = STABLE, since = "1.10") @Repeatable(SelectClasspathResources.class) public @interface SelectClasspathResource { /** * The name of the classpath resource to select. */ String value(); /** * The line number within the classpath resource; ignored if not greater than * zero. */ int line() default 0; /** * The column number within the classpath resource; ignored if the line number * is ignored or if not greater than zero. */ int column() default 0; } ================================================ FILE: junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasspathResources.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.api; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @SelectClasspathResources} is a container for one or more * {@link SelectClasspathResource @SelectClasspathResource} declarations. * *

Note, however, that use of the {@code @SelectClasspathResources} container is * completely optional since {@code @SelectClasspathResource} is a * {@linkplain java.lang.annotation.Repeatable repeatable} annotation. * * @since 1.8 * @see SelectClasspathResource */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited @Documented @API(status = STABLE, since = "1.10") public @interface SelectClasspathResources { /** * An array of one or more {@link SelectClasspathResource @SelectClasspathResource} * declarations. */ SelectClasspathResource[] value(); } ================================================ FILE: junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectDirectories.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.api; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @SelectDirectories} specifies the directories to select when * running a test suite on the JUnit Platform. * * @since 1.8 * @see Suite * @see org.junit.platform.engine.discovery.DiscoverySelectors#selectDirectory(String) */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited @Documented @API(status = STABLE, since = "1.10") public @interface SelectDirectories { /** * One or more directories to select. */ String[] value(); } ================================================ FILE: junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectFile.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.api; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @SelectFile} is a {@linkplain Repeatable repeatable} annotation that * specifies a file to select when running a test suite on the JUnit * Platform. * * @since 1.8 * @see Suite * @see org.junit.platform.engine.discovery.DiscoverySelectors#selectFile(String, org.junit.platform.engine.discovery.FilePosition) */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited @Documented @API(status = STABLE, since = "1.10") @Repeatable(SelectFiles.class) public @interface SelectFile { /** * The file to select. */ String value(); /** * The line number within the file; ignored if not greater than zero. */ int line() default 0; /** * The column number within the file; ignored if the line number is ignored * or if not greater than zero. */ int column() default 0; } ================================================ FILE: junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectFiles.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.api; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @SelectFiles} is a container for one or more * {@link SelectFile @SelectFile} declarations. * *

Note, however, that use of the {@code @SelectFiles} container is * completely optional since {@code @SelectFile} is a * {@linkplain java.lang.annotation.Repeatable repeatable} annotation. * * @since 1.8 * @see SelectFile */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited @Documented @API(status = STABLE, since = "1.10") public @interface SelectFiles { /** * An array of one or more {@link SelectFile @SelectFile} declarations. */ SelectFile[] value(); } ================================================ FILE: junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectMethod.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.api; import static org.apiguardian.api.API.Status.MAINTAINED; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @SelectMethod} is a {@linkplain Repeatable repeatable} annotation that * specifies a method to select when running a test suite on the JUnit * Platform. * * @since 1.10 * @see Suite * @see org.junit.platform.engine.discovery.DiscoverySelectors#selectMethod(String) * @see org.junit.platform.engine.discovery.DiscoverySelectors#selectMethod(String, String, String) * @see org.junit.platform.engine.discovery.DiscoverySelectors#selectMethod(String, String, Class...) * @see org.junit.platform.engine.discovery.DiscoverySelectors#selectMethod(Class, String, String) * @see org.junit.platform.engine.discovery.DiscoverySelectors#selectMethod(Class, String, Class...) */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited @Documented @API(status = MAINTAINED, since = "1.13.3") @Repeatable(SelectMethods.class) public @interface SelectMethod { /** * The fully qualified method name of the method to select. * *

The following formats are supported. * *

    *
  • {@code [fully qualified class name]#[methodName]}
  • *
  • {@code [fully qualified class name]#[methodName](parameter type list)} *
* *

The parameter type list is a comma-separated list of primitive * names or fully qualified class names for the types of parameters accepted * by the method. * *

Array parameter types may be specified using either the JVM's internal * String representation (e.g., {@code [[I} for {@code int[][]}, * {@code [Ljava.lang.String;} for {@code java.lang.String[]}, etc.) or * source code syntax (e.g., {@code int[][]}, {@code java.lang.String[]}, * etc.). * * * * * * * * * * * * * * * * *
Examples
MethodFully Qualified Method Name
{@code java.lang.String.chars()}{@code java.lang.String#chars}
{@code java.lang.String.chars()}{@code java.lang.String#chars()}
{@code java.lang.String.equalsIgnoreCase(String)}{@code java.lang.String#equalsIgnoreCase(java.lang.String)}
{@code java.lang.String.substring(int, int)}{@code java.lang.String#substring(int, int)}
{@code example.Calc.avg(int[])}{@code example.Calc#avg([I)}
{@code example.Calc.avg(int[])}{@code example.Calc#avg(int[])}
{@code example.Matrix.multiply(double[][])}{@code example.Matrix#multiply([[D)}
{@code example.Matrix.multiply(double[][])}{@code example.Matrix#multiply(double[][])}
{@code example.Service.process(String[])}{@code example.Service#process([Ljava.lang.String;)}
{@code example.Service.process(String[])}{@code example.Service#process(java.lang.String[])}
{@code example.Service.process(String[][])}{@code example.Service#process([[Ljava.lang.String;)}
{@code example.Service.process(String[][])}{@code example.Service#process(java.lang.String[][])}
* *

Cannot be combined with any other attribute. * * @see org.junit.platform.engine.discovery.DiscoverySelectors#selectMethod(String) */ String value() default ""; /** * The class in which the method is declared, or a subclass thereof. * *

Cannot be used in conjunction with {@link #typeName()}. */ Class type() default Class.class; /** * The fully qualified class name in which the method is declared, or a subclass thereof. * *

Cannot be used in conjunction with {@link #type()}. */ String typeName() default ""; /** * The name of the method to select; never blank unless {@link #value()} is used. */ String name() default ""; /** * The parameter types of the method to select. * *

Cannot be used in conjunction with {@link #parameterTypeNames()}. */ Class[] parameterTypes() default {}; /** * The parameter types of the method to select. * *

This is typically a comma-separated list of atomic types, fully * qualified class names, or array types; however, the exact syntax * depends on the underlying test engine. * *

If the method takes no parameters, this attribute must be an * empty string. * *

Array parameter types may be specified using either the JVM's internal * String representation (e.g., {@code [[I} for {@code int[][]}, * {@code [Ljava.lang.String;} for {@code java.lang.String[]}, etc.) or * source code syntax (e.g., {@code int[][]}, {@code java.lang.String[]}, * etc.). * * * * * * * * * * * * * * * *
Examples
MethodParameter types list
{@code java.lang.String.chars()}The empty string
{@code java.lang.String.equalsIgnoreCase(String)}{@code java.lang.String}
{@code java.lang.String.substring(int, int)}{@code int, int}
{@code example.Calc.avg(int[])}{@code [I}
{@code example.Calc.avg(int[])}{@code int[]}
{@code example.Matrix.multiply(double[][])}{@code [[D}
{@code example.Matrix.multiply(double[][])}{@code double[][]}
{@code example.Service.process(String[])}{@code [Ljava.lang.String;}
{@code example.Service.process(String[])}{@code java.lang.String[]}
{@code example.Service.process(String[][])}{@code [[Ljava.lang.String;}
{@code example.Service.process(String[][])}{@code java.lang.String[][]}
* *

Cannot be used in conjunction with {@link #parameterTypes()}. */ String parameterTypeNames() default ""; } ================================================ FILE: junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectMethods.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.api; import static org.apiguardian.api.API.Status.MAINTAINED; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @SelectMethods} is a container for one or more * {@link SelectMethod @SelectMethod} declarations. * *

Note, however, that use of the {@code @SelectMethods} container is * completely optional since {@code @SelectMethod} is a * {@linkplain java.lang.annotation.Repeatable repeatable} annotation. * * @since 1.10 * @see SelectMethod */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited @Documented @API(status = MAINTAINED, since = "1.13.3") public @interface SelectMethods { /** * An array of one or more {@link SelectMethod @SelectMethod} declarations. */ SelectMethod[] value(); } ================================================ FILE: junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectModules.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.api; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.Set; import org.apiguardian.api.API; /** * {@code @SelectModules} specifies the modules to select when running * a test suite on the JUnit Platform. * * @since 1.8 * @see Suite * @see org.junit.platform.engine.discovery.DiscoverySelectors#selectModules(Set) */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited @Documented @API(status = STABLE, since = "1.10") public @interface SelectModules { /** * One or more modules to select. */ String[] value(); } ================================================ FILE: junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectPackages.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.api; import static org.apiguardian.api.API.Status.MAINTAINED; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @SelectPackages} specifies the names of packages to select * when running a test suite on the JUnit Platform. * * @since 1.0 * @see Suite * @see org.junit.platform.engine.discovery.DiscoverySelectors#selectPackage(String) */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited @Documented @API(status = MAINTAINED, since = "1.0") public @interface SelectPackages { /** * One or more fully qualified package names to select. */ String[] value(); } ================================================ FILE: junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectUris.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.api; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @SelectUris} specifies the URIs to select when running a test * suite on the JUnit Platform. * * @since 1.8 * @see Suite * @see org.junit.platform.engine.discovery.DiscoverySelectors#selectUri(String) */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited @Documented @API(status = STABLE, since = "1.10") public @interface SelectUris { /** * One or more URIs to select. */ String[] value(); } ================================================ FILE: junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/Selects.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.api; import static org.apiguardian.api.API.Status.MAINTAINED; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @Selects} is a container for one or more * {@link Select @Select} declarations. * *

Note, however, that use of the {@code @Selects} container is * completely optional since {@code @Select} is a * {@linkplain java.lang.annotation.Repeatable repeatable} annotation. * * @since 1.11 * @see Select */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited @Documented @API(status = MAINTAINED, since = "1.13.3") public @interface Selects { /** * An array of one or more {@link Select @Select} declarations. */ Select[] value(); } ================================================ FILE: junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/Suite.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.api; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apiguardian.api.API; import org.junit.platform.commons.annotation.Testable; /** * {@code @Suite} marks a class as a test suite on the JUnit Platform. * *

Selector and filter annotations are used to control the contents of the * suite. Additionally, configuration can be passed to the suite via the * configuration annotations. * *

When the {@link IncludeClassNamePatterns @IncludeClassNamePatterns} * annotation is not present, the default include pattern * {@value org.junit.platform.engine.discovery.ClassNameFilter#STANDARD_INCLUDE_PATTERN} * will be used in order to avoid loading classes unnecessarily (see {@link * org.junit.platform.engine.discovery.ClassNameFilter#STANDARD_INCLUDE_PATTERN * ClassNameFilter#STANDARD_INCLUDE_PATTERN}). * *

By default a suite discovers tests using the configuration parameters * explicitly configured by {@link ConfigurationParameter @ConfigurationParameter} * and the configuration parameters from the discovery request that discovered * the suite. Annotating a suite with * {@link DisableParentConfigurationParameters @DisableParentConfigurationParameters} * disables the latter as a source of parameters so that only explicit configuration * parameters are taken into account. * *

Note: Depending on the declared selectors, different suites may contain the * same tests, potentially with different configurations. Moreover, tests in a suite * are executed in addition to the tests executed by every other test engine, which * can result in the same tests being executed twice. To prevent that, configure * your build tool to include only the {@code junit-platform-suite} engine, or use * a custom naming pattern. For example, name all suites {@code *Suite} and all * tests {@code *Test}, and configure your build tool to include only the former. * Alternatively, consider using tags to select specific groups of tests. * * @since 1.8 * @see Select * @see SelectClasses * @see SelectClasspathResource * @see SelectDirectories * @see SelectFile * @see SelectModules * @see SelectPackages * @see SelectUris * @see IncludeClassNamePatterns * @see ExcludeClassNamePatterns * @see IncludeEngines * @see ExcludeEngines * @see IncludePackages * @see ExcludePackages * @see IncludeTags * @see ExcludeTags * @see SuiteDisplayName * @see ConfigurationParameter * @see ConfigurationParametersResource * @see DisableParentConfigurationParameters * @see org.junit.platform.launcher.LauncherDiscoveryRequest * @see org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder * @see org.junit.platform.launcher.Launcher */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited @Documented @API(status = STABLE, since = "1.10") @Testable public @interface Suite { /** * Fail suite if no tests were discovered. * * @since 1.9 */ @API(status = STABLE, since = "1.11") boolean failIfNoTests() default true; } ================================================ FILE: junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SuiteDisplayName.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.api; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; import static org.apiguardian.api.API.Status.MAINTAINED; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import org.apiguardian.api.API; /** * {@code @SuiteDisplayName} is used to declare a {@linkplain #value custom * display name} for the annotated test class that is executed as a test suite * on the JUnit Platform. * *

Display names are typically used for test reporting in IDEs and build * tools and may contain spaces, special characters, and even emoji. * *

Execution

*

Test suites can be run on the JUnit Platform the * {@code junit-platform-suite-engine} module. * * @since 1.1 * @see Suite */ @Retention(RUNTIME) @Target(TYPE) @Documented @API(status = MAINTAINED, since = "1.1") public @interface SuiteDisplayName { /** * Custom display name for the annotated class. * * @return a custom display name; never blank or consisting solely of * whitespace */ String value(); } ================================================ FILE: junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Annotations for configuring a test suite on the JUnit Platform. * *

Execution

*

Test suites can be run on the JUnit Platform the * {@code junit-platform-suite-engine} module. */ @NullMarked package org.junit.platform.suite.api; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-suite-api/src/test/README.md ================================================ For compatibility with the Eclipse IDE, the test for this module are in the `platform-tests` project. ================================================ FILE: junit-platform-suite-engine/junit-platform-suite-engine.gradle.kts ================================================ plugins { id("junitbuild.java-library-conventions") } description = "JUnit Platform Suite Engine" dependencies { api(platform(projects.junitBom)) api(projects.junitPlatformEngine) api(projects.junitPlatformSuiteApi) compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) implementation(projects.junitPlatformLauncher) osgiVerification(projects.junitJupiterEngine) osgiVerification(projects.junitPlatformLauncher) } ================================================ FILE: junit-platform-suite-engine/src/main/java/module-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Provides a {@link org.junit.platform.engine.TestEngine} for running * declarative test suites. * * @since 1.8 * @provides org.junit.platform.engine.TestEngine */ module org.junit.platform.suite.engine { requires static org.apiguardian.api; requires static transitive org.jspecify; requires org.junit.platform.suite.api; requires org.junit.platform.commons; requires org.junit.platform.engine; requires org.junit.platform.launcher; provides org.junit.platform.engine.TestEngine with org.junit.platform.suite.engine.SuiteTestEngine; } ================================================ FILE: junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/AdditionalDiscoverySelectors.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.StringUtils; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.discovery.ClasspathResourceSelector; import org.junit.platform.engine.discovery.DirectorySelector; import org.junit.platform.engine.discovery.DiscoverySelectors; import org.junit.platform.engine.discovery.FilePosition; import org.junit.platform.engine.discovery.FileSelector; import org.junit.platform.engine.discovery.ModuleSelector; import org.junit.platform.engine.discovery.PackageSelector; import org.junit.platform.engine.discovery.UriSelector; /** * @since 1.8 */ class AdditionalDiscoverySelectors { static List selectUris(String... uris) { Preconditions.notNull(uris, "URI list must not be null"); Preconditions.containsNoNullElements(uris, "Individual URIs must not be null"); // @formatter:off return uniqueStreamOf(uris) .filter(StringUtils::isNotBlank) .map(DiscoverySelectors::selectUri) .toList(); // @formatter:on } static List selectDirectories(String... paths) { Preconditions.notNull(paths, "Directory paths must not be null"); Preconditions.containsNoNullElements(paths, "Individual directory paths must not be null"); // @formatter:off return uniqueStreamOf(paths) .filter(StringUtils::isNotBlank) .map(DiscoverySelectors::selectDirectory) .toList(); // @formatter:on } static List selectPackages(String... packageNames) { Preconditions.notNull(packageNames, "Package names must not be null"); Preconditions.containsNoNullElements(packageNames, "Individual package names must not be null"); // @formatter:off return uniqueStreamOf(packageNames) .map(DiscoverySelectors::selectPackage) .toList(); // @formatter:on } static List selectModules(String... moduleNames) { Preconditions.notNull(moduleNames, "Module names must not be null"); Preconditions.containsNoNullElements(moduleNames, "Individual module names must not be null"); return DiscoverySelectors.selectModules(uniqueStreamOf(moduleNames).collect(Collectors.toSet())); } static FileSelector selectFile(String path, int line, int column) { Preconditions.notBlank(path, "File path must not be null or blank"); if (line <= 0) { return DiscoverySelectors.selectFile(path); } if (column <= 0) { return DiscoverySelectors.selectFile(path, FilePosition.from(line)); } return DiscoverySelectors.selectFile(path, FilePosition.from(line, column)); } static ClasspathResourceSelector selectClasspathResource(String classpathResourceName, int line, int column) { Preconditions.notBlank(classpathResourceName, "Classpath resource name must not be null or blank"); if (line <= 0) { return DiscoverySelectors.selectClasspathResource(classpathResourceName); } if (column <= 0) { return DiscoverySelectors.selectClasspathResource(classpathResourceName, FilePosition.from(line)); } return DiscoverySelectors.selectClasspathResource(classpathResourceName, FilePosition.from(line, column)); } static List parseIdentifiers(String[] identifiers) { return DiscoverySelectors.parseAll(identifiers).toList(); } private static Stream uniqueStreamOf(T[] elements) { return Arrays.stream(elements).distinct(); } private AdditionalDiscoverySelectors() { } } ================================================ FILE: junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/ClassSelectorResolver.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine; import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.unresolved; import java.util.List; import java.util.Optional; import java.util.function.Predicate; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.EngineDiscoveryListener; import org.junit.platform.engine.OutputDirectoryCreator; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.UniqueId.Segment; import org.junit.platform.engine.discovery.ClassSelector; import org.junit.platform.engine.discovery.UniqueIdSelector; import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter; import org.junit.platform.engine.support.discovery.SelectorResolver; /** * @since 1.8 */ final class ClassSelectorResolver implements SelectorResolver { private final IsSuiteClass isSuiteClass; private final Predicate classNameFilter; private final SuiteEngineDescriptor suiteEngineDescriptor; private final ConfigurationParameters configurationParameters; private final OutputDirectoryCreator outputDirectoryCreator; private final EngineDiscoveryListener discoveryListener; private final DiscoveryIssueReporter issueReporter; ClassSelectorResolver(Predicate classNameFilter, SuiteEngineDescriptor suiteEngineDescriptor, ConfigurationParameters configurationParameters, OutputDirectoryCreator outputDirectoryCreator, EngineDiscoveryListener discoveryListener, DiscoveryIssueReporter issueReporter) { this.isSuiteClass = new IsSuiteClass(issueReporter); this.classNameFilter = classNameFilter; this.suiteEngineDescriptor = suiteEngineDescriptor; this.configurationParameters = configurationParameters; this.outputDirectoryCreator = outputDirectoryCreator; this.discoveryListener = discoveryListener; this.issueReporter = issueReporter; } @Override public Resolution resolve(ClassSelector selector, Context context) { Class testClass = selector.getJavaClass(); if (isSuiteClass.test(testClass)) { if (classNameFilter.test(testClass.getName())) { // @formatter:off Optional suiteWithDiscoveryRequest = context .addToParent(parent -> newSuiteDescriptor(testClass, parent)) .map(suite -> suite.addDiscoveryRequestFrom(testClass)); return toResolution(suiteWithDiscoveryRequest); // @formatter:on } } return unresolved(); } @Override public Resolution resolve(UniqueIdSelector selector, Context context) { UniqueId uniqueId = selector.getUniqueId(); UniqueId engineId = suiteEngineDescriptor.getUniqueId(); List resolvedSegments = engineId.getSegments(); // @formatter:off return uniqueId.getSegments() .stream() .skip(resolvedSegments.size()) .findFirst() .filter(suiteSegment -> SuiteTestDescriptor.SEGMENT_TYPE.equals(suiteSegment.getType())) .flatMap(ClassSelectorResolver::tryLoadSuiteClass) .filter(isSuiteClass) .map(suiteClass -> context .addToParent(parent -> newSuiteDescriptor(suiteClass, parent)) .map(suite -> uniqueId.equals(suite.getUniqueId()) // The uniqueId selector either targeted a class annotated with @Suite; ? suite.addDiscoveryRequestFrom(suiteClass) // or a specific test in that suite : suite.addDiscoveryRequestFrom(uniqueId))) .map(ClassSelectorResolver::toResolution) .orElseGet(Resolution::unresolved); // @formatter:on } private static Optional> tryLoadSuiteClass(UniqueId.Segment segment) { return ReflectionSupport.tryToLoadClass(segment.getValue()).toOptional(); } private static Resolution toResolution(Optional suite) { return suite.map(Match::exact).map(Resolution::match).orElseGet(Resolution::unresolved); } private Optional newSuiteDescriptor(Class suiteClass, TestDescriptor parent) { UniqueId id = parent.getUniqueId().append(SuiteTestDescriptor.SEGMENT_TYPE, suiteClass.getName()); if (containsCycle(id)) { issueReporter.reportIssue( DiscoveryIssue.builder(Severity.INFO, createConfigContainsCycleMessage(suiteClass, id)) // .source(ClassSource.from(suiteClass))); return Optional.empty(); } return Optional.of(new SuiteTestDescriptor(id, suiteClass, configurationParameters, outputDirectoryCreator, discoveryListener, issueReporter)); } private static boolean containsCycle(UniqueId id) { List segments = id.getSegments(); List engineAndSuiteSegment = segments.subList(segments.size() - 2, segments.size()); List ancestorSegments = segments.subList(0, segments.size() - 2); for (int i = 0; i < ancestorSegments.size() - 1; i++) { List candidate = ancestorSegments.subList(i, i + 2); if (engineAndSuiteSegment.equals(candidate)) { return true; } } return false; } private static String createConfigContainsCycleMessage(Class suiteClass, UniqueId suiteId) { return "The suite configuration of [%s] resulted in a cycle [%s] and will not be discovered a second time.".formatted( suiteClass.getName(), suiteId); } } ================================================ FILE: junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/DiscoverySelectorResolver.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter; import org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver; /** * @since 1.8 */ final class DiscoverySelectorResolver { // @formatter:off private static final EngineDiscoveryRequestResolver resolver = EngineDiscoveryRequestResolver.builder() .addClassContainerSelectorResolverWithContext(context -> new IsSuiteClass(context.getIssueReporter())) .addSelectorResolver(context -> new ClassSelectorResolver( context.getClassNameFilter(), context.getEngineDescriptor(), context.getDiscoveryRequest().getConfigurationParameters(), context.getDiscoveryRequest().getOutputDirectoryCreator(), context.getDiscoveryRequest().getDiscoveryListener(), context.getIssueReporter())) .build(); // @formatter:on private static void discoverSuites(SuiteEngineDescriptor engineDescriptor) { // @formatter:off engineDescriptor.getChildren().stream() .map(SuiteTestDescriptor.class::cast) .forEach(SuiteTestDescriptor::discover); // @formatter:on } void resolveSelectors(EngineDiscoveryRequest request, SuiteEngineDescriptor engineDescriptor) { DiscoveryIssueReporter issueReporter = DiscoveryIssueReporter.deduplicating( DiscoveryIssueReporter.forwarding(request.getDiscoveryListener(), engineDescriptor.getUniqueId())); resolver.resolve(request, engineDescriptor, issueReporter); discoverSuites(engineDescriptor); engineDescriptor.accept(TestDescriptor::prune); } } ================================================ FILE: junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/IsSuiteClass.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine; import static org.junit.platform.commons.support.ModifierSupport.isAbstract; import static org.junit.platform.commons.support.ModifierSupport.isNotAbstract; import static org.junit.platform.commons.support.ModifierSupport.isNotPrivate; import java.util.function.Predicate; import org.junit.platform.commons.support.AnnotationSupport; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter.Condition; import org.junit.platform.suite.api.Suite; /** * @since 1.8 */ final class IsSuiteClass implements Predicate> { private final Condition> condition; IsSuiteClass(DiscoveryIssueReporter issueReporter) { this.condition = isNotPrivateUnlessAbstract(issueReporter) // .and(isNotLocal(issueReporter)) // .and(isNotInner(issueReporter)); } @Override public boolean test(Class testClass) { return hasSuiteAnnotation(testClass) // && condition.check(testClass) // && isNotAbstract(testClass); } private boolean hasSuiteAnnotation(Class testClass) { return AnnotationSupport.isAnnotated(testClass, Suite.class); } private static Condition> isNotPrivateUnlessAbstract(DiscoveryIssueReporter issueReporter) { // Allow abstract test classes to be private because @Suite is inherited and subclasses may widen access. return issueReporter.createReportingCondition(testClass -> isNotPrivate(testClass) || isAbstract(testClass), testClass -> createIssue(testClass, "must not be private.")); } private static Condition> isNotLocal(DiscoveryIssueReporter issueReporter) { return issueReporter.createReportingCondition(testClass -> !testClass.isLocalClass(), testClass -> createIssue(testClass, "must not be a local class.")); } private static Condition> isNotInner(DiscoveryIssueReporter issueReporter) { return issueReporter.createReportingCondition(testClass -> !ReflectionUtils.isInnerClass(testClass), testClass -> createIssue(testClass, "must not be an inner class. Did you forget to declare it static?")); } private static DiscoveryIssue createIssue(Class testClass, String detailMessage) { String message = "@Suite class '%s' %s It will not be executed.".formatted(testClass.getName(), detailMessage); return DiscoveryIssue.builder(DiscoveryIssue.Severity.WARNING, message) // .source(ClassSource.from(testClass)) // .build(); } } ================================================ FILE: junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/LifecycleMethodUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotatedMethods; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.List; import org.junit.platform.commons.support.HierarchyTraversalMode; import org.junit.platform.commons.support.ModifierSupport; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.support.descriptor.MethodSource; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter; import org.junit.platform.suite.api.AfterSuite; import org.junit.platform.suite.api.BeforeSuite; /** * Collection of utilities for working with test lifecycle methods. * * @since 1.11 */ final class LifecycleMethodUtils { private LifecycleMethodUtils() { /* no-op */ } static List findBeforeSuiteMethods(Class testClass, DiscoveryIssueReporter issueReporter) { return findMethodsAndCheckStaticAndNonPrivate(testClass, BeforeSuite.class, HierarchyTraversalMode.TOP_DOWN, issueReporter); } static List findAfterSuiteMethods(Class testClass, DiscoveryIssueReporter issueReporter) { return findMethodsAndCheckStaticAndNonPrivate(testClass, AfterSuite.class, HierarchyTraversalMode.BOTTOM_UP, issueReporter); } private static List findMethodsAndCheckStaticAndNonPrivate(Class testClass, Class annotationType, HierarchyTraversalMode traversalMode, DiscoveryIssueReporter issueReporter) { return findAnnotatedMethods(testClass, annotationType, traversalMode).stream() // .filter(// returnsPrimitiveVoid(annotationType, issueReporter) // .and(isStatic(annotationType, issueReporter)) // .and(isNotPrivate(annotationType, issueReporter)) // .and(hasNoParameters(annotationType, issueReporter)) // .toPredicate()) // .toList(); } private static DiscoveryIssueReporter.Condition isStatic(Class annotationType, DiscoveryIssueReporter issueReporter) { return issueReporter.createReportingCondition(ModifierSupport::isStatic, method -> { String message = "@%s method '%s' must be static.".formatted(annotationType.getSimpleName(), method.toGenericString()); return createError(message, method); }); } private static DiscoveryIssueReporter.Condition isNotPrivate(Class annotationType, DiscoveryIssueReporter issueReporter) { return issueReporter.createReportingCondition(ModifierSupport::isNotPrivate, method -> { String message = "@%s method '%s' must not be private.".formatted(annotationType.getSimpleName(), method.toGenericString()); return createError(message, method); }); } private static DiscoveryIssueReporter.Condition returnsPrimitiveVoid( Class annotationType, DiscoveryIssueReporter issueReporter) { return issueReporter.createReportingCondition(ReflectionUtils::returnsPrimitiveVoid, method -> { String message = "@%s method '%s' must not return a value.".formatted(annotationType.getSimpleName(), method.toGenericString()); return createError(message, method); }); } private static DiscoveryIssueReporter.Condition hasNoParameters(Class annotationType, DiscoveryIssueReporter issueReporter) { return issueReporter.createReportingCondition(method -> method.getParameterCount() == 0, method -> { String message = "@%s method '%s' must not accept parameters.".formatted(annotationType.getSimpleName(), method.toGenericString()); return createError(message, method); }); } private static DiscoveryIssue createError(String message, Method method) { return DiscoveryIssue.builder(Severity.ERROR, message).source(MethodSource.from(method)).build(); } } ================================================ FILE: junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/NoTestsDiscoveredException.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine; import java.io.Serial; import org.junit.platform.commons.JUnitException; class NoTestsDiscoveredException extends JUnitException { @Serial private static final long serialVersionUID = 1L; NoTestsDiscoveredException(Class suiteClass) { super("Suite [%s] did not discover any tests".formatted(suiteClass.getName())); } } ================================================ FILE: junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteEngineDescriptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.EngineDescriptor; /** * @since 1.8 */ final class SuiteEngineDescriptor extends EngineDescriptor { static final String ENGINE_ID = "junit-platform-suite"; SuiteEngineDescriptor(UniqueId uniqueId) { super(uniqueId, "JUnit Platform Suite"); } @Override public Type getType() { return Type.CONTAINER; } } ================================================ FILE: junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncher.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine; import static java.util.Collections.emptyList; import java.util.LinkedHashSet; import java.util.Set; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.store.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.core.EngineDiscoveryOrchestrator; import org.junit.platform.launcher.core.EngineExecutionOrchestrator; import org.junit.platform.launcher.core.LauncherDiscoveryResult; import org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry; import org.junit.platform.launcher.listeners.SummaryGeneratingListener; import org.junit.platform.launcher.listeners.TestExecutionSummary; /** * @since 1.8 */ class SuiteLauncher { private final EngineExecutionOrchestrator executionOrchestrator = new EngineExecutionOrchestrator(); private final EngineDiscoveryOrchestrator discoveryOrchestrator; static SuiteLauncher create() { Set engines = new LinkedHashSet<>(); new ServiceLoaderTestEngineRegistry().loadTestEngines().forEach(engines::add); return new SuiteLauncher(engines); } private SuiteLauncher(Set testEngines) { Preconditions.condition(hasTestEngineOtherThanSuiteEngine(testEngines), () -> "Cannot create SuiteLauncher without at least one other TestEngine; " + "consider adding an engine implementation JAR to the classpath"); this.discoveryOrchestrator = new EngineDiscoveryOrchestrator(testEngines, emptyList()); } private boolean hasTestEngineOtherThanSuiteEngine(Set testEngines) { return testEngines.stream().anyMatch(testEngine -> !SuiteEngineDescriptor.ENGINE_ID.equals(testEngine.getId())); } LauncherDiscoveryResult discover(LauncherDiscoveryRequest discoveryRequest, UniqueId parentId) { return discoveryOrchestrator.discover(discoveryRequest, parentId); } TestExecutionSummary execute(LauncherDiscoveryResult discoveryResult, EngineExecutionListener executionListener, NamespacedHierarchicalStore requestLevelStore, CancellationToken cancellationToken) { SummaryGeneratingListener listener = new SummaryGeneratingListener(); executionOrchestrator.execute(discoveryResult, executionListener, listener, requestLevelStore, cancellationToken); return listener.getSummary(); } } ================================================ FILE: junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncherDiscoveryRequestBuilder.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import static org.junit.platform.commons.support.AnnotationSupport.findRepeatableAnnotations; import static org.junit.platform.engine.discovery.ClassNameFilter.STANDARD_INCLUDE_PATTERN; import static org.junit.platform.suite.engine.AdditionalDiscoverySelectors.selectClasspathResource; import static org.junit.platform.suite.engine.AdditionalDiscoverySelectors.selectFile; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.util.Arrays; import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.regex.Pattern; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.StringUtils; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.Filter; import org.junit.platform.engine.OutputDirectoryCreator; import org.junit.platform.engine.discovery.ClassNameFilter; import org.junit.platform.engine.discovery.ClassSelector; import org.junit.platform.engine.discovery.DiscoverySelectors; import org.junit.platform.engine.discovery.MethodSelector; import org.junit.platform.engine.discovery.PackageNameFilter; import org.junit.platform.launcher.EngineFilter; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.TagFilter; import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; import org.junit.platform.suite.api.ConfigurationParameter; import org.junit.platform.suite.api.ConfigurationParametersResource; import org.junit.platform.suite.api.DisableParentConfigurationParameters; import org.junit.platform.suite.api.ExcludeClassNamePatterns; import org.junit.platform.suite.api.ExcludeEngines; import org.junit.platform.suite.api.ExcludePackages; import org.junit.platform.suite.api.ExcludeTags; import org.junit.platform.suite.api.IncludeClassNamePatterns; import org.junit.platform.suite.api.IncludeEngines; import org.junit.platform.suite.api.IncludePackages; import org.junit.platform.suite.api.IncludeTags; import org.junit.platform.suite.api.Select; import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.SelectClasspathResource; import org.junit.platform.suite.api.SelectDirectories; import org.junit.platform.suite.api.SelectFile; import org.junit.platform.suite.api.SelectMethod; import org.junit.platform.suite.api.SelectModules; import org.junit.platform.suite.api.SelectPackages; import org.junit.platform.suite.api.SelectUris; /** * The {@code SuiteLauncherDiscoveryRequestBuilder} provides a light-weight DSL * for generating a {@link LauncherDiscoveryRequest} specifically tailored for * suite execution. * * @since 1.8 (originally in junit-platform-suite-commons) * @see org.junit.platform.engine.discovery.DiscoverySelectors * @see org.junit.platform.engine.discovery.ClassNameFilter * @see org.junit.platform.launcher.EngineFilter * @see org.junit.platform.launcher.TagFilter */ final class SuiteLauncherDiscoveryRequestBuilder { private final LauncherDiscoveryRequestBuilder delegate = LauncherDiscoveryRequestBuilder.request(); private final Set selectedClassNames = new LinkedHashSet<>(); private boolean includeClassNamePatternsUsed; private boolean filterStandardClassNamePatterns = false; private @Nullable ConfigurationParameters parentConfigurationParameters; private boolean enableParentConfigurationParameters = true; private SuiteLauncherDiscoveryRequestBuilder() { } /** * Create a new {@code SuiteLauncherDiscoveryRequestBuilder}. * * @return a new builder */ static SuiteLauncherDiscoveryRequestBuilder request() { return new SuiteLauncherDiscoveryRequestBuilder(); } /** * Add all supplied {@code selectors} to the request. * * @param selectors the {@code DiscoverySelectors} to add; never {@code null} */ void selectors(DiscoverySelector... selectors) { this.delegate.selectors(selectors); } /** * Add all supplied {@code selectors} to the request. * * @param selectors the {@code DiscoverySelectors} to add; never {@code null} */ private void selectors(List selectors) { this.delegate.selectors(selectors); } /** * Add all supplied {@code filters} to the request. *

The {@code filters} are combined using AND semantics, i.e. all of them * have to include a resource for it to end up in the test plan. *

Warning: be cautious when registering multiple competing * {@link EngineFilter#includeEngines include} {@code EngineFilters} or multiple * competing {@link EngineFilter#excludeEngines exclude} {@code EngineFilters} * for the same discovery request since doing so will likely lead to * undesirable results (i.e., zero engines being active). * * @param filters the {@code Filter}s to add; never {@code null} */ private void filters(Filter... filters) { this.delegate.filters(filters); } /** * Specify whether to filter standard class name patterns. *

If set to {@code true}, standard class name patterns are filtered. * * @return this builder for method chaining */ SuiteLauncherDiscoveryRequestBuilder filterStandardClassNamePatterns() { this.filterStandardClassNamePatterns = true; return this; } /** * Add the supplied configuration parameter to the request. * * @param key the configuration parameter key under which to store the * value; never {@code null} or blank * @param value the value to store * @return this builder for method chaining */ SuiteLauncherDiscoveryRequestBuilder configurationParameter(String key, String value) { this.delegate.configurationParameter(key, value); return this; } void configurationParametersResource(String resourceFile) { this.delegate.configurationParametersResources(resourceFile); } /** * Set the parent configuration parameters to use for the request. * *

Any explicit configuration parameters configured via * {@link #configurationParameter(String, String)} takes precedence over the * supplied configuration parameters. * * @param parentConfigurationParameters the parent instance to use for looking * up configuration parameters that have not been explicitly configured; * never {@code null} * @return this builder for method chaining * @see #configurationParameter(String, String) */ SuiteLauncherDiscoveryRequestBuilder parentConfigurationParameters( ConfigurationParameters parentConfigurationParameters) { this.parentConfigurationParameters = parentConfigurationParameters; return this; } /** * Configure whether implicit configuration parameters should be considered. *

By default, in addition to those parameters that are passed explicitly * to this builder, configuration parameters are read from system properties * and from the {@code junit-platform.properties} classpath resource. * Passing {@code false} to this method, disables the latter two sources so * that only explicit configuration parameters are taken into account. * * @return this builder for method chaining * @see #configurationParameter(String, String) */ SuiteLauncherDiscoveryRequestBuilder disableImplicitConfigurationParameters() { this.delegate.enableImplicitConfigurationParameters(false); return this; } SuiteLauncherDiscoveryRequestBuilder outputDirectoryCreator(OutputDirectoryCreator outputDirectoryCreator) { delegate.outputDirectoryCreator(outputDirectoryCreator); return this; } void listener(LauncherDiscoveryListener listener) { delegate.listeners(listener); } /** * Apply a suite's annotation-based configuration to this builder. * *

This will apply the configuration from the following annotations. *

    *
  • {@link ConfigurationParameter}
  • *
  • {@link DisableParentConfigurationParameters}
  • *
* * @param suiteClass the class to apply the configuration annotations from; * never {@code null} * @return this builder for method chaining * @since 1.11 * @see org.junit.platform.suite.api.Suite */ SuiteLauncherDiscoveryRequestBuilder applyConfigurationParametersFromSuite(Class suiteClass) { Preconditions.notNull(suiteClass, "Suite class must not be null"); // @formatter:off findRepeatableAnnotations(suiteClass, ConfigurationParameter.class) .forEach(configuration -> configurationParameter(configuration.key(), configuration.value())); findRepeatableAnnotations(suiteClass, ConfigurationParametersResource.class) .forEach(configResource -> configurationParametersResource(configResource.value())); findAnnotation(suiteClass, DisableParentConfigurationParameters.class) .ifPresent(__ -> this.enableParentConfigurationParameters = false); // @formatter:on return this; } /** * Apply a suite's annotation-based discovery selectors and filters to this * builder. * *

This will apply the configuration from the following annotations. *

    *
  • {@link ExcludeClassNamePatterns}
  • *
  • {@link ExcludeEngines}
  • *
  • {@link ExcludePackages}
  • *
  • {@link ExcludeTags}
  • *
  • {@link IncludeClassNamePatterns}
  • *
  • {@link IncludeEngines}
  • *
  • {@link IncludePackages}
  • *
  • {@link IncludeTags}
  • *
  • {@link SelectClasses}
  • *
  • {@link SelectClasspathResource}
  • *
  • {@link SelectDirectories}
  • *
  • {@link SelectFile}
  • *
  • {@link SelectMethod}
  • *
  • {@link SelectModules}
  • *
  • {@link SelectUris}
  • *
  • {@link SelectPackages}
  • *
  • {@link Select}
  • *
* * @param suiteClass the class to apply the discovery selectors and filter * annotations from; never {@code null} * @return this builder for method chaining * @since 1.11 * @see org.junit.platform.suite.api.Suite */ SuiteLauncherDiscoveryRequestBuilder applySelectorsAndFiltersFromSuite(Class suiteClass) { Preconditions.notNull(suiteClass, "Suite class must not be null"); addExcludeFilters(suiteClass); // Process @SelectClasses and @SelectMethod before @IncludeClassNamePatterns, since the names // of selected classes get automatically added to the include filter. addClassAndMethodSelectors(suiteClass); addIncludeFilters(suiteClass); addOtherSelectors(suiteClass); return this; } private void addExcludeFilters(Class suiteClass) { findAnnotationValues(suiteClass, ExcludeClassNamePatterns.class, ExcludeClassNamePatterns::value) // .flatMap(SuiteLauncherDiscoveryRequestBuilder::stripped) // .map(ClassNameFilter::excludeClassNamePatterns) // .ifPresent(this::filters); findAnnotationValues(suiteClass, ExcludeEngines.class, ExcludeEngines::value) // .map(EngineFilter::excludeEngines) // .ifPresent(this::filters); findAnnotationValues(suiteClass, ExcludePackages.class, ExcludePackages::value) // .map(PackageNameFilter::excludePackageNames) // .ifPresent(this::filters); findAnnotationValues(suiteClass, ExcludeTags.class, ExcludeTags::value) // .map(TagFilter::excludeTags) // .ifPresent(this::filters); } private void addClassAndMethodSelectors(Class suiteClass) { findAnnotation(suiteClass, SelectClasses.class) // .map(annotation -> selectClasses(suiteClass, annotation)) // .ifPresent(this::selectors); findRepeatableAnnotations(suiteClass, SelectMethod.class) // .stream() // .map(annotation -> selectMethod(suiteClass, annotation)) // .forEach(this::selectors); } private void addIncludeFilters(Class suiteClass) { findAnnotationValues(suiteClass, IncludeClassNamePatterns.class, IncludeClassNamePatterns::value) // .flatMap(SuiteLauncherDiscoveryRequestBuilder::stripped) // .map(this::createIncludeClassNameFilter) // .ifPresent(filters -> { this.includeClassNamePatternsUsed = true; filters(filters); }); findAnnotationValues(suiteClass, IncludeEngines.class, IncludeEngines::value) // .map(EngineFilter::includeEngines) // .ifPresent(this::filters); findAnnotationValues(suiteClass, IncludePackages.class, IncludePackages::value) // .map(PackageNameFilter::includePackageNames) // .ifPresent(this::filters); findAnnotationValues(suiteClass, IncludeTags.class, IncludeTags::value) // .map(TagFilter::includeTags) // .ifPresent(this::filters); } private void addOtherSelectors(Class suiteClass) { findRepeatableAnnotations(suiteClass, SelectClasspathResource.class) // .stream() // .map(annotation -> selectClasspathResource(annotation.value(), annotation.line(), annotation.column())) // .forEach(this::selectors); findAnnotationValues(suiteClass, SelectDirectories.class, SelectDirectories::value) // .map(AdditionalDiscoverySelectors::selectDirectories) // .ifPresent(this::selectors); findRepeatableAnnotations(suiteClass, SelectFile.class) // .stream() // .map(annotation -> selectFile(annotation.value(), annotation.line(), annotation.column())) // .forEach(this::selectors); findAnnotationValues(suiteClass, SelectModules.class, SelectModules::value) // .map(AdditionalDiscoverySelectors::selectModules) // .ifPresent(this::selectors); findAnnotationValues(suiteClass, SelectUris.class, SelectUris::value) // .map(AdditionalDiscoverySelectors::selectUris) // .ifPresent(this::selectors); findAnnotationValues(suiteClass, SelectPackages.class, SelectPackages::value) // .map(AdditionalDiscoverySelectors::selectPackages) // .ifPresent(this::selectors); findAnnotationValues(suiteClass, Select.class, Select::value) // .map(AdditionalDiscoverySelectors::parseIdentifiers) // .ifPresent(this::selectors); } /** * Build the {@link LauncherDiscoveryRequest} that has been configured via * this builder. */ LauncherDiscoveryRequest build() { if (this.filterStandardClassNamePatterns && !this.includeClassNamePatternsUsed) { this.delegate.filters(createIncludeClassNameFilter(STANDARD_INCLUDE_PATTERN)); } if (this.enableParentConfigurationParameters && this.parentConfigurationParameters != null) { this.delegate.parentConfigurationParameters(this.parentConfigurationParameters); } return this.delegate.build(); } private List selectClasses(Class suiteClass, SelectClasses annotation) { return toClassSelectors(suiteClass, annotation) // .distinct() // .peek(selector -> this.selectedClassNames.add(selector.getClassName())) // .toList(); } private static Stream toClassSelectors(Class suiteClass, SelectClasses annotation) { Preconditions.condition(annotation.value().length > 0 || annotation.names().length > 0, () -> "@SelectClasses on class [%s] must declare at least one class reference or name".formatted( suiteClass.getName())); return Stream.concat(// DiscoverySelectors.selectClasses(annotation.value()).stream(), // DiscoverySelectors.selectClassesByName(annotation.names()).stream() // ); } private MethodSelector selectMethod(Class suiteClass, SelectMethod annotation) { MethodSelector methodSelector = toMethodSelector(suiteClass, annotation); this.selectedClassNames.add(methodSelector.getClassName()); return methodSelector; } private MethodSelector toMethodSelector(Class suiteClass, SelectMethod annotation) { if (!annotation.value().isEmpty()) { return toMethodSelectorFromFQMN(suiteClass, annotation); } Class type = annotation.type() == Class.class ? null : annotation.type(); String typeName = annotation.typeName().isEmpty() ? null : annotation.typeName().strip(); String methodName = Preconditions.notBlank(annotation.name(), () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass, "method name must not be blank")); Class[] parameterTypes = annotation.parameterTypes().length == 0 ? null : annotation.parameterTypes(); String parameterTypeNames = annotation.parameterTypeNames().strip(); if (parameterTypes != null) { Preconditions.condition(parameterTypeNames.isEmpty(), () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass, "either parameter type names or parameter types must be set but not both")); } return toMethodSelector(suiteClass, type, typeName, parameterTypes, methodName, parameterTypeNames); } private static MethodSelector toMethodSelectorFromFQMN(Class suiteClass, SelectMethod annotation) { Preconditions.condition(annotation.type() == Class.class, () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass, "type must not be set in conjunction with fully qualified method name")); Preconditions.condition(annotation.typeName().isEmpty(), () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass, "type name must not be set in conjunction with fully qualified method name")); Preconditions.condition(annotation.name().isEmpty(), () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass, "method name must not be set in conjunction with fully qualified method name")); Preconditions.condition(annotation.parameterTypes().length == 0, () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass, "parameter types must not be set in conjunction with fully qualified method name")); Preconditions.condition(annotation.parameterTypeNames().isEmpty(), () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass, "parameter type names must not be set in conjunction with fully qualified method name")); return DiscoverySelectors.selectMethod(annotation.value()); } private static MethodSelector toMethodSelector(Class suiteClass, @Nullable Class type, @Nullable String typeName, Class @Nullable [] parameterTypes, String methodName, String parameterTypeNames) { if (type == null) { String nonBlankTypeName = Preconditions.notBlank(typeName, () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass, "type must be set or type name must not be blank")); return parameterTypes == null // ? DiscoverySelectors.selectMethod(nonBlankTypeName, methodName, parameterTypeNames) // : DiscoverySelectors.selectMethod(nonBlankTypeName, methodName, parameterTypes); } else { Preconditions.condition(typeName == null, () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass, "either type name or type must be set but not both")); return parameterTypes == null // ? DiscoverySelectors.selectMethod(type, methodName, parameterTypeNames) // : DiscoverySelectors.selectMethod(type, methodName, parameterTypes); } } private static String prefixErrorMessageForInvalidSelectMethodUsage(Class suiteClass, String detailMessage) { return "@SelectMethod on class [%s]: %s".formatted(suiteClass.getName(), detailMessage); } private ClassNameFilter createIncludeClassNameFilter(String... patterns) { String[] combinedPatterns = Stream.concat(// this.selectedClassNames.stream().map(Pattern::quote), // Arrays.stream(patterns)// ).toArray(String[]::new); return ClassNameFilter.includeClassNamePatterns(combinedPatterns); } private static Optional findAnnotationValues(AnnotatedElement element, Class annotationType, Function valueExtractor) { return findAnnotation(element, annotationType).map(valueExtractor).filter(values -> values.length > 0); } private static Optional stripped(String[] patterns) { if (patterns.length == 0) { return Optional.empty(); } // @formatter:off return Optional.of(Arrays.stream(patterns) .filter(StringUtils::isNotBlank) .map(String::strip) .toArray(String[]::new)); // @formatter:on } } ================================================ FILE: junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestDescriptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine; import static java.util.Objects.requireNonNull; import static java.util.function.Predicate.isEqual; import static java.util.stream.Collectors.joining; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import static org.junit.platform.commons.util.FunctionUtils.where; import static org.junit.platform.suite.engine.SuiteLauncherDiscoveryRequestBuilder.request; import java.lang.reflect.Method; import java.util.List; import java.util.function.BiFunction; import java.util.function.Predicate; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.StringUtils; import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.EngineDiscoveryListener; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.OutputDirectoryCreator; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.UniqueId.Segment; import org.junit.platform.engine.discovery.DiscoverySelectors; import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter; import org.junit.platform.engine.support.hierarchical.OpenTest4JAwareThrowableCollector; import org.junit.platform.engine.support.hierarchical.ThrowableCollector; import org.junit.platform.engine.support.store.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.core.LauncherDiscoveryResult; import org.junit.platform.launcher.listeners.TestExecutionSummary; import org.junit.platform.suite.api.Suite; import org.junit.platform.suite.api.SuiteDisplayName; /** * {@link TestDescriptor} for tests based on the JUnit Platform Suite API. * *

Default Display Names

* *

The default display name is the simple name of the class. * * @since 1.8 * @see SuiteDisplayName */ final class SuiteTestDescriptor extends AbstractTestDescriptor { static final String SEGMENT_TYPE = "suite"; private final SuiteLauncherDiscoveryRequestBuilder discoveryRequestBuilder = request(); private final ConfigurationParameters configurationParameters; private final OutputDirectoryCreator outputDirectoryCreator; private final Boolean failIfNoTests; private final Class suiteClass; private final LifecycleMethods lifecycleMethods; private @Nullable LauncherDiscoveryResult launcherDiscoveryResult; private @Nullable SuiteLauncher launcher; SuiteTestDescriptor(UniqueId id, Class suiteClass, ConfigurationParameters configurationParameters, OutputDirectoryCreator outputDirectoryCreator, EngineDiscoveryListener discoveryListener, DiscoveryIssueReporter issueReporter) { super(id, getSuiteDisplayName(suiteClass, issueReporter), ClassSource.from(suiteClass)); this.configurationParameters = configurationParameters; this.outputDirectoryCreator = outputDirectoryCreator; this.failIfNoTests = getFailIfNoTests(suiteClass); this.suiteClass = suiteClass; this.lifecycleMethods = new LifecycleMethods(suiteClass, issueReporter); this.discoveryRequestBuilder.listener(DiscoveryIssueForwardingListener.create(id, discoveryListener)); } private static Boolean getFailIfNoTests(Class suiteClass) { // @formatter:off return findAnnotation(suiteClass, Suite.class) .map(Suite::failIfNoTests) .orElseThrow(() -> new JUnitException("Suite [%s] was not annotated with @Suite".formatted(suiteClass.getName()))); // @formatter:on } SuiteTestDescriptor addDiscoveryRequestFrom(Class suiteClass) { Preconditions.condition(launcherDiscoveryResult == null, "discovery request cannot be modified after discovery"); discoveryRequestBuilder.applySelectorsAndFiltersFromSuite(suiteClass); return this; } SuiteTestDescriptor addDiscoveryRequestFrom(UniqueId uniqueId) { Preconditions.condition(launcherDiscoveryResult == null, "discovery request cannot be modified after discovery"); discoveryRequestBuilder.selectors(DiscoverySelectors.selectUniqueId(uniqueId)); return this; } void discover() { if (launcherDiscoveryResult != null) { return; } // @formatter:off LauncherDiscoveryRequest request = discoveryRequestBuilder .filterStandardClassNamePatterns() .disableImplicitConfigurationParameters() .parentConfigurationParameters(configurationParameters) .applyConfigurationParametersFromSuite(suiteClass) .outputDirectoryCreator(outputDirectoryCreator) .build(); // @formatter:on this.launcher = SuiteLauncher.create(); this.launcherDiscoveryResult = launcher.discover(request, getUniqueId()); // @formatter:off launcherDiscoveryResult.getTestEngines() .stream() .map(testEngine -> launcherDiscoveryResult.getEngineTestDescriptor(testEngine)) .forEach(this::addChild); // @formatter:on } @Override public Type getType() { return Type.CONTAINER; } private static String getSuiteDisplayName(Class suiteClass, DiscoveryIssueReporter issueReporter) { // @formatter:off var nonBlank = issueReporter.createReportingCondition(StringUtils::isNotBlank, __ -> { String message = "@SuiteDisplayName on %s must be declared with a non-blank value.".formatted( suiteClass.getName()); return DiscoveryIssue.builder(DiscoveryIssue.Severity.WARNING, message) .source(ClassSource.from(suiteClass)) .build(); }).toPredicate(); return findAnnotation(suiteClass, SuiteDisplayName.class) .map(SuiteDisplayName::value) .filter(nonBlank) .orElse(suiteClass.getSimpleName()); // @formatter:on } void execute(EngineExecutionListener executionListener, NamespacedHierarchicalStore requestLevelStore, CancellationToken cancellationToken) { if (cancellationToken.isCancellationRequested()) { executionListener.executionSkipped(this, "Execution cancelled"); return; } executionListener.executionStarted(this); ThrowableCollector throwableCollector = new OpenTest4JAwareThrowableCollector(); executeBeforeSuiteMethods(throwableCollector); TestExecutionSummary summary = executeTests(executionListener, requestLevelStore, cancellationToken, throwableCollector); executeAfterSuiteMethods(throwableCollector); TestExecutionResult testExecutionResult = computeTestExecutionResult(summary, throwableCollector); executionListener.executionFinished(this, testExecutionResult); } private void executeBeforeSuiteMethods(ThrowableCollector throwableCollector) { if (throwableCollector.isNotEmpty()) { return; } for (Method beforeSuiteMethod : lifecycleMethods.beforeSuite) { throwableCollector.execute(() -> ReflectionSupport.invokeMethod(beforeSuiteMethod, null)); if (throwableCollector.isNotEmpty()) { return; } } } private @Nullable TestExecutionSummary executeTests(EngineExecutionListener executionListener, NamespacedHierarchicalStore requestLevelStore, CancellationToken cancellationToken, ThrowableCollector throwableCollector) { if (throwableCollector.isNotEmpty()) { return null; } // #2838: The discovery result from a suite may have been filtered by // post discovery filters from the launcher. The discovery result should // be pruned accordingly. LauncherDiscoveryResult discoveryResult = requireNonNull(this.launcherDiscoveryResult).withRetainedEngines( getChildren()::contains); return requireNonNull(launcher).execute(discoveryResult, executionListener, requestLevelStore, cancellationToken); } private void executeAfterSuiteMethods(ThrowableCollector throwableCollector) { for (Method afterSuiteMethod : lifecycleMethods.afterSuite) { throwableCollector.execute(() -> ReflectionSupport.invokeMethod(afterSuiteMethod, null)); } } private TestExecutionResult computeTestExecutionResult(@Nullable TestExecutionSummary summary, ThrowableCollector throwableCollector) { var throwable = throwableCollector.getThrowable(); if (throwable != null) { return TestExecutionResult.failed(throwable); } if (failIfNoTests && requireNonNull(summary).getTestsFoundCount() == 0) { return TestExecutionResult.failed(new NoTestsDiscoveredException(suiteClass)); } return TestExecutionResult.successful(); } @Override public boolean mayRegisterTests() { // While a suite will not register new tests after discovery, we pretend // it does. This allows the suite to fail if no tests were discovered. // Otherwise, the empty suite would be pruned. return true; } private static class LifecycleMethods { final List beforeSuite; final List afterSuite; LifecycleMethods(Class suiteClass, DiscoveryIssueReporter issueReporter) { beforeSuite = LifecycleMethodUtils.findBeforeSuiteMethods(suiteClass, issueReporter); afterSuite = LifecycleMethodUtils.findAfterSuiteMethods(suiteClass, issueReporter); } } private record DiscoveryIssueForwardingListener(EngineDiscoveryListener discoveryListener, BiFunction issueTransformer) implements LauncherDiscoveryListener { private static final Predicate SUITE_SEGMENTS = where(Segment::getType, isEqual(SEGMENT_TYPE)); static DiscoveryIssueForwardingListener create(UniqueId id, EngineDiscoveryListener discoveryListener) { boolean isNestedSuite = id.getSegments().stream().filter(SUITE_SEGMENTS).count() > 1; if (isNestedSuite) { return new DiscoveryIssueForwardingListener(discoveryListener, (__, issue) -> issue); } return new DiscoveryIssueForwardingListener(discoveryListener, (engineUniqueId, issue) -> issue.withMessage(message -> { String engineId = engineUniqueId.getLastSegment().getValue(); if (SuiteEngineDescriptor.ENGINE_ID.equals(engineId)) { return message; } String suitePath = engineUniqueId.getSegments().stream() // .filter(SUITE_SEGMENTS) // .map(Segment::getValue) // .collect(joining(" > ")); if (message.endsWith(".")) { message = message.substring(0, message.length() - 1); } return "[%s] %s (via @Suite %s).".formatted(engineId, message, suitePath); })); } @Override public void issueEncountered(UniqueId engineUniqueId, DiscoveryIssue issue) { DiscoveryIssue transformedIssue = this.issueTransformer.apply(engineUniqueId, issue); this.discoveryListener.issueEncountered(engineUniqueId, transformedIssue); } } } ================================================ FILE: junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestEngine.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.LinkedHashSet; import java.util.Optional; import org.apiguardian.api.API; import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.store.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; /** * The JUnit Platform Suite {@link org.junit.platform.engine.TestEngine TestEngine}. * * @since 1.8 */ @API(status = INTERNAL, since = "1.8") public final class SuiteTestEngine implements TestEngine { @Override public String getId() { return SuiteEngineDescriptor.ENGINE_ID; } /** * Returns {@code org.junit.platform} as the group ID. */ @Override public Optional getGroupId() { return Optional.of("org.junit.platform"); } /** * Returns {@code junit-platform-suite-engine} as the artifact ID. */ @Override public Optional getArtifactId() { return Optional.of("junit-platform-suite-engine"); } @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { SuiteEngineDescriptor engineDescriptor = new SuiteEngineDescriptor(uniqueId); new DiscoverySelectorResolver().resolveSelectors(discoveryRequest, engineDescriptor); return engineDescriptor; } @Override public void execute(ExecutionRequest request) { SuiteEngineDescriptor suiteEngineDescriptor = (SuiteEngineDescriptor) request.getRootTestDescriptor(); EngineExecutionListener engineExecutionListener = request.getEngineExecutionListener(); NamespacedHierarchicalStore requestLevelStore = request.getStore(); CancellationToken cancellationToken = request.getCancellationToken(); engineExecutionListener.executionStarted(suiteEngineDescriptor); // Create a mutable copy so test descriptors can be made available for // GC immediately after execution. var children = new LinkedHashSet<>(suiteEngineDescriptor.getChildren()); for (var iterator = children.iterator(); iterator.hasNext();) { var suiteTestDescriptor = (SuiteTestDescriptor) iterator.next(); suiteTestDescriptor.execute(engineExecutionListener, requestLevelStore, cancellationToken); iterator.remove(); } engineExecutionListener.executionFinished(suiteEngineDescriptor, TestExecutionResult.successful()); } } ================================================ FILE: junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Core package for the JUnit Platform Suite test engine. */ @NullMarked package org.junit.platform.suite.engine; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-suite-engine/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine ================================================ org.junit.platform.suite.engine.SuiteTestEngine ================================================ FILE: junit-platform-suite-engine/src/test/README.md ================================================ For compatibility with the Eclipse IDE, the test for this module are in the `platform-tests` project. ================================================ FILE: junit-platform-testkit/LICENSE.md ================================================ Eclipse Public License - v 2.0 ============================== THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE (“AGREEMENT”). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. ### 1. Definitions “Contribution” means: * **a)** in the case of the initial Contributor, the initial content Distributed under this Agreement, and * **b)** in the case of each subsequent Contributor: * **i)** changes to the Program, and * **ii)** additions to the Program; where such changes and/or additions to the Program originate from and are Distributed by that particular Contributor. A Contribution “originates” from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include changes or additions to the Program that are not Modified Works. “Contributor” means any person or entity that Distributes the Program. “Licensed Patents” mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. “Program” means the Contributions Distributed in accordance with this Agreement. “Recipient” means anyone who receives the Program under this Agreement or any Secondary License (as applicable), including Contributors. “Derivative Works” shall mean any work, whether in Source Code or other form, that is based on (or derived from) the Program and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. “Modified Works” shall mean any work in Source Code or other form that results from an addition to, deletion from, or modification of the contents of the Program, including, for purposes of clarity any new file in Source Code form that contains any contents of the Program. Modified Works shall not include works that contain only declarations, interfaces, types, classes, structures, or files of the Program solely in each case in order to link to, bind by name, or subclass the Program or Modified Works thereof. “Distribute” means the acts of **a)** distributing or **b)** making available in any manner that enables the transfer of a copy. “Source Code” means the form of a Program preferred for making modifications, including but not limited to software source code, documentation source, and configuration files. “Secondary License” means either the GNU General Public License, Version 2.0, or any later versions of that license, including any exceptions or additional permissions as identified by the initial Contributor. ### 2. Grant of Rights **a)** Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, Distribute and sublicense the Contribution of such Contributor, if any, and such Derivative Works. **b)** Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in Source Code or other form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. **c)** Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to Distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. **d)** Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. **e)** Notwithstanding the terms of any Secondary License, no Contributor makes additional grants to any Recipient (other than those set forth in this Agreement) as a result of such Recipient's receipt of the Program under the terms of a Secondary License (if permitted under the terms of Section 3). ### 3. Requirements **3.1** If a Contributor Distributes the Program in any form, then: * **a)** the Program must also be made available as Source Code, in accordance with section 3.2, and the Contributor must accompany the Program with a statement that the Source Code for the Program is available under this Agreement, and informs Recipients how to obtain it in a reasonable manner on or through a medium customarily used for software exchange; and * **b)** the Contributor may Distribute the Program under a license different than this Agreement, provided that such license: * **i)** effectively disclaims on behalf of all other Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; * **ii)** effectively excludes on behalf of all other Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; * **iii)** does not attempt to limit or alter the recipients' rights in the Source Code under section 3.2; and * **iv)** requires any subsequent distribution of the Program by any party to be under a license that satisfies the requirements of this section 3. **3.2** When the Program is Distributed as Source Code: * **a)** it must be made available under this Agreement, or if the Program **(i)** is combined with other material in a separate file or files made available under a Secondary License, and **(ii)** the initial Contributor attached to the Source Code the notice described in Exhibit A of this Agreement, then the Program may be made available under the terms of such Secondary Licenses, and * **b)** a copy of this Agreement must be included with each copy of the Program. **3.3** Contributors may not remove or alter any copyright, patent, trademark, attribution notices, disclaimers of warranty, or limitations of liability (“notices”) contained within the Program from any copy of the Program which they Distribute, provided that Contributors may add their own appropriate notices. ### 4. Commercial Distribution Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor (“Commercial Contributor”) hereby agrees to defend and indemnify every other Contributor (“Indemnified Contributor”) against any losses, damages and costs (collectively “Losses”) arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: **a)** promptly notify the Commercial Contributor in writing of such claim, and **b)** allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. ### 5. No Warranty EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. ### 6. Disclaimer of Liability EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. ### 7. General If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be Distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to Distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. Nothing in this Agreement is intended to be enforceable by any entity that is not a Contributor or Recipient. No third-party beneficiary rights are created under this Agreement. #### Exhibit A - Form of Secondary Licenses Notice > “This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), version(s), and exceptions or additional permissions here}.” Simply including a copy of this Agreement, including this Exhibit A is not sufficient to license the Source Code under Secondary Licenses. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. ================================================ FILE: junit-platform-testkit/junit-platform-testkit.gradle.kts ================================================ plugins { id("junitbuild.java-library-conventions") } description = "JUnit Platform Test Kit" dependencies { api(platform(projects.junitBom)) api(libs.assertj) api(libs.opentest4j) api(projects.junitPlatformLauncher) compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) osgiVerification(projects.junitJupiterEngine) osgiVerification(projects.junitPlatformLauncher) } ================================================ FILE: junit-platform-testkit/src/main/java/module-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Defines the Test Kit API for the JUnit Platform. * * @since 1.4 * @uses org.junit.platform.engine.TestEngine */ module org.junit.platform.testkit { requires static transitive org.apiguardian.api; requires static transitive org.jspecify; requires transitive org.assertj.core; requires org.junit.platform.commons; requires transitive org.junit.platform.engine; requires transitive org.junit.platform.launcher; requires transitive org.opentest4j; // exports org.junit.platform.testkit; empty package exports org.junit.platform.testkit.engine; uses org.junit.platform.engine.TestEngine; } ================================================ FILE: junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Assertions.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.testkit.engine; import java.util.List; import java.util.Objects; import java.util.stream.Stream; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.StringUtils; import org.junit.platform.commons.util.UnrecoverableExceptions; import org.opentest4j.AssertionFailedError; import org.opentest4j.MultipleFailuresError; /** * {@code Assertions} is a collection of selected assertion utility methods * from JUnit Jupiter for use within the JUnit Platform Test Kit. * * @since 1.4 */ class Assertions { @FunctionalInterface interface Executable { void execute() throws Throwable; } static void assertAll(String heading, Stream executables) { Preconditions.notNull(executables, "executables stream must not be null"); List failures = executables // .map(executable -> { Preconditions.notNull(executable, "individual executables must not be null"); try { executable.execute(); return null; } catch (Throwable t) { UnrecoverableExceptions.rethrowIfUnrecoverable(t); return t; } }) // .filter(Objects::nonNull) // .toList(); if (!failures.isEmpty()) { MultipleFailuresError multipleFailuresError = new MultipleFailuresError(heading, failures); failures.forEach(multipleFailuresError::addSuppressed); throw multipleFailuresError; } } static void assertEquals(long expected, long actual, String message) { if (expected != actual) { failNotEqual(expected, actual, message); } } private static void failNotEqual(long expected, long actual, String message) { fail(format(expected, actual, message), expected, actual); } private static void fail(String message, Object expected, Object actual) { throw new AssertionFailedError(message, expected, actual); } private static String format(long expected, long actual, String message) { return buildPrefix(message) + formatValues(expected, actual); } private static String buildPrefix(String message) { return (StringUtils.isNotBlank(message) ? message + " ==> " : ""); } private static String formatValues(long expected, long actual) { return "expected: <%d> but was: <%d>".formatted(expected, actual); } private Assertions() { } } ================================================ FILE: junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineDiscoveryResults.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.testkit.engine; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import java.util.List; import org.apiguardian.api.API; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.TestDescriptor; /** * {@code EngineDiscoveryResults} represents the results of test discovery * by a {@link org.junit.platform.engine.TestEngine TestEngine} on the JUnit * Platform and provides access to the {@link TestDescriptor} of the engine * and any {@link DiscoveryIssue DiscoveryIssues} that were encountered. * * @since 1.13 */ @API(status = EXPERIMENTAL, since = "6.0") public class EngineDiscoveryResults { private final TestDescriptor engineDescriptor; private final List discoveryIssues; EngineDiscoveryResults(TestDescriptor engineDescriptor, List discoveryIssues) { this.engineDescriptor = Preconditions.notNull(engineDescriptor, "Engine descriptor must not be null"); this.discoveryIssues = List.copyOf( Preconditions.notNull(discoveryIssues, "Discovery issues list must not be null")); Preconditions.containsNoNullElements(discoveryIssues, "Discovery issues list must not contain null elements"); } /** * {@return the root {@link TestDescriptor} of the engine} */ public TestDescriptor getEngineDescriptor() { return engineDescriptor; } /** * {@return the issues that were encountered during discovery} */ public List getDiscoveryIssues() { return discoveryIssues; } } ================================================ FILE: junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineExecutionResults.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.testkit.engine; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.junit.platform.testkit.engine.Event.byTestDescriptor; import java.util.List; import java.util.function.Predicate; import java.util.stream.Stream; import org.apiguardian.api.API; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.TestDescriptor; /** * {@code EngineExecutionResults} provides a fluent API for processing the * results of executing a test plan on the JUnit Platform for a given * {@link org.junit.platform.engine.TestEngine TestEngine}. * * @since 1.4 * @see #allEvents() * @see #containerEvents() * @see #testEvents() * @see ExecutionRecorder * @see Events * @see Executions */ @API(status = MAINTAINED, since = "1.7") public class EngineExecutionResults { private final Events allEvents; private final Events testEvents; private final Events containerEvents; /** * Construct {@link EngineExecutionResults} from the supplied list of recorded * {@linkplain Event events}. * * @param events the list of events; never {@code null} or * containing {@code null} elements */ EngineExecutionResults(List events) { Preconditions.notNull(events, "Event list must not be null"); Preconditions.containsNoNullElements(events, "Event list must not contain null elements"); this.allEvents = new Events(events, "All"); this.testEvents = new Events(filterEvents(events, TestDescriptor::isTest), "Test"); this.containerEvents = new Events(filterEvents(events, TestDescriptor::isContainer), "Container"); } /** * Get all recorded events. * * @since 1.6 * @see #containerEvents() * @see #testEvents() */ public Events allEvents() { return this.allEvents; } /** * Get recorded events for containers. * *

In this context, the word "container" applies to {@link TestDescriptor * TestDescriptors} that return {@code true} from {@link TestDescriptor#isContainer()}. * * @since 1.6 * @see #allEvents() * @see #testEvents() */ public Events containerEvents() { return this.containerEvents; } /** * Get recorded events for tests. * *

In this context, the word "test" applies to {@link TestDescriptor * TestDescriptors} that return {@code true} from {@link TestDescriptor#isTest()}. * * @since 1.6 * @see #allEvents() * @see #containerEvents() */ public Events testEvents() { return this.testEvents; } /** * Filter the supplied list of events using the supplied predicate. */ private static Stream filterEvents(List events, Predicate predicate) { return events.stream().filter(byTestDescriptor(predicate)); } } ================================================ FILE: junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.testkit.engine; import static java.util.Collections.emptySet; import static java.util.Collections.singleton; import static java.util.Objects.requireNonNullElseGet; import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import static org.junit.platform.engine.support.store.NamespacedHierarchicalStore.CloseAction.closeAutoCloseables; import java.nio.file.Path; import java.util.List; import java.util.Map; import java.util.ServiceLoader; import java.util.function.Consumer; import java.util.stream.Stream; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.CollectionUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.DiscoveryFilter; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.Filter; import org.junit.platform.engine.OutputDirectoryCreator; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.support.store.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.core.EngineDiscoveryOrchestrator; import org.junit.platform.launcher.core.EngineExecutionOrchestrator; import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; import org.junit.platform.launcher.core.LauncherDiscoveryResult; import org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry; /** * {@code EngineTestKit} provides support for discovering and executing tests * for a given {@link TestEngine} and provides convenient access to the results. * *

For discovery, {@link EngineDiscoveryResults} provides access to * the {@link TestDescriptor} of the engine and any {@link DiscoveryIssue * DiscoveryIssues} that were encountered. * *

For execution, {@link EngineExecutionResults} provides a fluent * API to verify the expected results. * * @since 1.4 * @see #engine(String) * @see #engine(TestEngine) * @see #discover(String, LauncherDiscoveryRequest) * @see #discover(TestEngine, LauncherDiscoveryRequest) * @see #execute(String, LauncherDiscoveryRequest) * @see #execute(TestEngine, LauncherDiscoveryRequest) * @see EngineDiscoveryResults * @see EngineExecutionResults */ @API(status = MAINTAINED, since = "1.7") public final class EngineTestKit { /** * Create an execution {@link Builder} for the {@link TestEngine} with the * supplied ID. * *

The {@code TestEngine} will be loaded via Java's {@link ServiceLoader} * mechanism, analogous to the manner in which test engines are loaded in * the JUnit Platform Launcher API. * *

Example Usage

* *
	 * EngineTestKit
	 *     .engine("junit-jupiter")
	 *     .selectors(selectClass(MyTests.class))
	 *     .execute()
	 *     .testEvents()
	 *     .assertStatistics(stats -> stats.started(2).finished(2));
	 * 
* * @param engineId the ID of the {@code TestEngine} to use; must not be * {@code null} or blank * @return the engine execution {@code Builder} * @throws PreconditionViolationException if the supplied ID is {@code null} * or blank, or if the {@code TestEngine} with the supplied ID * cannot be loaded * @see #engine(TestEngine) * @see #execute(String, LauncherDiscoveryRequest) * @see #execute(TestEngine, LauncherDiscoveryRequest) */ public static Builder engine(String engineId) { Preconditions.notBlank(engineId, "TestEngine ID must not be null or blank"); return engine(loadTestEngine(engineId.strip())); } /** * Create an execution {@link Builder} for the supplied {@link TestEngine}. * *

Example Usage

* *
	 * EngineTestKit
	 *     .engine(new MyTestEngine())
	 *     .selectors(selectClass(MyTests.class))
	 *     .execute()
	 *     .testEvents()
	 *     .assertStatistics(stats -> stats.started(2).finished(2));
	 * 
* * @param testEngine the {@code TestEngine} to use; must not be {@code null} * @return the engine execution {@code Builder} * @throws PreconditionViolationException if the {@code TestEngine} is * {@code null} * @see #engine(String) * @see #execute(String, LauncherDiscoveryRequest) * @see #execute(TestEngine, LauncherDiscoveryRequest) */ public static Builder engine(TestEngine testEngine) { Preconditions.notNull(testEngine, "TestEngine must not be null"); return new Builder(testEngine); } /** * Discover tests for the given {@link LauncherDiscoveryRequest} using the * {@link TestEngine} with the supplied ID. * *

The {@code TestEngine} will be loaded via Java's {@link ServiceLoader} * mechanism, analogous to the manner in which test engines are loaded in * the JUnit Platform Launcher API. * *

{@link org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder} * provides a convenient way to build an appropriate discovery request to * supply to this method. As an alternative, consider using * {@link #engine(TestEngine)} for a more fluent API. * * @param engineId the ID of the {@code TestEngine} to use; must not be * {@code null} or blank * @param discoveryRequest the {@code LauncherDiscoveryRequest} to use * @return the results of the discovery * @throws PreconditionViolationException for invalid arguments or if the * {@code TestEngine} with the supplied ID cannot be loaded * @since 1.13 * @see #discover(TestEngine, LauncherDiscoveryRequest) * @see #engine(String) * @see #engine(TestEngine) */ @API(status = EXPERIMENTAL, since = "6.0") public static EngineDiscoveryResults discover(String engineId, LauncherDiscoveryRequest discoveryRequest) { Preconditions.notBlank(engineId, "TestEngine ID must not be null or blank"); return discover(loadTestEngine(engineId.strip()), discoveryRequest); } /** * Discover tests for the given {@link LauncherDiscoveryRequest} using the * supplied {@link TestEngine}. * *

{@link org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder} * provides a convenient way to build an appropriate discovery request to * supply to this method. As an alternative, consider using * {@link #engine(TestEngine)} for a more fluent API. * * @param testEngine the {@code TestEngine} to use; must not be {@code null} * @param discoveryRequest the {@code EngineDiscoveryResults} to use; must * not be {@code null} * @return the recorded {@code EngineExecutionResults} * @throws PreconditionViolationException for invalid arguments * @since 1.13 * @see #discover(String, LauncherDiscoveryRequest) * @see #engine(String) * @see #engine(TestEngine) */ @API(status = EXPERIMENTAL, since = "6.0") public static EngineDiscoveryResults discover(TestEngine testEngine, LauncherDiscoveryRequest discoveryRequest) { Preconditions.notNull(testEngine, "TestEngine must not be null"); Preconditions.notNull(discoveryRequest, "EngineDiscoveryRequest must not be null"); LauncherDiscoveryResult discoveryResult = discoverUsingOrchestrator(testEngine, discoveryRequest); TestDescriptor engineDescriptor = discoveryResult.getEngineTestDescriptor(testEngine); List discoveryIssues = discoveryResult.getDiscoveryIssues(testEngine); return new EngineDiscoveryResults(engineDescriptor, discoveryIssues); } /** * Execute tests for the given {@link LauncherDiscoveryRequest} using the * {@link TestEngine} with the supplied ID. * *

The {@code TestEngine} will be loaded via Java's {@link ServiceLoader} * mechanism, analogous to the manner in which test engines are loaded in * the JUnit Platform Launcher API. * *

{@link org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder} * provides a convenient way to build an appropriate discovery request to * supply to this method. As an alternative, consider using * {@link #engine(TestEngine)} for a more fluent API. * * @param engineId the ID of the {@code TestEngine} to use; must not be * {@code null} or blank * @param discoveryRequest the {@code LauncherDiscoveryRequest} to use * @return the results of the execution * @throws PreconditionViolationException for invalid arguments or if the * {@code TestEngine} with the supplied ID cannot be loaded * @since 1.7 * @see #execute(TestEngine, LauncherDiscoveryRequest) * @see #engine(String) * @see #engine(TestEngine) */ public static EngineExecutionResults execute(String engineId, LauncherDiscoveryRequest discoveryRequest) { Preconditions.notBlank(engineId, "TestEngine ID must not be null or blank"); return execute(loadTestEngine(engineId.strip()), discoveryRequest); } /** * Execute tests for the given {@link LauncherDiscoveryRequest} using the * supplied {@link TestEngine}. * *

{@link org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder} * provides a convenient way to build an appropriate discovery request to * supply to this method. As an alternative, consider using * {@link #engine(TestEngine)} for a more fluent API. * * @param testEngine the {@code TestEngine} to use; must not be {@code null} * @param discoveryRequest the {@code LauncherDiscoveryRequest} to use; must * not be {@code null} * @return the recorded {@code EngineExecutionResults} * @throws PreconditionViolationException for invalid arguments * @since 1.7 * @see #execute(String, LauncherDiscoveryRequest) * @see #engine(String) * @see #engine(TestEngine) */ public static EngineExecutionResults execute(TestEngine testEngine, LauncherDiscoveryRequest discoveryRequest) { Preconditions.notNull(testEngine, "TestEngine must not be null"); Preconditions.notNull(discoveryRequest, "EngineDiscoveryRequest must not be null"); ExecutionRecorder executionRecorder = new ExecutionRecorder(); executeUsingLauncherOrchestration(testEngine, discoveryRequest, executionRecorder, CancellationToken.disabled()); return executionRecorder.getExecutionResults(); } private static void executeUsingLauncherOrchestration(TestEngine testEngine, LauncherDiscoveryRequest discoveryRequest, EngineExecutionListener engineExecutionListener, CancellationToken cancellationToken) { LauncherDiscoveryResult discoveryResult = discoverUsingOrchestrator(testEngine, discoveryRequest); TestDescriptor engineTestDescriptor = discoveryResult.getEngineTestDescriptor(testEngine); Preconditions.notNull(engineTestDescriptor, "TestEngine did not yield a TestDescriptor"); TestExecutionListener noopTestExecutionListener = new TestExecutionListener() { }; withRequestLevelStore(store -> new EngineExecutionOrchestrator().execute(discoveryResult, engineExecutionListener, noopTestExecutionListener, store, cancellationToken)); } private static void withRequestLevelStore(Consumer> action) { try (NamespacedHierarchicalStore sessionLevelStore = newStore(null); NamespacedHierarchicalStore requestLevelStore = newStore(sessionLevelStore)) { action.accept(requestLevelStore); } } private static NamespacedHierarchicalStore newStore( @Nullable NamespacedHierarchicalStore parentStore) { return new NamespacedHierarchicalStore<>(parentStore, closeAutoCloseables()); } private static LauncherDiscoveryResult discoverUsingOrchestrator(TestEngine testEngine, LauncherDiscoveryRequest discoveryRequest) { return new EngineDiscoveryOrchestrator(singleton(testEngine), emptySet()) // .discover(discoveryRequest); } @SuppressWarnings("unchecked") private static TestEngine loadTestEngine(String engineId) { Iterable testEngines = new ServiceLoaderTestEngineRegistry().loadTestEngines(); return ((Stream) CollectionUtils.toStream(testEngines)) // .filter((TestEngine engine) -> engineId.equals(engine.getId()))// .findFirst()// .orElseThrow(() -> new PreconditionViolationException( "Failed to load TestEngine with ID [%s]".formatted(engineId))); } private EngineTestKit() { /* no-op */ } // ------------------------------------------------------------------------- /** * {@link TestEngine} execution builder. * *

See {@link EngineTestKit#engine(String)} and * {@link EngineTestKit#engine(TestEngine)} for example usage. * * @since 1.4 * @see #selectors(DiscoverySelector...) * @see #filters(Filter...) * @see #configurationParameter(String, String) * @see #configurationParameters(Map) * @see #execute() */ public static final class Builder { private final LauncherDiscoveryRequestBuilder requestBuilder = LauncherDiscoveryRequestBuilder.request() // .enableImplicitConfigurationParameters(false) // .outputDirectoryCreator(DisabledOutputDirectoryCreator.INSTANCE); private final TestEngine testEngine; private @Nullable CancellationToken cancellationToken; private Builder(TestEngine testEngine) { this.testEngine = testEngine; } /** * Add all of the supplied {@linkplain DiscoverySelector discovery selectors}. * *

Built-in discovery selectors can be created via the static factory * methods in {@link org.junit.platform.engine.discovery.DiscoverySelectors}. * * @param selectors the discovery selectors to add; never {@code null} * @return this builder for method chaining * @see #selectors(List) * @see #filters(Filter...) * @see #configurationParameter(String, String) * @see #configurationParameters(Map) * @see #execute() */ public Builder selectors(DiscoverySelector... selectors) { this.requestBuilder.selectors(selectors); return this; } /** * Add all of the supplied {@linkplain DiscoverySelector discovery selectors}. * *

Built-in discovery selectors can be created via the static factory * methods in {@link org.junit.platform.engine.discovery.DiscoverySelectors}. * * @param selectors the discovery selectors to add; never {@code null} * @return this builder for method chaining * @since 6.0 * @see #selectors(DiscoverySelector...) * @see #filters(Filter...) * @see #configurationParameter(String, String) * @see #configurationParameters(Map) * @see #execute() */ @API(status = MAINTAINED, since = "6.0") public Builder selectors(List selectors) { this.requestBuilder.selectors(selectors); return this; } /** * Add all of the supplied {@linkplain Filter filters}. * *

Built-in discovery filters can be created via the static factory * methods in {@link org.junit.platform.engine.discovery.ClassNameFilter} * and {@link org.junit.platform.engine.discovery.PackageNameFilter}. * *

Built-in post-discovery filters can be created via the static * factory methods in {@link org.junit.platform.launcher.TagFilter}. * * @param filters the filters to add; never {@code null} * @return this builder for method chaining * @since 1.7 * @see #selectors(DiscoverySelector...) * @see #configurationParameter(String, String) * @see #configurationParameters(Map) * @see #execute() */ @API(status = STABLE, since = "1.10") public Builder filters(Filter... filters) { this.requestBuilder.filters(filters); return this; } /** * Add the supplied configuration parameter. * * @param key the configuration parameter key under which to store the * value; never {@code null} or blank * @param value the value to store * @return this builder for method chaining * @see #selectors(DiscoverySelector...) * @see #filters(Filter...) * @see #configurationParameters(Map) * @see #execute() * @see org.junit.platform.engine.ConfigurationParameters */ public Builder configurationParameter(String key, String value) { this.requestBuilder.configurationParameter(key, value); return this; } /** * Add all of the supplied configuration parameters. * * @param configurationParameters the map of configuration parameters to add; * never {@code null} * @return this builder for method chaining * @see #selectors(DiscoverySelector...) * @see #filters(Filter...) * @see #configurationParameter(String, String) * @see #execute() * @see org.junit.platform.engine.ConfigurationParameters */ public Builder configurationParameters(Map configurationParameters) { this.requestBuilder.configurationParameters(configurationParameters); return this; } /** * Configure whether implicit configuration parameters should be * considered. * *

By default, only configuration parameters that are passed * explicitly to this builder are taken into account. Passing * {@code true} to this method, enables additionally reading * configuration parameters from implicit sources, i.e. system * properties and the {@code junit-platform.properties} classpath * resource. * * @see #configurationParameter(String, String) * @see #configurationParameters(Map) */ @API(status = STABLE, since = "1.10") public Builder enableImplicitConfigurationParameters(boolean enabled) { this.requestBuilder.enableImplicitConfigurationParameters(enabled); return this; } /** * Set the * {@link org.junit.platform.engine.reporting.OutputDirectoryProvider} * to use. * *

If not specified, a default provider will be used that throws an * exception when attempting to create output directories. This is done * to avoid accidentally writing output files to the file system. * * @param outputDirectoryProvider the output directory provider to use; * never {@code null} * @return this builder for method chaining * @since 1.12 * @see org.junit.platform.engine.reporting.OutputDirectoryProvider * @deprecated Please use * {@link #outputDirectoryCreator(OutputDirectoryCreator)} instead */ @SuppressWarnings("removal") @Deprecated(since = "1.14", forRemoval = true) @API(status = DEPRECATED, since = "1.14") public Builder outputDirectoryProvider( org.junit.platform.engine.reporting.OutputDirectoryProvider outputDirectoryProvider) { return outputDirectoryCreator(outputDirectoryProvider); } /** * Set the {@link OutputDirectoryCreator} to use. * *

If not specified, a default implementation will be used that * throws an exception when attempting to create output directories. * This is done to avoid accidentally writing output files to the file * system. * * @param outputDirectoryCreator the output directory creator to use; * never {@code null} * @return this builder for method chaining * @since 1.12 * @see OutputDirectoryCreator */ @API(status = MAINTAINED, since = "1.14") public Builder outputDirectoryCreator(OutputDirectoryCreator outputDirectoryCreator) { this.requestBuilder.outputDirectoryCreator(outputDirectoryCreator); return this; } /** * Set the {@link CancellationToken} to use for execution. * * @param cancellationToken the cancellation token to use; never * {@code null} * @return this builder for method chaining * @since 6.0 * @see CancellationToken */ @API(status = EXPERIMENTAL, since = "6.0") public Builder cancellationToken(CancellationToken cancellationToken) { this.cancellationToken = Preconditions.notNull(cancellationToken, "cancellationToken must not be null"); return this; } /** * Discover tests for the configured {@link TestEngine}, * {@linkplain DiscoverySelector discovery selectors}, * {@linkplain DiscoveryFilter discovery filters}, and * configuration parameters. * * @return the recorded {@code EngineDiscoveryResults} * @since 1.13 * @see #selectors(DiscoverySelector...) * @see #filters(Filter...) * @see #configurationParameter(String, String) * @see #configurationParameters(Map) */ @API(status = EXPERIMENTAL, since = "6.0") public EngineDiscoveryResults discover() { LauncherDiscoveryRequest request = this.requestBuilder.build(); return EngineTestKit.discover(this.testEngine, request); } /** * Execute tests for the configured {@link TestEngine}, * {@linkplain DiscoverySelector discovery selectors}, * {@linkplain DiscoveryFilter discovery filters}, and * configuration parameters. * * @return the recorded {@code EngineExecutionResults} * @see #selectors(DiscoverySelector...) * @see #filters(Filter...) * @see #configurationParameter(String, String) * @see #configurationParameters(Map) */ public EngineExecutionResults execute() { LauncherDiscoveryRequest request = this.requestBuilder.build(); ExecutionRecorder executionRecorder = new ExecutionRecorder(); EngineTestKit.executeUsingLauncherOrchestration(this.testEngine, request, executionRecorder, requireNonNullElseGet(this.cancellationToken, CancellationToken::disabled)); return executionRecorder.getExecutionResults(); } private static class DisabledOutputDirectoryCreator implements OutputDirectoryCreator { private static final OutputDirectoryCreator INSTANCE = new DisabledOutputDirectoryCreator(); private static final String FAILURE_MESSAGE = "Writing outputs is disabled by default when using EngineTestKit. " + "To enable, configure a custom OutputDirectoryCreator via EngineTestKit#outputDirectoryCreator."; private DisabledOutputDirectoryCreator() { } @Override public Path getRootDirectory() { throw new JUnitException(FAILURE_MESSAGE); } @Override public Path createOutputDirectory(TestDescriptor testDescriptor) { throw new JUnitException(FAILURE_MESSAGE); } } } } ================================================ FILE: junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Event.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.testkit.engine; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.junit.platform.commons.util.FunctionUtils.where; import java.time.Instant; import java.util.Optional; import java.util.function.Predicate; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; /** * {@code Event} represents a single event fired during execution of * a test plan on the JUnit Platform. * * @since 1.4 * @see EventType */ @API(status = MAINTAINED, since = "1.7") public class Event { // --- Factories ----------------------------------------------------------- /** * Create an {@code Event} for a reporting entry published for the * supplied {@link TestDescriptor} and {@link ReportEntry}. * * @param testDescriptor the {@code TestDescriptor} associated with the event; * never {@code null} * @param entry the {@code ReportEntry} that was published; never {@code null} * @return the newly created {@code Event} * @see EventType#REPORTING_ENTRY_PUBLISHED */ public static Event reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry entry) { Preconditions.notNull(entry, "ReportEntry must not be null"); return new Event(EventType.REPORTING_ENTRY_PUBLISHED, testDescriptor, entry); } /** * Create an {@code Event} for a published file for the supplied * {@link TestDescriptor} and {@link FileEntry}. * * @param testDescriptor the {@code TestDescriptor} associated with the event; * never {@code null} * @param file the {@code FileEntry} that was published; never {@code null} * @return the newly created {@code Event} * @since 1.12 * @see EventType#FILE_ENTRY_PUBLISHED */ @API(status = MAINTAINED, since = "1.13.3") public static Event fileEntryPublished(TestDescriptor testDescriptor, FileEntry file) { Preconditions.notNull(file, "FileEntry must not be null"); return new Event(EventType.FILE_ENTRY_PUBLISHED, testDescriptor, file); } /** * Create an {@code Event} for the dynamic registration of the * supplied {@link TestDescriptor}. * * @param testDescriptor the {@code TestDescriptor} associated with the event; * never {@code null} * @return the newly created {@code Event} * @see EventType#DYNAMIC_TEST_REGISTERED */ public static Event dynamicTestRegistered(TestDescriptor testDescriptor) { return new Event(EventType.DYNAMIC_TEST_REGISTERED, testDescriptor, null); } /** * Create a skipped {@code Event} for the supplied * {@link TestDescriptor} and {@code reason}. * * @param testDescriptor the {@code TestDescriptor} associated with the event; * never {@code null} * @param reason the reason the execution was skipped; may be {@code null} * @return the newly created {@code Event} * @see EventType#SKIPPED */ public static Event executionSkipped(TestDescriptor testDescriptor, @Nullable String reason) { return new Event(EventType.SKIPPED, testDescriptor, reason); } /** * Create a started {@code Event} for the supplied * {@link TestDescriptor}. * * @param testDescriptor the {@code TestDescriptor} associated with the event; * never {@code null} * @return the newly created {@code Event} * @see EventType#STARTED */ public static Event executionStarted(TestDescriptor testDescriptor) { return new Event(EventType.STARTED, testDescriptor, null); } /** * Create a finished {@code Event} for the supplied * {@link TestDescriptor} and {@link TestExecutionResult}. * * @param testDescriptor the {@code TestDescriptor} associated with the event; * never {@code null} * @param result the {@code TestExecutionResult} for the supplied * {@code TestDescriptor}; never {@code null} * @return the newly created {@code Event} * @see EventType#FINISHED */ public static Event executionFinished(TestDescriptor testDescriptor, TestExecutionResult result) { Preconditions.notNull(result, "Event of type FINISHED cannot have a null TestExecutionResult"); return new Event(EventType.FINISHED, testDescriptor, result); } // --- Predicates ---------------------------------------------------------- /** * Create a {@link Predicate} for {@linkplain Event events} whose payload * types match the supplied {@code payloadType} and whose payloads match the * supplied {@code payloadPredicate}. * * @param payloadType the required payload type * @param payloadPredicate a {@code Predicate} to match against payloads * @return the resulting {@code Predicate} */ public static Predicate byPayload(Class payloadType, Predicate payloadPredicate) { return event -> event.getPayload(payloadType).filter(payloadPredicate).isPresent(); } /** * Create a {@link Predicate} for {@linkplain Event events} whose * {@linkplain EventType event types} match the supplied {@code type}. * * @param type the type to match against * @return the resulting {@code Predicate} */ public static Predicate byType(EventType type) { return event -> event.type.equals(type); } /** * Create a {@link Predicate} for {@linkplain Event events} whose * {@link TestDescriptor TestDescriptors} match the supplied * {@code testDescriptorPredicate}. * * @param testDescriptorPredicate a {@code Predicate} to match against test * descriptors * @return the resulting {@link Predicate} */ public static Predicate byTestDescriptor(Predicate testDescriptorPredicate) { return where(Event::getTestDescriptor, testDescriptorPredicate); } // ------------------------------------------------------------------------- private final Instant timestamp = Instant.now(); private final EventType type; private final TestDescriptor testDescriptor; private final @Nullable Object payload; /** * Construct an {@code Event} with the supplied arguments. * * @param type the type of the event; never {@code null} * @param testDescriptor the {@code TestDescriptor} associated with the event; * never {@code null} * @param payload the generic payload associated with the event; may be {@code null} */ private Event(EventType type, TestDescriptor testDescriptor, @Nullable Object payload) { this.type = Preconditions.notNull(type, "EventType must not be null"); this.testDescriptor = Preconditions.notNull(testDescriptor, "TestDescriptor must not be null"); this.payload = payload; } /** * Get the type of this {@code Event}. * * @return the event type; never {@code null} * @see EventType */ public EventType getType() { return this.type; } /** * Get the {@link TestDescriptor} associated with this {@code Event}. * * @return the {@code TestDescriptor}; never {@code null} */ public TestDescriptor getTestDescriptor() { return this.testDescriptor; } /** * Get the {@link Instant} when this {@code Event} occurred. * * @return the {@code Instant} when this {@code Event} occurred; * never {@code null} */ public Instant getTimestamp() { return this.timestamp; } /** * Get the payload, if available. * * @return an {@code Optional} containing the payload; never {@code null} * but potentially empty * @see #getPayload(Class) * @see #getRequiredPayload(Class) */ public Optional getPayload() { return Optional.ofNullable(this.payload); } /** * Get the payload of the expected type, if available. * *

This is a convenience method that automatically casts the payload to * the expected type. If the payload is not present or is not of the expected * type, this method will return {@link Optional#empty()}. * * @param payloadType the expected payload type; never {@code null} * @return an {@code Optional} containing the payload; never {@code null} * but potentially empty * @see #getPayload() * @see #getRequiredPayload(Class) */ public Optional getPayload(Class payloadType) { Preconditions.notNull(payloadType, "Payload type must not be null"); return getPayload().filter(payloadType::isInstance).map(payloadType::cast); } /** * Get the payload of the required type. * *

This is a convenience method that automatically casts the payload to * the required type. If the payload is not present or is not of the expected * type, this method will throw an {@link IllegalArgumentException}. * * @param payloadType the required payload type; never {@code null} * @return the payload * @throws IllegalArgumentException if the payload is of a different type * or is not present * @see #getPayload() * @see #getPayload(Class) */ public T getRequiredPayload(Class payloadType) throws IllegalArgumentException { return getPayload(payloadType).orElseThrow(// () -> new IllegalArgumentException("Event does not contain a payload of type " + payloadType.getName())); } @Override public String toString() { // @formatter:off return new ToStringBuilder(this) .append("type", this.type) .append("testDescriptor", this.testDescriptor) .append("timestamp", this.timestamp) .append("payload", this.payload) .toString(); // @formatter:on } } ================================================ FILE: junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventConditions.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.testkit.engine; import static java.util.function.Predicate.isEqual; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import static org.assertj.core.api.Assertions.allOf; import static org.junit.platform.commons.util.FunctionUtils.where; import static org.junit.platform.engine.TestExecutionResult.Status.ABORTED; import static org.junit.platform.engine.TestExecutionResult.Status.FAILED; import static org.junit.platform.engine.TestExecutionResult.Status.SUCCESSFUL; import static org.junit.platform.testkit.engine.Event.byPayload; import static org.junit.platform.testkit.engine.Event.byTestDescriptor; import static org.junit.platform.testkit.engine.Event.byType; import static org.junit.platform.testkit.engine.EventType.DYNAMIC_TEST_REGISTERED; import static org.junit.platform.testkit.engine.EventType.FINISHED; import static org.junit.platform.testkit.engine.EventType.SKIPPED; import static org.junit.platform.testkit.engine.EventType.STARTED; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.function.Predicate; import org.apiguardian.api.API; import org.assertj.core.api.Condition; import org.assertj.core.description.Description; import org.assertj.core.description.JoinDescription; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.TestExecutionResult.Status; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.engine.support.descriptor.EngineDescriptor; /** * Collection of AssertJ {@linkplain Condition conditions} for {@link Event}. * * @since 1.4 * @see TestExecutionResultConditions */ @API(status = MAINTAINED, since = "1.7") public final class EventConditions { private EventConditions() { /* no-op */ } /** * Create a new {@link Condition} that matches if and only if an * {@link Event} matches all the supplied conditions. */ @SafeVarargs @SuppressWarnings("varargs") public static Condition event(Condition... conditions) { return allOf(conditions); } /** * Create a new {@link Condition} that matches if and only if an * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} is * an instance of {@link EngineDescriptor}. */ public static Condition engine() { return new Condition<>(byTestDescriptor(EngineDescriptor.class::isInstance), "is an engine"); } /** * Create a new {@link Condition} that matches if and only if an * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} is * a {@linkplain TestDescriptor#isTest() test} and its * {@linkplain TestDescriptor#getUniqueId() unique id} contains the supplied * {@link String}. * * @see #test() * @see #uniqueIdSubstring(String) */ public static Condition test(String uniqueIdSubstring) { return test(uniqueIdSubstring(uniqueIdSubstring)); } /** * Create a new {@link Condition} that matches if and only if an * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} is * a {@linkplain TestDescriptor#isTest() test}, its * {@linkplain TestDescriptor#getUniqueId() unique id} contains the supplied * {@link String}, and its {@linkplain TestDescriptor#getDisplayName() * display name} equals the supplied {@link String}. * * @see #test() * @see #test(Condition) * @see #uniqueIdSubstring(String) * @see #displayName(String) */ public static Condition test(String uniqueIdSubstring, String displayName) { return allOf(test(), uniqueIdSubstring(uniqueIdSubstring), displayName(displayName)); } /** * Create a new {@link Condition} that matches if and only if an * {@link Event} matches the supplied {@code Condition} and its * {@linkplain Event#getTestDescriptor() test descriptor} is a * {@linkplain TestDescriptor#isTest() test}. * *

For example, {@code test(displayName("my display name"))} can be used * to match against a test with the given display name. * * @since 1.8 * @see #test(String) * @see #test(String, String) * @see #displayName(String) */ @API(status = MAINTAINED, since = "1.8") public static Condition test(Condition condition) { return allOf(test(), condition); } /** * Create a new {@link Condition} that matches if and only if an * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} is * a {@linkplain TestDescriptor#isTest() test}. */ public static Condition test() { return new Condition<>(byTestDescriptor(TestDescriptor::isTest), "is a test"); } /** * Create a new {@link Condition} that matches if and only if an * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} is * a {@linkplain TestDescriptor#isContainer() container} and its * {@linkplain TestDescriptor#getUniqueId() unique id} contains the * fully qualified name of the supplied {@link Class}. */ public static Condition container(Class clazz) { Preconditions.notNull(clazz, "Class must not be null"); return container(clazz.getName()); } /** * Create a new {@link Condition} that matches if and only if an * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} is * a {@linkplain TestDescriptor#isContainer() container} and its * {@linkplain TestDescriptor#getUniqueId() unique id} contains the supplied * {@link String}. */ public static Condition container(String uniqueIdSubstring) { return container(uniqueIdSubstring(uniqueIdSubstring)); } /** * Create a new {@link Condition} that matches if and only if an * {@link Event} matches the supplied {@code Condition} and its * {@linkplain Event#getTestDescriptor() test descriptor} is a * {@linkplain TestDescriptor#isContainer() container}. */ public static Condition container(Condition condition) { return allOf(container(), condition); } /** * Create a new {@link Condition} that matches if and only if an * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} is * a {@linkplain TestDescriptor#isContainer() container}. */ public static Condition container() { return new Condition<>(byTestDescriptor(TestDescriptor::isContainer), "is a container"); } /** * Create a new {@link Condition} that matches if and only if an * {@link Event} matches the supplied {@code Condition}, its * {@linkplain Event#getTestDescriptor() test descriptor} is * a {@linkplain TestDescriptor#isContainer() container}, and its * {@linkplain TestDescriptor#getUniqueId() unique id} contains the * simple names of the supplied {@link Class} and all of its * {@linkplain Class#getEnclosingClass() enclosing classes}. * *

For example, {@code nestedContainer(MyNestedTests.class, displayName("my display name"))} * can be used to match against a nested container with the given display name. * *

Please note that this method does not differentiate between static * nested classes and non-static member classes (e.g., inner classes). * * @since 1.8 * @see #nestedContainer(Class) */ @API(status = MAINTAINED, since = "1.8") public static Condition nestedContainer(Class clazz, Condition condition) { return allOf(nestedContainer(clazz), condition); } /** * Create a new {@link Condition} that matches if and only if an * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} is * a {@linkplain TestDescriptor#isContainer() container} and its * {@linkplain TestDescriptor#getUniqueId() unique id} contains the * simple names of the supplied {@link Class} and all of its * {@linkplain Class#getEnclosingClass() enclosing classes}. * *

Please note that this method does not differentiate between static * nested classes and non-static member classes (e.g., inner classes). * * @see #nestedContainer(Class, Condition) */ public static Condition nestedContainer(Class clazz) { Preconditions.notNull(clazz, "Class must not be null"); Preconditions.notNull(clazz.getEnclosingClass(), () -> clazz.getName() + " must be a nested class"); List classNames = new ArrayList<>(); for (Class current = clazz; current != null; current = current.getEnclosingClass()) { classNames.add(0, current.getSimpleName()); } return allOf(container(), uniqueIdSubstrings(classNames)); } /** * Create a new {@link Condition} that matches if and only if an * {@link Event}'s {@linkplain Event#getType() type} is * {@link EventType#DYNAMIC_TEST_REGISTERED} and its * {@linkplain TestDescriptor#getUniqueId() unique id} contains the * supplied {@link String}. */ public static Condition dynamicTestRegistered(String uniqueIdSubstring) { return dynamicTestRegistered(uniqueIdSubstring(uniqueIdSubstring)); } /** * Create a new {@link Condition} that matches if and only if an * {@link Event}'s {@linkplain Event#getType() type} is * {@link EventType#DYNAMIC_TEST_REGISTERED} and it matches the supplied * {@code Condition}. */ public static Condition dynamicTestRegistered(Condition condition) { return allOf(type(DYNAMIC_TEST_REGISTERED), condition); } /** * Create a new {@link Condition} that matches if and only if the * {@linkplain TestDescriptor#getUniqueId() unique id} of an {@link Event}'s * {@linkplain Event#getTestDescriptor() test descriptor} is equal to the * {@link UniqueId} parsed from the supplied {@link String}. * * @since 1.13 */ @API(status = EXPERIMENTAL, since = "6.0") public static Condition uniqueId(String uniqueId) { return uniqueId(UniqueId.parse(uniqueId)); } /** * Create a new {@link Condition} that matches if and only if the * {@linkplain TestDescriptor#getUniqueId() unique id} of an {@link Event}'s * {@linkplain Event#getTestDescriptor() test descriptor} is equal to the * supplied {@link UniqueId}. * * @since 1.13 */ @API(status = EXPERIMENTAL, since = "6.0") public static Condition uniqueId(UniqueId uniqueId) { return uniqueId(new Condition<>(isEqual(uniqueId), "equal to '%s'", uniqueId)); } /** * Create a new {@link Condition} that matches if and only if the * {@linkplain TestDescriptor#getUniqueId() unique id} of an * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} * contains the supplied {@link String}. */ public static Condition uniqueIdSubstring(String uniqueIdSubstring) { Predicate predicate = segment -> { String text = segment.getType() + ":" + segment.getValue(); return text.contains(uniqueIdSubstring); }; return uniqueId(new Condition<>(uniqueId -> uniqueId.getSegments().stream().anyMatch(predicate), "substring '%s'", uniqueIdSubstring)); } /** * Create a new {@link Condition} that matches if and only if the * {@linkplain TestDescriptor#getUniqueId() unique id} of an {@link Event}'s * {@linkplain Event#getTestDescriptor() test descriptor} matches the * supplied {@link Condition}. * * @since 1.13 */ @API(status = EXPERIMENTAL, since = "6.0") public static Condition uniqueId(Condition condition) { return new Condition<>(byTestDescriptor(where(TestDescriptor::getUniqueId, condition::matches)), "descriptor with uniqueId %s", condition.description().value()); } /** * Create a new {@link Condition} that matches if and only if the * {@linkplain TestDescriptor#getUniqueId() unique id} of an * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} * contains all of the supplied strings. * * @since 1.6 */ public static Condition uniqueIdSubstrings(String... uniqueIdSubstrings) { return uniqueIdSubstrings(Arrays.asList(uniqueIdSubstrings)); } /** * Create a new {@link Condition} that matches if and only if the * {@linkplain TestDescriptor#getUniqueId() unique id} of an * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} * contains all of the supplied strings. * * @since 1.6 */ public static Condition uniqueIdSubstrings(List uniqueIdSubstrings) { // The following worked with AssertJ 3.13.2 // return allOf(uniqueIdSubstrings.stream().map(EventConditions::uniqueIdSubstring).toList()); // Workaround for a regression in AssertJ 3.14.0 that loses the individual descriptions // when multiple conditions are supplied as an Iterable instead of as an array. // The underlying cause is that org.assertj.core.condition.Join.Join(Condition...) // tracks all descriptions; whereas, // org.assertj.core.condition.Join.Join(Iterable>) // does not track all descriptions. List> conditions = uniqueIdSubstrings.stream()// .map(EventConditions::uniqueIdSubstring)// .toList(); List descriptions = conditions.stream().map(Condition::description).toList(); return allOf(conditions).describedAs(new JoinDescription("all of :[", "]", descriptions)); } /** * Create a new {@link Condition} that matches if and only if the * {@linkplain TestDescriptor#getDisplayName() display name} of an * {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} * is equal to the supplied {@link String}. */ public static Condition displayName(String displayName) { return new Condition<>(byTestDescriptor(where(TestDescriptor::getDisplayName, isEqual(displayName))), "descriptor with display name '%s'", displayName); } /** * Create a new {@link Condition} that matches if and only if the * {@linkplain TestDescriptor#getLegacyReportingName()} () legacy reporting name} * of an {@link Event}'s {@linkplain Event#getTestDescriptor() test descriptor} * is equal to the supplied {@link String}. * * @since 1.13 */ @API(status = EXPERIMENTAL, since = "6.0") public static Condition legacyReportingName(String legacyReportingName) { return new Condition<>( byTestDescriptor(where(TestDescriptor::getLegacyReportingName, isEqual(legacyReportingName))), "descriptor with legacy reporting name '%s'", legacyReportingName); } /** * Create a new {@link Condition} that matches if and only if an * {@link Event}'s {@linkplain Event#getType() type} is * {@link EventType#SKIPPED} and the * {@linkplain Event#getPayload() reason} is equal to the supplied * {@link String}. * * @see #reason(String) */ public static Condition skippedWithReason(String expectedReason) { return allOf(type(SKIPPED), reason(expectedReason)); } /** * Create a new {@link Condition} that matches if and only if an * {@link Event}'s {@linkplain Event#getType() type} is * {@link EventType#SKIPPED} and the * {@linkplain Event#getPayload() reason} matches the supplied * {@link Predicate}. * * @see #reason(Predicate) */ public static Condition skippedWithReason(Predicate predicate) { return allOf(type(SKIPPED), reason(predicate)); } /** * Create a new {@link Condition} that matches if and only if an * {@link Event}'s {@linkplain Event#getType() type} is * {@link EventType#STARTED}. */ public static Condition started() { return type(STARTED); } /** * Create a new {@link Condition} that matches if and only if an * {@link Event}'s {@linkplain Event#getType() type} is * {@link EventType#FINISHED} and its * {@linkplain Event#getPayload() result} has a * {@linkplain TestExecutionResult#getStatus() status} of * {@link TestExecutionResult.Status#ABORTED ABORTED} as well as a * {@linkplain TestExecutionResult#getThrowable() cause} that matches all of * the supplied conditions. */ @SafeVarargs public static Condition abortedWithReason(Condition... conditions) { return finishedWithCause(ABORTED, conditions); } /** * Create a new {@link Condition} that matches if and only if an * {@link Event}'s {@linkplain Event#getType() type} is * {@link EventType#FINISHED} and its * {@linkplain Event#getPayload() result} has a * {@linkplain TestExecutionResult#getStatus() status} of * {@link TestExecutionResult.Status#FAILED FAILED} as well as a * {@linkplain TestExecutionResult#getThrowable() cause} that matches all of * the supplied {@code Conditions}. */ @SafeVarargs public static Condition finishedWithFailure(Condition... conditions) { return finishedWithCause(FAILED, conditions); } @SuppressWarnings("unchecked") private static Condition finishedWithCause(Status expectedStatus, Condition... conditions) { List> list = Arrays.asList(TestExecutionResultConditions.status(expectedStatus), TestExecutionResultConditions.throwable(conditions)); return finished(allOf(list)); } /** * Create a new {@link Condition} that matches if and only if an * {@link Event}'s {@linkplain Event#getType() type} is * {@link EventType#FINISHED} and its * {@linkplain Event#getPayload() result} has a * {@linkplain TestExecutionResult#getStatus() status} of * {@link TestExecutionResult.Status#FAILED FAILED}. */ public static Condition finishedWithFailure() { return finished(TestExecutionResultConditions.status(FAILED)); } /** * Create a new {@link Condition} that matches if and only if an * {@link Event}'s {@linkplain Event#getType() type} is * {@link EventType#FINISHED} and its * {@linkplain Event#getPayload() result} has a * {@linkplain TestExecutionResult#getStatus() status} of * {@link TestExecutionResult.Status#SUCCESSFUL SUCCESSFUL}. */ public static Condition finishedSuccessfully() { return finished(TestExecutionResultConditions.status(SUCCESSFUL)); } /** * Create a new {@link Condition} that matches if and only if an * {@link Event}'s {@linkplain Event#getType() type} is * {@link EventType#FINISHED} and its * {@linkplain Event#getPayload() payload} is an instance of * {@link TestExecutionResult} that matches the supplied {@code Condition}. */ public static Condition finished(Condition resultCondition) { return allOf(type(FINISHED), result(resultCondition)); } /** * Create a new {@link Condition} that matches if and only if an * {@link Event}'s {@linkplain Event#getType() type} is equal to the * supplied {@link EventType}. */ public static Condition type(EventType expectedType) { return new Condition<>(byType(expectedType), "type is %s", expectedType); } /** * Create a new {@link Condition} that matches if and only if an * {@link Event}'s {@linkplain Event#getPayload() payload} is an instance of * {@link TestExecutionResult} that matches the supplied {@code Condition}. */ public static Condition result(Condition condition) { return new Condition<>(byPayload(TestExecutionResult.class, condition::matches), "event with result where %s", condition); } /** * Create a new {@link Condition} that matches if and only if an * {@link Event}'s {@linkplain Event#getPayload() payload} is an instance of * {@link String} that is equal to the supplied value. */ public static Condition reason(String expectedReason) { return new Condition<>(byPayload(String.class, isEqual(expectedReason)), "event with reason '%s'", expectedReason); } /** * Create a new {@link Condition} that matches if and only if an * {@link Event}'s {@linkplain Event#getPayload() payload} is an instance of * {@link String} that matches the supplied {@link Predicate}. */ public static Condition reason(Predicate predicate) { return new Condition<>(byPayload(String.class, predicate), "event with custom reason predicate"); } /** * Create a new {@link Condition} that matches if and only if an * {@link Event}'s {@linkplain Event#getPayload() payload} is an instance of * {@link ReportEntry} that contains the supplied key-value pairs. */ @API(status = STABLE, since = "1.10") public static Condition reportEntry(Map keyValuePairs) { return new Condition<>(byPayload(ReportEntry.class, it -> it.getKeyValuePairs().equals(keyValuePairs)), "event for report entry with key-value pairs %s", keyValuePairs); } /** * Create a new {@link Condition} that matches if and only if an * {@link Event}'s {@linkplain Event#getPayload() payload} is an instance of * {@link FileEntry} that contains a file that matches the supplied * {@link Predicate}. * * @since 1.12 */ @API(status = MAINTAINED, since = "1.13.3") public static Condition fileEntry(Predicate predicate) { return new Condition<>(byPayload(FileEntry.class, predicate), "event for file entry with custom predicate"); } } ================================================ FILE: junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventStatistics.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.testkit.engine; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.junit.platform.testkit.engine.Assertions.assertEquals; import java.util.ArrayList; import java.util.List; import org.apiguardian.api.API; import org.junit.platform.testkit.engine.Assertions.Executable; /** * {@code EventStatistics} provides a fluent API for asserting statistics * for {@linkplain Event events}. * *

{@code EventStatistics} is used in conjunction with * {@link Events#assertStatistics(java.util.function.Consumer)} as in the * following example. * *

{@code events.assertStatistics(stats -> stats.started(1).succeeded(1).failed(0));} * * @since 1.4 * @see Event * @see Events */ @API(status = MAINTAINED, since = "1.7") public class EventStatistics { private final List executables = new ArrayList<>(); private final Events events; EventStatistics(Events events, String category) { this.events = events; } void assertAll() { Assertions.assertAll(this.events.getCategory() + " Event Statistics", this.executables.stream()); } // ------------------------------------------------------------------------- /** * Specify the number of expected skipped events. * * @param expected the expected number of events * @return this {@code EventStatistics} for method chaining */ public EventStatistics skipped(long expected) { this.executables.add(() -> assertEquals(expected, this.events.skipped().count(), "skipped")); return this; } /** * Specify the number of expected started events. * * @param expected the expected number of events * @return this {@code EventStatistics} for method chaining */ public EventStatistics started(long expected) { this.executables.add(() -> assertEquals(expected, this.events.started().count(), "started")); return this; } /** * Specify the number of expected finished events. * * @param expected the expected number of events * @return this {@code EventStatistics} for method chaining */ public EventStatistics finished(long expected) { this.executables.add(() -> assertEquals(expected, this.events.finished().count(), "finished")); return this; } /** * Specify the number of expected aborted events. * * @param expected the expected number of events * @return this {@code EventStatistics} for method chaining */ public EventStatistics aborted(long expected) { this.executables.add(() -> assertEquals(expected, this.events.aborted().count(), "aborted")); return this; } /** * Specify the number of expected succeeded events. * * @param expected the expected number of events * @return this {@code EventStatistics} for method chaining */ public EventStatistics succeeded(long expected) { this.executables.add(() -> assertEquals(expected, this.events.succeeded().count(), "succeeded")); return this; } /** * Specify the number of expected failed events. * * @param expected the expected number of events * @return this {@code EventStatistics} for method chaining */ public EventStatistics failed(long expected) { this.executables.add(() -> assertEquals(expected, this.events.failed().count(), "failed")); return this; } /** * Specify the number of expected reporting entry publication events. * * @param expected the expected number of events * @return this {@code EventStatistics} for method chaining */ public EventStatistics reportingEntryPublished(long expected) { this.executables.add( () -> assertEquals(expected, this.events.reportingEntryPublished().count(), "reporting entry published")); return this; } /** * Specify the number of expected file entry publication events. * * @param expected the expected number of events * @return this {@code EventStatistics} for method chaining * @since 1.12 */ @API(status = MAINTAINED, since = "1.13.3") public EventStatistics fileEntryPublished(long expected) { this.executables.add( () -> assertEquals(expected, this.events.fileEntryPublished().count(), "file entry published")); return this; } /** * Specify the number of expected dynamic registration events. * * @param expected the expected number of events * @return this {@code EventStatistics} for method chaining */ public EventStatistics dynamicallyRegistered(long expected) { this.executables.add( () -> assertEquals(expected, this.events.dynamicallyRegistered().count(), "dynamically registered")); return this; } } ================================================ FILE: junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventType.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.testkit.engine; import static org.apiguardian.api.API.Status.MAINTAINED; import org.apiguardian.api.API; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; /** * Enumeration of the different possible {@link Event} types. * * @since 1.4 * @see Event */ @API(status = MAINTAINED, since = "1.7") public enum EventType { /** * Signals that a {@link TestDescriptor} has been dynamically registered. * * @see org.junit.platform.engine.EngineExecutionListener#dynamicTestRegistered(TestDescriptor) */ DYNAMIC_TEST_REGISTERED, /** * Signals that the execution of a {@link TestDescriptor} has been skipped. * * @see org.junit.platform.engine.EngineExecutionListener#executionSkipped(TestDescriptor, String) */ SKIPPED, /** * Signals that the execution of a {@link TestDescriptor} has started. * * @see org.junit.platform.engine.EngineExecutionListener#executionStarted(TestDescriptor) */ STARTED, /** * Signals that the execution of a {@link TestDescriptor} has finished, * regardless of the outcome. * * @see org.junit.platform.engine.EngineExecutionListener#executionFinished(TestDescriptor, TestExecutionResult) */ FINISHED, /** * Signals that a {@link TestDescriptor} published a reporting entry. * * @see org.junit.platform.engine.EngineExecutionListener#reportingEntryPublished(TestDescriptor, ReportEntry) */ REPORTING_ENTRY_PUBLISHED, /** * Signals that a {@link TestDescriptor} published a file entry. * * @since 1.12 * @see org.junit.platform.engine.EngineExecutionListener#fileEntryPublished(TestDescriptor, FileEntry) */ @API(status = MAINTAINED, since = "1.13.3") FILE_ENTRY_PUBLISHED } ================================================ FILE: junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Events.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.testkit.engine; import static java.util.Collections.sort; import static java.util.function.Predicate.isEqual; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.junit.platform.commons.util.FunctionUtils.where; import static org.junit.platform.testkit.engine.Event.byPayload; import static org.junit.platform.testkit.engine.Event.byType; import java.io.OutputStream; import java.io.PrintWriter; import java.io.Writer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Stream; import org.apiguardian.api.API; import org.assertj.core.api.Assertions; import org.assertj.core.api.Condition; import org.assertj.core.api.ListAssert; import org.assertj.core.api.SoftAssertions; import org.assertj.core.data.Index; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.TestExecutionResult.Status; import org.opentest4j.AssertionFailedError; /** * {@code Events} is a facade that provides a fluent API for working with * {@linkplain Event events}. * * @since 1.4 */ @API(status = MAINTAINED, since = "1.7") public final class Events { private final List events; private final String category; Events(Stream events, String category) { this(Preconditions.notNull(events, "Event stream must not be null").toList(), category); } Events(List events, String category) { Preconditions.notNull(events, "Event list must not be null"); Preconditions.containsNoNullElements(events, "Event list must not contain null elements"); this.events = List.copyOf(events); this.category = category; } String getCategory() { return this.category; } // --- Accessors ----------------------------------------------------------- /** * Get the {@linkplain Event events} as a {@link List}. * * @return the list of events; never {@code null} * @see #stream() */ public List list() { return this.events; } /** * Get the {@linkplain Event events} as a {@link Stream}. * * @return the stream of events; never {@code null} * @see #list() */ public Stream stream() { return this.events.stream(); } /** * Shortcut for {@code events.stream().map(mapper)}. * * @param mapper a {@code Function} to apply to each event; never {@code null} * @return the mapped stream of events; never {@code null} * @see #stream() * @see Stream#map(Function) */ public Stream map(Function mapper) { Preconditions.notNull(mapper, "Mapping function must not be null"); return stream().map(mapper); } /** * Shortcut for {@code events.stream().filter(predicate)}. * * @param predicate a {@code Predicate} to apply to each event to decide if * it should be included in the filtered stream; never {@code null} * @return the filtered stream of events; never {@code null} * @see #stream() * @see Stream#filter(Predicate) */ public Stream filter(Predicate predicate) { Preconditions.notNull(predicate, "Filter predicate must not be null"); return stream().filter(predicate); } /** * Get the {@link Executions} for the current set of {@linkplain Event events}. * *

The set of executions is derived from the current set of events by * taking single {@linkplain Event#executionSkipped(TestDescriptor, String) * execution skipped} events and pairs of {@linkplain Event#executionStarted(TestDescriptor) * execution started} and {@linkplain Event#executionFinished(TestDescriptor, TestExecutionResult) * execution finished} events. As a consequence, executions for which either * the started or finished event is not included in the current set of * events are not included in the {@code Executions} returned from this method. * * @return an instance of {@code Executions} for the current set of events; * never {@code null} */ public Executions executions() { return new Executions(this.events, this.category); } // --- Statistics ---------------------------------------------------------- /** * Get the number of {@linkplain Event events} contained in this {@code Events} * object. */ public long count() { return this.events.size(); } // --- Built-in Filters ---------------------------------------------------- /** * Get the skipped {@link Events} contained in this {@code Events} object. * * @return the filtered {@code Events}; never {@code null} */ public Events skipped() { return new Events(eventsByType(EventType.SKIPPED), this.category + " Skipped"); } /** * Get the started {@link Events} contained in this {@code Events} object. * * @return the filtered {@code Events}; never {@code null} */ public Events started() { return new Events(eventsByType(EventType.STARTED), this.category + " Started"); } /** * Get the finished {@link Events} contained in this {@code Events} object. * * @return the filtered {@code Events}; never {@code null} */ public Events finished() { return new Events(eventsByType(EventType.FINISHED), this.category + " Finished"); } /** * Get the aborted {@link Events} contained in this {@code Events} object. * * @return the filtered {@code Events}; never {@code null} */ public Events aborted() { return new Events(finishedEventsByStatus(Status.ABORTED), this.category + " Aborted"); } /** * Get the succeeded {@link Events} contained in this {@code Events} object. * * @return the filtered {@code Events}; never {@code null} */ public Events succeeded() { return new Events(finishedEventsByStatus(Status.SUCCESSFUL), this.category + " Successful"); } /** * Get the failed {@link Events} contained in this {@code Events} object. * * @return the filtered {@code Events}; never {@code null} */ public Events failed() { return new Events(finishedEventsByStatus(Status.FAILED), this.category + " Failed"); } /** * Get the reporting entry publication {@link Events} contained in this * {@code Events} object. * * @return the filtered {@code Events}; never {@code null} */ public Events reportingEntryPublished() { return new Events(eventsByType(EventType.REPORTING_ENTRY_PUBLISHED), this.category + " Reporting Entry Published"); } /** * Get the file entry publication {@link Events} contained in this * {@code Events} object. * * @return the filtered {@code Events}; never {@code null} * @since 1.12 */ @API(status = MAINTAINED, since = "1.13.3") public Events fileEntryPublished() { return new Events(eventsByType(EventType.FILE_ENTRY_PUBLISHED), this.category + " File Entry Published"); } /** * Get the dynamic registration {@link Events} contained in this * {@code Events} object. * * @return the filtered {@code Events}; never {@code null} */ public Events dynamicallyRegistered() { return new Events(eventsByType(EventType.DYNAMIC_TEST_REGISTERED), this.category + " Dynamically Registered"); } // --- Assertions ---------------------------------------------------------- /** * Assert statistics for the {@linkplain Event events} contained in this * {@code Events} object. * *

Example

* *

{@code events.assertStatistics(stats -> stats.started(1).succeeded(1).failed(0));} * * @param statisticsConsumer a {@link Consumer} of {@link EventStatistics}; * never {@code null} * @return this {@code Events} object for method chaining; never {@code null} */ public Events assertStatistics(Consumer statisticsConsumer) { Preconditions.notNull(statisticsConsumer, "Consumer must not be null"); EventStatistics eventStatistics = new EventStatistics(this, this.category); statisticsConsumer.accept(eventStatistics); eventStatistics.assertAll(); return this; } /** * Assert that all {@linkplain Event events} contained in this {@code Events} * object exactly match the provided conditions. * *

Conditions can be imported statically from {@link EventConditions} * and {@link TestExecutionResultConditions}. * *

Example

* *
	 * executionResults.testEvents().assertEventsMatchExactly(
	 *     event(test("exampleTestMethod"), started()),
	 *     event(test("exampleTestMethod"), finishedSuccessfully())
	 * );
	 * 
* * @param conditions the conditions to match against; never {@code null} * @see #assertEventsMatchLoosely(Condition...) * @see #assertEventsMatchLooselyInOrder(Condition...) * @see EventConditions * @see TestExecutionResultConditions */ @SafeVarargs public final void assertEventsMatchExactly(Condition... conditions) { Preconditions.notNull(conditions, "conditions must not be null"); assertEventsMatchExactly(this.events, conditions); } /** * Assert that all provided conditions are matched by an {@linkplain Event event} * contained in this {@code Events} object, regardless of order. * *

Note that this method performs a partial match. Thus, some events may * not match any of the provided conditions. * *

Conditions can be imported statically from {@link EventConditions} * and {@link TestExecutionResultConditions}. * *

Example

* *
	 * executionResults.testEvents().assertEventsMatchLoosely(
	 *     event(test("exampleTestMethod"), started()),
	 *     event(test("exampleTestMethod"), finishedSuccessfully())
	 * );
	 * 
* * @param conditions the conditions to match against; never {@code null} * @since 1.7 * @see #assertEventsMatchExactly(Condition...) * @see #assertEventsMatchLooselyInOrder(Condition...) * @see EventConditions * @see TestExecutionResultConditions */ @SafeVarargs @SuppressWarnings("varargs") public final void assertEventsMatchLoosely(Condition... conditions) { Preconditions.notNull(conditions, "conditions must not be null"); Preconditions.containsNoNullElements(conditions, "conditions must not contain null elements"); assertEventsMatchLoosely(this.events, conditions); } /** * Assert that all provided conditions are matched by an {@linkplain Event event} * contained in this {@code Events} object. * *

Note that this method performs a partial match. Thus, some events may * not match any of the provided conditions; however, the conditions provided * must be in the correct order. * *

Conditions can be imported statically from {@link EventConditions} * and {@link TestExecutionResultConditions}. * *

Example

* *
	 * executionResults.testEvents().assertEventsMatchLooselyInOrder(
	 *     event(test("exampleTestMethod"), started()),
	 *     event(test("exampleTestMethod"), finishedSuccessfully())
	 * );
	 * 
* * @param conditions the conditions to match against; never {@code null} * @since 1.7 * @see #assertEventsMatchExactly(Condition...) * @see #assertEventsMatchLoosely(Condition...) * @see EventConditions * @see TestExecutionResultConditions */ @SafeVarargs @SuppressWarnings("varargs") public final void assertEventsMatchLooselyInOrder(Condition... conditions) { Preconditions.notNull(conditions, "conditions must not be null"); Preconditions.containsNoNullElements(conditions, "conditions must not contain null elements"); assertEventsMatchLooselyInOrder(this.events, conditions); } /** * Shortcut for {@code org.assertj.core.api.Assertions.assertThat(events.list())}. * * @return an instance of {@link ListAssert} for events; never {@code null} * @see org.assertj.core.api.Assertions#assertThat(List) * @see org.assertj.core.api.ListAssert */ public ListAssert assertThatEvents() { return org.assertj.core.api.Assertions.assertThat(list()); } // --- Diagnostics --------------------------------------------------------- /** * Print all events to {@link System#out}. * * @return this {@code Events} object for method chaining; never {@code null} */ public Events debug() { debug(System.out); return this; } /** * Print all events to the supplied {@link OutputStream}. * * @param out the {@code OutputStream} to print to; never {@code null} * @return this {@code Events} object for method chaining; never {@code null} */ @SuppressWarnings("DefaultCharset") public Events debug(OutputStream out) { Preconditions.notNull(out, "OutputStream must not be null"); debug(new PrintWriter(out, true)); return this; } /** * Print all events to the supplied {@link Writer}. * * @param writer the {@code Writer} to print to; never {@code null} * @return this {@code Events} object for method chaining; never {@code null} */ public Events debug(Writer writer) { Preconditions.notNull(writer, "Writer must not be null"); debug(new PrintWriter(writer, true)); return this; } private Events debug(PrintWriter printWriter) { printWriter.println(this.category + " Events:"); this.events.forEach(event -> printWriter.printf("\t%s%n", event)); return this; } // --- Internals ----------------------------------------------------------- private Stream eventsByType(EventType type) { Preconditions.notNull(type, "EventType must not be null"); return stream().filter(byType(type)); } private Stream finishedEventsByStatus(Status status) { Preconditions.notNull(status, "Status must not be null"); return eventsByType(EventType.FINISHED)// .filter(byPayload(TestExecutionResult.class, where(TestExecutionResult::getStatus, isEqual(status)))); } @SafeVarargs private static void assertEventsMatchExactly(List events, Condition... conditions) { Assertions.assertThat(events).hasSize(conditions.length); SoftAssertions softly = new SoftAssertions(); for (int i = 0; i < conditions.length; i++) { softly.assertThat(events).has(conditions[i], Index.atIndex(i)); } softly.assertAll(); } @SafeVarargs private static void assertEventsMatchLoosely(List events, Condition... conditions) { SoftAssertions softly = new SoftAssertions(); for (Condition condition : conditions) { checkCondition(events, softly, condition); } softly.assertAll(); } @SafeVarargs @SuppressWarnings("varargs") private static void assertEventsMatchLooselyInOrder(List events, Condition... conditions) { Assertions.assertThat(conditions).hasSizeLessThanOrEqualTo(events.size()); SoftAssertions softly = new SoftAssertions(); // @formatter:off List indices = Arrays.stream(conditions) .map(condition -> findEvent(events, softly, condition)) .filter(Objects::nonNull) .map(events::indexOf) .toList(); // @formatter:on if (isNotInIncreasingOrder(indices)) { throw new AssertionFailedError("Conditions are not in the correct order."); } softly.assertAll(); } private static boolean isNotInIncreasingOrder(List indices) { List copy = new ArrayList<>(indices); sort(copy); return !indices.equals(copy); } private static void checkCondition(List events, SoftAssertions softly, Condition condition) { if (events.stream().noneMatch(condition::matches)) { softly.fail("Condition did not match any event: " + condition); } } private static @Nullable Event findEvent(List events, SoftAssertions softly, Condition condition) { // @formatter:off Optional matchedEvent = events.stream() .filter(condition::matches) .findFirst(); // @formatter:on if (matchedEvent.isEmpty()) { softly.fail("Condition did not match any event: " + condition); } return matchedEvent.orElse(null); } } ================================================ FILE: junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Execution.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.testkit.engine; import static org.apiguardian.api.API.Status.MAINTAINED; import java.time.Duration; import java.time.Instant; import org.apiguardian.api.API; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; /** * {@code Execution} encapsulates metadata for the execution of a single * {@link TestDescriptor}. * *

The execution either coincides with a single * {@linkplain org.junit.platform.engine.EngineExecutionListener#executionSkipped(TestDescriptor, String) skipped} * event or spans an * {@linkplain org.junit.platform.engine.EngineExecutionListener#executionStarted(TestDescriptor) execution started} and * {@linkplain org.junit.platform.engine.EngineExecutionListener#executionFinished(TestDescriptor, TestExecutionResult) * execution finished} event. * * @since 1.4 */ @API(status = MAINTAINED, since = "1.7") public class Execution { // --- Factories ----------------------------------------------------------- /** * Create a new instance of an {@code Execution} that finished with the * provided {@link TestExecutionResult}. * * @param testDescriptor the {@code TestDescriptor} that finished; * never {@code null} * @param startInstant the {@code Instant} that the {@code Execution} started; * never {@code null} * @param endInstant the {@code Instant} that the {@code Execution} completed; * never {@code null} * @param executionResult the {@code TestExecutionResult} of the finished * {@code TestDescriptor}; never {@code null} * @return the newly created {@code Execution} instance; never {@code null} */ public static Execution finished(TestDescriptor testDescriptor, Instant startInstant, Instant endInstant, TestExecutionResult executionResult) { return new Execution(testDescriptor, startInstant, endInstant, TerminationInfo.executed(executionResult)); } /** * Create a new instance of an {@code Execution} that was skipped with the * provided {@code skipReason}. * * @param testDescriptor the {@code TestDescriptor} that finished; * never {@code null} * @param startInstant the {@code Instant} that the {@code Execution} started; * never {@code null} * @param endInstant the {@code Instant} that the {@code Execution} completed; * never {@code null} * @param skipReason the reason the {@code TestDescriptor} was skipped; * may be {@code null} * @return the newly created {@code Execution} instance; never {@code null} */ public static Execution skipped(TestDescriptor testDescriptor, Instant startInstant, Instant endInstant, String skipReason) { return new Execution(testDescriptor, startInstant, endInstant, TerminationInfo.skipped(skipReason)); } // ------------------------------------------------------------------------- private final TestDescriptor testDescriptor; private final Instant startInstant; private final Instant endInstant; private final Duration duration; private final TerminationInfo terminationInfo; private Execution(TestDescriptor testDescriptor, Instant startInstant, Instant endInstant, TerminationInfo terminationInfo) { Preconditions.notNull(testDescriptor, "TestDescriptor must not be null"); Preconditions.notNull(startInstant, "Start Instant must not be null"); Preconditions.notNull(endInstant, "End Instant must not be null"); Preconditions.notNull(terminationInfo, "TerminationInfo must not be null"); this.testDescriptor = testDescriptor; this.startInstant = startInstant; this.endInstant = endInstant; this.duration = Duration.between(startInstant, endInstant); this.terminationInfo = terminationInfo; } /** * Get the {@link TestDescriptor} for this {@code Execution}. * * @return the {@code TestDescriptor} for this {@code Execution} */ public TestDescriptor getTestDescriptor() { return this.testDescriptor; } /** * Get the start {@link Instant} of this {@code Execution}. * * @return the start {@code Instant} of this {@code Execution} */ public Instant getStartInstant() { return this.startInstant; } /** * Get the end {@link Instant} of this {@code Execution}. * * @return the end {@code Instant} of this {@code Execution} */ public Instant getEndInstant() { return this.endInstant; } /** * Get the {@link Duration} of this {@code Execution}. * * @return the {@code Duration} of this {@code Execution} */ public Duration getDuration() { return this.duration; } /** * Get the {@link TerminationInfo} for this {@code Execution}. * * @return the {@code TerminationInfo} for this {@code Execution} */ public TerminationInfo getTerminationInfo() { return this.terminationInfo; } @Override public String toString() { // @formatter:off return new ToStringBuilder(this) .append("testDescriptor", this.testDescriptor) .append("startInstant", this.startInstant) .append("endInstant", this.endInstant) .append("duration", this.duration) .append("terminationInfo", this.terminationInfo) .toString(); // @formatter:on } } ================================================ FILE: junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/ExecutionRecorder.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.testkit.engine; import static org.apiguardian.api.API.Status.MAINTAINED; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import org.apiguardian.api.API; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; /** * {@code ExecutionRecorder} is an {@link EngineExecutionListener} that records * data from every event that occurs during the engine execution lifecycle and * provides functionality for retrieving execution state via * {@link EngineExecutionResults}. * * @since 1.4 * @see EngineExecutionResults * @see Event * @see Execution */ @API(status = MAINTAINED, since = "1.7") public class ExecutionRecorder implements EngineExecutionListener { private final List events = new CopyOnWriteArrayList<>(); public ExecutionRecorder() { } /** * Record an {@link Event} for a dynamically registered container * or test. */ @Override public void dynamicTestRegistered(TestDescriptor testDescriptor) { this.events.add(Event.dynamicTestRegistered(testDescriptor)); } /** * Record an {@link Event} for a container or test that was skipped. */ @Override public void executionSkipped(TestDescriptor testDescriptor, String reason) { this.events.add(Event.executionSkipped(testDescriptor, reason)); } /** * Record an {@link Event} for a container or test that started. */ @Override public void executionStarted(TestDescriptor testDescriptor) { this.events.add(Event.executionStarted(testDescriptor)); } /** * Record an {@link Event} for a container or test that completed * with the provided {@link TestExecutionResult}. */ @Override public void executionFinished(TestDescriptor testDescriptor, TestExecutionResult testExecutionResult) { this.events.add(Event.executionFinished(testDescriptor, testExecutionResult)); } /** * Record an {@link Event} for a published {@link ReportEntry}. */ @Override public void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry entry) { this.events.add(Event.reportingEntryPublished(testDescriptor, entry)); } /** * Record an {@link Event} for a published {@link FileEntry}. * * @since 1.12 */ @API(status = MAINTAINED, since = "1.13.3") @Override public void fileEntryPublished(TestDescriptor testDescriptor, FileEntry file) { this.events.add(Event.fileEntryPublished(testDescriptor, file)); } /** * Get the state of the engine's execution in the form of {@link EngineExecutionResults}. * * @return the {@code EngineExecutionResults} containing all current state information */ public EngineExecutionResults getExecutionResults() { return new EngineExecutionResults(this.events); } } ================================================ FILE: junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Executions.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.testkit.engine; import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.MAINTAINED; import java.io.OutputStream; import java.io.PrintWriter; import java.io.Writer; import java.time.Instant; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Stream; import org.apiguardian.api.API; import org.assertj.core.api.ListAssert; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.TestExecutionResult.Status; /** * {@code Executions} is a facade that provides a fluent API for working with * {@linkplain Execution executions}. * * @since 1.4 */ @API(status = MAINTAINED, since = "1.7") public final class Executions { private final List executions; private final String category; private Executions(Stream executions, String category) { Preconditions.notNull(executions, "Execution stream must not be null"); Preconditions.notNull(category, "Category must not be null"); this.executions = executions.toList(); this.category = category; } Executions(List events, String category) { Preconditions.notNull(events, "Event list must not be null"); Preconditions.containsNoNullElements(events, "Event list must not contain null elements"); Preconditions.notNull(category, "Category must not be null"); this.executions = List.copyOf(createExecutions(events)); this.category = category; } // --- Accessors ----------------------------------------------------------- /** * Get the {@linkplain Execution executions} as a {@link List}. * * @return the list of executions; never {@code null} * @see #stream() */ public List list() { return this.executions; } /** * Get the {@linkplain Execution executions} as a {@link Stream}. * * @return the stream of executions; never {@code null} * @see #list() */ public Stream stream() { return this.executions.stream(); } /** * Shortcut for {@code executions.stream().map(mapper)}. * * @param mapper a {@code Function} to apply to each execution; * never {@code null} * @return the mapped stream of executions; never {@code null} * @see #stream() * @see Stream#map(Function) */ public Stream map(Function mapper) { Preconditions.notNull(mapper, "Mapping function must not be null"); return stream().map(mapper); } /** * Shortcut for {@code executions.stream().filter(predicate)}. * * @param predicate a {@code Predicate} to apply to each execution to decide * if it should be included in the filtered stream; never {@code null} * @return the filtered stream of executions; never {@code null} * @see #stream() * @see Stream#filter(Predicate) */ public Stream filter(Predicate predicate) { Preconditions.notNull(predicate, "Filter predicate must not be null"); return stream().filter(predicate); } // --- Statistics ---------------------------------------------------------- /** * Get the number of {@linkplain Execution executions} contained in this * {@code Executions} object. */ public long count() { return this.executions.size(); } // --- Built-in Filters ---------------------------------------------------- /** * Get the skipped {@link Executions} contained in this {@code Executions} object. * *

Executions that are not skipped are {@linkplain #finished() finished}. * * @return the filtered {@code Executions}; never {@code null} */ public Executions skipped() { return new Executions(executionsByTerminationInfo(TerminationInfo::skipped), this.category + " Skipped"); } /** * Get the started {@link Executions} contained in this {@code Executions} object. * *

Executions that are not started are {@linkplain #skipped() skipped}. * * @return the filtered {@code Executions}; never {@code null} * * @deprecated by definition, all started executions are also finished executions. * Use {@link #finished()} instead. */ @API(status = DEPRECATED, since = "6.1") @Deprecated(since = "6.1", forRemoval = true) public Executions started() { return new Executions(executionsByTerminationInfo(TerminationInfo::notSkipped), this.category + " Started"); } /** * Get the finished {@link Executions} contained in this {@code Executions} object. * *

Executions that are not finished are {@linkplain #skipped() skipped}. * * @return the filtered {@code Executions}; never {@code null} */ public Executions finished() { return new Executions(finishedExecutions(), this.category + " Finished"); } /** * Get the aborted {@link Executions} contained in this {@code Executions} object. * *

The aborted executions are a subset of the {@linkplain #finished() finished} * executions. * * @return the filtered {@code Executions}; never {@code null} */ public Executions aborted() { return new Executions(finishedExecutionsByStatus(Status.ABORTED), this.category + " Aborted"); } /** * Get the succeeded {@link Executions} contained in this {@code Executions} object. * *

The succeeded executions are a subset of the {@linkplain #finished() finished} * executions. * * @return the filtered {@code Executions}; never {@code null} */ public Executions succeeded() { return new Executions(finishedExecutionsByStatus(Status.SUCCESSFUL), this.category + " Successful"); } /** * Get the failed {@link Executions} contained in this {@code Executions} object. * *

The failed executions are a subset of the {@linkplain #finished() finished} * executions. * * @return the filtered {@code Executions}; never {@code null} */ public Executions failed() { return new Executions(finishedExecutionsByStatus(Status.FAILED), this.category + " Failed"); } // --- Assertions ---------------------------------------------------------- /** * Shortcut for {@code org.assertj.core.api.Assertions.assertThat(executions.list())}. * * @return an instance of {@link ListAssert} for executions; never {@code null} * @see org.assertj.core.api.Assertions#assertThat(List) * @see org.assertj.core.api.ListAssert */ public ListAssert assertThatExecutions() { return org.assertj.core.api.Assertions.assertThat(list()); } // --- Diagnostics --------------------------------------------------------- /** * Print all executions to {@link System#out}. * * @return this {@code Executions} object for method chaining; never {@code null} */ public Executions debug() { debug(System.out); return this; } /** * Print all executions to the supplied {@link OutputStream}. * * @param out the {@code OutputStream} to print to; never {@code null} * @return this {@code Executions} object for method chaining; never {@code null} */ @SuppressWarnings("DefaultCharset") public Executions debug(OutputStream out) { Preconditions.notNull(out, "OutputStream must not be null"); debug(new PrintWriter(out, true)); return this; } /** * Print all executions to the supplied {@link Writer}. * * @param writer the {@code Writer} to print to; never {@code null} * @return this {@code Executions} object for method chaining; never {@code null} */ @SuppressWarnings("DefaultCharset") public Executions debug(Writer writer) { Preconditions.notNull(writer, "Writer must not be null"); debug(new PrintWriter(writer, true)); return this; } private Executions debug(PrintWriter printWriter) { printWriter.println(this.category + " Executions:"); this.executions.forEach(execution -> printWriter.printf("\t%s%n", execution)); return this; } // --- Internals ----------------------------------------------------------- private Stream finishedExecutions() { return executionsByTerminationInfo(TerminationInfo::executed); } private Stream finishedExecutionsByStatus(Status status) { Preconditions.notNull(status, "Status must not be null"); return finishedExecutions()// .filter(execution -> execution.getTerminationInfo().getExecutionResult().getStatus() == status); } private Stream executionsByTerminationInfo(Predicate predicate) { return filter(execution -> predicate.test(execution.getTerminationInfo())); } /** * Create executions from the supplied list of events. */ private static List createExecutions(List events) { List executions = new ArrayList<>(); Map executionStarts = new HashMap<>(); for (Event event : events) { switch (event.getType()) { case STARTED -> executionStarts.put(event.getTestDescriptor(), event.getTimestamp()); case SKIPPED -> { // Based on the Javadoc for EngineExecutionListener.executionSkipped(...), // a skipped descriptor must never be reported as started or finished, // but just in case a TestEngine does not adhere to that contract, we // make an attempt to remove any tracked "started" event. executionStarts.remove(event.getTestDescriptor()); // Use the same timestamp for both the start and end Instant values. Instant timestamp = event.getTimestamp(); Execution skippedEvent = Execution.skipped(event.getTestDescriptor(), timestamp, timestamp, event.getRequiredPayload(String.class)); executions.add(skippedEvent); } case FINISHED -> { Instant startInstant = executionStarts.remove(event.getTestDescriptor()); Instant endInstant = event.getTimestamp(); // If "started" events have already been filtered out, it is no // longer possible to create a legitimate Execution for the current // descriptor. So we effectively ignore it in that case. if (startInstant != null) { Execution finishedEvent = Execution.finished(event.getTestDescriptor(), startInstant, endInstant, event.getRequiredPayload(TestExecutionResult.class)); executions.add(finishedEvent); } } default -> { // Ignore other events } } } return executions; } } ================================================ FILE: junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/TerminationInfo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.testkit.engine; import static org.apiguardian.api.API.Status.MAINTAINED; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.TestExecutionResult; /** * {@code TerminationInfo} is a union type that allows propagation of terminated * container/test state, supporting either the reason if the container/test * was skipped or the {@link TestExecutionResult} if the container/test was executed. * * @since 1.4 * @see Execution#getTerminationInfo() */ @API(status = MAINTAINED, since = "1.7") public class TerminationInfo { // --- Factories ----------------------------------------------------------- /** * Create a skipped {@code TerminationInfo} instance for the * supplied reason. * * @param reason the reason the execution was skipped; may be {@code null} * @return the created {@code TerminationInfo}; never {@code null} * @see #executed(TestExecutionResult) */ public static TerminationInfo skipped(@Nullable String reason) { return new TerminationInfo(true, reason, null); } /** * Create an executed {@code TerminationInfo} instance for the * supplied {@link TestExecutionResult}. * * @param testExecutionResult the result of the execution; never {@code null} * @return the created {@code TerminationInfo}; never {@code null} * @see #skipped(String) */ public static TerminationInfo executed(TestExecutionResult testExecutionResult) { Preconditions.notNull(testExecutionResult, "TestExecutionResult must not be null"); return new TerminationInfo(false, null, testExecutionResult); } // ------------------------------------------------------------------------- private final boolean skipped; private final @Nullable String skipReason; private final @Nullable TestExecutionResult testExecutionResult; private TerminationInfo(boolean skipped, @Nullable String skipReason, @Nullable TestExecutionResult testExecutionResult) { boolean executed = (testExecutionResult != null); Preconditions.condition((skipped ^ executed), "TerminationInfo must represent either a skipped execution or a TestExecutionResult but not both"); this.skipped = skipped; this.skipReason = skipReason; this.testExecutionResult = testExecutionResult; } /** * Determine if this {@code TerminationInfo} represents a skipped execution. * * @return {@code true} if this this {@code TerminationInfo} represents a * skipped execution */ public boolean skipped() { return this.skipped; } /** * Determine if this {@code TerminationInfo} does not represent a skipped * execution. * * @return {@code true} if this this {@code TerminationInfo} does not * represent a skipped execution */ public boolean notSkipped() { return !skipped(); } /** * Determine if this {@code TerminationInfo} represents a completed execution. * * @return {@code true} if this this {@code TerminationInfo} represents a * completed execution */ public boolean executed() { return (this.testExecutionResult != null); } /** * Get the reason the execution was skipped. * * @return the reason the execution was skipped * @throws UnsupportedOperationException if this {@code TerminationInfo} * does not represent a skipped execution */ public @Nullable String getSkipReason() throws UnsupportedOperationException { if (skipped()) { return this.skipReason; } // else throw new UnsupportedOperationException("No skip reason contained in this TerminationInfo"); } /** * Get the {@link TestExecutionResult} for the completed execution. * * @return the result of the completed execution * @throws UnsupportedOperationException if this {@code TerminationInfo} * does not represent a completed execution */ public TestExecutionResult getExecutionResult() throws UnsupportedOperationException { if (this.testExecutionResult != null) { return this.testExecutionResult; } // else throw new UnsupportedOperationException("No TestExecutionResult contained in this TerminationInfo"); } @Override public String toString() { ToStringBuilder builder = new ToStringBuilder(this); if (skipped()) { builder.append("skipped", true).append("reason", this.skipReason); } else { builder.append("executed", true).append("result", this.testExecutionResult); } return builder.toString(); } } ================================================ FILE: junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/TestExecutionResultConditions.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.testkit.engine; import static java.util.function.Predicate.isEqual; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.junit.platform.commons.util.FunctionUtils.where; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.function.Predicate; import org.apiguardian.api.API; import org.assertj.core.api.Assertions; import org.assertj.core.api.Condition; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.FunctionUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.TestExecutionResult.Status; /** * Collection of AssertJ {@linkplain Condition conditions} for * {@link TestExecutionResult}. * * @since 1.4 * @see EventConditions */ @API(status = MAINTAINED, since = "1.7") public final class TestExecutionResultConditions { private TestExecutionResultConditions() { /* no-op */ } /** * Create a new {@link Condition} that matches if and only if a * {@link TestExecutionResult}'s {@linkplain TestExecutionResult#getStatus() * status} is equal to the supplied {@link Status Status}. */ public static Condition status(Status expectedStatus) { return new Condition<>(where(TestExecutionResult::getStatus, isEqual(expectedStatus)), "status is %s", expectedStatus); } /** * Create a new {@link Condition} that matches if and only if a * {@link TestExecutionResult}'s * {@linkplain TestExecutionResult#getThrowable() throwable} matches all * supplied conditions. */ @SafeVarargs @SuppressWarnings("varargs") public static Condition throwable(Condition... conditions) { List> list = Arrays.stream(conditions)// .map(TestExecutionResultConditions::throwable)// .toList(); return Assertions.allOf(list); } /** * Create a new {@link Condition} that matches if and only if a * {@link Throwable}'s {@linkplain Throwable#getCause() cause} matches all * supplied conditions. * * @see #rootCause(Condition...) * @see #suppressed(int, Condition...) */ @SafeVarargs @SuppressWarnings("varargs") public static Condition cause(Condition... conditions) { List> list = Arrays.stream(conditions)// .map(TestExecutionResultConditions::cause)// .toList(); return Assertions.allOf(list); } /** * Create a new {@link Condition} that matches if and only if a * {@link Throwable}'s root {@linkplain Throwable#getCause() cause} matches * all supplied conditions. * * @since 1.11 * @see #cause(Condition...) * @see #suppressed(int, Condition...) */ @API(status = MAINTAINED, since = "1.13.3") @SafeVarargs @SuppressWarnings("varargs") public static Condition rootCause(Condition... conditions) { List> list = Arrays.stream(conditions)// .map(TestExecutionResultConditions::rootCause)// .toList(); return Assertions.allOf(list); } /** * Create a new {@link Condition} that matches if and only if a * {@link Throwable}'s {@linkplain Throwable#getSuppressed() suppressed * throwable} at the supplied index matches all supplied conditions. * * @see #cause(Condition...) * @see #rootCause(Condition...) */ @SafeVarargs @SuppressWarnings("varargs") public static Condition suppressed(int index, Condition... conditions) { List> list = Arrays.stream(conditions)// .map(condition -> suppressed(index, condition))// .toList(); return Assertions.allOf(list); } /** * Create a new {@link Condition} that matches if and only if a * {@link Throwable} is an {@linkplain Class#isInstance(Object) instance of} * the supplied {@link Class}. */ public static Condition instanceOf(Class expectedType) { return new Condition<>(expectedType::isInstance, "instance of %s", expectedType.getName()); } /** * Create a new {@link Condition} that matches if and only if a * {@link Throwable}'s {@linkplain Throwable#getMessage() message} is equal * to the supplied {@link String}. */ public static Condition message(String expectedMessage) { return new Condition<>( FunctionUtils. where(Throwable::getMessage, isEqual(expectedMessage)), "message is '%s'", expectedMessage); } /** * Create a new {@link Condition} that matches if and only if a * {@link Throwable}'s {@linkplain Throwable#getMessage() message} matches * the supplied {@link Predicate}. */ public static Condition message(Predicate expectedMessagePredicate) { return new Condition<>( FunctionUtils. where(Throwable::getMessage, expectedMessagePredicate), "message matches predicate"); } private static Condition throwable(Condition condition) { return new Condition<>( where(TestExecutionResult::getThrowable, throwable -> throwable.isPresent() && condition.matches(throwable.get())), "throwable matches %s", condition); } private static Condition cause(Condition condition) { return new Condition<>(throwable -> condition.matches(throwable.getCause()), "throwable cause matches %s", condition); } private static Condition rootCause(Condition condition) { Predicate predicate = throwable -> { Preconditions.notNull(throwable, "Throwable must not be null"); Preconditions.notNull(throwable.getCause(), "Throwable does not have a cause"); Throwable rootCause = getRootCause(throwable, new ArrayList<>()); return condition.matches(rootCause); }; return new Condition<>(predicate, "throwable root cause matches %s", condition); } /** * Get the root cause of the supplied {@link Throwable}, or the supplied * {@link Throwable} if it has no cause. */ private static Throwable getRootCause(Throwable throwable, List causeChain) { // If we have already seen the current Throwable, that means we have // encountered recursion in the cause chain and therefore return the last // Throwable in the cause chain, which was the root cause before the recursion. if (causeChain.contains(throwable)) { return causeChain.get(causeChain.size() - 1); } Throwable cause = throwable.getCause(); if (cause == null) { return throwable; } // Track current Throwable before recursing. causeChain.add(throwable); return getRootCause(cause, causeChain); } private static Condition suppressed(int index, Condition condition) { return new Condition<>( throwable -> throwable.getSuppressed().length > index && condition.matches(throwable.getSuppressed()[index]), "suppressed throwable at index %d matches %s", index, condition); } } ================================================ FILE: junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Test Kit for testing the execution of a {@link org.junit.platform.engine.TestEngine} * running on the JUnit Platform. */ @NullMarked package org.junit.platform.testkit.engine; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-testkit/src/main/java/org/junit/platform/testkit/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Test Kit for the JUnit Platform. */ @NullMarked package org.junit.platform.testkit; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-platform-testkit/src/test/README.md ================================================ For compatibility with the Eclipse IDE, the test for this module are in the `platform-tests` project. ================================================ FILE: junit-start/junit-start.gradle.kts ================================================ plugins { id("junitbuild.java-library-conventions") } description = "JUnit Start Module" dependencies { api(platform(projects.junitBom)) api(projects.junitJupiter) compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) compileOnlyApi(projects.junitJupiterEngine) implementation(projects.junitPlatformLauncher) implementation(projects.junitPlatformConsole) } backwardCompatibilityChecks { enabled = false // TODO enable after initial release } ================================================ FILE: junit-start/src/main/java/module-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /// Defines the API of the JUnit Start module for writing and running tests. /// /// Usage example in a `HelloTests.java` compact source-file program: /// ```java /// import module org.junit.start; /// /// void main() { /// JUnit.run(); /// } /// /// @Test /// void addition() { /// Assertions.assertEquals(2, 1 + 1, "Addition error detected!"); /// } /// ``` /// @since 6.1 module org.junit.start { requires static transitive org.apiguardian.api; requires static transitive org.jspecify; requires transitive org.junit.jupiter; requires org.junit.platform.launcher; requires org.junit.platform.console; exports org.junit.start; } ================================================ FILE: junit-start/src/main/java/org/junit/start/JUnit.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.start; import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectModule; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import java.io.PrintWriter; import java.nio.charset.Charset; import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; import org.junit.platform.console.output.ColorPalette; import org.junit.platform.console.output.Theme; import org.junit.platform.console.output.TreePrintingListener; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.launcher.core.LauncherFactory; import org.junit.platform.launcher.listeners.SummaryGeneratingListener; /// This class provides simple helpers to discover and execute tests. /// /// @since 6.1 @API(status = EXPERIMENTAL, since = "6.1") public final class JUnit { /// Run all tests defined in the caller class. public static void run() { var walker = StackWalker.getInstance(RETAIN_CLASS_REFERENCE); run(selectClass(walker.getCallerClass())); } /// Run all tests defined in the given test class. /// /// @param testClass the class to discover and execute tests in public static void run(Class testClass) { run(selectClass(testClass)); } /// Run all tests defined in the given module. /// /// @param testModule the module to discover and execute tests in public static void run(Module testModule) { run(selectModule(testModule)); } private static void run(DiscoverySelector selector) { var listener = new SummaryGeneratingListener(); var charset = Charset.defaultCharset(); var writer = new PrintWriter(System.out, true, charset); var palette = System.getenv("NO_COLOR") != null ? ColorPalette.NONE : ColorPalette.DEFAULT; var theme = Theme.valueOf(charset); var printer = new TreePrintingListener(writer, palette, theme); var request = request().selectors(selector).forExecution() // .listeners(listener, printer) // .build(); var launcher = LauncherFactory.create(); launcher.execute(request); var summary = listener.getSummary(); if (summary.getTotalFailureCount() == 0) return; summary.printFailuresTo(new PrintWriter(System.err, true, charset)); throw new JUnitException("JUnit run finished with %d failure%s".formatted( // summary.getTotalFailureCount(), // summary.getTotalFailureCount() == 1 ? "" : "s")); } private JUnit() { } } ================================================ FILE: junit-start/src/main/java/org/junit/start/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /// Contains JUnit Start API for writing and running tests. /// /// @since 6.1 @NullMarked package org.junit.start; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-vintage-engine/junit-vintage-engine.gradle.kts ================================================ plugins { id("junitbuild.java-library-conventions") id("junitbuild.junit4-compatibility") id("junitbuild.testing-conventions") `java-test-fixtures` groovy } description = "JUnit Vintage Engine" dependencies { api(platform(projects.junitBom)) api(projects.junitPlatformEngine) api(libs.junit4) compileOnlyApi(libs.apiguardian) compileOnlyApi(libs.jspecify) testFixturesApi(platform(libs.groovy2.bom)) testFixturesApi(libs.spock1) testFixturesImplementation(projects.junitPlatformSuiteApi) testImplementation(projects.junitPlatformLauncher) testImplementation(projects.junitPlatformSuiteEngine) testImplementation(projects.junitPlatformTestkit) testImplementation(testFixtures(projects.junitPlatformCommons)) testImplementation(testFixtures(projects.junitJupiterApi)) testImplementation(testFixtures(projects.junitPlatformLauncher)) testImplementation(testFixtures(projects.junitPlatformReporting)) osgiVerification(projects.junitPlatformLauncher) } tasks { compileJava { options.compilerArgs.add("-Xlint:-requires-automatic") // JUnit 4 } compileTestFixturesGroovy { javaLauncher = project.javaToolchains.launcherFor { // Groovy 2.x (used for Spock tests) does not run on more recent JDKs languageVersion = JavaLanguageVersion.of(17) } } jar { bundle { val junit4Min = libs.versions.junit4Min.get() val version = project.version val importAPIGuardian: String by extra val importJSpecify: String by extra val importCommonsLogging: String by extra bnd(""" # Import JUnit4 packages with a version Import-Package: \ ${importAPIGuardian},\ ${importJSpecify},\ ${importCommonsLogging},\ junit.runner;version="[${junit4Min},5)",\ org.junit;version="[${junit4Min},5)",\ org.junit.experimental.categories;version="[${junit4Min},5)",\ org.junit.internal.builders;version="[${junit4Min},5)",\ org.junit.runner.*;version="[${junit4Min},5)",\ org.junit.runners.model;version="[${junit4Min},5)",\ * Provide-Capability:\ org.junit.platform.engine;\ org.junit.platform.engine='junit-vintage';\ version:Version="${'$'}{version_cleanup;$version}" Require-Capability:\ org.junit.platform.launcher;\ filter:='(&(org.junit.platform.launcher=junit-platform-launcher)(version>=${'$'}{version_cleanup;$version})(!(version>=${'$'}{versionmask;+;${'$'}{version_cleanup;$version}})))';\ effective:=active """) } } val testWithoutJUnit4 by registering(Test::class) { val test by testing.suites.existing(JvmTestSuite::class) (options as JUnitPlatformOptions).apply { includeTags("missing-junit4") } setIncludes(listOf("**/JUnit4VersionCheckTests.class")) testClassesDirs = files(test.map { it.sources.output.classesDirs }) classpath = files(test.map { it.sources.runtimeClasspath }).filter { !it.name.startsWith("junit-4") } } withType().named { it != testWithoutJUnit4.name }.configureEach { (options as JUnitPlatformOptions).apply { excludeTags("missing-junit4") } } withType().configureEach { // Workaround for Groovy 2.5 jvmArgs("--add-opens", "java.base/java.lang=ALL-UNNAMED") jvmArgs("--add-opens", "java.base/java.util=ALL-UNNAMED") } check { dependsOn(testWithoutJUnit4) } } eclipse { classpath { // Avoid exposing test resources to dependent projects containsTestFixtures = false } project { // Remove Groovy Nature, since we don't require a Groovy plugin for Eclipse // in order for developers to work with the code base. natures.removeAll { it == "org.eclipse.jdt.groovy.core.groovyNature" } } } ================================================ FILE: junit-vintage-engine/src/main/java/module-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Provides a {@link org.junit.platform.engine.TestEngine} for running JUnit 3 * and 4 based tests on the platform. * * @since 4.12 * @provides org.junit.platform.engine.TestEngine The {@code VintageTestEngine} * runs JUnit 3 and 4 based tests on the platform. */ @SuppressWarnings("deprecation") module org.junit.vintage.engine { requires static org.apiguardian.api; requires static transitive org.jspecify; requires junit; // 4 requires org.junit.platform.engine; provides org.junit.platform.engine.TestEngine with org.junit.vintage.engine.VintageTestEngine; } ================================================ FILE: junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine; import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.MAINTAINED; import org.apiguardian.api.API; /** * Collection of constants related to the {@link VintageTestEngine}. * * @deprecated Should only be used temporarily while migrating tests to JUnit * Jupiter or another testing framework with native JUnit Platform support */ @Deprecated(since = "6.0") @API(status = DEPRECATED, since = "6.0") public final class Constants { /** * Property name used to indicate whether parallel execution is enabled for the JUnit Vintage engine: {@value} * *

Set this property to {@code true} to enable parallel execution of tests. * Defaults to {@code false}. * * @since 5.12 */ @API(status = MAINTAINED, since = "5.13.3") public static final String PARALLEL_EXECUTION_ENABLED = "junit.vintage.execution.parallel.enabled"; /** * Property name used to specify the size of the thread pool to be used for parallel execution: {@value} * *

Set this property to an integer value to specify the number of threads * to be used for parallel execution. Defaults to the number of available * processors. * * @since 5.12 */ @API(status = MAINTAINED, since = "5.13.3") public static final String PARALLEL_POOL_SIZE = "junit.vintage.execution.parallel.pool-size"; /** * Property name used to indicate whether parallel execution is enabled for test classes in the * JUnit Vintage engine: {@value} * *

Set this property to {@code true} to enable parallel execution of test * classes. Defaults to {@code false}. * * @since 5.12 */ @API(status = MAINTAINED, since = "5.13.3") public static final String PARALLEL_CLASS_EXECUTION = "junit.vintage.execution.parallel.classes"; /** * Property name used to indicate whether parallel execution is enabled for test methods in the * JUnit Vintage engine: {@value} * *

Set this property to {@code true} to enable parallel execution of test * methods. Defaults to {@code false}. * * @since 5.12 */ @API(status = MAINTAINED, since = "5.13.3") public static final String PARALLEL_METHOD_EXECUTION = "junit.vintage.execution.parallel.methods"; /** * Property name used to configure whether the JUnit Vintage engine should * report discovery issues such as deprecation notices: {@value} * *

Set this property to {@code false} to disable reporting of discovery * issues. Defaults to {@code true}. * * @since 6.0.1 */ @API(status = MAINTAINED, since = "6.0.1") public static final String DISCOVERY_ISSUE_REPORTING_ENABLED_PROPERTY_NAME = "junit.vintage.discovery.issue.reporting.enabled"; private Constants() { /* no-op */ } } ================================================ FILE: junit-vintage-engine/src/main/java/org/junit/vintage/engine/JUnit4VersionCheck.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine; import static org.junit.vintage.engine.descriptor.VintageTestDescriptor.ENGINE_ID; import java.math.BigDecimal; import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; import junit.runner.Version; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.UnrecoverableExceptions; /** * @since 5.4 */ class JUnit4VersionCheck { private static final Pattern versionPattern = Pattern.compile("^(\\d+\\.\\d+).*"); private static final BigDecimal minVersion = new BigDecimal("4.12"); static void checkSupported() { try { checkSupported(Version::id); } catch (NoClassDefFoundError e) { throw new JUnitException( "Invalid class/module path: junit-vintage-engine is present but junit:junit is not. " + "Please either remove junit-vintage-engine or add junit:junit, or " + "alternatively use an excludeEngines(\"" + ENGINE_ID + "\") filter."); } } static void checkSupported(Supplier versionSupplier) { String versionString = readVersion(versionSupplier); BigDecimal version = parseVersion(versionString); if (version.compareTo(minVersion) < 0) { throw new JUnitException("Unsupported version of junit:junit: " + versionString + ". Please upgrade to version " + minVersion + " or later."); } } static BigDecimal parseVersion(String versionString) { try { Matcher matcher = versionPattern.matcher(versionString); if (matcher.matches()) { return new BigDecimal(matcher.group(1)); } } catch (Exception e) { throw new JUnitException("Failed to parse version of junit:junit: " + versionString, e); } throw new JUnitException("Failed to parse version of junit:junit: " + versionString); } private static String readVersion(Supplier versionSupplier) { try { return versionSupplier.get(); } catch (Throwable t) { UnrecoverableExceptions.rethrowIfUnrecoverable(t); throw new JUnitException("Failed to read version of junit:junit", t); } } private JUnit4VersionCheck() { } } ================================================ FILE: junit-vintage-engine/src/main/java/org/junit/vintage/engine/VintageTestEngine.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine; import static org.apiguardian.api.API.Status.DEPRECATED; import static org.junit.platform.engine.TestExecutionResult.successful; import static org.junit.vintage.engine.descriptor.VintageTestDescriptor.ENGINE_ID; import java.util.Optional; import org.apiguardian.api.API; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.UniqueId; import org.junit.vintage.engine.descriptor.VintageEngineDescriptor; import org.junit.vintage.engine.discovery.VintageDiscoverer; import org.junit.vintage.engine.execution.VintageExecutor; /** * The JUnit Vintage {@link TestEngine}. * * @since 4.12 * @deprecated Should only be used temporarily while migrating tests to JUnit * Jupiter or another testing framework with native JUnit Platform support */ @Deprecated(since = "6.0") @API(status = DEPRECATED, since = "6.0") public final class VintageTestEngine implements TestEngine { @Override public String getId() { return ENGINE_ID; } /** * Returns {@code org.junit.vintage} as the group ID. */ @Override public Optional getGroupId() { return Optional.of("org.junit.vintage"); } /** * Returns {@code junit-vintage-engine} as the artifact ID. */ @Override public Optional getArtifactId() { return Optional.of("junit-vintage-engine"); } @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { JUnit4VersionCheck.checkSupported(); return new VintageDiscoverer().discover(discoveryRequest, uniqueId); } @Override public void execute(ExecutionRequest request) { EngineExecutionListener engineExecutionListener = request.getEngineExecutionListener(); VintageEngineDescriptor engineDescriptor = (VintageEngineDescriptor) request.getRootTestDescriptor(); engineExecutionListener.executionStarted(engineDescriptor); new VintageExecutor(engineDescriptor, engineExecutionListener, request.getConfigurationParameters()).executeAllChildren(request.getCancellationToken()); engineExecutionListener.executionFinished(engineDescriptor, successful()); } } ================================================ FILE: junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/DescriptionUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.descriptor; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.runner.Description; @API(status = API.Status.INTERNAL, since = "5.8") public class DescriptionUtils { private DescriptionUtils() { } public static @Nullable String getMethodName(Description description) { String displayName = description.getDisplayName(); int i = displayName.indexOf('('); if (i >= 0) { int j = displayName.lastIndexOf('('); if (i == j) { char lastChar = displayName.charAt(displayName.length() - 1); if (lastChar == ')') { return displayName.substring(0, i); } } } return description.getMethodName(); } } ================================================ FILE: junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/OrFilter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.descriptor; import static java.util.stream.Collectors.joining; import java.util.Collection; import org.junit.platform.commons.util.Preconditions; import org.junit.runner.Description; import org.junit.runner.manipulation.Filter; /** * @since 5.4 */ class OrFilter extends Filter { private final Collection filters; OrFilter(Collection filters) { this.filters = Preconditions.notEmpty(filters, "filters must not be empty"); } @Override public boolean shouldRun(Description description) { return filters.stream().anyMatch(filter -> filter.shouldRun(description)); } @Override public String describe() { return filters.stream().map(Filter::describe).collect(joining(" OR ")); } } ================================================ FILE: junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerDecorator.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.descriptor; import static org.apiguardian.api.API.Status.INTERNAL; import org.apiguardian.api.API; import org.junit.runner.Runner; @API(status = INTERNAL, since = "5.4") public interface RunnerDecorator { Runner getDecoratedRunner(); } ================================================ FILE: junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerRequest.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.descriptor; import org.junit.runner.Request; import org.junit.runner.Runner; /** * @since 4.12 */ class RunnerRequest extends Request { private final Runner runner; RunnerRequest(Runner runner) { this.runner = runner; } @Override public Runner getRunner() { return runner; } } ================================================ FILE: junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerTestDescriptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.descriptor; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.platform.engine.support.hierarchical.OpenTest4JAwareThrowableCollector; import org.junit.platform.engine.support.hierarchical.ThrowableCollector; import org.junit.runner.Description; import org.junit.runner.Request; import org.junit.runner.Runner; import org.junit.runner.manipulation.Filter; import org.junit.runner.manipulation.Filterable; import org.junit.runner.manipulation.NoTestsRemainException; import org.junit.runners.ParentRunner; import org.junit.runners.model.RunnerScheduler; /** * @since 4.12 */ @API(status = INTERNAL, since = "4.12") public class RunnerTestDescriptor extends VintageTestDescriptor { private static final Logger logger = LoggerFactory.getLogger(RunnerTestDescriptor.class); private final Set rejectedExclusions = new HashSet<>(); private Runner runner; private final boolean ignored; private boolean wasFiltered; private @Nullable List filters = new ArrayList<>(); public RunnerTestDescriptor(UniqueId uniqueId, Class testClass, Runner runner, boolean ignored) { super(uniqueId, runner.getDescription(), testClass.getSimpleName(), ClassSource.from(testClass)); this.runner = runner; this.ignored = ignored; } @Override public String getLegacyReportingName() { return getSource().map(source -> ((ClassSource) source).getClassName()) // .orElseThrow(() -> new JUnitException("source should have been present")); } public Request toRequest() { return new RunnerRequest(this.runner); } public Runner getRunner() { return runner; } @Override protected boolean tryToExcludeFromRunner(Description description) { boolean excluded = tryToFilterRunner(description); if (excluded) { wasFiltered = true; } else { rejectedExclusions.add(description); } return excluded; } private boolean tryToFilterRunner(Description description) { if (runner instanceof Filterable filterable) { ExcludeDescriptionFilter filter = new ExcludeDescriptionFilter(description); try { filterable.filter(filter); } catch (NoTestsRemainException ignore) { // it's safe to ignore this exception because childless TestDescriptors will get pruned } return filter.wasSuccessful(); } return false; } @Override protected boolean canBeRemovedFromHierarchy() { return true; } @Override public void prune() { if (wasFiltered) { // filtering the runner may render intermediate Descriptions obsolete // (e.g. test classes without any remaining children in a suite) pruneDescriptorsForObsoleteDescriptions(List.of(runner.getDescription())); } if (rejectedExclusions.isEmpty()) { super.prune(); } else if (rejectedExclusions.containsAll(getDescription().getChildren())) { // since the Runner was asked to remove all of its direct children, // it's safe to remove it entirely removeFromHierarchy(); } else { logIncompleteFiltering(); } } private void logIncompleteFiltering() { if (runner instanceof Filterable) { logger.warn(() -> "Runner " + getRunnerToReport().getClass().getName() // + " (used on class " + getLegacyReportingName() + ") was not able to satisfy all filter requests."); } else { warnAboutUnfilterableRunner(); } } private void warnAboutUnfilterableRunner() { logger.warn(() -> "Runner " + getRunnerToReport().getClass().getName() // + " (used on class " + getLegacyReportingName() + ") does not support filtering" // + " and will therefore be run completely."); } public Optional> getFilters() { return Optional.ofNullable(filters); } public void clearFilters() { this.filters = null; } public void applyFilters(Consumer childrenCreator) { if (filters != null && !filters.isEmpty()) { if (runner instanceof Filterable) { this.runner = toRequest().filterWith(new OrFilter(filters)).getRunner(); this.description = runner.getDescription(); this.children.clear(); childrenCreator.accept(this); } else { warnAboutUnfilterableRunner(); } } clearFilters(); } private Runner getRunnerToReport() { return (runner instanceof RunnerDecorator decorator) ? decorator.getDecoratedRunner() : runner; } public boolean isIgnored() { return ignored; } public void setExecutorService(ExecutorService executorService) { Runner runner = getRunnerToReport(); if (runner instanceof ParentRunner parentRunner) { parentRunner.setScheduler(new RunnerScheduler() { private final List> futures = new CopyOnWriteArrayList<>(); @Override public void schedule(Runnable childStatement) { futures.add(executorService.submit(childStatement)); } @Override public void finished() { ThrowableCollector collector = new OpenTest4JAwareThrowableCollector(); AtomicBoolean wasInterrupted = new AtomicBoolean(false); for (Future future : futures) { collector.execute(() -> { // We're calling `Future.get()` individually to allow for work stealing // in case `ExecutorService` is a `ForkJoinPool` try { future.get(); } catch (ExecutionException e) { throw e.getCause(); } catch (InterruptedException e) { wasInterrupted.set(true); } }); } collector.assertEmpty(); if (wasInterrupted.get()) { logger.warn(() -> "Interrupted while waiting for runner to finish"); Thread.currentThread().interrupt(); } } }); } } private static class ExcludeDescriptionFilter extends Filter { private final Description description; private boolean successful; ExcludeDescriptionFilter(Description description) { this.description = description; } @Override public boolean shouldRun(Description description) { if (this.description.equals(description)) { successful = true; return false; } return true; } @Override public String describe() { return "exclude " + description; } boolean wasSuccessful() { return successful; } } } ================================================ FILE: junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/TestSourceProvider.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.descriptor; import static java.util.Collections.synchronizedMap; import static java.util.function.Predicate.isEqual; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.commons.support.HierarchyTraversalMode.TOP_DOWN; import static org.junit.platform.commons.support.ReflectionSupport.findMethods; import static org.junit.platform.commons.util.FunctionUtils.where; import java.lang.reflect.Method; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.support.ModifierSupport; import org.junit.platform.commons.util.LruCache; import org.junit.platform.engine.TestSource; import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.platform.engine.support.descriptor.MethodSource; import org.junit.runner.Description; /** * @since 5.6 */ @API(status = INTERNAL, since = "5.6") public class TestSourceProvider { @SuppressWarnings("serial") private static final TestSource NULL_SOURCE = new TestSource() { }; private final Map testSourceCache = new ConcurrentHashMap<>(); private final Map, List> methodsCache = synchronizedMap(new LruCache<>(31)); public @Nullable TestSource findTestSource(Description description) { TestSource testSource = testSourceCache.computeIfAbsent(description, this::computeTestSource); return testSource == NULL_SOURCE ? null : testSource; } private TestSource computeTestSource(Description description) { Class testClass = description.getTestClass(); if (testClass != null) { String methodName = DescriptionUtils.getMethodName(description); if (methodName != null) { Method method = findMethod(testClass, sanitizeMethodName(methodName)); if (method != null) { return MethodSource.from(testClass, method); } } return ClassSource.from(testClass); } return NULL_SOURCE; } private String sanitizeMethodName(String methodName) { if (methodName.contains("[") && methodName.endsWith("]")) { // special case for parameterized tests return methodName.substring(0, methodName.indexOf("[")); } return methodName; } private @Nullable Method findMethod(Class testClass, String methodName) { List methods = methodsCache.computeIfAbsent(testClass, clazz -> findMethods(clazz, m -> true, TOP_DOWN)).stream() // .filter(where(Method::getName, isEqual(methodName))) // .toList(); if (methods.isEmpty()) { return null; } if (methods.size() == 1) { return methods.get(0); } methods = methods.stream().filter(ModifierSupport::isPublic).toList(); if (methods.size() == 1) { return methods.get(0); } return null; } } ================================================ FILE: junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageEngineDescriptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.descriptor; import static org.apiguardian.api.API.Status.INTERNAL; import org.apiguardian.api.API; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.EngineDescriptor; /** * @since 5.6 */ @API(status = INTERNAL, since = "5.6") public class VintageEngineDescriptor extends EngineDescriptor { public VintageEngineDescriptor(UniqueId uniqueId) { super(uniqueId, "JUnit Vintage"); } } ================================================ FILE: junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageTestDescriptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.descriptor; import static java.util.Arrays.stream; import static java.util.function.Predicate.isEqual; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.commons.util.StringUtils.isNotBlank; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.Set; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.experimental.categories.Category; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestSource; import org.junit.platform.engine.TestTag; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; import org.junit.runner.Description; /** * @since 4.12 */ @API(status = INTERNAL, since = "4.12") public class VintageTestDescriptor extends AbstractTestDescriptor { public static final String ENGINE_ID = "junit-vintage"; public static final String SEGMENT_TYPE_RUNNER = "runner"; public static final String SEGMENT_TYPE_TEST = "test"; public static final String SEGMENT_TYPE_DYNAMIC = "dynamic"; protected Description description; public VintageTestDescriptor(UniqueId uniqueId, Description description, @Nullable TestSource source) { this(uniqueId, description, generateDisplayName(description), source); } VintageTestDescriptor(UniqueId uniqueId, Description description, String displayName, @Nullable TestSource source) { super(uniqueId, displayName, source); this.description = description; } private static String generateDisplayName(Description description) { String methodName = DescriptionUtils.getMethodName(description); return isNotBlank(methodName) ? methodName : description.getDisplayName(); } public Description getDescription() { return description; } @Override public String getLegacyReportingName() { String methodName = DescriptionUtils.getMethodName(description); if (methodName == null) { String className = description.getClassName(); if (isNotBlank(className)) { return className; } } return super.getLegacyReportingName(); } @Override public Type getType() { return description.isTest() ? Type.TEST : Type.CONTAINER; } @Override public Set getTags() { Set tags = new LinkedHashSet<>(); addTagsFromParent(tags); addCategoriesAsTags(tags); return tags; } @Override public void removeFromHierarchy() { if (canBeRemovedFromHierarchy()) { super.removeFromHierarchy(); } } protected boolean canBeRemovedFromHierarchy() { return tryToExcludeFromRunner(this.description); } protected boolean tryToExcludeFromRunner(Description description) { // @formatter:off return getParent().map(VintageTestDescriptor.class::cast) .map(parent -> parent.tryToExcludeFromRunner(description)) .orElse(false); // @formatter:on } void pruneDescriptorsForObsoleteDescriptions(List newSiblingDescriptions) { Optional newDescription = newSiblingDescriptions.stream().filter(isEqual(description)).findAny(); if (newDescription.isPresent()) { List newChildren = newDescription.get().getChildren(); new ArrayList<>(children).stream().map(VintageTestDescriptor.class::cast).forEach( childDescriptor -> childDescriptor.pruneDescriptorsForObsoleteDescriptions(newChildren)); } else { super.removeFromHierarchy(); } } private void addTagsFromParent(Set tags) { getParent().map(TestDescriptor::getTags).ifPresent(tags::addAll); } private void addCategoriesAsTags(Set tags) { Category annotation = description.getAnnotation(Category.class); if (annotation != null) { // @formatter:off stream(annotation.value()) .map(ReflectionUtils::getAllAssignmentCompatibleClasses) .flatMap(Collection::stream) .distinct() .map(Class::getName) .map(TestTag::create) .forEachOrdered(tags::add); // @formatter:on } } } ================================================ FILE: junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Test descriptors used within the JUnit Vintage test engine. */ @NullMarked package org.junit.vintage.engine.descriptor; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/ClassSelectorResolver.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.discovery; import static java.util.Collections.emptySet; import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.unresolved; import static org.junit.vintage.engine.descriptor.VintageTestDescriptor.SEGMENT_TYPE_RUNNER; import java.util.Optional; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.support.scanning.ClassFilter; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.UniqueId.Segment; import org.junit.platform.engine.discovery.ClassSelector; import org.junit.platform.engine.discovery.UniqueIdSelector; import org.junit.platform.engine.support.discovery.SelectorResolver; import org.junit.runner.Runner; import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; /** * @since 4.12 */ class ClassSelectorResolver implements SelectorResolver { private static final DefensiveAllDefaultPossibilitiesBuilder RUNNER_BUILDER = new DefensiveAllDefaultPossibilitiesBuilder(); private final ClassFilter classFilter; ClassSelectorResolver(ClassFilter classFilter) { this.classFilter = classFilter; } @Override public Resolution resolve(ClassSelector selector, Context context) { if (classFilter.match(selector.getClassName())) { return resolveTestClassThatPassedNameFilter(selector.getJavaClass(), context); } return unresolved(); } @Override public Resolution resolve(UniqueIdSelector selector, Context context) { Segment lastSegment = selector.getUniqueId().getLastSegment(); if (SEGMENT_TYPE_RUNNER.equals(lastSegment.getType())) { String testClassName = lastSegment.getValue(); if (classFilter.match(testClassName)) { Class testClass = ReflectionSupport.tryToLoadClass(testClassName)// .getNonNullOrThrow(cause -> new JUnitException("Unknown class: " + testClassName, cause)); return resolveTestClassThatPassedNameFilter(testClass, context); } } return unresolved(); } private Resolution resolveTestClassThatPassedNameFilter(Class testClass, Context context) { if (!classFilter.match(testClass)) { return unresolved(); } Runner runner = RUNNER_BUILDER.safeRunnerForClass(testClass); if (runner == null) { return unresolved(); } return context.addToParent(parent -> Optional.of(createRunnerTestDescriptor(parent, testClass, runner))).map( runnerTestDescriptor -> Match.exact(runnerTestDescriptor, () -> { runnerTestDescriptor.clearFilters(); return emptySet(); })).map(Resolution::match).orElse(unresolved()); } private RunnerTestDescriptor createRunnerTestDescriptor(TestDescriptor parent, Class testClass, Runner runner) { UniqueId uniqueId = parent.getUniqueId().append(SEGMENT_TYPE_RUNNER, testClass.getName()); return new RunnerTestDescriptor(uniqueId, testClass, runner, RUNNER_BUILDER.isIgnored(runner)); } } ================================================ FILE: junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/DefensiveAllDefaultPossibilitiesBuilder.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.discovery; import java.lang.reflect.Method; import java.util.function.Predicate; import org.jspecify.annotations.Nullable; import org.junit.Ignore; import org.junit.internal.builders.AllDefaultPossibilitiesBuilder; import org.junit.internal.builders.AnnotatedBuilder; import org.junit.internal.builders.IgnoredBuilder; import org.junit.internal.builders.IgnoredClassRunner; import org.junit.internal.builders.JUnit4Builder; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.runner.Runner; import org.junit.runner.manipulation.Filterable; import org.junit.runners.model.RunnerBuilder; /** * Customization of {@link AllDefaultPossibilitiesBuilder} from JUnit 4 to * ignore certain classes that would otherwise be reported as errors or cause * infinite recursion. * * @since 4.12 * @see DefensiveAnnotatedBuilder * @see DefensiveJUnit4Builder * @see IgnoredClassRunner */ class DefensiveAllDefaultPossibilitiesBuilder extends AllDefaultPossibilitiesBuilder { private static final Logger logger = LoggerFactory.getLogger(DefensiveAllDefaultPossibilitiesBuilder.class); private final AnnotatedBuilder annotatedBuilder; private final JUnit4Builder junit4Builder; private final IgnoredBuilder ignoredBuilder; @SuppressWarnings("deprecation") DefensiveAllDefaultPossibilitiesBuilder() { super(true); annotatedBuilder = new DefensiveAnnotatedBuilder(this); junit4Builder = new DefensiveJUnit4Builder(); ignoredBuilder = new NullIgnoredBuilder(); } @Override public Runner runnerForClass(Class testClass) throws Throwable { Runner runner = super.runnerForClass(testClass); if (testClass.getAnnotation(Ignore.class) != null) { if (runner == null) { return new IgnoredClassRunner(testClass); } return decorateIgnoredTestClass(runner); } return runner; } boolean isIgnored(Runner runner) { return runner instanceof IgnoredClassRunner || runner instanceof IgnoringRunnerDecorator; } /** * Instead of checking for the {@link Ignore} annotation and returning an * {@link IgnoredClassRunner} from {@link IgnoredBuilder}, we've let the * super class determine the regular runner that would have been used if * {@link Ignore} hadn't been present. Now, we decorate the runner to * override its runtime behavior (i.e. skip execution) but return its * regular {@link org.junit.runner.Description}. */ private IgnoringRunnerDecorator decorateIgnoredTestClass(Runner runner) { if (runner instanceof Filterable) { return new FilterableIgnoringRunnerDecorator(runner); } return new IgnoringRunnerDecorator(runner); } @Override protected AnnotatedBuilder annotatedBuilder() { return annotatedBuilder; } @Override protected JUnit4Builder junit4Builder() { return junit4Builder; } @Override protected IgnoredBuilder ignoredBuilder() { return ignoredBuilder; } /** * Customization of {@link AnnotatedBuilder} that ignores classes annotated * with {@code @RunWith(JUnitPlatform.class)} to avoid infinite recursion. */ private static class DefensiveAnnotatedBuilder extends AnnotatedBuilder { DefensiveAnnotatedBuilder(RunnerBuilder suiteBuilder) { super(suiteBuilder); } @Override public @Nullable Runner buildRunner(Class runnerClass, Class testClass) throws Exception { // Referenced by name because it might not be available at runtime. if ("org.junit.platform.runner.JUnitPlatform".equals(runnerClass.getName())) { logger.warn(() -> "Ignoring test class using JUnitPlatform runner: " + testClass.getName()); return null; } return super.buildRunner(runnerClass, testClass); } } /** * Customization of {@link JUnit4Builder} that ignores classes that do not * contain any test methods in order not to report errors for them. */ private static class DefensiveJUnit4Builder extends JUnit4Builder { private static final Predicate isPotentialJUnit4TestMethod = new IsPotentialJUnit4TestMethod(); @Override public @Nullable Runner runnerForClass(Class testClass) throws Throwable { if (containsTestMethods(testClass)) { return super.runnerForClass(testClass); } return null; } private boolean containsTestMethods(Class testClass) { return ReflectionUtils.isMethodPresent(testClass, isPotentialJUnit4TestMethod); } } /** * Customization of {@link IgnoredBuilder} that always returns {@code null}. * * @since 5.1 */ private static class NullIgnoredBuilder extends IgnoredBuilder { @Override public @Nullable Runner runnerForClass(Class testClass) { // don't ignore entire test classes just yet return null; } } } ================================================ FILE: junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/FilterableIgnoringRunnerDecorator.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.discovery; import org.junit.platform.commons.util.Preconditions; import org.junit.runner.Runner; import org.junit.runner.manipulation.Filter; import org.junit.runner.manipulation.Filterable; import org.junit.runner.manipulation.NoTestsRemainException; /** * {@link Filterable} {@link IgnoringRunnerDecorator}. * * @since 5.1 */ class FilterableIgnoringRunnerDecorator extends IgnoringRunnerDecorator implements Filterable { FilterableIgnoringRunnerDecorator(Runner runner) { super(runner); Preconditions.condition(runner instanceof Filterable, () -> "Runner must be an instance of Filterable: " + runner.getClass().getName()); } @Override public void filter(Filter filter) throws NoTestsRemainException { ((Filterable) runner).filter(filter); } } ================================================ FILE: junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IgnoringRunnerDecorator.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.discovery; import org.junit.platform.commons.util.Preconditions; import org.junit.runner.Description; import org.junit.runner.Runner; import org.junit.runner.notification.RunNotifier; import org.junit.vintage.engine.descriptor.RunnerDecorator; /** * Decorator for Runners that will be ignored completely. * *

Contrary to {@link org.junit.internal.builders.IgnoredClassRunner}, this * runner returns a complete description including all children. * * @since 5.1 */ class IgnoringRunnerDecorator extends Runner implements RunnerDecorator { protected final Runner runner; IgnoringRunnerDecorator(Runner runner) { this.runner = Preconditions.notNull(runner, "Runner must not be null"); } @Override public Description getDescription() { return runner.getDescription(); } @Override public void run(RunNotifier notifier) { notifier.fireTestIgnored(getDescription()); } @Override public Runner getDecoratedRunner() { return runner; } } ================================================ FILE: junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestClass.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.discovery; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.commons.support.ModifierSupport.isAbstract; import static org.junit.platform.commons.support.ModifierSupport.isPublic; import static org.junit.platform.commons.util.ReflectionUtils.isInnerClass; import java.util.function.Predicate; import org.apiguardian.api.API; /** * @since 4.12 */ @API(status = INTERNAL, since = "5.8", consumers = "org.junit.vintage.**") public class IsPotentialJUnit4TestClass implements Predicate> { @Override public boolean test(Class candidate) { // Do not collapse into a single return statement. if (!isPublic(candidate)) { return false; } if (isAbstract(candidate)) { return false; } if (isInnerClass(candidate)) { return false; } return true; } } ================================================ FILE: junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestMethod.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.discovery; import java.lang.reflect.Method; import java.util.function.Predicate; import org.junit.Test; /** * @since 4.12 */ class IsPotentialJUnit4TestMethod implements Predicate { @Override public boolean test(Method method) { // Don't use AnnotationUtils.isAnnotated since JUnit 4 does not support // meta-annotations return method.isAnnotationPresent(Test.class); } } ================================================ FILE: junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/MethodSelectorResolver.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.discovery; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.unresolved; import static org.junit.vintage.engine.descriptor.VintageTestDescriptor.SEGMENT_TYPE_RUNNER; import java.util.Optional; import java.util.function.Function; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.discovery.MethodSelector; import org.junit.platform.engine.discovery.UniqueIdSelector; import org.junit.platform.engine.support.discovery.SelectorResolver; import org.junit.runner.Description; import org.junit.runner.manipulation.Filter; import org.junit.vintage.engine.descriptor.DescriptionUtils; import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; /** * @since 4.12 */ class MethodSelectorResolver implements SelectorResolver { @Override public Resolution resolve(MethodSelector selector, Context context) { Class testClass = selector.getJavaClass(); return resolveParentAndAddFilter(context, selectClass(testClass), parent -> toMethodFilter(selector)); } @Override public Resolution resolve(UniqueIdSelector selector, Context context) { for (UniqueId current = selector.getUniqueId(); !current.getSegments().isEmpty(); current = current.removeLastSegment()) { if (SEGMENT_TYPE_RUNNER.equals(current.getLastSegment().getType())) { return resolveParentAndAddFilter(context, selectUniqueId(current), parent -> toUniqueIdFilter(parent, selector.getUniqueId())); } } return unresolved(); } private Resolution resolveParentAndAddFilter(Context context, DiscoverySelector selector, Function filterCreator) { return context.resolve(selector).flatMap(parent -> addFilter(parent, filterCreator)).map( this::toResolution).orElse(unresolved()); } private Optional addFilter(TestDescriptor parent, Function filterCreator) { if (parent instanceof RunnerTestDescriptor runnerTestDescriptor) { runnerTestDescriptor.getFilters().ifPresent( filters -> filters.add(filterCreator.apply(runnerTestDescriptor))); return Optional.of(runnerTestDescriptor); } return Optional.empty(); } private Resolution toResolution(RunnerTestDescriptor parent) { return Resolution.match(Match.partial(parent)); } private Filter toMethodFilter(MethodSelector methodSelector) { Class testClass = methodSelector.getJavaClass(); String methodName = methodSelector.getMethodName(); return matchMethodDescription(Description.createTestDescription(testClass, methodName)); } private Filter toUniqueIdFilter(RunnerTestDescriptor runnerTestDescriptor, UniqueId uniqueId) { return new UniqueIdFilter(runnerTestDescriptor, uniqueId); } /** * The method {@link Filter#matchMethodDescription(Description)} returns a * filter that does not account for the case when the description is for a * {@link org.junit.runners.Parameterized} runner. */ private static Filter matchMethodDescription(final Description desiredDescription) { String desiredMethodName = DescriptionUtils.getMethodName(desiredDescription); return new Filter() { @Override public boolean shouldRun(Description description) { if (description.isTest()) { return desiredDescription.equals(description) || isParameterizedMethod(description); } // explicitly check if any children want to run for (Description each : description.getChildren()) { if (shouldRun(each)) { return true; } } return false; } private boolean isParameterizedMethod(Description description) { String methodName = DescriptionUtils.getMethodName(description); return methodName != null && methodName.startsWith(desiredMethodName + "["); } @Override public String describe() { return "Method %s".formatted(desiredDescription.getDisplayName()); } }; } } ================================================ FILE: junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/RunnerTestDescriptorPostProcessor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.discovery; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.toCollection; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.function.IntFunction; import org.junit.platform.engine.UniqueId; import org.junit.runner.Description; import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; import org.junit.vintage.engine.descriptor.TestSourceProvider; import org.junit.vintage.engine.descriptor.VintageTestDescriptor; import org.junit.vintage.engine.support.UniqueIdReader; import org.junit.vintage.engine.support.UniqueIdStringifier; /** * @since 5.5 */ class RunnerTestDescriptorPostProcessor { private final UniqueIdReader uniqueIdReader = new UniqueIdReader(); private final UniqueIdStringifier uniqueIdStringifier = new UniqueIdStringifier(); private final TestSourceProvider testSourceProvider = new TestSourceProvider(); void applyFiltersAndCreateDescendants(RunnerTestDescriptor runnerTestDescriptor) { addChildrenRecursively(runnerTestDescriptor); runnerTestDescriptor.applyFilters(this::addChildrenRecursively); } private void addChildrenRecursively(VintageTestDescriptor parent) { if (parent.getDescription().isTest()) { return; } List children = parent.getDescription().getChildren(); // Use LinkedHashMap to preserve order, ArrayList for fast access by index Map> childrenByUniqueId = children.stream().collect( groupingBy(uniqueIdReader.andThen(uniqueIdStringifier), LinkedHashMap::new, toCollection(ArrayList::new))); for (Entry> entry : childrenByUniqueId.entrySet()) { String uniqueId = entry.getKey(); List childrenWithSameUniqueId = entry.getValue(); IntFunction uniqueIdGenerator = determineUniqueIdGenerator(uniqueId, childrenWithSameUniqueId); for (int index = 0; index < childrenWithSameUniqueId.size(); index++) { String reallyUniqueId = uniqueIdGenerator.apply(index); Description description = childrenWithSameUniqueId.get(index); UniqueId id = parent.getUniqueId().append(VintageTestDescriptor.SEGMENT_TYPE_TEST, reallyUniqueId); VintageTestDescriptor child = new VintageTestDescriptor(id, description, testSourceProvider.findTestSource(description)); parent.addChild(child); addChildrenRecursively(child); } } } private IntFunction determineUniqueIdGenerator(String uniqueId, List childrenWithSameUniqueId) { if (childrenWithSameUniqueId.size() == 1) { return index -> uniqueId; } return index -> uniqueId + "[" + index + "]"; } } ================================================ FILE: junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/UniqueIdFilter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.discovery; import static java.util.stream.Collectors.toSet; import java.util.ArrayDeque; import java.util.Collections; import java.util.Deque; import java.util.Optional; import java.util.Set; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.runner.Description; import org.junit.runner.manipulation.Filter; import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; import org.junit.vintage.engine.descriptor.VintageTestDescriptor; /** * @since 4.12 */ class UniqueIdFilter extends Filter { private final RunnerTestDescriptor runnerTestDescriptor; private final UniqueId uniqueId; @SuppressWarnings({ "NullAway.Init", "NotNullFieldNotInitialized" }) private Deque path; @SuppressWarnings({ "NullAway.Init", "NotNullFieldNotInitialized" }) private Set descendants; UniqueIdFilter(RunnerTestDescriptor runnerTestDescriptor, UniqueId uniqueId) { this.runnerTestDescriptor = runnerTestDescriptor; this.uniqueId = uniqueId; } @SuppressWarnings("ConstantValue") private void ensureInitialized() { if (descendants == null) { Optional identifiedTestDescriptor = runnerTestDescriptor.findByUniqueId(uniqueId); descendants = determineDescendants(identifiedTestDescriptor); path = determinePath(runnerTestDescriptor, identifiedTestDescriptor); } } private Deque determinePath(RunnerTestDescriptor runnerTestDescriptor, Optional identifiedTestDescriptor) { Deque path = new ArrayDeque<>(); Optional current = identifiedTestDescriptor; while (current.isPresent() && !current.get().equals(runnerTestDescriptor)) { path.addFirst(((VintageTestDescriptor) current.get()).getDescription()); current = current.get().getParent(); } return path; } private Set determineDescendants(Optional identifiedTestDescriptor) { // @formatter:off return identifiedTestDescriptor.map( testDescriptor -> testDescriptor .getDescendants() .stream() .map(VintageTestDescriptor.class::cast) .map(VintageTestDescriptor::getDescription) .collect(toSet())) .orElseGet(Collections::emptySet); // @formatter:on } @Override public boolean shouldRun(Description description) { ensureInitialized(); return path.contains(description) || descendants.contains(description); } @Override public String describe() { return "Unique ID " + uniqueId; } } ================================================ FILE: junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/VintageDiscoverer.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.discovery; import static org.apiguardian.api.API.Status.INTERNAL; import org.apiguardian.api.API; import org.junit.platform.commons.support.scanning.ClassFilter; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver; import org.junit.vintage.engine.Constants; import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; import org.junit.vintage.engine.descriptor.VintageEngineDescriptor; /** * @since 4.12 */ @API(status = INTERNAL, since = "4.12") public class VintageDiscoverer { private static final IsPotentialJUnit4TestClass isPotentialJUnit4TestClass = new IsPotentialJUnit4TestClass(); // @formatter:off private static final EngineDiscoveryRequestResolver resolver = EngineDiscoveryRequestResolver.builder() .addClassContainerSelectorResolver(isPotentialJUnit4TestClass) .addSelectorResolver(context -> new ClassSelectorResolver(ClassFilter.of(context.getClassNameFilter(), isPotentialJUnit4TestClass))) .addSelectorResolver(new MethodSelectorResolver()) .build(); // @formatter:on public VintageEngineDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { VintageEngineDescriptor engineDescriptor = new VintageEngineDescriptor(uniqueId); resolver.resolve(discoveryRequest, engineDescriptor); RunnerTestDescriptorPostProcessor postProcessor = new RunnerTestDescriptorPostProcessor(); for (TestDescriptor testDescriptor : engineDescriptor.getChildren()) { RunnerTestDescriptor runnerTestDescriptor = (RunnerTestDescriptor) testDescriptor; postProcessor.applyFiltersAndCreateDescendants(runnerTestDescriptor); } if (isDiscoveryIssueReportingEnabled(discoveryRequest) && !engineDescriptor.getChildren().isEmpty()) { var issue = DiscoveryIssue.create(DiscoveryIssue.Severity.INFO, // "The JUnit Vintage engine is deprecated and should only be " // + "used temporarily while migrating tests to JUnit Jupiter or another testing " // + "framework with native JUnit Platform support."); discoveryRequest.getDiscoveryListener().issueEncountered(uniqueId, issue); } return engineDescriptor; } @SuppressWarnings("deprecation") private static boolean isDiscoveryIssueReportingEnabled(EngineDiscoveryRequest discoveryRequest) { return discoveryRequest.getConfigurationParameters() // .getBoolean(Constants.DISCOVERY_ISSUE_REPORTING_ENABLED_PROPERTY_NAME) // .orElse(true); } } ================================================ FILE: junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Internal classes for test discovery within the JUnit Vintage test engine. */ @NullMarked package org.junit.vintage.engine.discovery; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/CancellationTokenAwareRunNotifier.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.execution; import org.junit.platform.engine.CancellationToken; import org.junit.runner.Description; import org.junit.runner.notification.RunNotifier; import org.junit.runner.notification.StoppedByUserException; /** * @since 6.0 */ class CancellationTokenAwareRunNotifier extends RunNotifier { private final CancellationToken cancellationToken; CancellationTokenAwareRunNotifier(CancellationToken cancellationToken) { this.cancellationToken = cancellationToken; } @Override public void fireTestStarted(Description description) throws StoppedByUserException { if (cancellationToken.isCancellationRequested()) { pleaseStop(); } super.fireTestStarted(description); } } ================================================ FILE: junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/EventType.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.execution; /** * @since 5.4.1 */ enum EventType { REPORTED, SYNTHETIC } ================================================ FILE: junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunListenerAdapter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.execution; import static org.junit.vintage.engine.descriptor.VintageTestDescriptor.SEGMENT_TYPE_DYNAMIC; import java.util.Optional; import java.util.function.Function; import org.jspecify.annotations.Nullable; import org.junit.Ignore; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.runner.Description; import org.junit.runner.Result; import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunListener; import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; import org.junit.vintage.engine.descriptor.TestSourceProvider; import org.junit.vintage.engine.descriptor.VintageTestDescriptor; import org.junit.vintage.engine.support.UniqueIdReader; import org.junit.vintage.engine.support.UniqueIdStringifier; /** * @since 4.12 */ class RunListenerAdapter extends RunListener { private final TestRun testRun; private final EngineExecutionListener listener; private final TestSourceProvider testSourceProvider; private final Function uniqueIdExtractor; RunListenerAdapter(TestRun testRun, EngineExecutionListener listener, TestSourceProvider testSourceProvider) { this.testRun = testRun; this.listener = listener; this.testSourceProvider = testSourceProvider; this.uniqueIdExtractor = new UniqueIdReader().andThen(new UniqueIdStringifier()); } @Override public void testRunStarted(Description description) { if (description.isSuite() && !testRun.getRunnerTestDescriptor().isIgnored()) { fireExecutionStarted(testRun.getRunnerTestDescriptor(), EventType.REPORTED); } } @Override public void testSuiteStarted(Description description) { RunnerTestDescriptor runnerTestDescriptor = testRun.getRunnerTestDescriptor(); // runnerTestDescriptor is reported in testRunStarted if (!runnerTestDescriptor.getDescription().equals(description)) { testStarted(lookupOrRegisterNextTestDescriptor(description), EventType.REPORTED); } } @Override public void testIgnored(Description description) { TestDescriptor testDescriptor = lookupOrRegisterNextTestDescriptor(description); String reason = determineReasonForIgnoredTest(testDescriptor, description).orElse(""); testIgnored(testDescriptor, reason); } @Override public void testStarted(Description description) { testStarted(lookupOrRegisterNextTestDescriptor(description), EventType.REPORTED); } @Override public void testAssumptionFailure(Failure failure) { handleFailure(failure, TestExecutionResult::aborted); } @Override public void testFailure(Failure failure) { handleFailure(failure, TestExecutionResult::failed); } @Override public void testFinished(Description description) { testFinished(lookupOrRegisterCurrentTestDescriptor(description)); } @Override public void testSuiteFinished(Description description) { RunnerTestDescriptor runnerTestDescriptor = testRun.getRunnerTestDescriptor(); // runnerTestDescriptor is reported in testRunFinished if (!runnerTestDescriptor.getDescription().equals(description)) { reportContainerFinished(lookupOrRegisterCurrentTestDescriptor(description)); } } @Override public void testRunFinished(Result result) { testRunFinished(); } void testRunFinished() { reportContainerFinished(testRun.getRunnerTestDescriptor()); } private void reportContainerFinished(TestDescriptor containerTestDescriptor) { if (testRun.isNotSkipped(containerTestDescriptor)) { if (testRun.isNotStarted(containerTestDescriptor)) { fireExecutionStarted(containerTestDescriptor, EventType.SYNTHETIC); } testRun.getInProgressTestDescriptorsWithSyntheticStartEvents().stream() // .filter(this::canFinish) // .forEach(this::fireExecutionFinished); if (testRun.isNotFinished(containerTestDescriptor)) { fireExecutionFinished(containerTestDescriptor); } } } private TestDescriptor lookupOrRegisterNextTestDescriptor(Description description) { return lookupOrRegisterTestDescriptor(description, testRun::lookupNextTestDescriptor); } private TestDescriptor lookupOrRegisterCurrentTestDescriptor(Description description) { return lookupOrRegisterTestDescriptor(description, testRun::lookupCurrentTestDescriptor); } private TestDescriptor lookupOrRegisterTestDescriptor(Description description, Function> lookup) { return lookup.apply(description).orElseGet(() -> registerDynamicTestDescriptor(description, lookup)); } private VintageTestDescriptor registerDynamicTestDescriptor(Description description, Function> lookup) { // workaround for dynamic children as used by Spock's Runner TestDescriptor parent = findParent(description, lookup); UniqueId uniqueId = parent.getUniqueId().append(SEGMENT_TYPE_DYNAMIC, uniqueIdExtractor.apply(description)); VintageTestDescriptor dynamicDescriptor = new VintageTestDescriptor(uniqueId, description, testSourceProvider.findTestSource(description)); parent.addChild(dynamicDescriptor); testRun.registerDynamicTest(dynamicDescriptor); dynamicTestRegistered(dynamicDescriptor); return dynamicDescriptor; } private TestDescriptor findParent(Description description, Function> lookup) { // @formatter:off return Optional.ofNullable(description.getTestClass()) .map(Description::createSuiteDescription) .flatMap(lookup) .orElseGet(testRun::getRunnerTestDescriptor); // @formatter:on } private void handleFailure(Failure failure, Function resultCreator) { handleFailure(failure, resultCreator, lookupOrRegisterCurrentTestDescriptor(failure.getDescription())); } private void handleFailure(Failure failure, Function resultCreator, TestDescriptor testDescriptor) { TestExecutionResult result = resultCreator.apply(failure.getException()); testRun.storeResult(testDescriptor, result); if (testRun.isNotStarted(testDescriptor)) { testStarted(testDescriptor, EventType.SYNTHETIC); } if (testRun.isNotFinished(testDescriptor) && testDescriptor.isContainer() && testRun.hasSyntheticStartEvent(testDescriptor) && testRun.isDescendantOfRunnerTestDescriptor(testDescriptor)) { testFinished(testDescriptor); } } private void testIgnored(TestDescriptor testDescriptor, String reason) { fireExecutionFinishedForInProgressNonAncestorTestDescriptorsWithSyntheticStartEvents(testDescriptor); fireExecutionStartedIncludingUnstartedAncestors(testDescriptor.getParent()); fireExecutionSkipped(testDescriptor, reason); } private Optional determineReasonForIgnoredTest(TestDescriptor testDescriptor, Description description) { Optional reason = getReason(description.getAnnotation(Ignore.class)); if (reason.isPresent()) { return reason; } // Workaround for some runners (e.g. JUnit38ClassRunner) don't include the @Ignore annotation // in the description, so we read it from the test class directly return testDescriptor.getSource() // .filter(ClassSource.class::isInstance) // .map(source -> ((ClassSource) source).getJavaClass()) // .flatMap(testClass -> getReason(testClass.getAnnotation(Ignore.class))); } private static Optional getReason(@Nullable Ignore annotation) { return Optional.ofNullable(annotation).map(Ignore::value); } private void dynamicTestRegistered(TestDescriptor testDescriptor) { fireExecutionStartedIncludingUnstartedAncestors(testDescriptor.getParent()); listener.dynamicTestRegistered(testDescriptor); } private void testStarted(TestDescriptor testDescriptor, EventType eventType) { fireExecutionFinishedForInProgressNonAncestorTestDescriptorsWithSyntheticStartEvents(testDescriptor); fireExecutionStartedIncludingUnstartedAncestors(testDescriptor.getParent()); fireExecutionStarted(testDescriptor, eventType); } private void fireExecutionFinishedForInProgressNonAncestorTestDescriptorsWithSyntheticStartEvents( TestDescriptor testDescriptor) { testRun.getInProgressTestDescriptorsWithSyntheticStartEvents().stream() // .filter(it -> !isAncestor(it, testDescriptor) && canFinish(it)) // .forEach(this::fireExecutionFinished); } private boolean isAncestor(TestDescriptor candidate, TestDescriptor testDescriptor) { Optional parent = testDescriptor.getParent(); if (parent.isEmpty()) { return false; } if (parent.get().equals(candidate)) { return true; } return isAncestor(candidate, parent.get()); } private void testFinished(TestDescriptor descriptor) { fireExecutionFinished(descriptor); } private void fireExecutionStartedIncludingUnstartedAncestors(Optional parent) { if (parent.isPresent() && canStart(parent.get())) { fireExecutionStartedIncludingUnstartedAncestors(parent.get().getParent()); fireExecutionStarted(parent.get(), EventType.SYNTHETIC); } } private boolean canStart(TestDescriptor testDescriptor) { return testRun.isNotStarted(testDescriptor) // && (testDescriptor.equals(testRun.getRunnerTestDescriptor()) || testRun.isDescendantOfRunnerTestDescriptor(testDescriptor)); } private boolean canFinish(TestDescriptor testDescriptor) { return testRun.isNotFinished(testDescriptor) // && testRun.isDescendantOfRunnerTestDescriptor(testDescriptor) && testRun.areAllFinishedOrSkipped(testDescriptor.getChildren()); } private void fireExecutionSkipped(TestDescriptor testDescriptor, String reason) { testRun.markSkipped(testDescriptor); listener.executionSkipped(testDescriptor, reason); } private void fireExecutionStarted(TestDescriptor testDescriptor, EventType eventType) { testRun.markStarted(testDescriptor, eventType); listener.executionStarted(testDescriptor); } private void fireExecutionFinished(TestDescriptor testDescriptor) { testRun.markFinished(testDescriptor); listener.executionFinished(testDescriptor, testRun.getStoredResultOrSuccessful(testDescriptor)); } } ================================================ FILE: junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunnerExecutor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.execution; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.engine.TestExecutionResult.failed; import org.apiguardian.api.API; import org.junit.platform.commons.util.UnrecoverableExceptions; import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.TestExecutionResult; import org.junit.runner.notification.RunNotifier; import org.junit.runner.notification.StoppedByUserException; import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; import org.junit.vintage.engine.descriptor.TestSourceProvider; /** * @since 4.12 */ @API(status = INTERNAL, since = "4.12") public class RunnerExecutor { private final EngineExecutionListener engineExecutionListener; private final CancellationToken cancellationToken; private final TestSourceProvider testSourceProvider = new TestSourceProvider(); public RunnerExecutor(EngineExecutionListener engineExecutionListener, CancellationToken cancellationToken) { this.engineExecutionListener = engineExecutionListener; this.cancellationToken = cancellationToken; } public void execute(RunnerTestDescriptor runnerTestDescriptor) { if (cancellationToken.isCancellationRequested()) { engineExecutionListener.executionSkipped(runnerTestDescriptor, "Execution cancelled"); return; } RunNotifier notifier = new CancellationTokenAwareRunNotifier(cancellationToken); var testRun = new TestRun(runnerTestDescriptor); var listener = new RunListenerAdapter(testRun, engineExecutionListener, testSourceProvider); notifier.addListener(listener); try { listener.testRunStarted(runnerTestDescriptor.getDescription()); runnerTestDescriptor.getRunner().run(notifier); listener.testRunFinished(); } catch (StoppedByUserException e) { reportEventsForCancellation(e, testRun); } catch (Throwable t) { UnrecoverableExceptions.rethrowIfUnrecoverable(t); reportUnexpectedFailure(testRun, runnerTestDescriptor, failed(t)); } } private void reportEventsForCancellation(StoppedByUserException exception, TestRun testRun) { testRun.getInProgressTestDescriptors().forEach(startedDescriptor -> { startedDescriptor.getChildren().forEach(child -> { if (!testRun.isFinishedOrSkipped(child)) { engineExecutionListener.executionSkipped(child, "Execution cancelled"); testRun.markSkipped(child); } }); engineExecutionListener.executionFinished(startedDescriptor, TestExecutionResult.aborted(exception)); testRun.markFinished(startedDescriptor); }); } private void reportUnexpectedFailure(TestRun testRun, RunnerTestDescriptor runnerTestDescriptor, TestExecutionResult result) { if (testRun.isNotStarted(runnerTestDescriptor)) { engineExecutionListener.executionStarted(runnerTestDescriptor); } engineExecutionListener.executionFinished(runnerTestDescriptor, result); } } ================================================ FILE: junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/TestRun.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.execution; import static java.util.Collections.emptyList; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toCollection; import static java.util.stream.Collectors.toMap; import static java.util.stream.Stream.concat; import static org.junit.platform.engine.TestExecutionResult.failed; import static org.junit.platform.engine.TestExecutionResult.successful; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.stream.Stream; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.runner.Description; import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; import org.junit.vintage.engine.descriptor.VintageTestDescriptor; import org.opentest4j.MultipleFailuresError; /** * @since 4.12 */ class TestRun { private final RunnerTestDescriptor runnerTestDescriptor; private final Set runnerDescendants; private final Map descriptionToDescriptors; private final Map> executionResults = new LinkedHashMap<>(); private final Set skippedDescriptors = new LinkedHashSet<>(); private final Set startedDescriptors = new HashSet<>(); private final Map inProgressDescriptors = new LinkedHashMap<>(); private final Set finishedDescriptors = new LinkedHashSet<>(); private final ThreadLocal> inProgressDescriptorsByStartingThread = ThreadLocal.withInitial( ArrayDeque::new); TestRun(RunnerTestDescriptor runnerTestDescriptor) { this.runnerTestDescriptor = runnerTestDescriptor; runnerDescendants = new LinkedHashSet<>(runnerTestDescriptor.getDescendants()); // @formatter:off descriptionToDescriptors = concat(Stream.of(runnerTestDescriptor), runnerDescendants.stream()) .map(VintageTestDescriptor.class::cast) .collect(toMap(VintageTestDescriptor::getDescription, VintageDescriptors::new, VintageDescriptors::merge, HashMap::new)); // @formatter:on } void registerDynamicTest(VintageTestDescriptor testDescriptor) { descriptionToDescriptors.computeIfAbsent(testDescriptor.getDescription(), __ -> new VintageDescriptors()).add( testDescriptor); runnerDescendants.add(testDescriptor); } RunnerTestDescriptor getRunnerTestDescriptor() { return runnerTestDescriptor; } Collection getInProgressTestDescriptorsWithSyntheticStartEvents() { List result = inProgressDescriptors.entrySet().stream() // .filter(entry -> entry.getValue().equals(EventType.SYNTHETIC)) // .map(Entry::getKey) // .collect(toCollection(ArrayList::new)); Collections.reverse(result); return result; } Collection getInProgressTestDescriptors() { List result = new ArrayList<>(inProgressDescriptors.keySet()); Collections.reverse(result); return result; } boolean isDescendantOfRunnerTestDescriptor(TestDescriptor testDescriptor) { return runnerDescendants.contains(testDescriptor); } boolean hasSyntheticStartEvent(TestDescriptor testDescriptor) { return inProgressDescriptors.get(testDescriptor) == EventType.SYNTHETIC; } Optional lookupNextTestDescriptor(Description description) { return lookupUnambiguouslyOrApplyFallback(description, VintageDescriptors::getNextUnstarted); } Optional lookupCurrentTestDescriptor(Description description) { return lookupUnambiguouslyOrApplyFallback(description, __ -> { VintageTestDescriptor lastStarted = inProgressDescriptorsByStartingThread.get().peekLast(); if (lastStarted != null && description.equals(lastStarted.getDescription())) { return Optional.of(lastStarted); } return Optional.empty(); }); } private Optional lookupUnambiguouslyOrApplyFallback(Description description, Function> fallback) { VintageDescriptors vintageDescriptors = descriptionToDescriptors.getOrDefault(description, VintageDescriptors.NONE); Optional result = vintageDescriptors.getUnambiguously(description); if (result.isEmpty()) { result = fallback.apply(vintageDescriptors); } return result; } void markSkipped(TestDescriptor testDescriptor) { skippedDescriptors.add(testDescriptor); if (testDescriptor instanceof VintageTestDescriptor vintageDescriptor) { getVintageDescriptors(vintageDescriptor).incrementSkippedOrStarted(); } } boolean isNotSkipped(TestDescriptor testDescriptor) { return !isSkipped(testDescriptor); } boolean isSkipped(TestDescriptor testDescriptor) { return skippedDescriptors.contains(testDescriptor); } void markStarted(TestDescriptor testDescriptor, EventType eventType) { inProgressDescriptors.put(testDescriptor, eventType); startedDescriptors.add(testDescriptor); if (testDescriptor instanceof VintageTestDescriptor vintageDescriptor) { inProgressDescriptorsByStartingThread.get().addLast(vintageDescriptor); getVintageDescriptors(vintageDescriptor).incrementSkippedOrStarted(); } } private VintageDescriptors getVintageDescriptors(VintageTestDescriptor vintageDescriptor) { return requireNonNull(descriptionToDescriptors.get(vintageDescriptor.getDescription()), () -> "No descriptors for " + vintageDescriptor); } boolean isNotStarted(TestDescriptor testDescriptor) { return !startedDescriptors.contains(testDescriptor); } void markFinished(TestDescriptor testDescriptor) { inProgressDescriptors.remove(testDescriptor); finishedDescriptors.add(testDescriptor); if (testDescriptor instanceof VintageTestDescriptor descriptor) { inProgressDescriptorsByStartingThread.get().removeLastOccurrence(descriptor); } } boolean isNotFinished(TestDescriptor testDescriptor) { return !isFinished(testDescriptor); } boolean isFinished(TestDescriptor testDescriptor) { return finishedDescriptors.contains(testDescriptor); } boolean areAllFinishedOrSkipped(Set testDescriptors) { return testDescriptors.stream().allMatch(this::isFinishedOrSkipped); } boolean isFinishedOrSkipped(TestDescriptor testDescriptor) { return isFinished(testDescriptor) || isSkipped(testDescriptor); } void storeResult(TestDescriptor testDescriptor, TestExecutionResult result) { List testExecutionResults = executionResults.computeIfAbsent(testDescriptor, key -> new ArrayList<>()); testExecutionResults.add(result); } TestExecutionResult getStoredResultOrSuccessful(TestDescriptor testDescriptor) { List testExecutionResults = executionResults.get(testDescriptor); if (testExecutionResults == null) { return successful(); } if (testExecutionResults.size() == 1) { return testExecutionResults.get(0); } // @formatter:off List failures = testExecutionResults .stream() .map(TestExecutionResult::getThrowable) .map(Optional::orElseThrow) .toList(); // @formatter:on MultipleFailuresError multipleFailuresError = new MultipleFailuresError("", failures); failures.forEach(multipleFailuresError::addSuppressed); return failed(multipleFailuresError); } private static class VintageDescriptors { private static final VintageDescriptors NONE = new VintageDescriptors(emptyList()); private final List descriptors; private int skippedOrStartedCount; static VintageDescriptors merge(VintageDescriptors a, VintageDescriptors b) { List mergedDescriptors = new ArrayList<>( a.descriptors.size() + b.descriptors.size()); mergedDescriptors.addAll(a.descriptors); mergedDescriptors.addAll(b.descriptors); return new VintageDescriptors(mergedDescriptors); } VintageDescriptors(VintageTestDescriptor vintageTestDescriptor) { this(); add(vintageTestDescriptor); } VintageDescriptors() { this(new ArrayList<>(1)); } VintageDescriptors(List descriptors) { this.descriptors = descriptors; } void add(VintageTestDescriptor descriptor) { descriptors.add(descriptor); } /** * Returns the {@link TestDescriptor} that represents the specified * {@link Description}. * *

There are edge cases where multiple {@link Description Descriptions} * with the same {@code uniqueId} exist, e.g. when using overloaded methods * to define {@linkplain org.junit.experimental.theories.Theory theories}. * In this case, we try to find the correct {@link TestDescriptor} by * checking for object identity on the {@link Description} it represents. * * @param description the {@code Description} to look up */ @SuppressWarnings("ReferenceEquality") Optional getUnambiguously(Description description) { if (descriptors.isEmpty()) { return Optional.empty(); } if (descriptors.size() == 1) { return Optional.of(descriptors.get(0)); } // @formatter:off return descriptors.stream() .filter(testDescriptor -> description == testDescriptor.getDescription()) .findFirst(); // @formatter:on } private void incrementSkippedOrStarted() { skippedOrStartedCount++; } private Optional getNextUnstarted() { if (skippedOrStartedCount < descriptors.size()) { return Optional.of(descriptors.get(skippedOrStartedCount)); } return Optional.empty(); } } } ================================================ FILE: junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/VintageExecutor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.execution; import static java.util.Objects.requireNonNullElse; import static org.apiguardian.api.API.Status.INTERNAL; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import org.apiguardian.api.API; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.EngineExecutionListener; import org.junit.vintage.engine.Constants; import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; import org.junit.vintage.engine.descriptor.VintageEngineDescriptor; /** * @since 5.12 */ @SuppressWarnings({ "deprecation", "RedundantSuppression" }) @API(status = INTERNAL, since = "5.12") public class VintageExecutor { private static final Logger logger = LoggerFactory.getLogger(VintageExecutor.class); private static final int DEFAULT_THREAD_POOL_SIZE = Runtime.getRuntime().availableProcessors(); private static final int SHUTDOWN_TIMEOUT_SECONDS = 30; private final VintageEngineDescriptor engineDescriptor; private final EngineExecutionListener engineExecutionListener; private final ConfigurationParameters configurationParameters; private final boolean parallelExecutionEnabled; private final boolean classes; private final boolean methods; public VintageExecutor(VintageEngineDescriptor engineDescriptor, EngineExecutionListener engineExecutionListener, ConfigurationParameters configurationParameters) { this.engineDescriptor = engineDescriptor; this.engineExecutionListener = engineExecutionListener; this.configurationParameters = configurationParameters; this.parallelExecutionEnabled = configurationParameters.getBoolean(Constants.PARALLEL_EXECUTION_ENABLED).orElse( false); this.classes = configurationParameters.getBoolean(Constants.PARALLEL_CLASS_EXECUTION).orElse(false); this.methods = configurationParameters.getBoolean(Constants.PARALLEL_METHOD_EXECUTION).orElse(false); } public void executeAllChildren(CancellationToken cancellationToken) { if (!parallelExecutionEnabled) { executeClassesAndMethodsSequentially(cancellationToken); return; } if (!classes && !methods) { logger.warn(() -> "Parallel execution is enabled but no scope is defined. " + "Falling back to sequential execution."); executeClassesAndMethodsSequentially(cancellationToken); return; } boolean wasInterrupted = executeInParallel(cancellationToken); if (wasInterrupted) { Thread.currentThread().interrupt(); } } private void executeClassesAndMethodsSequentially(CancellationToken cancellationToken) { RunnerExecutor runnerExecutor = new RunnerExecutor(engineExecutionListener, cancellationToken); // Create a mutable copy so test descriptors can be made available for // GC immediately after execution. var children = new LinkedHashSet<>(engineDescriptor.getChildren()); for (var iterator = children.iterator(); iterator.hasNext();) { var testDescriptor = (RunnerTestDescriptor) iterator.next(); runnerExecutor.execute(testDescriptor); // Remove the test descriptor from the engine and iterable to allow GC. engineDescriptor.removeChild(testDescriptor); iterator.remove(); } } private boolean executeInParallel(CancellationToken cancellationToken) { ExecutorService executorService = Executors.newWorkStealingPool(getThreadPoolSize()); RunnerExecutor runnerExecutor = new RunnerExecutor(engineExecutionListener, cancellationToken); List runnerTestDescriptors = collectRunnerTestDescriptors(executorService); if (!classes) { executeClassesSequentially(runnerTestDescriptors, runnerExecutor); return false; } return executeClassesInParallel(runnerTestDescriptors, runnerExecutor, executorService); } private int getThreadPoolSize() { Optional optionalPoolSize = configurationParameters.get(Constants.PARALLEL_POOL_SIZE); if (optionalPoolSize.isPresent()) { try { int poolSize = Integer.parseInt(optionalPoolSize.get()); if (poolSize > 0) { return poolSize; } logger.warn(() -> "Invalid value for parallel pool size: " + poolSize); } catch (NumberFormatException e) { logger.warn(() -> "Invalid value for parallel pool size: " + optionalPoolSize.get()); } } return DEFAULT_THREAD_POOL_SIZE; } private List collectRunnerTestDescriptors(ExecutorService executorService) { return engineDescriptor.getChildren().stream() // .map(RunnerTestDescriptor.class::cast) // .map(it -> methods ? parallelMethodExecutor(it, executorService) : it) // .toList(); } private RunnerTestDescriptor parallelMethodExecutor(RunnerTestDescriptor runnerTestDescriptor, ExecutorService executorService) { runnerTestDescriptor.setExecutorService(executorService); return runnerTestDescriptor; } private void executeClassesSequentially(List runnerTestDescriptors, RunnerExecutor runnerExecutor) { for (RunnerTestDescriptor runnerTestDescriptor : runnerTestDescriptors) { runnerExecutor.execute(runnerTestDescriptor); } } private boolean executeClassesInParallel(List runnerTestDescriptors, RunnerExecutor runnerExecutor, ExecutorService executorService) { List> futures = new ArrayList<>(); for (RunnerTestDescriptor runnerTestDescriptor : runnerTestDescriptors) { CompletableFuture future = CompletableFuture.runAsync( () -> runnerExecutor.execute(runnerTestDescriptor), executorService); futures.add(future); } CompletableFuture allOf = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); boolean wasInterrupted = false; try { allOf.get(); } catch (InterruptedException e) { logger.warn(e, () -> "Interruption while waiting for parallel test execution to finish"); wasInterrupted = true; } catch (ExecutionException e) { throw ExceptionUtils.throwAsUncheckedException(requireNonNullElse(e.getCause(), e)); } finally { shutdownExecutorService(executorService); } return wasInterrupted; } private void shutdownExecutorService(ExecutorService executorService) { try { executorService.shutdown(); if (!executorService.awaitTermination(SHUTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS)) { logger.warn(() -> "Executor service did not terminate within the specified timeout"); executorService.shutdownNow(); } } catch (InterruptedException e) { logger.warn(e, () -> "Interruption while waiting for executor service to shut down"); Thread.currentThread().interrupt(); } } } ================================================ FILE: junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Internal classes for test execution within the JUnit Vintage test engine. */ @NullMarked package org.junit.vintage.engine.execution; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-vintage-engine/src/main/java/org/junit/vintage/engine/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Core package for the JUnit Vintage test engine. */ @NullMarked package org.junit.vintage.engine; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/UniqueIdReader.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.support; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.commons.util.ReflectionUtils.tryToReadFieldValue; import java.io.Serializable; import java.util.function.Function; import org.apiguardian.api.API; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.runner.Description; /** * @since 4.12 */ @API(status = INTERNAL, since = "4.12") public class UniqueIdReader implements Function { private static final Logger logger = LoggerFactory.getLogger(UniqueIdReader.class); private final String fieldName; public UniqueIdReader() { this("fUniqueId"); } // For tests only UniqueIdReader(String fieldName) { this.fieldName = fieldName; } @Override public Serializable apply(Description description) { // @formatter:off return tryToReadFieldValue(Description.class, fieldName, description) .andThenTry(Serializable.class::cast) .ifFailure(cause -> logger.warn(cause, () -> "Could not read unique ID for Description; using display name instead: %s".formatted(description))) .toOptional() .orElseGet(description::getDisplayName); // @formatter:on } } ================================================ FILE: junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/UniqueIdStringifier.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.support; import static org.apiguardian.api.API.Status.INTERNAL; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.Serializable; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.text.NumberFormat; import java.util.Base64; import java.util.Locale; import java.util.function.Function; import org.apiguardian.api.API; /** * @since 4.12 */ @API(status = INTERNAL, since = "4.12") public class UniqueIdStringifier implements Function { static final Charset CHARSET = StandardCharsets.UTF_8; @Override public String apply(Serializable uniqueId) { if (uniqueId instanceof CharSequence) { return uniqueId.toString(); } if (uniqueId instanceof Number) { return NumberFormat.getInstance(Locale.US).format(uniqueId); } return encodeBase64(serialize(uniqueId)); } private byte[] serialize(Serializable uniqueId) { ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); try (ObjectOutputStream out = new ObjectOutputStream(byteStream)) { out.writeObject(uniqueId); } catch (IOException e) { return uniqueId.toString().getBytes(CHARSET); } return byteStream.toByteArray(); } private String encodeBase64(byte[] bytes) { return new String(Base64.getEncoder().encode(bytes), CHARSET); } } ================================================ FILE: junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/package-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ /** * Internal support classes for test discovery and execution within the JUnit * Vintage test engine. */ @NullMarked package org.junit.vintage.engine.support; import org.jspecify.annotations.NullMarked; ================================================ FILE: junit-vintage-engine/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine ================================================ org.junit.vintage.engine.VintageTestEngine ================================================ FILE: junit-vintage-engine/src/test/java/org/junit/vintage/engine/JUnit4ParameterizedTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.platform.engine.TestExecutionResult.Status.FAILED; import static org.junit.platform.engine.TestExecutionResult.Status.SUCCESSFUL; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.launcher.EngineFilter.includeEngines; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Test; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.core.LauncherFactory; import org.junit.vintage.engine.samples.junit4.JUnit4ParameterizedTestCase; /** * @since 4.12 */ class JUnit4ParameterizedTests { private final Map callCounts = new HashMap<>(); @Test void selectingWholeParameterizedClassRunsTestsWithAllValues() { executeTests(selectClass(JUnit4ParameterizedTestCase.class)); Map expectedCallCounts = new HashMap<>(); expectedCallCounts.put(SUCCESSFUL, 3); expectedCallCounts.put(FAILED, 9); assertEquals(expectedCallCounts, callCounts); } @Test void selectingOneTestFromParameterizedClassRunsWithAllValues() { executeTests(selectMethod(JUnit4ParameterizedTestCase.class, "test1")); assertEquals(Map.of(FAILED, 3), callCounts); } private void executeTests(DiscoverySelector selector) { var launcher = LauncherFactory.create(); launcher.registerTestExecutionListeners(new StatusTrackingListener()); // @formatter:off launcher.execute( request() .selectors(selector) .filters(includeEngines("junit-vintage")) .enableImplicitConfigurationParameters(false) .forExecution() .build() ); // @formatter:on } private class StatusTrackingListener implements TestExecutionListener { @Override public void executionFinished(TestIdentifier identifier, TestExecutionResult result) { if (identifier.isTest()) { callCounts.merge(result.getStatus(), 1, Integer::sum); } } } } ================================================ FILE: junit-vintage-engine/src/test/java/org/junit/vintage/engine/JUnit4VersionCheckTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.platform.commons.JUnitException; /** * @since 5.4 */ class JUnit4VersionCheckTests { /** * @since 5.7 */ @Test void handlesParsingSupportedVersionIdWithStandardVersionFormat() { assertDoesNotThrow(() -> JUnit4VersionCheck.checkSupported(() -> "4.12")); assertDoesNotThrow(() -> JUnit4VersionCheck.checkSupported(() -> "4.13")); assertDoesNotThrow(() -> JUnit4VersionCheck.checkSupported(() -> "4.13.1")); assertDoesNotThrow(() -> JUnit4VersionCheck.checkSupported(() -> "4.13.2")); } /** * @since 5.7 */ @Test void handlesParsingSupportedVersionIdWithCustomizedVersionFormat() { assertDoesNotThrow(() -> JUnit4VersionCheck.checkSupported(() -> "4.12-patch_1")); assertDoesNotThrow(() -> JUnit4VersionCheck.checkSupported(() -> "4.12.0")); assertDoesNotThrow(() -> JUnit4VersionCheck.checkSupported(() -> "4.12.0.1")); assertDoesNotThrow(() -> JUnit4VersionCheck.checkSupported(() -> "4.12.0.patch-042")); } @Test void throwsExceptionForUnsupportedVersion() { var exception = assertThrows(JUnitException.class, () -> JUnit4VersionCheck.checkSupported(() -> "4.11")); assertEquals("Unsupported version of junit:junit: 4.11. Please upgrade to version 4.12 or later.", exception.getMessage()); } @Test void handlesErrorsReadingVersion() { Error error = new NoClassDefFoundError(); var exception = assertThrows(JUnitException.class, () -> JUnit4VersionCheck.checkSupported(() -> { throw error; })); assertEquals("Failed to read version of junit:junit", exception.getMessage()); assertSame(error, exception.getCause()); } @Test void handlesErrorsParsingVersion() { var exception = assertThrows(JUnitException.class, () -> JUnit4VersionCheck.checkSupported(() -> "not a version")); assertEquals("Failed to parse version of junit:junit: not a version", exception.getMessage()); } @Test @Tag("missing-junit4") void handlesMissingJUnit() { var exception = assertThrows(JUnitException.class, JUnit4VersionCheck::checkSupported); assertEquals("Invalid class/module path: junit-vintage-engine is present but junit:junit is not. " + "Please either remove junit-vintage-engine or add junit:junit, or alternatively use " + "an excludeEngines(\"junit-vintage\") filter.", exception.getMessage()); } } ================================================ FILE: junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageLauncherIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import static org.junit.platform.engine.FilterResult.excluded; import static org.junit.platform.engine.FilterResult.includedIf; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.launcher.EngineFilter.includeEngines; import static org.junit.platform.launcher.TagFilter.excludeTags; import static org.junit.platform.launcher.TagFilter.includeTags; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.junit.vintage.engine.descriptor.VintageTestDescriptor.ENGINE_ID; import java.util.LinkedHashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.LogRecord; import org.junit.internal.runners.SuiteMethod; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.fixtures.TrackLogRecords; import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.launcher.PostDiscoveryFilter; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; import org.junit.platform.launcher.core.LauncherFactory; import org.junit.runners.Suite; import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; import org.junit.vintage.engine.samples.junit3.JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails; import org.junit.vintage.engine.samples.junit3.PlainJUnit3TestCaseWithSingleTestWhichFails; import org.junit.vintage.engine.samples.junit4.Categories; import org.junit.vintage.engine.samples.junit4.EnclosedJUnit4TestCase; import org.junit.vintage.engine.samples.junit4.JUnit4SuiteOfSuiteWithFilterableChildRunner; import org.junit.vintage.engine.samples.junit4.JUnit4SuiteWithTwoTestCases; import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithNotFilterableRunner; import org.junit.vintage.engine.samples.junit4.NotFilterableRunner; import org.junit.vintage.engine.samples.junit4.ParameterizedTestCase; import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithFiveTestMethods; import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithTwoTestMethods; /** * @since 5.1 */ class VintageLauncherIntegrationTests { @Test void executesOnlyTaggedMethodOfRegularTestClass() { Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; var request = request() // .selectors(selectClass(testClass)) // .filters(includeTags(Categories.Failing.class.getName())); var testPlan = discover(request); assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).hasSize(2); var results = execute(request); assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // .containsExactlyInAnyOrder("JUnit Vintage", testClass.getSimpleName(), "failingTest"); } @Test void executesIncludedTaggedMethodOfNestedTestClass() { Class testClass = EnclosedJUnit4TestCase.class; Class nestedTestClass = EnclosedJUnit4TestCase.NestedClass.class; var request = request() // .selectors(selectClass(testClass)) // .filters(includeTags(Categories.Failing.class.getName())); var testPlan = discover(request); assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).hasSize(3); var results = execute(request); assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // .containsExactlyInAnyOrder("JUnit Vintage", testClass.getSimpleName(), nestedTestClass.getName(), "failingTest"); } @Test void executesOnlyNotExcludedTaggedMethodOfNestedTestClass() { Class testClass = EnclosedJUnit4TestCase.class; Class nestedTestClass = EnclosedJUnit4TestCase.NestedClass.class; var request = request() // .selectors(selectClass(testClass)) // .filters(excludeTags(Categories.Failing.class.getName())); var testPlan = discover(request); assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).hasSize(3); var results = execute(request); assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // .containsExactlyInAnyOrder("JUnit Vintage", testClass.getSimpleName(), nestedTestClass.getName(), "successfulTest"); } @Test void removesWholeSubtree() { Class testClass = EnclosedJUnit4TestCase.class; var request = request() // .selectors(selectClass(testClass)) // .filters(excludeTags(Categories.Plain.class.getName())); var testPlan = discover(request); assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).isEmpty(); var results = execute(request); assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // .containsExactlyInAnyOrder("JUnit Vintage"); } @Test void removesCompleteClassIfNoMethodHasMatchingTags() { Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; var request = request() // .selectors(selectClass(testClass)) // .filters(includeTags("wrong-tag")); var testPlan = discover(request); assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).isEmpty(); var results = execute(request); assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // .containsExactly("JUnit Vintage"); } @Test void removesCompleteClassIfItHasExcludedTag() { Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; var request = request() // .selectors(selectClass(testClass)) // .filters(excludeTags(Categories.Plain.class.getName())); var testPlan = discover(request); assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).isEmpty(); var results = execute(request); assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // .containsExactly("JUnit Vintage"); } @Test void executesAllTestsForNotFilterableRunner(@TrackLogRecords LogRecordListener logRecordListener) { Class testClass = JUnit4TestCaseWithNotFilterableRunner.class; var request = request() // .selectors(selectClass(testClass)) // .filters((PostDiscoveryFilter) descriptor -> includedIf(descriptor.getDisplayName().contains("#1"))); var testPlan = discover(request); logRecordListener.clear(); assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).hasSize(3); var results = execute(request); assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // .containsExactlyInAnyOrder("JUnit Vintage", testClass.getSimpleName(), "Test #0", "Test #1"); assertThat(logRecordListener.stream(RunnerTestDescriptor.class, Level.WARNING).map(LogRecord::getMessage)) // .containsExactly( "Runner " + NotFilterableRunner.class.getName() + " (used on class " + testClass.getName() + ")" // + " does not support filtering and will therefore be run completely."); } @Test void executesAllTestsForNotFilterableChildRunnerOfSuite(@TrackLogRecords LogRecordListener logRecordListener) { Class suiteClass = JUnit4SuiteOfSuiteWithFilterableChildRunner.class; Class testClass = JUnit4TestCaseWithNotFilterableRunner.class; var request = request() // .selectors(selectClass(suiteClass)) // .filters((PostDiscoveryFilter) descriptor -> includedIf(descriptor.getDisplayName().contains("#1"))); var testPlan = discover(request); logRecordListener.clear(); assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).hasSize(4); var results = execute(request); assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // .containsExactlyInAnyOrder("JUnit Vintage", suiteClass.getSimpleName(), testClass.getName(), "Test #0", "Test #1"); assertThat(logRecordListener.stream(RunnerTestDescriptor.class, Level.WARNING).map(LogRecord::getMessage)) // .containsExactly("Runner " + Suite.class.getName() + " (used on class " + suiteClass.getName() + ")" // + " was not able to satisfy all filter requests."); } @Test void executesAllTestsWhenFilterDidNotExcludeTestForJUnit3Suite( @TrackLogRecords LogRecordListener logRecordListener) { Class suiteClass = JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails.class; Class testClass = PlainJUnit3TestCaseWithSingleTestWhichFails.class; var request = request() // .selectors(selectClass(suiteClass)) // .filters((PostDiscoveryFilter) descriptor -> excluded("not today")); var testPlan = discover(request); logRecordListener.clear(); assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).hasSize(3); var results = execute(request); assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // .containsExactlyInAnyOrder("JUnit Vintage", suiteClass.getSimpleName(), testClass.getName(), "test"); assertThat(logRecordListener.stream(RunnerTestDescriptor.class, Level.WARNING).map(LogRecord::getMessage)) // .containsExactly( "Runner " + SuiteMethod.class.getName() + " (used on class " + suiteClass.getName() + ")" // + " was not able to satisfy all filter requests."); } @Test void executesOnlyTaggedMethodsForSuite() { Class suiteClass = JUnit4SuiteWithTwoTestCases.class; Class testClass = PlainJUnit4TestCaseWithTwoTestMethods.class; var request = request() // .selectors(selectClass(suiteClass)) // .filters(includeTags(Categories.Successful.class.getName())); var testPlan = discover(request); assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).hasSize(3); var results = execute(request); assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // .containsExactlyInAnyOrder("JUnit Vintage", suiteClass.getSimpleName(), testClass.getName(), "successfulTest"); } @Test void removesCompleteClassWithNotFilterableRunnerIfItHasExcludedTag() { Class testClass = JUnit4TestCaseWithNotFilterableRunner.class; var request = request() // .selectors(selectClass(testClass)) // .filters(excludeTags(Categories.Successful.class.getName())); var testPlan = discover(request); assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).isEmpty(); var results = execute(request); assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // .containsExactly("JUnit Vintage"); } @Test void filtersOutAllDescendantsOfParameterizedTestCase() { Class testClass = ParameterizedTestCase.class; var request = request() // .selectors(selectClass(testClass)) // .filters((PostDiscoveryFilter) descriptor -> excluded("excluded")); var testPlan = discover(request); assertThat(testPlan.getDescendants(getOnlyElement(testPlan.getRoots()))).isEmpty(); var results = execute(request); assertThat(results.keySet().stream().map(TestIdentifier::getDisplayName)) // .containsExactly("JUnit Vintage"); } private TestPlan discover(LauncherDiscoveryRequestBuilder requestBuilder) { var launcher = LauncherFactory.create(); return launcher.discover(toDiscoveryRequest(requestBuilder).build()); } private Map execute(LauncherDiscoveryRequestBuilder requestBuilder) { Map results = new LinkedHashMap<>(); var launcher = LauncherFactory.create(); var listener = new TestExecutionListener() { @Override public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { results.put(testIdentifier, testExecutionResult); } }; var executionRequest = toDiscoveryRequest(requestBuilder) // .forExecution() // .listeners(listener) // .build(); launcher.execute(executionRequest); return results; } private LauncherDiscoveryRequestBuilder toDiscoveryRequest(LauncherDiscoveryRequestBuilder requestBuilder) { return requestBuilder // .filters(includeEngines(ENGINE_ID)) // .enableImplicitConfigurationParameters(false); } } ================================================ FILE: junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineBasicTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; /** * Basic assertions regarding {@link org.junit.platform.engine.TestEngine} * functionality in JUnit Vintage. * * @since 4.12 */ @SuppressWarnings("deprecation") class VintageTestEngineBasicTests { private final VintageTestEngine vintage = new VintageTestEngine(); @Test void id() { assertEquals("junit-vintage", vintage.getId()); } @Test void groupId() { assertEquals("org.junit.vintage", vintage.getGroupId().get()); } @Test void artifactId() { assertEquals("junit-vintage-engine", vintage.getArtifactId().get()); } } ================================================ FILE: junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineDiscoveryTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine; import static java.text.MessageFormat.format; import static java.util.function.Predicate.isEqual; import static org.assertj.core.api.Assertions.assertThat; 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.assertTrue; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import static org.junit.platform.commons.util.FunctionUtils.where; import static org.junit.platform.engine.discovery.ClassNameFilter.includeClassNamePatterns; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathRoots; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.engine.discovery.PackageNameFilter.includePackageNames; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.ArrayList; import java.util.Base64; import java.util.List; import java.util.Set; import org.junit.jupiter.api.Test; import org.junit.platform.commons.util.ClassUtils; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestTag; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.platform.engine.support.descriptor.MethodSource; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.testkit.engine.EngineDiscoveryResults; import org.junit.platform.testkit.engine.EngineTestKit; import org.junit.runner.manipulation.Filter; import org.junit.vintage.engine.samples.PlainOldJavaClassWithoutAnyTestsTestCase; import org.junit.vintage.engine.samples.junit3.JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails; import org.junit.vintage.engine.samples.junit3.PlainJUnit3TestCaseWithSingleTestWhichFails; import org.junit.vintage.engine.samples.junit4.Categories.Failing; import org.junit.vintage.engine.samples.junit4.Categories.Plain; import org.junit.vintage.engine.samples.junit4.Categories.Skipped; import org.junit.vintage.engine.samples.junit4.Categories.SkippedWithReason; import org.junit.vintage.engine.samples.junit4.EmptyIgnoredTestCase; import org.junit.vintage.engine.samples.junit4.IgnoredJUnit4TestCase; import org.junit.vintage.engine.samples.junit4.JUnit4SuiteWithJUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames; import org.junit.vintage.engine.samples.junit4.JUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored; import org.junit.vintage.engine.samples.junit4.JUnit4SuiteWithTwoTestCases; import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithDistinguishableOverloadedMethod; import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithIndistinguishableOverloadedMethod; import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithNotFilterableRunner; import org.junit.vintage.engine.samples.junit4.ParameterizedTestCase; import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithFiveTestMethods; import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithSingleInheritedTestWhichFails; import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithSingleTestWhichFails; import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithSingleTestWhichIsIgnored; import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithTwoTestMethods; import org.junit.vintage.engine.samples.junit4.SingleFailingTheoryTestCase; import org.junit.vintage.engine.samples.junit4.TestCaseRunWithJUnitPlatformRunner; /** * @since 4.12 */ class VintageTestEngineDiscoveryTests { @Test void resolvesSimpleJUnit4TestClass() throws Exception { Class testClass = PlainJUnit4TestCaseWithSingleTestWhichFails.class; var discoveryRequest = discoveryRequestForClass(testClass); var engineDescriptor = discoverTests(discoveryRequest); var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); assertRunnerTestDescriptor(runnerDescriptor, testClass); var childDescriptor = getOnlyElement(runnerDescriptor.getChildren()); assertTestMethodDescriptor(childDescriptor, testClass, "failingTest", VintageUniqueIdBuilder.uniqueIdForClass(testClass)); } @Test void resolvesIgnoredJUnit4TestClass() throws Exception { Class testClass = IgnoredJUnit4TestCase.class; var discoveryRequest = discoveryRequestForClass(testClass); var engineDescriptor = discoverTests(discoveryRequest); var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); assertRunnerTestDescriptor(runnerDescriptor, testClass); assertThat(runnerDescriptor.getChildren()).hasSize(2); List children = new ArrayList<>(runnerDescriptor.getChildren()); assertTestMethodDescriptor(children.get(0), testClass, "failingTest", VintageUniqueIdBuilder.uniqueIdForClass(testClass)); assertTestMethodDescriptor(children.get(1), testClass, "succeedingTest", VintageUniqueIdBuilder.uniqueIdForClass(testClass)); } @Test void resolvesEmptyIgnoredTestClass() { Class testClass = EmptyIgnoredTestCase.class; var discoveryRequest = discoveryRequestForClass(testClass); var engineDescriptor = discoverTests(discoveryRequest); var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); assertFalse(runnerDescriptor.isContainer()); assertTrue(runnerDescriptor.isTest()); assertEquals(testClass.getSimpleName(), runnerDescriptor.getDisplayName()); assertEquals(VintageUniqueIdBuilder.uniqueIdForClass(testClass), runnerDescriptor.getUniqueId()); assertThat(runnerDescriptor.getChildren()).isEmpty(); } @Test void resolvesJUnit4TestClassWithCustomRunner() throws Exception { Class testClass = SingleFailingTheoryTestCase.class; var discoveryRequest = discoveryRequestForClass(testClass); var engineDescriptor = discoverTests(discoveryRequest); var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); assertRunnerTestDescriptor(runnerDescriptor, testClass); var childDescriptor = getOnlyElement(runnerDescriptor.getChildren()); assertTestMethodDescriptor(childDescriptor, testClass, "theory", VintageUniqueIdBuilder.uniqueIdForClass(testClass)); } @Test void resolvesJUnit3TestCase() throws Exception { Class testClass = PlainJUnit3TestCaseWithSingleTestWhichFails.class; var discoveryRequest = discoveryRequestForClass(testClass); var engineDescriptor = discoverTests(discoveryRequest); var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); assertRunnerTestDescriptor(runnerDescriptor, testClass); var childDescriptor = getOnlyElement(runnerDescriptor.getChildren()); assertTestMethodDescriptor(childDescriptor, testClass, "test", VintageUniqueIdBuilder.uniqueIdForClass(testClass)); } @Test void resolvesJUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails() throws Exception { Class suiteClass = JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails.class; Class testClass = PlainJUnit3TestCaseWithSingleTestWhichFails.class; var discoveryRequest = discoveryRequestForClass(suiteClass); var engineDescriptor = discoverTests(discoveryRequest); var suiteDescriptor = getOnlyElement(engineDescriptor.getChildren()); assertRunnerTestDescriptor(suiteDescriptor, suiteClass); assertThat(suiteDescriptor.getDisplayName()).describedAs("display name") // .startsWith(suiteClass.getSimpleName()); assertThat(suiteDescriptor.getLegacyReportingName()).describedAs("legacy reporting name") // .isEqualTo(suiteClass.getName()); var testClassDescriptor = getOnlyElement(suiteDescriptor.getChildren()); assertContainerTestDescriptor(testClassDescriptor, suiteClass, testClass); var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren()); assertTestMethodDescriptor(testMethodDescriptor, testClass, "test", VintageUniqueIdBuilder.uniqueIdForClasses(suiteClass, testClass)); } @Test void resolvesJUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored() throws Exception { Class suiteClass = JUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored.class; Class testClass = PlainJUnit4TestCaseWithSingleTestWhichIsIgnored.class; var discoveryRequest = discoveryRequestForClass(suiteClass); var engineDescriptor = discoverTests(discoveryRequest); var suiteDescriptor = getOnlyElement(engineDescriptor.getChildren()); assertRunnerTestDescriptor(suiteDescriptor, suiteClass); var testClassDescriptor = getOnlyElement(suiteDescriptor.getChildren()); assertContainerTestDescriptor(testClassDescriptor, suiteClass, testClass); var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren()); assertTestMethodDescriptor(testMethodDescriptor, testClass, "ignoredTest", VintageUniqueIdBuilder.uniqueIdForClasses(suiteClass, testClass)); } @Test void resolvesJUnit4TestCaseWithIndistinguishableOverloadedMethod() { Class testClass = JUnit4TestCaseWithIndistinguishableOverloadedMethod.class; var discoveryRequest = discoveryRequestForClass(testClass); var engineDescriptor = discoverTests(discoveryRequest); var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); assertRunnerTestDescriptor(runnerDescriptor, testClass); List testMethodDescriptors = new ArrayList<>(runnerDescriptor.getChildren()); assertThat(testMethodDescriptors).hasSize(2); var testMethodDescriptor = testMethodDescriptors.getFirst(); assertEquals("theory", testMethodDescriptor.getDisplayName()); assertEquals(VintageUniqueIdBuilder.uniqueIdForMethod(testClass, "theory", "0"), testMethodDescriptor.getUniqueId()); assertClassSource(testClass, testMethodDescriptor); testMethodDescriptor = testMethodDescriptors.get(1); assertEquals("theory", testMethodDescriptor.getDisplayName()); assertEquals(VintageUniqueIdBuilder.uniqueIdForMethod(testClass, "theory", "1"), testMethodDescriptor.getUniqueId()); assertClassSource(testClass, testMethodDescriptor); } @Test void resolvesJUnit4TestCaseWithDistinguishableOverloadedMethod() throws Exception { Class testClass = JUnit4TestCaseWithDistinguishableOverloadedMethod.class; var discoveryRequest = discoveryRequestForClass(testClass); var engineDescriptor = discoverTests(discoveryRequest); var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); assertRunnerTestDescriptor(runnerDescriptor, testClass); List testMethodDescriptors = new ArrayList<>(runnerDescriptor.getChildren()); var testMethodDescriptor = getOnlyElement(testMethodDescriptors); assertEquals("test", testMethodDescriptor.getDisplayName()); assertEquals(VintageUniqueIdBuilder.uniqueIdForMethod(testClass, "test"), testMethodDescriptor.getUniqueId()); assertMethodSource(testClass.getMethod("test"), testMethodDescriptor); } @Test void doesNotResolvePlainOldJavaClassesWithoutAnyTest() { assertYieldsNoDescriptors(PlainOldJavaClassWithoutAnyTestsTestCase.class); } @Test void doesNotResolveClassRunWithJUnitPlatform() { assertYieldsNoDescriptors(TestCaseRunWithJUnitPlatformRunner.class); } @Test void resolvesClasspathSelector() throws Exception { var root = getClasspathRoot(PlainJUnit4TestCaseWithSingleTestWhichFails.class); var discoveryRequest = request().selectors(selectClasspathRoots(Set.of(root))).build(); var engineDescriptor = discoverTests(discoveryRequest); // @formatter:off assertThat(engineDescriptor.getChildren()) .extracting(TestDescriptor::getDisplayName) .contains(PlainJUnit4TestCaseWithSingleTestWhichFails.class.getSimpleName()) .contains(PlainJUnit3TestCaseWithSingleTestWhichFails.class.getSimpleName()) .doesNotContain(PlainOldJavaClassWithoutAnyTestsTestCase.class.getSimpleName()); // @formatter:on } @Test void resolvesClasspathSelectorForJarFile() throws Exception { var jarUrl = getClass().getResource("/vintage-testjar.jar"); var jarFile = Path.of(jarUrl.toURI()); var originalClassLoader = Thread.currentThread().getContextClassLoader(); try (var classLoader = new URLClassLoader(new URL[] { jarUrl })) { Thread.currentThread().setContextClassLoader(classLoader); var discoveryRequest = request().selectors(selectClasspathRoots(Set.of(jarFile))).build(); var engineDescriptor = discoverTests(discoveryRequest); // @formatter:off assertThat(engineDescriptor.getChildren()) .extracting(TestDescriptor::getDisplayName) .containsExactly("JUnit4Test"); // @formatter:on } finally { Thread.currentThread().setContextClassLoader(originalClassLoader); } } @Test void resolvesApplyingClassNameFilters() throws Exception { var root = getClasspathRoot(PlainJUnit4TestCaseWithSingleTestWhichFails.class); var discoveryRequest = request().selectors(selectClasspathRoots(Set.of(root))).filters( includeClassNamePatterns(".*JUnit4.*"), includeClassNamePatterns(".*Plain.*")).build(); var engineDescriptor = discoverTests(discoveryRequest); // @formatter:off assertThat(engineDescriptor.getChildren()) .extracting(TestDescriptor::getDisplayName) .contains(PlainJUnit4TestCaseWithSingleTestWhichFails.class.getSimpleName()) .doesNotContain(JUnit4TestCaseWithIndistinguishableOverloadedMethod.class.getSimpleName()) .doesNotContain(PlainJUnit3TestCaseWithSingleTestWhichFails.class.getSimpleName()); // @formatter:on } @Test void resolvesApplyingPackageNameFilters() throws Exception { var root = getClasspathRoot(PlainJUnit4TestCaseWithSingleTestWhichFails.class); var discoveryRequest = request().selectors(selectClasspathRoots(Set.of(root))).filters( includePackageNames("org"), includePackageNames("org.junit")).build(); var engineDescriptor = discoverTests(discoveryRequest); // @formatter:off assertThat(engineDescriptor.getChildren()) .extracting(TestDescriptor::getDisplayName) .contains(PlainJUnit4TestCaseWithSingleTestWhichFails.class.getSimpleName()); // @formatter:on } @Test void resolvesPackageSelectorForJUnit4SamplesPackage() { Class testClass = PlainJUnit4TestCaseWithSingleTestWhichFails.class; var discoveryRequest = request().selectors(selectPackage(testClass.getPackage().getName())).build(); var engineDescriptor = discoverTests(discoveryRequest); // @formatter:off assertThat(engineDescriptor.getChildren()) .extracting(TestDescriptor::getDisplayName) .contains(testClass.getSimpleName()) .doesNotContain(PlainJUnit3TestCaseWithSingleTestWhichFails.class.getSimpleName()); // @formatter:on } @Test void resolvesPackageSelectorForJUnit3SamplesPackage() { Class testClass = PlainJUnit3TestCaseWithSingleTestWhichFails.class; var discoveryRequest = request().selectors(selectPackage(testClass.getPackage().getName())).build(); var engineDescriptor = discoverTests(discoveryRequest); // @formatter:off assertThat(engineDescriptor.getChildren()) .extracting(TestDescriptor::getDisplayName) .contains(testClass.getSimpleName()) .doesNotContain(PlainJUnit4TestCaseWithSingleTestWhichFails.class.getSimpleName()); // @formatter:on } @Test void resolvesClassesWithInheritedMethods() throws Exception { Class superclass = PlainJUnit4TestCaseWithSingleTestWhichFails.class; Class testClass = PlainJUnit4TestCaseWithSingleInheritedTestWhichFails.class; var discoveryRequest = discoveryRequestForClass(testClass); var engineDescriptor = discoverTests(discoveryRequest); var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); assertEquals(testClass.getSimpleName(), runnerDescriptor.getDisplayName()); assertClassSource(testClass, runnerDescriptor); var testDescriptor = getOnlyElement(runnerDescriptor.getChildren()); assertEquals("failingTest", testDescriptor.getDisplayName()); assertMethodSource(testClass, superclass.getMethod("failingTest"), testDescriptor); } @Test void resolvesCategoriesIntoTags() { Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; var discoveryRequest = discoveryRequestForClass(testClass); var engineDescriptor = discoverTests(discoveryRequest); var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); assertThat(runnerDescriptor.getTags()).containsOnly(TestTag.create(Plain.class.getName())); var failingTest = findChildByDisplayName(runnerDescriptor, "failingTest"); assertThat(failingTest.getTags()).containsOnly(// TestTag.create(Plain.class.getName()), // TestTag.create(Failing.class.getName())); var ignoredWithoutReason = findChildByDisplayName(runnerDescriptor, "ignoredTest1_withoutReason"); assertThat(ignoredWithoutReason.getTags()).containsOnly(// TestTag.create(Plain.class.getName()), // TestTag.create(Skipped.class.getName())); var ignoredWithReason = findChildByDisplayName(runnerDescriptor, "ignoredTest2_withReason"); assertThat(ignoredWithReason.getTags()).containsOnly(// TestTag.create(Plain.class.getName()), // TestTag.create(Skipped.class.getName()), // TestTag.create(SkippedWithReason.class.getName())); } @Test void resolvesMethodSelectorForSingleMethod() throws Exception { Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; var discoveryRequest = request().selectors(selectMethod(testClass, testClass.getMethod("failingTest"))).build(); var engineDescriptor = discoverTests(discoveryRequest); var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); assertRunnerTestDescriptor(runnerDescriptor, testClass); var childDescriptor = getOnlyElement(runnerDescriptor.getChildren()); assertTestMethodDescriptor(childDescriptor, testClass, "failingTest", VintageUniqueIdBuilder.uniqueIdForClass(testClass)); } @Test void resolvesMethodOfIgnoredJUnit4TestClass() throws Exception { Class testClass = IgnoredJUnit4TestCase.class; var discoveryRequest = request().selectors(selectMethod(testClass, testClass.getMethod("failingTest"))).build(); var engineDescriptor = discoverTests(discoveryRequest); var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); assertRunnerTestDescriptor(runnerDescriptor, testClass); var childDescriptor = getOnlyElement(runnerDescriptor.getChildren()); assertTestMethodDescriptor(childDescriptor, testClass, "failingTest", VintageUniqueIdBuilder.uniqueIdForClass(testClass)); } @Test void resolvesMethodSelectorForTwoMethodsOfSameClass() throws Exception { Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; var discoveryRequest = request().selectors(selectMethod(testClass, testClass.getMethod("failingTest")), selectMethod(testClass, testClass.getMethod("successfulTest"))).build(); var engineDescriptor = discoverTests(discoveryRequest); var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); assertRunnerTestDescriptor(runnerDescriptor, testClass); List testMethodDescriptors = new ArrayList<>(runnerDescriptor.getChildren()); assertThat(testMethodDescriptors).hasSize(2); var failingTest = testMethodDescriptors.get(0); assertTestMethodDescriptor(failingTest, testClass, "failingTest", VintageUniqueIdBuilder.uniqueIdForClass(testClass)); var successfulTest = testMethodDescriptors.get(1); assertTestMethodDescriptor(successfulTest, testClass, "successfulTest", VintageUniqueIdBuilder.uniqueIdForClass(testClass)); } @Test void resolvesUniqueIdSelectorForSingleMethod() throws Exception { Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; var discoveryRequest = request().selectors( selectUniqueId(VintageUniqueIdBuilder.uniqueIdForMethod(testClass, "failingTest"))).build(); var engineDescriptor = discoverTests(discoveryRequest); var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); assertRunnerTestDescriptor(runnerDescriptor, testClass); var childDescriptor = getOnlyElement(runnerDescriptor.getChildren()); assertTestMethodDescriptor(childDescriptor, testClass, "failingTest", VintageUniqueIdBuilder.uniqueIdForClass(testClass)); } @Test void resolvesUniqueIdSelectorForSingleClass() { Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; var discoveryRequest = request().selectors( selectUniqueId(VintageUniqueIdBuilder.uniqueIdForClass(testClass))).build(); var engineDescriptor = discoverTests(discoveryRequest); var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); assertRunnerTestDescriptor(runnerDescriptor, testClass); assertThat(runnerDescriptor.getChildren()).hasSize(5); } @Test void resolvesUniqueIdSelectorOfSingleClassWithinSuite() throws Exception { Class suiteClass = JUnit4SuiteWithTwoTestCases.class; Class testClass = PlainJUnit4TestCaseWithSingleTestWhichFails.class; var discoveryRequest = request().selectors( selectUniqueId(VintageUniqueIdBuilder.uniqueIdForClasses(suiteClass, testClass))).build(); var engineDescriptor = discoverTests(discoveryRequest); var suiteDescriptor = getOnlyElement(engineDescriptor.getChildren()); assertRunnerTestDescriptor(suiteDescriptor, suiteClass); var testClassDescriptor = getOnlyElement(suiteDescriptor.getChildren()); assertContainerTestDescriptor(testClassDescriptor, suiteClass, testClass); var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren()); assertTestMethodDescriptor(testMethodDescriptor, testClass, "failingTest", VintageUniqueIdBuilder.uniqueIdForClasses(suiteClass, testClass)); } @Test void resolvesUniqueIdSelectorOfSingleMethodWithinSuite() throws Exception { Class suiteClass = JUnit4SuiteWithTwoTestCases.class; Class testClass = PlainJUnit4TestCaseWithTwoTestMethods.class; var discoveryRequest = request().selectors(selectUniqueId(VintageUniqueIdBuilder.uniqueIdForMethod( VintageUniqueIdBuilder.uniqueIdForClasses(suiteClass, testClass), testClass, "successfulTest"))).build(); var engineDescriptor = discoverTests(discoveryRequest); var suiteDescriptor = getOnlyElement(engineDescriptor.getChildren()); assertRunnerTestDescriptor(suiteDescriptor, suiteClass); var testClassDescriptor = getOnlyElement(suiteDescriptor.getChildren()); assertContainerTestDescriptor(testClassDescriptor, suiteClass, testClass); var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren()); assertTestMethodDescriptor(testMethodDescriptor, testClass, "successfulTest", VintageUniqueIdBuilder.uniqueIdForClasses(suiteClass, testClass)); } @Test void resolvesMultipleUniqueIdSelectorsForMethodsOfSameClass() throws Exception { Class testClass = PlainJUnit4TestCaseWithTwoTestMethods.class; var discoveryRequest = request().selectors( selectUniqueId(VintageUniqueIdBuilder.uniqueIdForMethod(testClass, "successfulTest")), selectUniqueId(VintageUniqueIdBuilder.uniqueIdForMethod(testClass, "failingTest"))).build(); var engineDescriptor = discoverTests(discoveryRequest); var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); assertRunnerTestDescriptor(runnerDescriptor, testClass); List testMethodDescriptors = new ArrayList<>(runnerDescriptor.getChildren()); assertThat(testMethodDescriptors).hasSize(2); assertTestMethodDescriptor(testMethodDescriptors.get(0), testClass, "failingTest", VintageUniqueIdBuilder.uniqueIdForClass(testClass)); assertTestMethodDescriptor(testMethodDescriptors.get(1), testClass, "successfulTest", VintageUniqueIdBuilder.uniqueIdForClass(testClass)); } @Test void doesNotResolveMissingUniqueIdSelectorForSingleClass() { Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; var discoveryRequest = request().selectors( selectUniqueId(VintageUniqueIdBuilder.uniqueIdForClass(testClass) + "/[test:doesNotExist]")).build(); var engineDescriptor = discoverTests(discoveryRequest); var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); assertRunnerTestDescriptor(runnerDescriptor, testClass); var testDescriptor = getOnlyElement(runnerDescriptor.getChildren()); assertInitializationError(testDescriptor, Filter.class, testClass); } @Test void ignoresMoreFineGrainedSelectorsWhenClassIsSelectedAsWell() throws Exception { Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; var discoveryRequest = request().selectors( // selectMethod(testClass, testClass.getMethod("failingTest")), // selectUniqueId(VintageUniqueIdBuilder.uniqueIdForMethod(testClass, "abortedTest")), selectClass(testClass) // ).build(); var engineDescriptor = discoverTests(discoveryRequest); var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); assertRunnerTestDescriptor(runnerDescriptor, testClass); assertThat(runnerDescriptor.getChildren()).hasSize(5); } @Test void resolvesCombinationOfMethodAndUniqueIdSelector() throws Exception { Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; var discoveryRequest = request().selectors( // selectMethod(testClass, testClass.getMethod("failingTest")), // selectUniqueId(VintageUniqueIdBuilder.uniqueIdForMethod(testClass, "abortedTest") // )).build(); var engineDescriptor = discoverTests(discoveryRequest); var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); assertRunnerTestDescriptor(runnerDescriptor, testClass); List testMethodDescriptors = new ArrayList<>(runnerDescriptor.getChildren()); assertThat(testMethodDescriptors).hasSize(2); assertTestMethodDescriptor(testMethodDescriptors.get(0), testClass, "abortedTest", VintageUniqueIdBuilder.uniqueIdForClass(testClass)); assertTestMethodDescriptor(testMethodDescriptors.get(1), testClass, "failingTest", VintageUniqueIdBuilder.uniqueIdForClass(testClass)); } @Test void ignoresRedundantSelector() throws Exception { Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; var discoveryRequest = request().selectors( // selectMethod(testClass, testClass.getMethod("failingTest")), // selectUniqueId(VintageUniqueIdBuilder.uniqueIdForMethod(testClass, "failingTest") // )).build(); var engineDescriptor = discoverTests(discoveryRequest); var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); assertRunnerTestDescriptor(runnerDescriptor, testClass); var testMethodDescriptor = getOnlyElement(runnerDescriptor.getChildren()); assertTestMethodDescriptor(testMethodDescriptor, testClass, "failingTest", VintageUniqueIdBuilder.uniqueIdForClass(testClass)); } @Test void doesNotResolveMethodOfClassNotAcceptedByClassNameFilter() throws Exception { Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; // @formatter:off var request = request() .selectors(selectMethod(testClass, testClass.getMethod("failingTest"))) .filters(includeClassNamePatterns("Foo")) .build(); // @formatter:on assertYieldsNoDescriptors(request); } @Test void doesNotResolveMethodOfClassNotAcceptedByPackageNameFilter() throws Exception { Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; // @formatter:off var request = request() .selectors(selectMethod(testClass, testClass.getMethod("failingTest"))) .filters(includePackageNames("com.acme")) .build(); // @formatter:on assertYieldsNoDescriptors(request); } @Test void resolvesClassForMethodSelectorForClassWithNonFilterableRunner() { Class testClass = JUnit4TestCaseWithNotFilterableRunner.class; // @formatter:off var request = request() .selectors(selectUniqueId(VintageUniqueIdBuilder.uniqueIdForMethod(testClass, "Test #0"))) .build(); // @formatter:on var engineDescriptor = discoverTests(request); var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); assertEquals(testClass.getSimpleName(), runnerDescriptor.getDisplayName()); assertEquals(VintageUniqueIdBuilder.uniqueIdForClass(testClass), runnerDescriptor.getUniqueId()); assertThat(runnerDescriptor.getChildren()).isNotEmpty(); } @Test void usesCustomUniqueIdsAndDisplayNamesWhenPresent() { Class suiteClass = JUnit4SuiteWithJUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.class; var request = request().selectors(selectClass(suiteClass)).build(); var engineDescriptor = discoverTests(request); var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); assertRunnerTestDescriptor(runnerDescriptor, suiteClass); var testClassDescriptor = getOnlyElement(runnerDescriptor.getChildren()); assertEquals("(TestClass)", testClassDescriptor.getDisplayName()); var childDescriptor = getOnlyElement(testClassDescriptor.getChildren()); var prefix = VintageUniqueIdBuilder.uniqueIdForClass(suiteClass); assertThat(childDescriptor.getUniqueId().toString()).startsWith(prefix.toString()); assertEquals("(TestMethod)", childDescriptor.getDisplayName()); var customUniqueIdValue = childDescriptor.getUniqueId().getSegments().get(2).getType(); assertNotNull(Base64.getDecoder().decode(customUniqueIdValue.getBytes(StandardCharsets.UTF_8)), "is a valid Base64 encoding scheme"); } @Test void resolvesTestSourceForParameterizedTests() throws Exception { Class testClass = ParameterizedTestCase.class; var request = request().selectors(selectClass(testClass)).build(); var engineDescriptor = discoverTests(request); var runnerDescriptor = getOnlyElement(engineDescriptor.getChildren()); assertRunnerTestDescriptor(runnerDescriptor, testClass); var fooParentDescriptor = findChildByDisplayName(runnerDescriptor, "[foo]"); assertTrue(fooParentDescriptor.isContainer()); assertFalse(fooParentDescriptor.isTest()); assertThat(fooParentDescriptor.getSource()).isEmpty(); var testMethodDescriptor = getOnlyElement(fooParentDescriptor.getChildren()); assertEquals("test[foo]", testMethodDescriptor.getDisplayName()); assertTrue(testMethodDescriptor.isTest()); assertFalse(testMethodDescriptor.isContainer()); assertMethodSource(testClass.getMethod("test"), testMethodDescriptor); } @Test void reportsNoDiscoveryIssuesWhenNoTestsAreFound() { var request = discoveryRequestForClass(PlainOldJavaClassWithoutAnyTestsTestCase.class); var results = discover(request); assertThat(results.getDiscoveryIssues()).isEmpty(); } @Test void reportDiscoveryIssueWhenTestsAreFoundByDefault() { var request = discoveryRequestForClass(PlainJUnit4TestCaseWithSingleTestWhichFails.class); var results = discover(request); assertThat(results.getDiscoveryIssues()).hasSize(1); var issue = results.getDiscoveryIssues().getFirst(); assertThat(issue.severity()).isEqualTo(Severity.INFO); assertThat(issue.message()).contains("JUnit Vintage engine is deprecated"); } @SuppressWarnings("deprecation") @Test void reportNoDiscoveryIssueWhenTestsAreFoundButConfigurationParameterIsSet() { var request = request() // .selectors(selectClass(PlainJUnit4TestCaseWithSingleTestWhichFails.class)) // .configurationParameter(Constants.DISCOVERY_ISSUE_REPORTING_ENABLED_PROPERTY_NAME, "false").build(); var results = discover(request); assertThat(results.getDiscoveryIssues()).isEmpty(); } private TestDescriptor findChildByDisplayName(TestDescriptor runnerDescriptor, String displayName) { // @formatter:off var children = runnerDescriptor.getChildren(); return children .stream() .filter(where(TestDescriptor::getDisplayName, isEqual(displayName))) .findAny() .orElseThrow(() -> new AssertionError(format("No child with display name \"{0}\" in {1}", displayName, children))); // @formatter:on } private TestDescriptor discoverTests(LauncherDiscoveryRequest discoveryRequest) { return discover(discoveryRequest).getEngineDescriptor(); } @SuppressWarnings("deprecation") private static EngineDiscoveryResults discover(LauncherDiscoveryRequest discoveryRequest) { return EngineTestKit.discover(new VintageTestEngine(), discoveryRequest); } private Path getClasspathRoot(Class testClass) throws Exception { var location = testClass.getProtectionDomain().getCodeSource().getLocation(); return Path.of(location.toURI()); } private void assertYieldsNoDescriptors(Class testClass) { var request = discoveryRequestForClass(testClass); assertYieldsNoDescriptors(request); } private void assertYieldsNoDescriptors(LauncherDiscoveryRequest request) { var engineDescriptor = discoverTests(request); assertThat(engineDescriptor.getChildren()).isEmpty(); } private static void assertRunnerTestDescriptor(TestDescriptor runnerDescriptor, Class testClass) { assertTrue(runnerDescriptor.isContainer()); assertFalse(runnerDescriptor.isTest()); assertEquals(testClass.getSimpleName(), runnerDescriptor.getDisplayName()); assertEquals(VintageUniqueIdBuilder.uniqueIdForClass(testClass), runnerDescriptor.getUniqueId()); assertClassSource(testClass, runnerDescriptor); } private static void assertTestMethodDescriptor(TestDescriptor testMethodDescriptor, Class testClass, String methodName, UniqueId uniqueContainerId) throws Exception { assertTrue(testMethodDescriptor.isTest()); assertFalse(testMethodDescriptor.isContainer()); assertEquals(methodName, testMethodDescriptor.getDisplayName()); assertEquals(VintageUniqueIdBuilder.uniqueIdForMethod(uniqueContainerId, testClass, methodName), testMethodDescriptor.getUniqueId()); assertThat(testMethodDescriptor.getChildren()).isEmpty(); assertMethodSource(testClass.getMethod(methodName), testMethodDescriptor); } private static void assertContainerTestDescriptor(TestDescriptor containerDescriptor, Class suiteClass, Class testClass) { assertTrue(containerDescriptor.isContainer()); assertFalse(containerDescriptor.isTest()); assertEquals(testClass.getName(), containerDescriptor.getDisplayName()); assertEquals(VintageUniqueIdBuilder.uniqueIdForClasses(suiteClass, testClass), containerDescriptor.getUniqueId()); assertClassSource(testClass, containerDescriptor); } private static void assertInitializationError(TestDescriptor testDescriptor, Class failingClass, Class testClass) { assertTrue(testDescriptor.isTest()); assertFalse(testDescriptor.isContainer()); assertEquals("initializationError", testDescriptor.getDisplayName()); assertEquals(VintageUniqueIdBuilder.uniqueIdForErrorInClass(testClass, failingClass), testDescriptor.getUniqueId()); assertThat(testDescriptor.getChildren()).isEmpty(); assertClassSource(failingClass, testDescriptor); } private static void assertClassSource(Class expectedClass, TestDescriptor testDescriptor) { assertThat(testDescriptor.getSource()).containsInstanceOf(ClassSource.class); var classSource = (ClassSource) testDescriptor.getSource().get(); assertThat(classSource.getJavaClass()).isEqualTo(expectedClass); } private static void assertMethodSource(Method expectedMethod, TestDescriptor testDescriptor) { assertMethodSource(expectedMethod.getDeclaringClass(), expectedMethod, testDescriptor); } private static void assertMethodSource(Class expectedClass, Method expectedMethod, TestDescriptor testDescriptor) { assertThat(testDescriptor.getSource()).containsInstanceOf(MethodSource.class); var methodSource = (MethodSource) testDescriptor.getSource().get(); assertThat(methodSource.getClassName()).isEqualTo(expectedClass.getName()); assertThat(methodSource.getMethodName()).isEqualTo(expectedMethod.getName()); assertThat(methodSource.getMethodParameterTypes()).isEqualTo( ClassUtils.nullSafeToString(expectedMethod.getParameterTypes())); } private static LauncherDiscoveryRequest discoveryRequestForClass(Class testClass) { return request().selectors(selectClass(testClass)).build(); } } ================================================ FILE: junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine; import static java.util.function.Predicate.isEqual; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.testkit.engine.EventConditions.abortedWithReason; import static org.junit.platform.testkit.engine.EventConditions.container; import static org.junit.platform.testkit.engine.EventConditions.displayName; import static org.junit.platform.testkit.engine.EventConditions.dynamicTestRegistered; import static org.junit.platform.testkit.engine.EventConditions.engine; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.EventConditions.skippedWithReason; import static org.junit.platform.testkit.engine.EventConditions.started; import static org.junit.platform.testkit.engine.EventConditions.test; import static org.junit.platform.testkit.engine.EventConditions.uniqueIdSubstring; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import static org.junit.runner.Description.createSuiteDescription; import static org.junit.runner.Description.createTestDescription; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.math.BigDecimal; import junit.runner.Version; import org.assertj.core.api.Condition; import org.junit.AssumptionViolatedException; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.DisabledInEclipse; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.discovery.DiscoverySelectors; import org.junit.platform.launcher.LauncherConstants; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; import org.junit.platform.testkit.engine.EngineExecutionResults; import org.junit.platform.testkit.engine.EngineTestKit; import org.junit.runner.Description; import org.junit.runner.RunWith; import org.junit.runner.Runner; import org.junit.runner.notification.RunNotifier; import org.junit.runner.notification.StoppedByUserException; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; import org.junit.vintage.engine.samples.junit3.IgnoredJUnit3TestCase; import org.junit.vintage.engine.samples.junit3.JUnit3ParallelSuiteWithSubsuites; import org.junit.vintage.engine.samples.junit3.JUnit3SuiteWithSubsuites; import org.junit.vintage.engine.samples.junit3.JUnit4SuiteWithIgnoredJUnit3TestCase; import org.junit.vintage.engine.samples.junit3.PlainJUnit3TestCaseWithSingleTestWhichFails; import org.junit.vintage.engine.samples.junit4.CancellingTestCase; import org.junit.vintage.engine.samples.junit4.CompletelyDynamicTestCase; import org.junit.vintage.engine.samples.junit4.EmptyIgnoredTestCase; import org.junit.vintage.engine.samples.junit4.EnclosedJUnit4TestCase; import org.junit.vintage.engine.samples.junit4.EnclosedWithParameterizedChildrenJUnit4TestCase; import org.junit.vintage.engine.samples.junit4.IgnoredJUnit4TestCase; import org.junit.vintage.engine.samples.junit4.IgnoredParameterizedTestCase; import org.junit.vintage.engine.samples.junit4.JUnit4SuiteOfSuiteWithIgnoredJUnit4TestCase; import org.junit.vintage.engine.samples.junit4.JUnit4SuiteOfSuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass; import org.junit.vintage.engine.samples.junit4.JUnit4SuiteOfSuiteWithJUnit4TestCaseWithErrorInBeforeClass; import org.junit.vintage.engine.samples.junit4.JUnit4SuiteWithExceptionThrowingRunner; import org.junit.vintage.engine.samples.junit4.JUnit4SuiteWithIgnoredJUnit4TestCase; import org.junit.vintage.engine.samples.junit4.JUnit4SuiteWithJUnit3SuiteWithSingleTestCase; import org.junit.vintage.engine.samples.junit4.JUnit4SuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass; import org.junit.vintage.engine.samples.junit4.JUnit4SuiteWithJUnit4TestCaseWithErrorInBeforeClass; import org.junit.vintage.engine.samples.junit4.JUnit4SuiteWithJUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished; import org.junit.vintage.engine.samples.junit4.JUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored; import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithAssumptionFailureInBeforeClass; import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithErrorCollectorStoringMultipleFailures; import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithErrorInAfterClass; import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithErrorInBeforeClass; import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithExceptionThrowingRunner; import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished; import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithIndistinguishableOverloadedMethod; import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames; import org.junit.vintage.engine.samples.junit4.JUnit4TestCaseWithRunnerWithDuplicateChangingChildDescriptions; import org.junit.vintage.engine.samples.junit4.MalformedJUnit4TestCase; import org.junit.vintage.engine.samples.junit4.ParameterizedTestCase; import org.junit.vintage.engine.samples.junit4.ParameterizedTimingTestCase; import org.junit.vintage.engine.samples.junit4.ParameterizedWithAfterParamFailureTestCase; import org.junit.vintage.engine.samples.junit4.ParameterizedWithBeforeParamFailureTestCase; import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithFiveTestMethods; import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithLifecycleMethods; import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithSingleTestWhichFails; import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithSingleTestWhichIsIgnored; import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithTwoTestMethods; import org.opentest4j.MultipleFailuresError; /** * @since 4.12 */ class VintageTestEngineExecutionTests { @Test void executesPlainJUnit4TestCaseWithSingleTestWhichFails() { Class testClass = PlainJUnit4TestCaseWithSingleTestWhichFails.class; execute(testClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(test("failingTest"), started()), // event(test("failingTest"), finishedWithFailure(instanceOf(AssertionError.class), message("this test should fail"))), // event(container(testClass), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void executesPlainJUnit4TestCaseWithTwoTests() { Class testClass = PlainJUnit4TestCaseWithTwoTestMethods.class; execute(testClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(test("failingTest"), started()), // event(test("failingTest"), finishedWithFailure(instanceOf(AssertionError.class), message("this test should fail"))), // event(test("successfulTest"), started()), // event(test("successfulTest"), finishedSuccessfully()), // event(container(testClass), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void executesPlainJUnit4TestCaseWithFiveTests() { Class testClass = PlainJUnit4TestCaseWithFiveTestMethods.class; execute(testClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(test("abortedTest"), started()), // event(test("abortedTest"), abortedWithReason(instanceOf(AssumptionViolatedException.class), message("this test should be aborted"))), // event(test("failingTest"), started()), // event(test("failingTest"), finishedWithFailure(instanceOf(AssertionError.class), message("this test should fail"))), // event(test("ignoredTest1_withoutReason"), skippedWithReason("")), // event(test("ignoredTest2_withReason"), skippedWithReason("a custom reason")), // event(test("successfulTest"), started()), // event(test("successfulTest"), finishedSuccessfully()), // event(container(testClass), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void executesMultiplePlainJUnit4TestCases() { LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() // .selectors(DiscoverySelectors.selectClass(PlainJUnit4TestCaseWithTwoTestMethods.class), DiscoverySelectors.selectClass(PlainJUnit4TestCaseWithFiveTestMethods.class)) // .enableImplicitConfigurationParameters(false) // .build(); execute(request).allEvents().assertEventsMatchLoosely( // event(engine(), started()), // event(container(PlainJUnit4TestCaseWithTwoTestMethods.class), started()), // event(container(PlainJUnit4TestCaseWithTwoTestMethods.class), finishedSuccessfully()), // event(container(PlainJUnit4TestCaseWithFiveTestMethods.class), started()), // event(container(PlainJUnit4TestCaseWithFiveTestMethods.class), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void executesMultiplePlainJUnit4TestCasesWithMemoryCleanupEnabled() { LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() // .configurationParameter(LauncherConstants.MEMORY_CLEANUP_ENABLED_PROPERTY_NAME, "true") // .configurationParameter(LauncherConstants.STACKTRACE_PRUNING_ENABLED_PROPERTY_NAME, "false") // .selectors( // DiscoverySelectors.selectClass(PlainJUnit4TestCaseWithTwoTestMethods.class), // DiscoverySelectors.selectClass(PlainJUnit4TestCaseWithFiveTestMethods.class)) // .enableImplicitConfigurationParameters(false) // .build(); EngineExecutionResults execute = execute(request); execute.allEvents().assertEventsMatchLoosely( // event(engine(), started()), // event(container(PlainJUnit4TestCaseWithTwoTestMethods.class), started()), // event(container(PlainJUnit4TestCaseWithTwoTestMethods.class), finishedSuccessfully()), // event(container(PlainJUnit4TestCaseWithFiveTestMethods.class), started()), // event(container(PlainJUnit4TestCaseWithFiveTestMethods.class), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void executesEnclosedJUnit4TestCase() { Class testClass = EnclosedJUnit4TestCase.class; Class nestedClass = EnclosedJUnit4TestCase.NestedClass.class; execute(testClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(container(nestedClass), started()), // event(test("successfulTest"), started()), // event(test("successfulTest"), finishedSuccessfully()), // event(test("failingTest"), started()), // event(test("failingTest"), finishedWithFailure(instanceOf(AssertionError.class), message("this test should fail"))), // event(container(nestedClass), finishedSuccessfully()), // event(container(testClass), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void executesEnclosedWithParameterizedChildrenJUnit4TestCase() { Class testClass = EnclosedWithParameterizedChildrenJUnit4TestCase.class; String commonNestedClassPrefix = EnclosedWithParameterizedChildrenJUnit4TestCase.class.getName() + "$NestedTestCase"; execute(testClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(container(commonNestedClassPrefix), started()), // event(container("[0]"), started()), // event(test("test[0]"), started()), // event(test("test[0]"), finishedSuccessfully()), // event(container("[0]"), finishedSuccessfully()), // event(container("[1]"), started()), // event(test("test[1]"), started()), // event(test("test[1]"), finishedSuccessfully()), // event(container("[1]"), finishedSuccessfully()), // event(container(commonNestedClassPrefix), finishedSuccessfully()), // event(container(commonNestedClassPrefix), started()), // event(container("[0]"), started()), // event(test("test[0]"), started()), // event(test("test[0]"), finishedSuccessfully()), // event(container("[0]"), finishedSuccessfully()), // event(container("[1]"), started()), // event(test("test[1]"), started()), // event(test("test[1]"), finishedSuccessfully()), // event(container("[1]"), finishedSuccessfully()), // event(container(commonNestedClassPrefix), finishedSuccessfully()), // event(container(testClass), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void executesJUnit4SuiteWithJUnit3SuiteWithSingleTestCase() { Class junit4SuiteClass = JUnit4SuiteWithJUnit3SuiteWithSingleTestCase.class; Class testClass = PlainJUnit3TestCaseWithSingleTestWhichFails.class; execute(junit4SuiteClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(junit4SuiteClass), started()), // event(container("TestSuite with 1 tests"), started()), // event(container(testClass), started()), // event(test("test"), started()), // event(test("test"), finishedWithFailure(instanceOf(AssertionError.class), message("this test should fail"))), // event(container(testClass), finishedSuccessfully()), // event(container("TestSuite with 1 tests"), finishedSuccessfully()), // event(container(junit4SuiteClass), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void executesMalformedJUnit4TestCase() { Class testClass = MalformedJUnit4TestCase.class; execute(testClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(test("initializationError"), started()), // event(test("initializationError"), finishedWithFailure(message(it -> it.contains("Method nonPublicTest() should be public")))), // event(container(testClass), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void executesJUnit4TestCaseWithErrorInBeforeClass() { Class testClass = JUnit4TestCaseWithErrorInBeforeClass.class; execute(testClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(container(testClass), finishedWithFailure(instanceOf(AssertionError.class), message("something went wrong"))), // event(engine(), finishedSuccessfully())); } @Test void executesJUnit4SuiteWithJUnit4TestCaseWithErrorInBeforeClass() { Class suiteClass = JUnit4SuiteWithJUnit4TestCaseWithErrorInBeforeClass.class; Class testClass = JUnit4TestCaseWithErrorInBeforeClass.class; execute(suiteClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(suiteClass), started()), // event(container(testClass), started()), // event(container(testClass), finishedWithFailure(instanceOf(AssertionError.class), message("something went wrong"))), // event(container(suiteClass), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void executesJUnit4SuiteOfSuiteWithJUnit4TestCaseWithErrorInBeforeClass() { Class suiteOfSuiteClass = JUnit4SuiteOfSuiteWithJUnit4TestCaseWithErrorInBeforeClass.class; Class suiteClass = JUnit4SuiteWithJUnit4TestCaseWithErrorInBeforeClass.class; Class testClass = JUnit4TestCaseWithErrorInBeforeClass.class; execute(suiteOfSuiteClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(suiteOfSuiteClass), started()), // event(container(suiteClass), started()), // event(container(testClass), started()), // event(container(testClass), finishedWithFailure(instanceOf(AssertionError.class), message("something went wrong"))), // event(container(suiteClass), finishedSuccessfully()), // event(container(suiteOfSuiteClass), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void executesJUnit4TestCaseWithAssumptionFailureInBeforeClass() { Class testClass = JUnit4TestCaseWithAssumptionFailureInBeforeClass.class; execute(testClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(container(testClass), abortedWithReason(instanceOf(AssumptionViolatedException.class), message("assumption violated"))), // event(engine(), finishedSuccessfully())); } @Test void executesJUnit4SuiteOfSuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass() { Class suiteOfSuiteClass = JUnit4SuiteOfSuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.class; Class suiteClass = JUnit4SuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.class; Class testClass = JUnit4TestCaseWithAssumptionFailureInBeforeClass.class; execute(suiteOfSuiteClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(suiteOfSuiteClass), started()), // event(container(suiteClass), started()), // event(container(testClass), started()), // event(container(testClass), abortedWithReason(instanceOf(AssumptionViolatedException.class), message("assumption violated"))), // event(container(suiteClass), finishedSuccessfully()), // event(container(suiteOfSuiteClass), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void executesJUnit4TestCaseWithErrorInAfterClass() { Class testClass = JUnit4TestCaseWithErrorInAfterClass.class; execute(testClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(test("failingTest"), started()), // event(test("failingTest"), finishedWithFailure(instanceOf(AssertionError.class), message("expected to fail"))), // event(test("succeedingTest"), started()), // event(test("succeedingTest"), finishedSuccessfully()), // event(container(testClass), finishedWithFailure(instanceOf(AssertionError.class), message("error in @AfterClass"))), // event(engine(), finishedSuccessfully())); } @Test void executesJUnit4TestCaseWithOverloadedMethod() { Class testClass = JUnit4TestCaseWithIndistinguishableOverloadedMethod.class; execute(testClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(test("theory(" + JUnit4TestCaseWithIndistinguishableOverloadedMethod.class.getName() + ")[0]"), started()), // event(test("theory(" + JUnit4TestCaseWithIndistinguishableOverloadedMethod.class.getName() + ")[0]"), finishedWithFailure()), // event(test("theory(" + JUnit4TestCaseWithIndistinguishableOverloadedMethod.class.getName() + ")[1]"), started()), // event(test("theory(" + JUnit4TestCaseWithIndistinguishableOverloadedMethod.class.getName() + ")[1]"), finishedWithFailure()), // event(container(testClass), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void executesIgnoredJUnit4TestCase() { Class testClass = IgnoredJUnit4TestCase.class; execute(testClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), skippedWithReason("complete class is ignored")), // event(engine(), finishedSuccessfully())); } @Test void executesEmptyIgnoredTestClass() { Class testClass = EmptyIgnoredTestCase.class; execute(testClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(test(testClass.getName()), skippedWithReason("empty")), // event(engine(), finishedSuccessfully())); } @Test void reportsExecutionEventsAroundLifecycleMethods() { Class testClass = PlainJUnit4TestCaseWithLifecycleMethods.class; PlainJUnit4TestCaseWithLifecycleMethods.EVENTS.clear(); var listener = new EngineExecutionListener() { @Override public void executionStarted(TestDescriptor testDescriptor) { PlainJUnit4TestCaseWithLifecycleMethods.EVENTS.add( "executionStarted:" + testDescriptor.getDisplayName()); } @Override public void executionFinished(TestDescriptor testDescriptor, TestExecutionResult testExecutionResult) { PlainJUnit4TestCaseWithLifecycleMethods.EVENTS.add( "executionFinished:" + testDescriptor.getDisplayName()); } @Override public void executionSkipped(TestDescriptor testDescriptor, String reason) { PlainJUnit4TestCaseWithLifecycleMethods.EVENTS.add( "executionSkipped:" + testDescriptor.getDisplayName()); } }; execute(testClass, listener); // @formatter:off assertThat(PlainJUnit4TestCaseWithLifecycleMethods.EVENTS).containsExactly( "executionStarted:JUnit Vintage", "executionStarted:" + testClass.getSimpleName(), "beforeClass", "executionStarted:failingTest", "before", "failingTest", "after", "executionFinished:failingTest", "executionSkipped:skippedTest", "executionStarted:succeedingTest", "before", "succeedingTest", "after", "executionFinished:succeedingTest", "afterClass", "executionFinished:" + testClass.getSimpleName(), "executionFinished:JUnit Vintage" ); // @formatter:on } @Test void executesJUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored() { Class suiteClass = JUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored.class; Class testClass = PlainJUnit4TestCaseWithSingleTestWhichIsIgnored.class; execute(suiteClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(suiteClass), started()), // event(container(testClass), started()), // event(test("ignoredTest"), skippedWithReason("ignored test")), // event(container(testClass), finishedSuccessfully()), // event(container(suiteClass), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void executesJUnit4SuiteOfSuiteWithIgnoredJUnit4TestCase() { Class suiteOfSuiteClass = JUnit4SuiteOfSuiteWithIgnoredJUnit4TestCase.class; Class suiteClass = JUnit4SuiteWithIgnoredJUnit4TestCase.class; Class testClass = IgnoredJUnit4TestCase.class; execute(suiteOfSuiteClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(suiteOfSuiteClass), started()), // event(container(suiteClass), started()), // event(container(testClass), skippedWithReason("complete class is ignored")), // event(container(suiteClass), finishedSuccessfully()), // event(container(suiteOfSuiteClass), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void executesParameterizedTestCase() { Class testClass = ParameterizedTestCase.class; execute(testClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(container("[foo]"), started()), // event(test("test[foo]"), started()), // event(test("test[foo]"), finishedSuccessfully()), // event(container("[foo]"), finishedSuccessfully()), // event(container("[bar]"), started()), // event(test("test[bar]"), started()), // event(test("test[bar]"), finishedWithFailure(instanceOf(AssertionError.class), message("expected:<[foo]> but was:<[bar]>"))), // event(container("[bar]"), finishedSuccessfully()), // event(container(testClass), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void executesIgnoredParameterizedTestCase() { Class testClass = IgnoredParameterizedTestCase.class; execute(testClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(container("[foo]"), started()), // event(test("test[foo]"), skippedWithReason("")), // event(container("[foo]"), finishedSuccessfully()), // event(container("[bar]"), started()), // event(test("test[bar]"), skippedWithReason("")), // event(container("[bar]"), finishedSuccessfully()), // event(container(testClass), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void executesParameterizedTimingTestCase() { assumeTrue(atLeastJUnit4_13(), "@BeforeParam and @AfterParam were introduced in JUnit 4.13"); Class testClass = ParameterizedTimingTestCase.class; var events = execute(testClass).allEvents(); var firstParamStartedEvent = events.filter(event(container("[foo]"), started())::matches).findFirst() // .orElseThrow(() -> new AssertionError("No start event for [foo]")); var firstParamFinishedEvent = events.filter( event(container("[foo]"), finishedSuccessfully())::matches).findFirst() // .orElseThrow(() -> new AssertionError("No finish event for [foo]")); var secondParamStartedEvent = events.filter(event(container("[bar]"), started())::matches).findFirst() // .orElseThrow(() -> new AssertionError("No start event for [bar]")); var secondParamFinishedEvent = events.filter( event(container("[bar]"), finishedSuccessfully())::matches).findFirst() // .orElseThrow(() -> new AssertionError("No finish event for [bar]")); assertThat(ParameterizedTimingTestCase.EVENTS.get("beforeParam(foo)")).isAfterOrEqualTo( firstParamStartedEvent.getTimestamp()); assertThat(ParameterizedTimingTestCase.EVENTS.get("afterParam(foo)")).isBeforeOrEqualTo( firstParamFinishedEvent.getTimestamp()); assertThat(ParameterizedTimingTestCase.EVENTS.get("beforeParam(bar)")).isAfterOrEqualTo( secondParamStartedEvent.getTimestamp()); assertThat(ParameterizedTimingTestCase.EVENTS.get("afterParam(bar)")).isBeforeOrEqualTo( secondParamFinishedEvent.getTimestamp()); } @Test void executesParameterizedWithAfterParamFailureTestCase() { assumeTrue(atLeastJUnit4_13(), "@AfterParam was introduced in JUnit 4.13"); Class testClass = ParameterizedWithAfterParamFailureTestCase.class; execute(testClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(container("[foo]"), started()), // event(test("test[foo]"), started()), // event(test("test[foo]"), finishedSuccessfully()), // event(container("[foo]"), finishedWithFailure(instanceOf(AssertionError.class))), // event(container("[bar]"), started()), // event(test("test[bar]"), started()), // event(test("test[bar]"), finishedWithFailure(instanceOf(AssertionError.class), message("expected:<[foo]> but was:<[bar]>"))), // event(container("[bar]"), finishedWithFailure(instanceOf(AssertionError.class))), // event(container(testClass), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void executesParameterizedWithBeforeParamFailureTestCase() { assumeTrue(atLeastJUnit4_13(), "@BeforeParam was introduced in JUnit 4.13"); Class testClass = ParameterizedWithBeforeParamFailureTestCase.class; execute(testClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(container("[foo]"), started()), // event(container("[foo]"), finishedWithFailure(instanceOf(AssertionError.class))), // event(container("[bar]"), started()), // event(container("[bar]"), finishedWithFailure(instanceOf(AssertionError.class))), // event(container(testClass), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void executesJUnit4TestCaseWithExceptionThrowingRunner() { Class testClass = JUnit4TestCaseWithExceptionThrowingRunner.class; execute(testClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(test(testClass.getName()), started()), // event(test(testClass.getName()), finishedWithFailure()), // event(engine(), finishedSuccessfully())); } @Test void executesJUnit4SuiteWithExceptionThrowingRunner() { Class testClass = JUnit4SuiteWithExceptionThrowingRunner.class; execute(testClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(container(testClass), finishedWithFailure()), // event(engine(), finishedSuccessfully())); } public static class DynamicSuiteRunner extends Runner { private final Class testClass; @SuppressWarnings("RedundantModifier") public DynamicSuiteRunner(Class testClass) { this.testClass = testClass; } @Override public Description getDescription() { return createSuiteDescription(testClass); } @Override public void run(RunNotifier notifier) { var dynamicDescription = createTestDescription(testClass, "dynamicTest"); notifier.fireTestStarted(dynamicDescription); notifier.fireTestFinished(dynamicDescription); } } @RunWith(DynamicSuiteRunner.class) public static class DynamicTestClass { } @Test void reportsDynamicTestsForUnknownDescriptions() { Class testClass = DynamicTestClass.class; execute(testClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(test(testClass.getName()), started()), // event(dynamicTestRegistered("dynamicTest")), // event(test("dynamicTest"), started()), // event(test("dynamicTest"), finishedSuccessfully()), // event(test(testClass.getName()), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } public static class DynamicAndStaticChildrenRunner extends Runner { private final Class testClass; @SuppressWarnings("RedundantModifier") public DynamicAndStaticChildrenRunner(Class testClass) { this.testClass = testClass; } @Override public Description getDescription() { var suiteDescription = createSuiteDescription(testClass); suiteDescription.addChild(createTestDescription(testClass, "staticTest")); return suiteDescription; } @Override public void run(RunNotifier notifier) { var staticDescription = getDescription().getChildren().getFirst(); notifier.fireTestStarted(staticDescription); notifier.fireTestFinished(staticDescription); var dynamicDescription = createTestDescription(testClass, "dynamicTest"); notifier.fireTestStarted(dynamicDescription); notifier.fireTestFinished(dynamicDescription); } } @RunWith(DynamicAndStaticChildrenRunner.class) public static class DynamicAndStaticTestClass { } @RunWith(Suite.class) @SuiteClasses(DynamicAndStaticTestClass.class) public static class SuiteWithDynamicAndStaticTestClass { } @Test void reportsIntermediateContainersFinishedAfterTheirDynamicChildren() { Class suiteClass = SuiteWithDynamicAndStaticTestClass.class; Class testClass = DynamicAndStaticTestClass.class; execute(suiteClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(suiteClass.getName()), started()), // event(container(testClass.getName()), started()), // event(test("staticTest"), started()), // event(test("staticTest"), finishedSuccessfully()), // event(dynamicTestRegistered("dynamicTest")), // event(test("dynamicTest"), started()), // event(test("dynamicTest"), finishedSuccessfully()), // event(container(testClass.getName()), finishedSuccessfully()), // event(container(suiteClass.getName()), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } public static class MisbehavingChildlessRunner extends Runner { private final Class testClass; @SuppressWarnings("RedundantModifier") public MisbehavingChildlessRunner(Class testClass) { this.testClass = testClass; } @Override public Description getDescription() { return createSuiteDescription(testClass); } @Override public void run(RunNotifier notifier) { notifier.fireTestStarted(createTestDescription(testClass, "doesNotExist")); } } @RunWith(MisbehavingChildlessRunner.class) public static class MisbehavingChildTestClass { } @Test void ignoreEventsForUnknownDescriptionsByMisbehavingChildlessRunner() { Class testClass = MisbehavingChildTestClass.class; execute(testClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(test(testClass.getName()), started()), // event(dynamicTestRegistered("doesNotExist")), // event(test("doesNotExist"), started()), // event(test(testClass.getName()), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void executesJUnit4TestCaseWithRunnerWithCustomUniqueIds() { Class testClass = JUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.class; execute(testClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(uniqueIdSubstring(testClass.getName()), started()), // event(uniqueIdSubstring(testClass.getName()), finishedWithFailure()), // event(container(testClass), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void executesJUnit4TestCaseWithErrorCollectorStoringMultipleFailures() { Class testClass = JUnit4TestCaseWithErrorCollectorStoringMultipleFailures.class; execute(testClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(test("example"), started()), // event(test("example"), // finishedWithFailure(// instanceOf(MultipleFailuresError.class), // new Condition<>(throwable -> ((MultipleFailuresError) throwable).getFailures().size() == 3, "MultipleFailuresError must contain 3 failures"), // new Condition<>(throwable -> throwable.getSuppressed().length == 3, "MultipleFailuresError must contain 3 suppressed exceptions")// )), // event(container(testClass), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void executesJUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished() { Class testClass = JUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.class; execute(testClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(test("testWithMissingEvents"), started()), // event(test("testWithMissingEvents"), finishedWithFailure()), // event(container(testClass), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void executesJUnit4SuiteWithJUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished() { Class suiteClass = JUnit4SuiteWithJUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.class; Class firstTestClass = JUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.class; Class secondTestClass = PlainJUnit4TestCaseWithSingleTestWhichFails.class; execute(suiteClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(suiteClass), started()), // event(container(firstTestClass), started()), // event(test("testWithMissingEvents"), started()), // event(test("testWithMissingEvents"), finishedWithFailure()), // event(container(firstTestClass), finishedSuccessfully()), // event(container(secondTestClass), started()), // event(test("failingTest"), started()), // event(test("failingTest"), finishedWithFailure()), // event(container(secondTestClass), finishedSuccessfully()), // event(container(suiteClass), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void executesCompletelyDynamicTestCaseDiscoveredByUniqueId() { Class testClass = CompletelyDynamicTestCase.class; var request = LauncherDiscoveryRequestBuilder.request() // .selectors(selectUniqueId(VintageUniqueIdBuilder.uniqueIdForClass(testClass))) // .enableImplicitConfigurationParameters(false) // .build(); execute(request).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(displayName(testClass.getSimpleName()), started()), // event(dynamicTestRegistered("Test #0")), // event(test("Test #0"), started()), // event(test("Test #0"), finishedSuccessfully()), // event(displayName(testClass.getSimpleName()), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void executesJUnit3ParallelSuiteWithSubsuites() { var suiteClass = JUnit3ParallelSuiteWithSubsuites.class; var results = execute(suiteClass); results.containerEvents() // .assertStatistics(stats -> stats.started(4).dynamicallyRegistered(0).finished(4).succeeded(4)) // .assertEventsMatchExactly( // event(engine(), started()), // event(container(suiteClass), started()), // event(container("Case"), started()), // event(container("Case")), // event(container("Case")), // event(container("Case"), finishedSuccessfully()), // event(container(suiteClass), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); results.testEvents() // .assertStatistics(stats -> stats.started(2).dynamicallyRegistered(0).finished(2).succeeded(2)) // .assertEventsMatchExactly( // event(test("hello"), started()), // event(test("hello")), // event(test("hello")), // event(test("hello"), finishedSuccessfully())); } @Test void executesJUnit3SuiteWithSubsuites() { var suiteClass = JUnit3SuiteWithSubsuites.class; execute(suiteClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(suiteClass), started()), // event(container("Case1"), started()), // event(test("hello"), started()), // event(test("hello"), finishedSuccessfully()), // event(container("Case1"), finishedSuccessfully()), // event(container("Case2"), started()), // event(test("hello"), started()), // event(test("hello"), finishedSuccessfully()), // event(container("Case2"), finishedSuccessfully()), // event(container(suiteClass), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void executesJUnit4TestCaseWithRunnerWithDuplicateChangingChildDescriptions() { Class testClass = JUnit4TestCaseWithRunnerWithDuplicateChangingChildDescriptions.class; execute(testClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(container("1st"), started()), // event(test("0"), skippedWithReason(__ -> true)), // event(test("1"), started()), // event(test("1"), finishedSuccessfully()), // event(container("1st"), finishedSuccessfully()), // event(container("2nd"), started()), // event(test("0"), skippedWithReason(__ -> true)), // event(test("1"), started()), // event(test("1"), finishedSuccessfully()), // event(container("2nd"), finishedSuccessfully()), // event(container(testClass), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test @DisabledInEclipse void executesUnrolledSpockFeatureMethod() { // Load Groovy class via reflection to avoid compilation errors in Eclipse IDE. String testClassName = "org.junit.vintage.engine.samples.spock.SpockTestCaseWithUnrolledAndRegularFeatureMethods"; Class testClass = ReflectionUtils.loadRequiredClass(testClassName, getClass().getClassLoader()); var request = LauncherDiscoveryRequestBuilder.request() // .selectors(selectMethod(testClass, "unrolled feature for #input"))// .enableImplicitConfigurationParameters(false) // .build(); execute(request).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(uniqueIdSubstring(testClassName), started()), // event(dynamicTestRegistered("unrolled feature for 23")), // event(test("unrolled feature for 23"), started()), // event(test("unrolled feature for 23"), finishedWithFailure()), // event(dynamicTestRegistered("unrolled feature for 42")), // event(test("unrolled feature for 42"), started()), // event(test("unrolled feature for 42"), finishedSuccessfully()), // event(uniqueIdSubstring(testClass.getName()), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test @DisabledInEclipse void executesRegularSpockFeatureMethod() { // Load Groovy class via reflection to avoid compilation errors in Eclipse IDE. String testClassName = "org.junit.vintage.engine.samples.spock.SpockTestCaseWithUnrolledAndRegularFeatureMethods"; Class testClass = ReflectionUtils.loadRequiredClass(testClassName, getClass().getClassLoader()); var request = LauncherDiscoveryRequestBuilder.request() // .selectors(selectMethod(testClass, "regular")) // .enableImplicitConfigurationParameters(false) // .build(); execute(request).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(test("regular"), started()), // event(test("regular"), finishedSuccessfully()), // event(container(testClass), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void executesIgnoredJUnit3TestCase() { var suiteClass = IgnoredJUnit3TestCase.class; execute(suiteClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(suiteClass), skippedWithReason(isEqual("testing"))), // event(engine(), finishedSuccessfully())); } @Test void executesJUnit4SuiteWithIgnoredJUnit3TestCase() { var suiteClass = JUnit4SuiteWithIgnoredJUnit3TestCase.class; var testClass = IgnoredJUnit3TestCase.class; execute(suiteClass).allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(suiteClass), started()), // event(container(testClass), skippedWithReason(isEqual("testing"))), // event(container(suiteClass), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void supportsCancellation() { CancellingTestCase.cancellationToken = CancellationToken.create(); try { var results = vintageTestEngine() // .selectors(selectClass(CancellingTestCase.class), selectClass(PlainJUnit4TestCaseWithSingleTestWhichFails.class)) // .cancellationToken(CancellingTestCase.cancellationToken) // .execute(); results.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(CancellingTestCase.class), started()), // event(test(), started()), // event(test(), finishedWithFailure()), // event(test(), skippedWithReason("Execution cancelled")), // event(container(CancellingTestCase.class), abortedWithReason(instanceOf(StoppedByUserException.class))), // event(container(PlainJUnit4TestCaseWithSingleTestWhichFails.class), skippedWithReason("Execution cancelled")), // event(engine(), finishedSuccessfully())); } finally { CancellingTestCase.cancellationToken = null; } } private static EngineExecutionResults execute(Class testClass) { return execute(request(testClass)); } @SuppressWarnings("deprecation") private static EngineExecutionResults execute(LauncherDiscoveryRequest request) { return EngineTestKit.execute(new VintageTestEngine(), request); } @SuppressWarnings("deprecation") private static EngineTestKit.Builder vintageTestEngine() { return EngineTestKit.engine(new VintageTestEngine()) // .enableImplicitConfigurationParameters(false); } @SuppressWarnings("deprecation") private static void execute(Class testClass, EngineExecutionListener listener) { var testEngine = new VintageTestEngine(); var engineTestDescriptor = testEngine.discover(request(testClass), UniqueId.forEngine(testEngine.getId())); ExecutionRequest executionRequest = mock(); when(executionRequest.getRootTestDescriptor()).thenReturn(engineTestDescriptor); when(executionRequest.getEngineExecutionListener()).thenReturn(listener); when(executionRequest.getConfigurationParameters()).thenReturn(mock()); when(executionRequest.getCancellationToken()).thenReturn(CancellationToken.disabled()); testEngine.execute(executionRequest); } private static LauncherDiscoveryRequest request(Class testClass) { return LauncherDiscoveryRequestBuilder.request() // .selectors(selectClass(testClass)) // .enableImplicitConfigurationParameters(false) // .build(); } private static boolean atLeastJUnit4_13() { return JUnit4VersionCheck.parseVersion(Version.id()).compareTo(new BigDecimal("4.13")) >= 0; } } ================================================ FILE: junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineTestSuite.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine; import org.junit.platform.suite.api.ExcludeTags; import org.junit.platform.suite.api.IncludeClassNamePatterns; import org.junit.platform.suite.api.IncludeEngines; import org.junit.platform.suite.api.SelectPackages; import org.junit.platform.suite.api.Suite; /** * Test suite for the {@link VintageTestEngine}. * *

Logging Configuration

* *

In order for our log4j2 configuration to be used in an IDE, you must * set the following system property before running any tests — for * example, in Run Configurations in Eclipse. * *

 * -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager
 * 
* * @since 4.12 */ @Suite @SelectPackages("org.junit.vintage.engine") @IncludeClassNamePatterns(".*Tests?") @IncludeEngines("junit-jupiter") @ExcludeTags("missing-junit4") class VintageTestEngineTestSuite { } ================================================ FILE: junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageUniqueIdBuilder.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine; import org.junit.platform.engine.UniqueId; import org.junit.vintage.engine.descriptor.VintageTestDescriptor; /** * Test data builder for building unique IDs for the {@link VintageTestEngine}. * * Used to decouple tests from concrete unique ID strings. * * @since 4.12 */ public class VintageUniqueIdBuilder { public static UniqueId uniqueIdForErrorInClass(Class clazz, Class failingClass) { return uniqueIdForClasses(clazz).append(VintageTestDescriptor.SEGMENT_TYPE_TEST, "initializationError(" + failingClass.getName() + ")"); } public static UniqueId uniqueIdForClass(Class clazz) { return uniqueIdForClasses(clazz); } public static UniqueId uniqueIdForClasses(Class clazz, Class... clazzes) { var uniqueId = uniqueIdForClass(clazz.getName()); for (var each : clazzes) { uniqueId = uniqueId.append(VintageTestDescriptor.SEGMENT_TYPE_TEST, each.getName()); } return uniqueId; } public static UniqueId uniqueIdForClass(String fullyQualifiedClassName) { var containerId = engineId(); return containerId.append(VintageTestDescriptor.SEGMENT_TYPE_RUNNER, fullyQualifiedClassName); } public static UniqueId uniqueIdForMethod(Class testClass, String methodName) { return uniqueIdForClass(testClass).append(VintageTestDescriptor.SEGMENT_TYPE_TEST, methodValue(testClass, methodName)); } private static String methodValue(Class testClass, String methodName) { return methodName + "(" + testClass.getName() + ")"; } public static UniqueId uniqueIdForMethod(Class testClass, String methodName, String index) { return uniqueIdForClass(testClass).append(VintageTestDescriptor.SEGMENT_TYPE_TEST, methodValue(testClass, methodName) + "[" + index + "]"); } public static UniqueId uniqueIdForMethod(UniqueId containerId, Class testClass, String methodName) { return containerId.append(VintageTestDescriptor.SEGMENT_TYPE_TEST, methodValue(testClass, methodName)); } public static UniqueId engineId() { return UniqueId.forEngine(VintageTestDescriptor.ENGINE_ID); } } ================================================ FILE: junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/DescriptionUtilsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.descriptor; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import java.util.stream.Stream; import org.junit.internal.builders.AllDefaultPossibilitiesBuilder; import org.junit.jupiter.api.DynamicNode; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.TestFactory; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.runner.Description; import org.junit.vintage.engine.discovery.IsPotentialJUnit4TestClass; class DescriptionUtilsTests { @SuppressWarnings("deprecation") AllDefaultPossibilitiesBuilder builder = new AllDefaultPossibilitiesBuilder(true); @TestFactory Stream computedMethodNameCorrectly() { var testClasses = ReflectionSupport.findAllClassesInPackage("org.junit.vintage.engine.samples", new IsPotentialJUnit4TestClass(), name -> true); return testClasses.stream().flatMap(this::toDynamicTests); } private Stream toDynamicTests(Class testClass) { try { var runner = builder.runnerForClass(testClass); return toDynamicTests(Stream.of(runner.getDescription())); } catch (Throwable throwable) { throw new RuntimeException(throwable); } } Stream toDynamicTests(Stream children) { return children.map(description -> description.isTest() // ? toDynamicTest(description, "child: " + description) // : dynamicContainer("class: " + description, Stream.concat( // Stream.of(toDynamicTest(description, "self")), // toDynamicTests(description.getChildren().stream())))); } private DynamicTest toDynamicTest(Description description, String displayName) { return dynamicTest(displayName, () -> assertEquals(description.getMethodName(), DescriptionUtils.getMethodName(description))); } } ================================================ FILE: junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/OrFilterTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.descriptor; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotEmptyFor; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.List; import java.util.Set; import org.junit.jupiter.api.Test; import org.junit.runner.Description; import org.junit.runner.manipulation.Filter; /** * @since 5.5 */ class OrFilterTests { @Test void exceptionWithoutAnyFilters() { assertPreconditionViolationNotEmptyFor("filters", () -> new OrFilter(Set.of())); } @Test void evaluatesSingleFilter() { var filter = mockFilter("foo", true); var orFilter = new OrFilter(Set.of(filter)); assertEquals("foo", orFilter.describe()); var description = Description.createTestDescription(getClass(), "evaluatesSingleFilter"); assertTrue(orFilter.shouldRun(description)); verify(filter).shouldRun(same(description)); } @Test void evaluatesMultipleFilters() { var filter1 = mockFilter("foo", false); var filter2 = mockFilter("bar", true); var orFilter = new OrFilter(List.of(filter1, filter2)); assertEquals("foo OR bar", orFilter.describe()); var description = Description.createTestDescription(getClass(), "evaluatesMultipleFilters"); assertTrue(orFilter.shouldRun(description)); verify(filter1).shouldRun(same(description)); verify(filter2).shouldRun(same(description)); } private Filter mockFilter(String description, boolean result) { var filter = mock(Filter.class); when(filter.describe()).thenReturn(description); when(filter.shouldRun(any())).thenReturn(result); return filter; } } ================================================ FILE: junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/TestSourceProviderTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.descriptor; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; import org.junit.platform.engine.support.descriptor.MethodSource; import org.junit.runner.Description; import org.junit.vintage.engine.samples.junit4.ConcreteJUnit4TestCase; /** * @since 5.6 */ class TestSourceProviderTests { @Test void findsInheritedMethod() { var description = Description.createTestDescription(ConcreteJUnit4TestCase.class, "theTest"); var source = new TestSourceProvider().findTestSource(description); assertThat(source).isInstanceOf(MethodSource.class); var methodSource = (MethodSource) source; assertEquals(ConcreteJUnit4TestCase.class.getName(), methodSource.getClassName()); assertEquals("theTest", methodSource.getMethodName()); } } ================================================ FILE: junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/VintageTestDescriptorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.descriptor; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; import org.junit.platform.engine.UniqueId; import org.junit.runner.Description; import org.junit.vintage.engine.samples.junit4.ConcreteJUnit4TestCase; class VintageTestDescriptorTests { private static final UniqueId uniqueId = UniqueId.forEngine("vintage"); @Test void legacyReportingNameUsesClassName() { var description = Description.createSuiteDescription(ConcreteJUnit4TestCase.class); var testDescriptor = new VintageTestDescriptor(uniqueId, description, null); assertEquals("org.junit.vintage.engine.samples.junit4.ConcreteJUnit4TestCase", testDescriptor.getLegacyReportingName()); } @Test void legacyReportingNameUsesMethodName() { var description = Description.createTestDescription(ConcreteJUnit4TestCase.class, "legacyTest"); var testDescriptor = new VintageTestDescriptor(uniqueId, description, null); assertEquals("legacyTest", testDescriptor.getLegacyReportingName()); } @Test void legacyReportingNameFallbackToDisplayName() { var suiteName = "Legacy Suite"; var description = Description.createSuiteDescription(suiteName); var testDescriptor = new VintageTestDescriptor(uniqueId, description, null); assertEquals(testDescriptor.getDisplayName(), testDescriptor.getLegacyReportingName()); assertEquals(suiteName, testDescriptor.getLegacyReportingName()); } } ================================================ FILE: junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestClassTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.discovery; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; class IsPotentialJUnit4TestClassTests { private final IsPotentialJUnit4TestClass isPotentialJUnit4TestClass = new IsPotentialJUnit4TestClass(); @Test void staticMemberClass() { assertTrue(isPotentialJUnit4TestClass.test(Foo.class)); } public static class Foo { } @Test void nonPublicClass() { assertFalse(isPotentialJUnit4TestClass.test(Bar.class)); } static class Bar { } @Test void abstractClass() { assertFalse(isPotentialJUnit4TestClass.test(Baz.class)); } public static abstract class Baz { } @Test void anonymousClass() { var foo = new Foo() { }; assertFalse(isPotentialJUnit4TestClass.test(foo.getClass())); } public class FooBaz { } @Test void publicInnerClass() { assertFalse(isPotentialJUnit4TestClass.test(FooBaz.class)); } } ================================================ FILE: junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/RunnerTestDescriptorPostProcessorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.discovery; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.mockito.Mockito.mock; import java.util.logging.Level; import java.util.logging.LogRecord; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.fixtures.TrackLogRecords; import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; import org.junit.vintage.engine.VintageUniqueIdBuilder; import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; import org.junit.vintage.engine.samples.junit4.IgnoredJUnit4TestCase; import org.junit.vintage.engine.samples.junit4.IgnoredJUnit4TestCaseWithNotFilterableRunner; import org.junit.vintage.engine.samples.junit4.NotFilterableRunner; import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithFiveTestMethods; /** * Tests for {@link RunnerTestDescriptorPostProcessor}. * * @since 5.5 */ @TrackLogRecords class RunnerTestDescriptorPostProcessorTests { @Test void doesNotLogAnythingForFilterableRunner(LogRecordListener listener) { resolve(selectMethod(PlainJUnit4TestCaseWithFiveTestMethods.class, "successfulTest")); assertThat(listener.stream(RunnerTestDescriptor.class)).isEmpty(); } @Test void doesNotLogAnythingForNonFilterableRunnerIfNoFiltersAreToBeApplied(LogRecordListener listener) { resolve(selectClass(IgnoredJUnit4TestCase.class)); assertThat(listener.stream(RunnerTestDescriptor.class)).isEmpty(); } @Test void logsWarningOnNonFilterableRunner(LogRecordListener listener) { Class testClass = IgnoredJUnit4TestCaseWithNotFilterableRunner.class; resolve(selectMethod(testClass, "someTest")); // @formatter:off assertThat(listener.stream(RunnerTestDescriptor.class, Level.WARNING).map(LogRecord::getMessage)) .containsOnlyOnce("Runner " + NotFilterableRunner.class.getName() + " (used on class " + testClass.getName() + ") does not support filtering" + " and will therefore be run completely."); // @formatter:on } private void resolve(DiscoverySelector selector) { var request = LauncherDiscoveryRequestBuilder.request().selectors(selector).listeners( mock(LauncherDiscoveryListener.class)).build(); TestDescriptor engineDescriptor = new VintageDiscoverer().discover(request, VintageUniqueIdBuilder.engineId()); var runnerTestDescriptor = (RunnerTestDescriptor) getOnlyElement(engineDescriptor.getChildren()); new RunnerTestDescriptorPostProcessor().applyFiltersAndCreateDescendants(runnerTestDescriptor); } } ================================================ FILE: junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/VintageDiscovererTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.discovery; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import static org.junit.platform.engine.SelectorResolutionResult.Status.FAILED; import static org.junit.platform.engine.SelectorResolutionResult.Status.UNRESOLVED; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.junit.vintage.engine.VintageUniqueIdBuilder.engineId; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import java.util.function.Consumer; import org.junit.jupiter.api.Test; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.SelectorResolutionResult; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.discovery.ClassNameFilter; import org.junit.platform.engine.discovery.PackageNameFilter; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.vintage.engine.VintageUniqueIdBuilder; import org.junit.vintage.engine.samples.junit3.AbstractJUnit3TestCase; import org.junit.vintage.engine.samples.junit4.AbstractJunit4TestCaseWithConstructorParameter; import org.mockito.ArgumentCaptor; /** * Tests for {@link VintageDiscoverer}. * * @since 4.12 */ class VintageDiscovererTests { @Test void classNameFilterExcludesClass() { // @formatter:off EngineDiscoveryRequest request = request() .selectors(selectClass(Foo.class), selectClass(Bar.class)) .filters(ClassNameFilter.includeClassNamePatterns(".*Foo")) .build(); // @formatter:on var testDescriptor = discover(request); assertThat(testDescriptor.getChildren()).hasSize(1); assertThat(getOnlyElement(testDescriptor.getChildren()).getUniqueId().toString()).contains(Foo.class.getName()); } @Test void packageNameFilterExcludesClasses() { // @formatter:off EngineDiscoveryRequest request = request() .selectors(selectClass(Foo.class), selectClass(Bar.class)) .filters(PackageNameFilter.excludePackageNames("org.junit.vintage.engine.discovery")) .build(); // @formatter:on var testDescriptor = discover(request); assertThat(testDescriptor.getChildren()).isEmpty(); } @Test void doesNotResolveAbstractJUnit3Classes() { doesNotResolve(selectClass(AbstractJUnit3TestCase.class)); } @Test void doesNotResolveAbstractJUnit4Classes() { doesNotResolve(selectClass(AbstractJunit4TestCaseWithConstructorParameter.class)); } @Test void failsToResolveUnloadableTestClass() { var uniqueId = VintageUniqueIdBuilder.uniqueIdForClass("foo.bar.UnknownClass"); doesNotResolve(selectUniqueId(uniqueId), result -> { assertThat(result.getStatus()).isEqualTo(FAILED); assertThat(result.getThrowable().orElseThrow()).hasMessageContaining("Unknown class"); }); } @Test void ignoresUniqueIdsOfOtherEngines() { doesNotResolve(selectUniqueId(UniqueId.forEngine("someEngine"))); } private void doesNotResolve(DiscoverySelector selector) { doesNotResolve(selector, result -> assertThat(result.getStatus()).isEqualTo(UNRESOLVED)); } private void doesNotResolve(DiscoverySelector selector, Consumer resultCheck) { var discoveryListener = mock(LauncherDiscoveryListener.class); var request = request() // .selectors(selector) // .listeners(discoveryListener) // .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // .build(); var testDescriptor = discover(request); assertThat(testDescriptor.getChildren()).isEmpty(); var resultCaptor = ArgumentCaptor.forClass(SelectorResolutionResult.class); verify(discoveryListener).selectorProcessed(eq(UniqueId.forEngine("junit-vintage")), eq(selector), resultCaptor.capture()); resultCheck.accept(resultCaptor.getValue()); } private TestDescriptor discover(EngineDiscoveryRequest request) { return new VintageDiscoverer().discover(request, engineId()); } @SuppressWarnings("NewClassNamingConvention") public static class Foo { @org.junit.Test public void test() { } } @SuppressWarnings("NewClassNamingConvention") public static class Bar { @org.junit.Test public void test() { } } } ================================================ FILE: junit-vintage-engine/src/test/java/org/junit/vintage/engine/execution/ParallelExecutionIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.execution; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.testkit.engine.EventConditions.container; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; import static org.junit.platform.testkit.engine.EventConditions.started; import static org.junit.platform.testkit.engine.EventConditions.test; import static org.junit.vintage.engine.descriptor.VintageTestDescriptor.SEGMENT_TYPE_RUNNER; import static org.junit.vintage.engine.descriptor.VintageTestDescriptor.SEGMENT_TYPE_TEST; import static org.junit.vintage.engine.samples.junit4.JUnit4ParallelClassesTestCase.FirstClassTestCase; import static org.junit.vintage.engine.samples.junit4.JUnit4ParallelClassesTestCase.SecondClassTestCase; import static org.junit.vintage.engine.samples.junit4.JUnit4ParallelClassesTestCase.ThirdClassTestCase; import static org.junit.vintage.engine.samples.junit4.JUnit4ParallelMethodsTestCase.FirstMethodTestCase; import static org.junit.vintage.engine.samples.junit4.JUnit4ParallelMethodsTestCase.SecondMethodTestCase; import static org.junit.vintage.engine.samples.junit4.JUnit4ParallelMethodsTestCase.ThirdMethodTestCase; import java.time.Instant; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.logging.Level; import java.util.logging.LogRecord; import org.assertj.core.api.Condition; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestReporter; import org.junit.jupiter.api.fixtures.TrackLogRecords; import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.engine.discovery.ClassSelector; import org.junit.platform.engine.discovery.DiscoverySelectors; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; import org.junit.platform.testkit.engine.EngineExecutionResults; import org.junit.platform.testkit.engine.EngineTestKit; import org.junit.platform.testkit.engine.Event; import org.junit.platform.testkit.engine.Events; import org.junit.vintage.engine.Constants; import org.junit.vintage.engine.VintageTestEngine; import org.junit.vintage.engine.samples.junit4.JUnit4ParallelClassesTestCase; import org.junit.vintage.engine.samples.junit4.JUnit4ParallelMethodsTestCase; class ParallelExecutionIntegrationTests { @Test void executesTestClassesInParallel(TestReporter reporter) { JUnit4ParallelClassesTestCase.AbstractBlockingTestCase.threadNames.clear(); JUnit4ParallelClassesTestCase.AbstractBlockingTestCase.countDownLatch = new CountDownLatch(3); var events = executeInParallelSuccessfully(3, true, false, FirstClassTestCase.class, SecondClassTestCase.class, ThirdClassTestCase.class).list(); var startedTimestamps = getTimestampsFor(events, event(container(SEGMENT_TYPE_RUNNER), started())); var finishedTimestamps = getTimestampsFor(events, event(container(SEGMENT_TYPE_RUNNER), finishedSuccessfully())); var threadNames = new HashSet<>(JUnit4ParallelClassesTestCase.AbstractBlockingTestCase.threadNames); reporter.publishEntry("startedTimestamps", startedTimestamps.toString()); reporter.publishEntry("finishedTimestamps", finishedTimestamps.toString()); assertThat(startedTimestamps).hasSize(3); assertThat(finishedTimestamps).hasSize(3); assertThat(startedTimestamps).allMatch(startTimestamp -> finishedTimestamps.stream().noneMatch( finishedTimestamp -> finishedTimestamp.isBefore(startTimestamp))); assertThat(threadNames).hasSize(3); } @Test void executesTestMethodsInParallel(TestReporter reporter) { JUnit4ParallelMethodsTestCase.AbstractBlockingTestCase.threadNames.clear(); JUnit4ParallelMethodsTestCase.AbstractBlockingTestCase.countDownLatch = new CountDownLatch(3); var events = executeInParallelSuccessfully(3, false, true, FirstMethodTestCase.class).list(); var startedTimestamps = getTimestampsFor(events, event(test(SEGMENT_TYPE_TEST), started())); var finishedTimestamps = getTimestampsFor(events, event(test(SEGMENT_TYPE_TEST), finishedSuccessfully())); var threadNames = new HashSet<>(JUnit4ParallelMethodsTestCase.AbstractBlockingTestCase.threadNames); reporter.publishEntry("startedTimestamps", startedTimestamps.toString()); reporter.publishEntry("finishedTimestamps", finishedTimestamps.toString()); assertAll( // () -> assertThat(startedTimestamps).hasSize(3), // () -> assertThat(finishedTimestamps).hasSize(3), // () -> assertThat(startedTimestamps).allMatch(startTimestamp -> finishedTimestamps.stream().noneMatch( // finishedTimestamp -> finishedTimestamp.isBefore(startTimestamp))), // () -> assertThat(threadNames).hasSize(3)); } @Test void executesTestClassesAndMethodsInParallel(TestReporter reporter) { JUnit4ParallelMethodsTestCase.AbstractBlockingTestCase.threadNames.clear(); JUnit4ParallelMethodsTestCase.AbstractBlockingTestCase.countDownLatch = new CountDownLatch(9); var events = executeInParallelSuccessfully(3, true, true, FirstMethodTestCase.class, SecondMethodTestCase.class, ThirdMethodTestCase.class).list(); var startedClassesTimestamps = getTimestampsFor(events, event(container(SEGMENT_TYPE_RUNNER), started())); var finishedClassesTimestamps = getTimestampsFor(events, event(container(SEGMENT_TYPE_RUNNER), finishedSuccessfully())); var startedMethodsTimestamps = getTimestampsFor(events, event(test(SEGMENT_TYPE_TEST), started())); var finishedMethodsTimestamps = getTimestampsFor(events, event(test(SEGMENT_TYPE_TEST), finishedSuccessfully())); var threadNames = new HashSet<>(JUnit4ParallelMethodsTestCase.AbstractBlockingTestCase.threadNames); reporter.publishEntry("startedClassesTimestamps", startedClassesTimestamps.toString()); reporter.publishEntry("finishedClassesTimestamps", finishedClassesTimestamps.toString()); reporter.publishEntry("startedMethodsTimestamps", startedMethodsTimestamps.toString()); reporter.publishEntry("finishedMethodsTimestamps", finishedMethodsTimestamps.toString()); assertThat(startedClassesTimestamps).hasSize(3); assertThat(finishedClassesTimestamps).hasSize(3); assertThat(startedMethodsTimestamps).hasSize(9); assertThat(finishedMethodsTimestamps).hasSize(9); assertThat(threadNames).hasSize(3); } @Test void executesInParallelWhenNoScopeIsDefined(@TrackLogRecords LogRecordListener listener) { JUnit4ParallelMethodsTestCase.AbstractBlockingTestCase.threadNames.clear(); JUnit4ParallelMethodsTestCase.AbstractBlockingTestCase.countDownLatch = new CountDownLatch(9); execute(3, false, false, FirstMethodTestCase.class, SecondMethodTestCase.class, ThirdMethodTestCase.class); // @formatter:off assertTrue(listener.stream(Level.WARNING) .map(LogRecord::getMessage) .anyMatch(m -> m.startsWith( "Parallel execution is enabled but no scope is defined. Falling back to sequential execution."))); // @formatter:on var threadNames = new HashSet<>(JUnit4ParallelMethodsTestCase.AbstractBlockingTestCase.threadNames); assertThat(threadNames).hasSize(1); } @Test void executesInParallelWhenInvalidPoolSizeIsDefined(@TrackLogRecords LogRecordListener listener) { execute(-1, true, true, FirstMethodTestCase.class, SecondMethodTestCase.class, ThirdMethodTestCase.class); // @formatter:off assertTrue(listener.stream(Level.WARNING) .map(LogRecord::getMessage) .anyMatch(m -> m.startsWith("Invalid value for parallel pool size: -1"))); // @formatter:on } private List getTimestampsFor(List events, Condition condition) { // @formatter:off return events.stream() .filter(condition::matches) .map(Event::getTimestamp) .toList(); // @formatter:on } private Events executeInParallelSuccessfully(int poolSize, boolean parallelClasses, boolean parallelMethods, Class... testClasses) { var events = execute(poolSize, parallelClasses, parallelMethods, testClasses).allEvents(); try { return events.assertStatistics(it -> it.failed(0)); } catch (AssertionError error) { events.debug(); throw error; } } @SuppressWarnings("deprecation") private static EngineExecutionResults execute(int poolSize, boolean parallelClasses, boolean parallelMethods, Class... testClass) { return EngineTestKit.execute(new VintageTestEngine(), request(poolSize, parallelClasses, parallelMethods, testClass)); } @SuppressWarnings("deprecation") private static LauncherDiscoveryRequest request(int poolSize, boolean parallelClasses, boolean parallelMethods, Class... testClasses) { var classSelectors = Arrays.stream(testClasses) // .map(DiscoverySelectors::selectClass) // .toArray(ClassSelector[]::new); return LauncherDiscoveryRequestBuilder.request() // .selectors(classSelectors) // .configurationParameter(Constants.PARALLEL_EXECUTION_ENABLED, String.valueOf(true)) // .configurationParameter(Constants.PARALLEL_POOL_SIZE, String.valueOf(poolSize)) // .configurationParameter(Constants.PARALLEL_CLASS_EXECUTION, String.valueOf(parallelClasses)) // .configurationParameter(Constants.PARALLEL_METHOD_EXECUTION, String.valueOf(parallelMethods)) // .enableImplicitConfigurationParameters(false) // .build(); } } ================================================ FILE: junit-vintage-engine/src/test/java/org/junit/vintage/engine/execution/TestRunTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.execution; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.runner.Description.createTestDescription; import static org.junit.vintage.engine.VintageUniqueIdBuilder.engineId; import static org.junit.vintage.engine.descriptor.VintageTestDescriptor.SEGMENT_TYPE_DYNAMIC; import static org.junit.vintage.engine.descriptor.VintageTestDescriptor.SEGMENT_TYPE_RUNNER; import org.junit.jupiter.api.Test; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; import org.junit.vintage.engine.descriptor.VintageTestDescriptor; import org.junit.vintage.engine.samples.junit4.PlainJUnit4TestCaseWithSingleTestWhichFails; /** * @since 4.12 */ class TestRunTests { @Test void returnsEmptyOptionalForUnknownDescriptions() throws Exception { Class testClass = PlainJUnit4TestCaseWithSingleTestWhichFails.class; var runnerId = engineId().append(SEGMENT_TYPE_RUNNER, testClass.getName()); var runnerTestDescriptor = new RunnerTestDescriptor(runnerId, testClass, new BlockJUnit4ClassRunner(testClass), false); var unknownDescription = createTestDescription(testClass, "dynamicTest"); var testRun = new TestRun(runnerTestDescriptor); var testDescriptor = testRun.lookupNextTestDescriptor(unknownDescription); assertThat(testDescriptor).isEmpty(); } @Test void registersDynamicTestDescriptors() throws Exception { Class testClass = PlainJUnit4TestCaseWithSingleTestWhichFails.class; var runnerId = engineId().append(SEGMENT_TYPE_RUNNER, testClass.getName()); var runnerTestDescriptor = new RunnerTestDescriptor(runnerId, testClass, new BlockJUnit4ClassRunner(testClass), false); var dynamicTestId = runnerId.append(SEGMENT_TYPE_DYNAMIC, "dynamicTest"); var dynamicDescription = createTestDescription(testClass, "dynamicTest"); var dynamicTestDescriptor = new VintageTestDescriptor(dynamicTestId, dynamicDescription, null); var testRun = new TestRun(runnerTestDescriptor); testRun.registerDynamicTest(dynamicTestDescriptor); assertThat(testRun.lookupNextTestDescriptor(dynamicDescription)).contains(dynamicTestDescriptor); assertTrue(testRun.isDescendantOfRunnerTestDescriptor(dynamicTestDescriptor)); } } ================================================ FILE: junit-vintage-engine/src/test/java/org/junit/vintage/engine/support/UniqueIdReaderTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.support; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.runner.Description.createTestDescription; import java.util.logging.Level; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.fixtures.TrackLogRecords; import org.junit.platform.commons.logging.LogRecordListener; /** * Tests for {@link UniqueIdReader}. * * @since 4.12 */ @TrackLogRecords class UniqueIdReaderTests { @Test void readsUniqueId(LogRecordListener listener) { var description = createTestDescription("ClassName", "methodName", "uniqueId"); var uniqueId = new UniqueIdReader().apply(description); assertEquals("uniqueId", uniqueId); assertThat(listener.stream(UniqueIdReader.class)).isEmpty(); } @Test void returnsDisplayNameWhenUniqueIdCannotBeRead(LogRecordListener listener) { var description = createTestDescription("ClassName", "methodName", "uniqueId"); assertEquals("methodName(ClassName)", description.getDisplayName()); var uniqueId = new UniqueIdReader("wrongFieldName").apply(description); assertEquals(description.getDisplayName(), uniqueId); var logRecord = listener.stream(UniqueIdReader.class, Level.WARNING).findFirst(); assertThat(logRecord).isPresent(); assertThat(logRecord.get().getMessage()).isEqualTo( "Could not read unique ID for Description; using display name instead: " + description.getDisplayName()); assertThat(logRecord.get().getThrown()).isInstanceOf(NoSuchFieldException.class); } } ================================================ FILE: junit-vintage-engine/src/test/java/org/junit/vintage/engine/support/UniqueIdStringifierTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.support; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.ByteArrayInputStream; import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.ObjectStreamException; import java.io.Serial; import java.io.Serializable; import java.util.Base64; import org.junit.jupiter.api.Test; /** * @since 4.12 */ class UniqueIdStringifierTests { @Test void returnsReadableStringForKnownTypes() { var stringifier = new UniqueIdStringifier(); assertEquals("foo", stringifier.apply("foo")); assertEquals("42", stringifier.apply(42)); assertEquals("42", stringifier.apply(42L)); assertEquals("42.23", stringifier.apply(42.23d)); } @Test void serializesUnknownTypes() throws Exception { var stringifier = new UniqueIdStringifier(); var serialized = stringifier.apply(new MyCustomId(42)); var deserializedObject = deserialize(decodeBase64(serialized)); assertThat(deserializedObject).isInstanceOf(MyCustomId.class); assertEquals(42, ((MyCustomId) deserializedObject).value()); } @Test void usesToStringWhenSerializationFails() { var stringifier = new UniqueIdStringifier(); var serialized = stringifier.apply(new ClassWithErroneousSerialization()); var deserializedString = new String(decodeBase64(serialized), UniqueIdStringifier.CHARSET); assertEquals("value from toString()", deserializedString); } private byte[] decodeBase64(String value) { return Base64.getDecoder().decode(value.getBytes(UniqueIdStringifier.CHARSET)); } private Object deserialize(byte[] bytes) throws Exception { try (var inputStream = new ObjectInputStream(new ByteArrayInputStream(bytes))) { return inputStream.readObject(); } } private record MyCustomId(int value) implements Serializable { @Serial private static final long serialVersionUID = 1L; } private static class ClassWithErroneousSerialization implements Serializable { @Serial private static final long serialVersionUID = 1L; @Serial Object writeReplace() throws ObjectStreamException { throw new InvalidObjectException("failed on purpose"); } @Override public String toString() { return "value from toString()"; } } } ================================================ FILE: junit-vintage-engine/src/test/resources/junit-platform.properties ================================================ junit.jupiter.extensions.autodetection.enabled=true ================================================ FILE: junit-vintage-engine/src/test/resources/log4j2-test.xml ================================================ ================================================ FILE: junit-vintage-engine/src/testFixtures/groovy/org/junit/vintage/engine/samples/spock/SpockTestCaseWithUnrolledAndRegularFeatureMethods.groovy ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.spock import spock.lang.Specification import spock.lang.Unroll class SpockTestCaseWithUnrolledAndRegularFeatureMethods extends Specification { @Unroll def "unrolled feature for #input"() { expect: input == 42 where: input << [23, 42] } def "regular"() { expect: true } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/platform/runner/JUnitPlatform.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.runner; import java.util.List; import org.junit.runner.Description; import org.junit.runner.notification.RunNotifier; import org.junit.runners.ParentRunner; import org.junit.runners.model.InitializationError; /** * Dummy Runner class mimicking the one from the discontinued * {@code junit-platform-runner} module. */ public class JUnitPlatform extends ParentRunner { public JUnitPlatform(Class testClass) throws InitializationError { super(testClass); } @Override protected List getChildren() { return List.of(); } @Override protected Description describeChild(Void child) { throw new UnsupportedOperationException(); } @Override protected void runChild(Void child, RunNotifier notifier) { throw new UnsupportedOperationException(); } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/PlainOldJavaClassWithoutAnyTestsTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples; /** * @since 4.12 */ public class PlainOldJavaClassWithoutAnyTestsTestCase { public void doSomething() { // no-op } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/AbstractJUnit3TestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit3; import junit.framework.TestCase; import org.junit.Assert; /** * @since 4.12 */ public abstract class AbstractJUnit3TestCase extends TestCase { public void test() { Assert.fail("this test should not be run"); } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/IgnoredJUnit3TestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit3; import junit.framework.TestCase; import org.junit.Assert; import org.junit.Ignore; /** * @since 4.12 */ @Ignore("testing") public class IgnoredJUnit3TestCase extends TestCase { public void test() { Assert.fail("this test should be ignored"); } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3ParallelSuiteWithSubsuites.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit3; import junit.extensions.ActiveTestSuite; import junit.framework.TestCase; import junit.framework.TestSuite; @SuppressWarnings("JUnitMalformedDeclaration") public class JUnit3ParallelSuiteWithSubsuites extends TestCase { private final String arg; public JUnit3ParallelSuiteWithSubsuites(String name, String arg) { super(name); this.arg = arg; } public void hello() { assertNotNull(arg); } public static TestSuite suite() { TestSuite root = new ActiveTestSuite("allTests"); var case1 = new TestSuite("Case1"); case1.addTest(new JUnit3ParallelSuiteWithSubsuites("hello", "world")); root.addTest(case1); var case2 = new TestSuite("Case2"); case2.addTest(new JUnit3ParallelSuiteWithSubsuites("hello", "WORLD")); root.addTest(case2); return root; } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit3; import junit.framework.TestCase; import junit.framework.TestSuite; /** * @since 4.12 */ public class JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails extends TestCase { public static junit.framework.Test suite() { var suite = new TestSuite(); suite.addTestSuite(PlainJUnit3TestCaseWithSingleTestWhichFails.class); return suite; } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3SuiteWithSubsuites.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit3; import junit.framework.TestCase; import junit.framework.TestSuite; @SuppressWarnings("JUnitMalformedDeclaration") public class JUnit3SuiteWithSubsuites extends TestCase { private final String arg; public JUnit3SuiteWithSubsuites(String name, String arg) { super(name); this.arg = arg; } public void hello() { assertNotNull(arg); } public static TestSuite suite() { var root = new TestSuite("allTests"); var case1 = new TestSuite("Case1"); case1.addTest(new JUnit3SuiteWithSubsuites("hello", "world")); root.addTest(case1); var case2 = new TestSuite("Case2"); case2.addTest(new JUnit3SuiteWithSubsuites("hello", "WORLD")); root.addTest(case2); return root; } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit4SuiteWithIgnoredJUnit3TestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit3; import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; @RunWith(Suite.class) @SuiteClasses({ IgnoredJUnit3TestCase.class }) public class JUnit4SuiteWithIgnoredJUnit3TestCase { } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/PlainJUnit3TestCaseWithSingleTestWhichFails.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit3; import junit.framework.TestCase; import org.junit.Assert; /** * @since 4.12 */ public class PlainJUnit3TestCaseWithSingleTestWhichFails extends TestCase { public void test() { Assert.fail("this test should fail"); } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/AbstractJUnit4TestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import org.junit.Test; public abstract class AbstractJUnit4TestCase { @Test public void theTest() { } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/AbstractJunit4TestCaseWithConstructorParameter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import org.junit.Test; public abstract class AbstractJunit4TestCaseWithConstructorParameter { public AbstractJunit4TestCaseWithConstructorParameter(int parameter) { } @Test public void test() { } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/CancellingTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import static java.util.Objects.requireNonNull; import static org.junit.Assert.fail; import org.junit.Before; import org.junit.Test; import org.junit.platform.engine.CancellationToken; public class CancellingTestCase { public static CancellationToken cancellationToken; @Before public void cancelExecution() { requireNonNull(cancellationToken).cancel(); } @Test public void first() { fail(); } @Test public void second() { fail(); } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/Categories.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; /** * @since 4.12 */ public class Categories { public interface Plain { } public interface Failing { } public interface Skipped { } public interface SkippedWithReason extends Skipped { } public interface Successful { } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/CompletelyDynamicTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import org.junit.runner.RunWith; import org.junit.vintage.engine.samples.junit4.ConfigurableRunner.ChildCount; /** * Simulates a Spock 1.x test with only {@code @Unroll} feature methods. */ @RunWith(DynamicRunner.class) @ChildCount(1) public class CompletelyDynamicTestCase { } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ConcreteJUnit4TestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; public class ConcreteJUnit4TestCase extends AbstractJUnit4TestCase { } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ConfigurableRunner.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import static java.util.stream.IntStream.range; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.ArrayList; import java.util.List; import java.util.Optional; import org.junit.runner.Description; import org.junit.runner.Runner; import org.junit.runner.notification.RunNotifier; /** * @since 5.1 */ abstract class ConfigurableRunner extends Runner { @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface ChildCount { int value(); } protected final Class testClass; protected final List filteredChildren = new ArrayList<>(); ConfigurableRunner(Class testClass) { this.testClass = testClass; var childCountAnnotation = testClass.getAnnotation(ChildCount.class); int childCount = Optional.ofNullable(childCountAnnotation).map(ChildCount::value).orElse(0); // @formatter:off range(0, childCount) .mapToObj(index -> Description.createTestDescription(testClass, "Test #" + index)) .forEach(filteredChildren::add); // @formatter:on } @Override public Description getDescription() { var suiteDescription = Description.createSuiteDescription(testClass); filteredChildren.forEach(suiteDescription::addChild); return suiteDescription; } @Override public void run(RunNotifier notifier) { filteredChildren.forEach(child -> { notifier.fireTestStarted(child); notifier.fireTestFinished(child); }); } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/DynamicRunner.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import org.junit.runner.Description; import org.junit.runner.manipulation.Filter; import org.junit.runner.manipulation.Filterable; import org.junit.runner.manipulation.NoTestsRemainException; public class DynamicRunner extends ConfigurableRunner implements Filterable { public DynamicRunner(Class testClass) { super(testClass); } @Override public Description getDescription() { return Description.createSuiteDescription(testClass); } @Override public void filter(Filter filter) throws NoTestsRemainException { filteredChildren.removeIf(each -> !filter.shouldRun(each)); if (filteredChildren.isEmpty()) { throw new NoTestsRemainException(); } } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EmptyIgnoredTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import org.junit.Ignore; @Ignore("empty") public class EmptyIgnoredTestCase { } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EnclosedJUnit4TestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; /** * @since 4.12 */ @RunWith(Enclosed.class) public class EnclosedJUnit4TestCase { @Category(Categories.Plain.class) public static class NestedClass { @Test @Category(Categories.Failing.class) public void failingTest() { fail("this test should fail"); } @Test public void successfulTest() { assertEquals(3, 1 + 2); } } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EnclosedWithParameterizedChildrenJUnit4TestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import java.util.Arrays; import java.util.Collection; import org.junit.Test; import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; // Source: https://github.com/junit-team/junit-framework/issues/3083 @RunWith(Enclosed.class) public class EnclosedWithParameterizedChildrenJUnit4TestCase { @RunWith(Parameterized.class) public static class NestedTestCase1 { @Parameters public static Collection data() { return Arrays.asList(new Object[] { 1, 2 }, new Object[] { 3, 4 }); } @SuppressWarnings("unused") public NestedTestCase1(final int a, final int b) { } @Test public void test() { } } @RunWith(Parameterized.class) public static class NestedTestCase2 { @Parameters public static Collection data() { return Arrays.asList(new Object[] { 1, 2 }, new Object[] { 3, 4 }); } @SuppressWarnings("unused") public NestedTestCase2(final int a, final int b) { } @Test public void test() { } } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ExceptionThrowingRunner.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import org.junit.runner.notification.RunNotifier; /** * @since 4.12 */ public class ExceptionThrowingRunner extends ConfigurableRunner { public ExceptionThrowingRunner(Class testClass) { super(testClass); } @Override public void run(RunNotifier notifier) { throw new RuntimeException("Simulated exception in custom runner for " + testClass.getName()); } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredJUnit4TestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import static org.junit.Assert.fail; import static org.junit.runners.MethodSorters.NAME_ASCENDING; import org.junit.FixMethodOrder; import org.junit.Ignore; import org.junit.Test; /** * @since 4.12 */ @Ignore("complete class is ignored") @FixMethodOrder(NAME_ASCENDING) public class IgnoredJUnit4TestCase { @Test public void failingTest() { fail("this test is discovered, but skipped"); } @Test public void succeedingTest() { } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredJUnit4TestCaseWithNotFilterableRunner.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import org.junit.Ignore; /** * @since 5.1 */ @Ignore public class IgnoredJUnit4TestCaseWithNotFilterableRunner extends JUnit4TestCaseWithNotFilterableRunner { } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredParameterizedTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import java.util.List; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; /** * @since 5.4.1 */ @RunWith(Parameterized.class) public class IgnoredParameterizedTestCase { @Parameters(name = "{0}") public static Iterable parameters() { return List.of("foo", "bar"); } @Parameter public String value; @Test @Ignore public void test() { // never called } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4ParallelClassesTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import static java.util.concurrent.TimeUnit.MILLISECONDS; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.runners.Enclosed; import org.junit.rules.TestWatcher; import org.junit.runner.Description; import org.junit.runner.RunWith; @RunWith(Enclosed.class) public class JUnit4ParallelClassesTestCase { public static class AbstractBlockingTestCase { public static final Set threadNames = ConcurrentHashMap.newKeySet(); public static CountDownLatch countDownLatch; @Rule public final TestWatcher testWatcher = new TestWatcher() { @Override protected void starting(Description description) { AbstractBlockingTestCase.threadNames.add(Thread.currentThread().getName()); } }; @Test public void test() throws Exception { countDownAndBlock(countDownLatch); } @SuppressWarnings("ResultOfMethodCallIgnored") private static void countDownAndBlock(CountDownLatch countDownLatch) throws InterruptedException { countDownLatch.countDown(); countDownLatch.await(estimateSimulatedTestDurationInMilliseconds(), MILLISECONDS); } private static long estimateSimulatedTestDurationInMilliseconds() { var runningInCi = Boolean.parseBoolean(System.getenv("CI")); return runningInCi ? 1000 : 100; } } public static class FirstClassTestCase extends AbstractBlockingTestCase { } public static class SecondClassTestCase extends AbstractBlockingTestCase { } public static class ThirdClassTestCase extends AbstractBlockingTestCase { } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4ParallelMethodsTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import static java.util.concurrent.TimeUnit.MILLISECONDS; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.runners.Enclosed; import org.junit.rules.TestWatcher; import org.junit.runner.Description; import org.junit.runner.RunWith; @RunWith(Enclosed.class) public class JUnit4ParallelMethodsTestCase { public static class AbstractBlockingTestCase { public static final Set threadNames = ConcurrentHashMap.newKeySet(); public static CountDownLatch countDownLatch; @Rule public final TestWatcher testWatcher = new TestWatcher() { @Override protected void starting(Description description) { AbstractBlockingTestCase.threadNames.add(Thread.currentThread().getName()); } }; @Test public void fistTest() throws Exception { countDownAndBlock(countDownLatch); } @Test public void secondTest() throws Exception { countDownAndBlock(countDownLatch); } @Test public void thirdTest() throws Exception { countDownAndBlock(countDownLatch); } @SuppressWarnings("ResultOfMethodCallIgnored") private static void countDownAndBlock(CountDownLatch countDownLatch) throws InterruptedException { countDownLatch.countDown(); countDownLatch.await(estimateSimulatedTestDurationInMilliseconds(), MILLISECONDS); } private static long estimateSimulatedTestDurationInMilliseconds() { var runningInCi = Boolean.parseBoolean(System.getenv("CI")); return runningInCi ? 1000 : 100; } } public static class FirstMethodTestCase extends JUnit4ParallelMethodsTestCase.AbstractBlockingTestCase { } public static class SecondMethodTestCase extends JUnit4ParallelMethodsTestCase.AbstractBlockingTestCase { } public static class ThirdMethodTestCase extends JUnit4ParallelMethodsTestCase.AbstractBlockingTestCase { } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4ParameterizedTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import static org.junit.Assert.fail; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; /** * Test case used in {@link JUnit4ParameterizedTests}. * * @since 4.12 */ @RunWith(Parameterized.class) public class JUnit4ParameterizedTestCase { @Parameters public static Object[] data() { return new Object[] { 1, 2, 3 }; } public JUnit4ParameterizedTestCase(int i) { } @Test public void test1() { fail("this test should fail"); } @Test public void endingIn_test1() { fail("this test should fail"); } @Test public void test1_atTheBeginning() { fail("this test should fail"); } @Test public void test2() { /* always succeeds */ } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithFilterableChildRunner.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; /** * @since 5.1 */ @RunWith(Suite.class) @SuiteClasses(JUnit4TestCaseWithNotFilterableRunner.class) public class JUnit4SuiteOfSuiteWithFilterableChildRunner { } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithIgnoredJUnit4TestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; /** * @since 4.12 */ @RunWith(Suite.class) @SuiteClasses(JUnit4SuiteWithIgnoredJUnit4TestCase.class) public class JUnit4SuiteOfSuiteWithIgnoredJUnit4TestCase { } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; /** * @since 4.12 */ @RunWith(Suite.class) @SuiteClasses(JUnit4SuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.class) public class JUnit4SuiteOfSuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass { } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithJUnit4TestCaseWithErrorInBeforeClass.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; /** * @since 4.12 */ @RunWith(Suite.class) @SuiteClasses(JUnit4SuiteWithJUnit4TestCaseWithErrorInBeforeClass.class) public class JUnit4SuiteOfSuiteWithJUnit4TestCaseWithErrorInBeforeClass { } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithExceptionThrowingRunner.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import org.junit.runner.RunWith; import org.junit.vintage.engine.samples.junit4.ConfigurableRunner.ChildCount; /** * @since 4.12 */ @RunWith(ExceptionThrowingRunner.class) @ChildCount(1) public class JUnit4SuiteWithExceptionThrowingRunner { } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithIgnoredJUnit4TestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; /** * @since 4.12 */ @RunWith(Suite.class) @SuiteClasses(IgnoredJUnit4TestCase.class) public class JUnit4SuiteWithIgnoredJUnit4TestCase { } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit3SuiteWithSingleTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; import org.junit.vintage.engine.samples.junit3.JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails; /** * @since 4.12 */ @RunWith(Suite.class) @SuiteClasses(JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails.class) public class JUnit4SuiteWithJUnit3SuiteWithSingleTestCase { } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; /** * @since 4.12 */ @RunWith(Suite.class) @SuiteClasses(JUnit4TestCaseWithAssumptionFailureInBeforeClass.class) public class JUnit4SuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass { } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithErrorInBeforeClass.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; /** * @since 4.12 */ @RunWith(Suite.class) @SuiteClasses(JUnit4TestCaseWithErrorInBeforeClass.class) public class JUnit4SuiteWithJUnit4TestCaseWithErrorInBeforeClass { } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; @RunWith(Suite.class) @SuiteClasses({ JUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.class, PlainJUnit4TestCaseWithSingleTestWhichFails.class }) public class JUnit4SuiteWithJUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished { } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; /** * @since 5.6.2 */ @RunWith(Suite.class) @SuiteClasses(JUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.class) public class JUnit4SuiteWithJUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames { } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; /** * @since 4.12 */ @RunWith(Suite.class) @SuiteClasses(PlainJUnit4TestCaseWithSingleTestWhichIsIgnored.class) public class JUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored { } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithTwoTestCases.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; /** * @since 4.12 */ @RunWith(Suite.class) @SuiteClasses({ PlainJUnit4TestCaseWithTwoTestMethods.class, PlainJUnit4TestCaseWithSingleTestWhichFails.class }) public class JUnit4SuiteWithTwoTestCases { } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithAssumptionFailureInBeforeClass.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import static org.junit.Assert.fail; import org.junit.AssumptionViolatedException; import org.junit.BeforeClass; import org.junit.Test; /** * @since 4.12 */ public class JUnit4TestCaseWithAssumptionFailureInBeforeClass { @BeforeClass public static void failingBeforeClass() { throw new AssumptionViolatedException("assumption violated"); } @Test public void test() { fail("this should never be called"); } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithDistinguishableOverloadedMethod.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import static org.junit.Assert.fail; import org.junit.Test; import org.junit.experimental.theories.Theories; import org.junit.runner.RunWith; /** * @since 5.5 */ @RunWith(Theories.class) public class JUnit4TestCaseWithDistinguishableOverloadedMethod { @Test public void test() { test("foo"); } @SuppressWarnings("SameParameterValue") private void test(String message) { fail(message); } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorCollectorStoringMultipleFailures.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import static org.hamcrest.core.IsNot.not; import static org.hamcrest.core.StringContains.containsString; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ErrorCollector; public class JUnit4TestCaseWithErrorCollectorStoringMultipleFailures { @Rule public ErrorCollector collector = new ErrorCollector(); @Test public void example() { collector.addError(new Throwable("first thing went wrong")); collector.addError(new Throwable("second thing went wrong")); collector.checkThat(getResult(), not(containsString("ERROR!"))); // all lines will run, and then a combined failure logged at the end. } private String getResult() { return "This is an ERROR!"; } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorInAfterClass.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import static org.junit.Assert.fail; import static org.junit.runners.MethodSorters.NAME_ASCENDING; import org.junit.AfterClass; import org.junit.FixMethodOrder; import org.junit.Test; /** * @since 4.12 */ @FixMethodOrder(NAME_ASCENDING) public class JUnit4TestCaseWithErrorInAfterClass { @AfterClass public static void failingAfterClass() { fail("error in @AfterClass"); } @Test public void failingTest() { fail("expected to fail"); } @Test public void succeedingTest() { // no-op } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorInBeforeClass.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import static org.junit.Assert.fail; import org.junit.BeforeClass; import org.junit.Test; /** * @since 4.12 */ public class JUnit4TestCaseWithErrorInBeforeClass { @BeforeClass public static void failingBeforeClass() { fail("something went wrong"); } @Test public void test() { fail("this should never be called"); } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithExceptionThrowingRunner.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import org.junit.runner.RunWith; import org.junit.vintage.engine.samples.junit4.ConfigurableRunner.ChildCount; /** * @since 4.12 */ @RunWith(ExceptionThrowingRunner.class) @ChildCount(0) public class JUnit4TestCaseWithExceptionThrowingRunner { } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import static org.junit.Assert.fail; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(RunnerThatOnlyReportsFailures.class) public class JUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished { @Test public void testWithMissingEvents() { fail("boom"); } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithIndistinguishableOverloadedMethod.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import static org.junit.Assert.fail; import org.junit.experimental.theories.DataPoint; import org.junit.experimental.theories.Theories; import org.junit.experimental.theories.Theory; import org.junit.runner.RunWith; /** * @since 4.12 */ @RunWith(Theories.class) public class JUnit4TestCaseWithIndistinguishableOverloadedMethod { @DataPoint public static int MAGIC_NUMBER = 42; @Theory public void theory(int i) { fail("failing theory with single parameter"); } @Theory public void theory(int i, int j) { fail("failing theory with two parameters"); } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithNotFilterableRunner.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; import org.junit.vintage.engine.samples.junit4.ConfigurableRunner.ChildCount; /** * @since 5.1 */ @RunWith(NotFilterableRunner.class) @ChildCount(2) @Category(Categories.Successful.class) public class JUnit4TestCaseWithNotFilterableRunner { @Test public void someTest() { } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; /** * @since 4.12 */ @Label("(TestClass)") @RunWith(RunnerWithCustomUniqueIdsAndDisplayNames.class) public class JUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames { @Test @Label("(TestMethod)") public void test() { Assert.fail(); } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithRunnerWithDuplicateChangingChildDescriptions.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import org.junit.runner.Description; import org.junit.runner.RunWith; import org.junit.runner.notification.RunNotifier; @RunWith(JUnit4TestCaseWithRunnerWithDuplicateChangingChildDescriptions.Runner.class) public class JUnit4TestCaseWithRunnerWithDuplicateChangingChildDescriptions { public static class Runner extends org.junit.runner.Runner { private final Class testClass; public Runner(Class testClass) { this.testClass = testClass; } @Override public Description getDescription() { var suiteDescription = Description.createSuiteDescription(testClass); suiteDescription.addChild(getContainerDescription("1st")); suiteDescription.addChild(getContainerDescription("2nd")); return suiteDescription; } private Description getContainerDescription(String name) { var parent = Description.createSuiteDescription(name); parent.addChild(getLeafDescription()); parent.addChild(getLeafDescription()); return parent; } private Description getLeafDescription() { return Description.createTestDescription(testClass, "leaf"); } @Override public void run(RunNotifier notifier) { for (var i = 0; i < 2; i++) { notifier.fireTestIgnored(getLeafDescription()); notifier.fireTestStarted(getLeafDescription()); notifier.fireTestFinished(getLeafDescription()); } } } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/Label.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Retention; @Retention(RUNTIME) @interface Label { String value(); } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/MalformedJUnit4TestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import static org.junit.Assert.fail; import org.junit.Test; /** * @since 4.12 */ public class MalformedJUnit4TestCase { @Test @SuppressWarnings("TestMethodWithIncorrectSignature") // intentionally not public void nonPublicTest() { fail("this should never be called"); } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/NotFilterableRunner.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; /** * @since 5.1 */ public class NotFilterableRunner extends ConfigurableRunner { public NotFilterableRunner(Class testClass) { super(testClass); } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import static org.junit.Assert.assertEquals; import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; /** * @since 4.12 */ @RunWith(Parameterized.class) public class ParameterizedTestCase { @Parameters(name = "{0}") public static Iterable parameters() { return List.of("foo", "bar"); } @Parameter public String value; @Test public void test() { assertEquals("foo", value); } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedTimingTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import static org.junit.Assert.assertEquals; import java.time.Instant; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.AfterParam; import org.junit.runners.Parameterized.BeforeParam; import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; /** * @since 5.9 */ @RunWith(Parameterized.class) public class ParameterizedTimingTestCase { public static Map EVENTS = new LinkedHashMap<>(); @BeforeClass public static void beforeClass() throws Exception { EVENTS.clear(); } @BeforeParam public static void beforeParam(String param) throws Exception { EVENTS.put("beforeParam(" + param + ")", Instant.now()); Thread.sleep(100); } @AfterParam public static void afterParam(String param) throws Exception { Thread.sleep(100); System.out.println("ParameterizedTimingTestCase.afterParam"); EVENTS.put("afterParam(" + param + ")", Instant.now()); } @Parameters(name = "{0}") public static Iterable parameters() { return List.of("foo", "bar"); } @Parameter public String value; @Test public void test() { assertEquals("foo", value); } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedWithAfterParamFailureTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.AfterParam; import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; /** * @since 5.9 */ @RunWith(Parameterized.class) public class ParameterizedWithAfterParamFailureTestCase { @AfterParam public static void afterParam() { fail(); } @Parameters(name = "{0}") public static Iterable parameters() { return List.of("foo", "bar"); } @Parameter public String value; @Test public void test() { assertEquals("foo", value); } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedWithBeforeParamFailureTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.BeforeParam; import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; /** * @since 5.9 */ @RunWith(Parameterized.class) public class ParameterizedWithBeforeParamFailureTestCase { @BeforeParam public static void beforeParam() { fail(); } @Parameters(name = "{0}") public static Iterable parameters() { return List.of("foo", "bar"); } @Parameter public String value; @Test public void test() { assertEquals("foo", value); } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithFiveTestMethods.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.junit.Assume.assumeFalse; import static org.junit.runners.MethodSorters.NAME_ASCENDING; import org.junit.FixMethodOrder; import org.junit.Ignore; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.vintage.engine.samples.junit4.Categories.Failing; import org.junit.vintage.engine.samples.junit4.Categories.Plain; import org.junit.vintage.engine.samples.junit4.Categories.Skipped; import org.junit.vintage.engine.samples.junit4.Categories.SkippedWithReason; /** * @since 4.12 */ @FixMethodOrder(NAME_ASCENDING) @Category(Plain.class) public class PlainJUnit4TestCaseWithFiveTestMethods { @Test public void abortedTest() { assumeFalse("this test should be aborted", true); } @Test @Category(Failing.class) public void failingTest() { fail("this test should fail"); } @Test @Ignore @Category(Skipped.class) public void ignoredTest1_withoutReason() { fail("this should never be called"); } @Test @Ignore("a custom reason") @Category(SkippedWithReason.class) public void ignoredTest2_withReason() { fail("this should never be called"); } @Test public void successfulTest() { assertEquals(3, 1 + 2); } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithLifecycleMethods.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import static org.junit.Assert.fail; import java.util.ArrayList; import java.util.List; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.FixMethodOrder; import org.junit.Ignore; import org.junit.Test; import org.junit.runners.MethodSorters; @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class PlainJUnit4TestCaseWithLifecycleMethods { public static final List EVENTS = new ArrayList<>(); @BeforeClass public static void beforeClass() { EVENTS.add("beforeClass"); } @Before public void before() { EVENTS.add("before"); } @Test public void failingTest() { EVENTS.add("failingTest"); fail(); } @Test @Ignore("skipped") public void skippedTest() { EVENTS.add("this should never ever be executed because the test is skipped"); } @Test public void succeedingTest() { EVENTS.add("succeedingTest"); } @After public void after() { EVENTS.add("after"); } @AfterClass public static void afterClass() { EVENTS.add("afterClass"); } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleInheritedTestWhichFails.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; /** * @since 4.12 */ public class PlainJUnit4TestCaseWithSingleInheritedTestWhichFails extends PlainJUnit4TestCaseWithSingleTestWhichFails { } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleTestWhichFails.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import static org.junit.Assert.fail; import org.junit.Test; /** * @since 4.12 */ public class PlainJUnit4TestCaseWithSingleTestWhichFails { @Test public void failingTest() { fail("this test should fail"); } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleTestWhichIsIgnored.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; /** * @since 4.12 */ public class PlainJUnit4TestCaseWithSingleTestWhichIsIgnored { @Test @Ignore("ignored test") public void ignoredTest() { Assert.fail("this should not be called"); } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithTwoTestMethods.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.junit.runners.MethodSorters.NAME_ASCENDING; import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.experimental.categories.Category; /** * @since 4.12 */ @FixMethodOrder(NAME_ASCENDING) public class PlainJUnit4TestCaseWithTwoTestMethods { @Test public void failingTest() { fail("this test should fail"); } @Test @Category(Categories.Successful.class) public void successfulTest() { assertEquals(3, 1 + 2); } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/RunnerThatOnlyReportsFailures.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunNotifier; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; public class RunnerThatOnlyReportsFailures extends BlockJUnit4ClassRunner { public RunnerThatOnlyReportsFailures(Class klass) throws InitializationError { super(klass); } @Override protected void runChild(FrameworkMethod method, RunNotifier notifier) { var statement = methodBlock(method); try { statement.evaluate(); } catch (Throwable e) { notifier.fireTestFailure(new Failure(describeChild(method), e)); } } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/RunnerWithCustomUniqueIdsAndDisplayNames.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import static org.junit.runner.Description.createTestDescription; import java.io.Serial; import java.io.Serializable; import java.util.Objects; import java.util.function.Supplier; import org.junit.runner.Description; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.model.Annotatable; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; /** * @since 4.12 */ public class RunnerWithCustomUniqueIdsAndDisplayNames extends BlockJUnit4ClassRunner { public RunnerWithCustomUniqueIdsAndDisplayNames(Class klass) throws InitializationError { super(klass); } @Override protected String getName() { return getLabel(getTestClass(), super::getName); } @Override protected Description describeChild(FrameworkMethod method) { var testName = testName(method); return createTestDescription(getTestClass().getJavaClass().getName(), testName, new CustomUniqueId(testName)); } @Override protected String testName(FrameworkMethod method) { return getLabel(method, () -> super.testName(method)); } private String getLabel(Annotatable element, Supplier fallback) { var label = element.getAnnotation(Label.class); return label == null ? fallback.get() : label.value(); } private record CustomUniqueId(String testName) implements Serializable { @Serial private static final long serialVersionUID = 1L; @Override public boolean equals(Object obj) { if (obj instanceof CustomUniqueId(String name)) { return Objects.equals(this.testName, name); } return false; } } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/SingleFailingTheoryTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import org.junit.Assert; import org.junit.experimental.theories.Theories; import org.junit.experimental.theories.Theory; import org.junit.runner.RunWith; /** * @since 4.12 */ @RunWith(Theories.class) public class SingleFailingTheoryTestCase { @Theory public void theory() { Assert.fail("this theory should fail"); } } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/TestCaseRunWithJUnitPlatformRunner.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.vintage.engine.samples.junit4; import org.junit.platform.runner.JUnitPlatform; import org.junit.platform.suite.api.SelectClasses; import org.junit.runner.RunWith; /** * @since 4.12 */ @RunWith(JUnitPlatform.class) @SelectClasses(PlainJUnit4TestCaseWithSingleTestWhichFails.class) public class TestCaseRunWithJUnitPlatformRunner { } ================================================ FILE: junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/package-info.java ================================================ @NullUnmarked package org.junit.vintage.engine.samples.junit4; import org.jspecify.annotations.NullUnmarked; ================================================ FILE: jupiter-tests/jupiter-tests.gradle.kts ================================================ import org.gradle.api.tasks.PathSensitivity.RELATIVE import org.gradle.plugins.ide.eclipse.model.Classpath import org.gradle.plugins.ide.eclipse.model.SourceFolder plugins { id("junitbuild.code-generator") id("junitbuild.kotlin-library-conventions") id("junitbuild.junit4-compatibility") id("junitbuild.testing-conventions") groovy } dependencies { testImplementation(projects.junitJupiter) testImplementation(projects.junitJupiterMigrationsupport) testImplementation(projects.junitPlatformLauncher) testImplementation(projects.junitPlatformSuiteEngine) testImplementation(projects.junitPlatformTestkit) testImplementation(testFixtures(projects.junitPlatformCommons)) testImplementation(kotlin("stdlib")) testImplementation(libs.jimfs) testImplementation(libs.junit4) testImplementation(libs.kotlinx.coroutines.core) testImplementation(libs.groovy) testImplementation(libs.memoryfilesystem) testImplementation(testFixtures(projects.junitJupiterApi)) testImplementation(testFixtures(projects.junitJupiterEngine)) testImplementation(testFixtures(projects.junitPlatformLauncher)) testImplementation(testFixtures(projects.junitPlatformReporting)) testRuntimeOnly(kotlin("reflect")) } tasks { test { inputs.dir("src/test/resources").withPathSensitivity(RELATIVE) systemProperty("developmentVersion", version) } test_4_12 { filter { includeTestsMatching("org.junit.jupiter.migrationsupport.*") } } } eclipse { classpath.file.whenMerged { this as Classpath entries.filterIsInstance().forEach { if (it.path == "src/test/java") { // Exclude test classes that depend on compiled Kotlin code. it.excludes.add("**/AtypicalJvmMethodNameTests.java") it.excludes.add("**/TestInstanceLifecycleKotlinTests.java") } } } project { // Remove Groovy Nature, since we don't require a Groovy plugin for Eclipse // in order for developers to work with the code base. natures.removeAll { it == "org.eclipse.jdt.groovy.core.groovyNature" } } } ================================================ FILE: jupiter-tests/src/templates/resources/test/org/junit/jupiter/api/condition/DisabledOnJreConditionTests.java.jte ================================================ @import java.util.List @import junitbuild.generator.model.JRE @param List supportedJres @param List supportedJresSortedByStringValue @param String licenseHeader ${licenseHeader} package org.junit.jupiter.api.condition; @for(var jre : supportedJresSortedByStringValue)<%-- --%>import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava${jre.getVersion()}; @endfor<%-- --%>import static org.junit.jupiter.api.condition.JavaVersionPredicates.onKnownVersion; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExecutionCondition; /** * Unit tests for {@link DisabledOnJreCondition}, generated from * {@code DisabledOnJreConditionTests.java.jte}. * *

Note that test method names MUST match the test method names in * {@link DisabledOnJreIntegrationTests}. * * @since 5.1 */ class DisabledOnJreConditionTests extends AbstractExecutionConditionTests { private static final String JAVA_VERSION = System.getProperty("java.version"); @Override protected ExecutionCondition getExecutionCondition() { return new DisabledOnJreCondition(); } @Override protected Class getTestClass() { return DisabledOnJreIntegrationTests.class; } /** * @see DisabledOnJreIntegrationTests#enabledBecauseAnnotationIsNotPresent() */ @Test void enabledBecauseAnnotationIsNotPresent() { evaluateCondition(); assertEnabled(); assertReasonContains("@DisabledOnJre is not present"); } /** * @see DisabledOnJreIntegrationTests#missingVersionDeclaration() */ @Test void missingVersionDeclaration() { assertPreconditionViolationFor(this::evaluateCondition)// .withMessage("You must declare at least one JRE or version in @DisabledOnJre"); } /** * @see DisabledOnJreIntegrationTests#jreUndefined() */ @Test void jreUndefined() { assertPreconditionViolationFor(this::evaluateCondition)// .withMessage("JRE.UNDEFINED is not supported in @DisabledOnJre"); } /** * @see DisabledOnJreIntegrationTests#version7() */ @Test void version7() { assertPreconditionViolationFor(this::evaluateCondition)// .withMessage("Version [7] in @DisabledOnJre must be greater than or equal to 8"); } /** * @see DisabledOnJreIntegrationTests#disabledOnAllJavaVersions() */ @Test void disabledOnAllJavaVersions() { evaluateCondition(); assertDisabledOnCurrentJreIf(true); assertCustomDisabledReasonIs("Disabled on every JRE"); } @for(var jre : supportedJres) /** * @see DisabledOnJreIntegrationTests#jre${jre.getVersion()}() */ @Test void jre${jre.getVersion()}() { evaluateCondition(); assertDisabledOnCurrentJreIf(onJava${jre.getVersion()}()); } @endfor<%-- --%>@for(var jre : supportedJres) /** * @see DisabledOnJreIntegrationTests#version${jre.getVersion()}() */ @Test void version${jre.getVersion()}() { evaluateCondition(); assertDisabledOnCurrentJreIf(onJava${jre.getVersion()}()); } @endfor /** * @see DisabledOnJreIntegrationTests#other() */ @Test void other() { evaluateCondition(); assertDisabledOnCurrentJreIf(!onKnownVersion()); } private void assertDisabledOnCurrentJreIf(boolean condition) { if (condition) { assertDisabled(); assertReasonContains("Disabled on JRE version: " + JAVA_VERSION); } else { assertEnabled(); assertReasonContains("Enabled on JRE version: " + JAVA_VERSION); } } } ================================================ FILE: jupiter-tests/src/templates/resources/test/org/junit/jupiter/api/condition/DisabledOnJreIntegrationTests.java.jte ================================================ @import java.util.List @import junitbuild.generator.model.JRE @param List allJres @param List supportedJres @param List supportedJresSortedByStringValue @param String licenseHeader ${licenseHeader} package org.junit.jupiter.api.condition; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @for(var jre : supportedJresSortedByStringValue)<%-- --%>import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava${jre.getVersion()}; @endfor<%-- --%>import static org.junit.jupiter.api.condition.JavaVersionPredicates.onKnownVersion; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; /** * Integration tests for {@link DisabledOnJre @DisabledOnJre}, generated from * {@code DisabledOnJreIntegrationTests.java.jte}. * * @since 5.1 */ class DisabledOnJreIntegrationTests { @Test @Disabled("Only used in a unit test via reflection") void enabledBecauseAnnotationIsNotPresent() { } @Test @Disabled("Only used in a unit test via reflection") @DisabledOnJre void missingVersionDeclaration() { } @Test @Disabled("Only used in a unit test via reflection") @DisabledOnJre(JRE.UNDEFINED) void jreUndefined() { } @Test @Disabled("Only used in a unit test via reflection") @DisabledOnJre(value = JRE.JAVA_17, versions = { 21, 7 }) void version7() { } @SuppressWarnings({ "removal", "deprecation" }) @Test @DisabledOnJre(disabledReason = "Disabled on every JRE", value = { // @for(var jre : allJres)<%-- --%> JRE.JAVA_${jre.getVersion()}, // @endfor<%-- --%> JRE.OTHER // }) void disabledOnAllJavaVersions() { fail("should be disabled"); } @for(var jre : supportedJres) @Test @DisabledOnJre(JRE.JAVA_${jre.getVersion()}) void jre${jre.getVersion()}() { assertFalse(onJava${jre.getVersion()}()); } @endfor<%-- --%>@for(var jre : supportedJres) @Test @DisabledOnJre(versions = ${jre.getVersion()}) void version${jre.getVersion()}() { assertFalse(onJava${jre.getVersion()}()); } @endfor @Test @SuppressWarnings("deprecation") @DisabledOnJre(JRE.OTHER) void other() { assertTrue(onKnownVersion()); } } ================================================ FILE: jupiter-tests/src/templates/resources/test/org/junit/jupiter/api/condition/EnabledOnJreConditionTests.java.jte ================================================ @import java.util.List @import junitbuild.generator.model.JRE @param List supportedJres @param List supportedJresSortedByStringValue @param String licenseHeader ${licenseHeader} package org.junit.jupiter.api.condition; @for(var jre : supportedJresSortedByStringValue)<%-- --%>import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava${jre.getVersion()}; @endfor<%-- --%>import static org.junit.jupiter.api.condition.JavaVersionPredicates.onKnownVersion; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExecutionCondition; /** * Unit tests for {@link EnabledOnJreCondition}, generated from * {@code EnabledOnJreConditionTests.java.jte}. * *

Note that test method names MUST match the test method names in * {@link EnabledOnJreIntegrationTests}. * * @since 5.1 */ class EnabledOnJreConditionTests extends AbstractExecutionConditionTests { private static final String JAVA_VERSION = System.getProperty("java.version"); @Override protected ExecutionCondition getExecutionCondition() { return new EnabledOnJreCondition(); } @Override protected Class getTestClass() { return EnabledOnJreIntegrationTests.class; } /** * @see EnabledOnJreIntegrationTests#enabledBecauseAnnotationIsNotPresent() */ @Test void enabledBecauseAnnotationIsNotPresent() { evaluateCondition(); assertEnabled(); assertReasonContains("@EnabledOnJre is not present"); } /** * @see EnabledOnJreIntegrationTests#missingVersionDeclaration() */ @Test void missingVersionDeclaration() { assertPreconditionViolationFor(this::evaluateCondition)// .withMessage("You must declare at least one JRE or version in @EnabledOnJre"); } /** * @see EnabledOnJreIntegrationTests#jreUndefined() */ @Test void jreUndefined() { assertPreconditionViolationFor(this::evaluateCondition)// .withMessage("JRE.UNDEFINED is not supported in @EnabledOnJre"); } /** * @see EnabledOnJreIntegrationTests#version7() */ @Test void version7() { assertPreconditionViolationFor(this::evaluateCondition)// .withMessage("Version [7] in @EnabledOnJre must be greater than or equal to 8"); } /** * @see EnabledOnJreIntegrationTests#enabledOnAllJavaVersions() */ @Test void enabledOnAllJavaVersions() { evaluateCondition(); assertEnabledOnCurrentJreIf(true); } @for(var jre : supportedJres) /** * @see EnabledOnJreIntegrationTests#jre${jre.getVersion()}() */ @Test void jre${jre.getVersion()}() { evaluateCondition(); assertEnabledOnCurrentJreIf(onJava${jre.getVersion()}()); } @endfor<%-- --%>@for(var jre : supportedJres) /** * @see EnabledOnJreIntegrationTests#version${jre.getVersion()}() */ @Test void version${jre.getVersion()}() { evaluateCondition(); assertEnabledOnCurrentJreIf(onJava${jre.getVersion()}()); } @endfor /** * @see EnabledOnJreIntegrationTests#other() */ @Test void other() { evaluateCondition(); assertEnabledOnCurrentJreIf(!onKnownVersion()); assertCustomDisabledReasonIs("Disabled on almost every JRE"); } private void assertEnabledOnCurrentJreIf(boolean condition) { if (condition) { assertEnabled(); assertReasonContains("Enabled on JRE version: " + JAVA_VERSION); } else { assertDisabled(); assertReasonContains("Disabled on JRE version: " + JAVA_VERSION); } } } ================================================ FILE: jupiter-tests/src/templates/resources/test/org/junit/jupiter/api/condition/EnabledOnJreIntegrationTests.java.jte ================================================ @import java.util.List @import junitbuild.generator.model.JRE @param List allJres @param List supportedJres @param List supportedJresSortedByStringValue @param String licenseHeader ${licenseHeader} package org.junit.jupiter.api.condition; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @for(var jre : supportedJresSortedByStringValue)<%-- --%>import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava${jre.getVersion()}; @endfor<%-- --%>import static org.junit.jupiter.api.condition.JavaVersionPredicates.onKnownVersion; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; /** * Integration tests for {@link EnabledOnJre @EnabledOnJre}, generated from * {@code EnabledOnJreIntegrationTests.java.jte}. * * @since 5.1 */ class EnabledOnJreIntegrationTests { @Test @Disabled("Only used in a unit test via reflection") void enabledBecauseAnnotationIsNotPresent() { } @Test @Disabled("Only used in a unit test via reflection") @EnabledOnJre void missingVersionDeclaration() { } @Test @Disabled("Only used in a unit test via reflection") @EnabledOnJre(JRE.UNDEFINED) void jreUndefined() { } @Test @Disabled("Only used in a unit test via reflection") @EnabledOnJre(value = JRE.JAVA_17, versions = { 21, 7 }) void version7() { } @SuppressWarnings({ "removal", "deprecation" }) @Test @EnabledOnJre({ // @for(var jre : allJres)<%-- --%> JRE.JAVA_${jre.getVersion()}, // @endfor<%-- --%> JRE.OTHER // }) void enabledOnAllJavaVersions() { } @for(var jre : supportedJres) @Test @EnabledOnJre(JRE.JAVA_${jre.getVersion()}) void jre${jre.getVersion()}() { assertTrue(onJava${jre.getVersion()}()); } @endfor<%-- --%>@for(var jre : supportedJres) @Test @EnabledOnJre(versions = ${jre.getVersion()}) void version${jre.getVersion()}() { assertTrue(onJava${jre.getVersion()}()); } @endfor @Test @SuppressWarnings("deprecation") @EnabledOnJre(value = JRE.OTHER, disabledReason = "Disabled on almost every JRE") void other() { assertFalse(onKnownVersion()); } } ================================================ FILE: jupiter-tests/src/test/groovy/org/junit/jupiter/api/GroovyAssertEqualsTests.groovy ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api import java.util.function.Supplier import static org.junit.jupiter.api.Assertions.assertEquals import static org.junit.jupiter.api.PrimitiveAndWrapperTypeHelpers.* class GroovyAssertEqualsTests { Supplier supplier = { '' } @Test void "null references can be passed to assertEquals"() { Object null1 = null Object null2 = null assertEquals(null1, null) assertEquals(null, null2) assertEquals(null1, null2) } @Test void "integers can be passed to assertEquals"() { assertEquals(i(42), i(42)) assertEquals(i(42), I(42)) assertEquals(I(42), i(42)) assertEquals(I(42), I(42)) assertEquals(i(42), i(42), '') assertEquals(i(42), I(42), '') assertEquals(I(42), i(42), '') assertEquals(I(42), I(42), '') assertEquals(i(42), i(42), supplier) assertEquals(i(42), I(42), supplier) assertEquals(I(42), i(42), supplier) assertEquals(I(42), I(42), supplier) } @Test void "floats can be passed to assertEquals"() { assertEquals(f(42), f(42)) assertEquals(f(42), F(42)) assertEquals(F(42), f(42)) assertEquals(F(42), F(42)) assertEquals(f(42), f(42), '') assertEquals(f(42), F(42), '') assertEquals(F(42), f(42), '') assertEquals(F(42), F(42), '') assertEquals(f(42), f(42), supplier) assertEquals(f(42), F(42), supplier) assertEquals(F(42), f(42), supplier) assertEquals(F(42), F(42), supplier) } @Test void "floats can be passed to assertEquals with delta"() { assertEquals(f(42), f(42), 0.01f) assertEquals(f(42), F(42), 0.01f) assertEquals(F(42), f(42), 0.01f) assertEquals(F(42), F(42), 0.01f) assertEquals(f(42), f(42), 0.01f, '') assertEquals(f(42), F(42), 0.01f, '') assertEquals(F(42), f(42), 0.01f, '') assertEquals(F(42), F(42), 0.01f, '') assertEquals(f(42), f(42), 0.01f, supplier) assertEquals(f(42), F(42), 0.01f, supplier) assertEquals(F(42), f(42), 0.01f, supplier) assertEquals(F(42), F(42), 0.01f, supplier) } @Test void "bytes can be passed to assertEquals"() { assertEquals(b(42), b(42)) assertEquals(b(42), B(42)) assertEquals(B(42), b(42)) assertEquals(B(42), B(42)) assertEquals(b(42), b(42), '') assertEquals(b(42), B(42), '') assertEquals(B(42), b(42), '') assertEquals(B(42), B(42), '') assertEquals(b(42), b(42), supplier) assertEquals(b(42), B(42), supplier) assertEquals(B(42), b(42), supplier) assertEquals(B(42), B(42), supplier) } @Test void "doubles can be passed to assertEquals"() { assertEquals(d(42), d(42)) assertEquals(d(42), D(42)) assertEquals(D(42), d(42)) assertEquals(D(42), D(42)) assertEquals(d(42), d(42), '') assertEquals(d(42), D(42), '') assertEquals(D(42), d(42), '') assertEquals(D(42), D(42), '') assertEquals(d(42), d(42), supplier) assertEquals(d(42), D(42), supplier) assertEquals(D(42), d(42), supplier) assertEquals(D(42), D(42), supplier) } @Test void "doubles can be passed to assertEquals with delta"() { assertEquals(d(42), d(42), 0.01d) assertEquals(d(42), D(42), 0.01d) assertEquals(D(42), d(42), 0.01d) assertEquals(D(42), D(42), 0.01d) assertEquals(d(42), d(42), 0.01d, '') assertEquals(d(42), D(42), 0.01d, '') assertEquals(D(42), d(42), 0.01d, '') assertEquals(D(42), D(42), 0.01d, '') assertEquals(d(42), d(42), 0.01d, supplier) assertEquals(d(42), D(42), 0.01d, supplier) assertEquals(D(42), d(42), 0.01d, supplier) assertEquals(D(42), D(42), 0.01d, supplier) } @Test void "chars can be passed to assertEquals"() { assertEquals(c(42), c(42)) assertEquals(c(42), C(42)) assertEquals(C(42), c(42)) assertEquals(C(42), C(42)) assertEquals(c(42), c(42), '') assertEquals(c(42), C(42), '') assertEquals(C(42), c(42), '') assertEquals(C(42), C(42), '') assertEquals(c(42), c(42), supplier) assertEquals(c(42), C(42), supplier) assertEquals(C(42), c(42), supplier) assertEquals(C(42), C(42), supplier) } @Test void "longs can be passed to assertEquals"() { assertEquals(l(42), l(42)) assertEquals(l(42), L(42)) assertEquals(L(42), l(42)) assertEquals(L(42), L(42)) assertEquals(l(42), l(42), '') assertEquals(l(42), L(42), '') assertEquals(L(42), l(42), '') assertEquals(L(42), L(42), '') assertEquals(l(42), l(42), supplier) assertEquals(l(42), L(42), supplier) assertEquals(L(42), l(42), supplier) assertEquals(L(42), L(42), supplier) } @Test void "shorts can be passed to assertEquals"() { assertEquals(s(42), s(42)) assertEquals(s(42), S(42)) assertEquals(S(42), s(42)) assertEquals(S(42), S(42)) assertEquals(s(42), s(42), '') assertEquals(s(42), S(42), '') assertEquals(S(42), s(42), '') assertEquals(S(42), S(42), '') assertEquals(s(42), s(42), supplier) assertEquals(s(42), S(42), supplier) assertEquals(S(42), s(42), supplier) assertEquals(S(42), S(42), supplier) } } ================================================ FILE: jupiter-tests/src/test/groovy/org/junit/jupiter/api/GroovyAssertNotEqualsTests.groovy ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api import org.opentest4j.AssertionFailedError import java.util.function.Supplier import static org.junit.jupiter.api.Assertions.assertNotEquals import static org.junit.jupiter.api.Assertions.assertThrows import static org.junit.jupiter.api.PrimitiveAndWrapperTypeHelpers.* class GroovyAssertNotEqualsTests { Supplier supplier = { '' } @Test void "null references can be passed to assertNotEquals"() { Object null1 = null Object null2 = null assertThrows(AssertionFailedError, { assertNotEquals(null1, null) } ) assertThrows(AssertionFailedError, { assertNotEquals(null, null2) } ) assertThrows(AssertionFailedError, { assertNotEquals(null1, null2) } ) } @Test void "integers can be passed to assertNotEquals"() { assertNotEquals(i(42), i(2)) assertNotEquals(i(42), I(2)) assertNotEquals(I(42), i(2)) assertNotEquals(I(42), I(2)) assertNotEquals(i(42), i(2), '') assertNotEquals(i(42), I(2), '') assertNotEquals(I(42), i(2), '') assertNotEquals(I(42), I(2), '') assertNotEquals(i(42), i(2), supplier) assertNotEquals(i(42), I(2), supplier) assertNotEquals(I(42), i(2), supplier) assertNotEquals(I(42), I(2), supplier) } @Test void "floats can be passed to assertNotEquals"() { assertNotEquals(f(42), f(2)) assertNotEquals(f(42), F(2)) assertNotEquals(F(42), f(2)) assertNotEquals(F(42), F(2)) assertNotEquals(f(42), f(2), '') assertNotEquals(f(42), F(2), '') assertNotEquals(F(42), f(2), '') assertNotEquals(F(42), F(2), '') assertNotEquals(f(42), f(2), supplier) assertNotEquals(f(42), F(2), supplier) assertNotEquals(F(42), f(2), supplier) assertNotEquals(F(42), F(2), supplier) } @Test void "floats can be passed to assertNotEquals with delta"() { assertNotEquals(f(42), f(2), 0.01f) assertNotEquals(f(42), F(2), 0.01f) assertNotEquals(F(42), f(2), 0.01f) assertNotEquals(F(42), F(2), 0.01f) assertNotEquals(f(42), f(2), 0.01f, '') assertNotEquals(f(42), F(2), 0.01f, '') assertNotEquals(F(42), f(2), 0.01f, '') assertNotEquals(F(42), F(2), 0.01f, '') assertNotEquals(f(42), f(2), 0.01f, supplier) assertNotEquals(f(42), F(2), 0.01f, supplier) assertNotEquals(F(42), f(2), 0.01f, supplier) assertNotEquals(F(42), F(2), 0.01f, supplier) } @Test void "bytes can be passed to assertNotEquals"() { assertNotEquals(b(42), b(2)) assertNotEquals(b(42), B(2)) assertNotEquals(B(42), b(2)) assertNotEquals(B(42), B(2)) assertNotEquals(b(42), b(2), '') assertNotEquals(b(42), B(2), '') assertNotEquals(B(42), b(2), '') assertNotEquals(B(42), B(2), '') assertNotEquals(b(42), b(2), supplier) assertNotEquals(b(42), B(2), supplier) assertNotEquals(B(42), b(2), supplier) assertNotEquals(B(42), B(2), supplier) } @Test void "doubles can be passed to assertNotEquals"() { assertNotEquals(d(42), d(2)) assertNotEquals(d(42), D(2)) assertNotEquals(D(42), d(2)) assertNotEquals(D(42), D(2)) assertNotEquals(d(42), d(2), '') assertNotEquals(d(42), D(2), '') assertNotEquals(D(42), d(2), '') assertNotEquals(D(42), D(2), '') assertNotEquals(d(42), d(2), supplier) assertNotEquals(d(42), D(2), supplier) assertNotEquals(D(42), d(2), supplier) assertNotEquals(D(42), D(2), supplier) } @Test void "doubles can be passed to assertNotEquals with delta"() { assertNotEquals(d(42), d(2), 0.01d) assertNotEquals(d(42), D(2), 0.01d) assertNotEquals(D(42), d(2), 0.01d) assertNotEquals(D(42), D(2), 0.01d) assertNotEquals(d(42), d(2), 0.01d, '') assertNotEquals(d(42), D(2), 0.01d, '') assertNotEquals(D(42), d(2), 0.01d, '') assertNotEquals(D(42), D(2), 0.01d, '') assertNotEquals(d(42), d(2), 0.01d, supplier) assertNotEquals(d(42), D(2), 0.01d, supplier) assertNotEquals(D(42), d(2), 0.01d, supplier) assertNotEquals(D(42), D(2), 0.01d, supplier) } @Test void "chars can be passed to assertNotEquals"() { assertNotEquals(c(42), c(2)) assertNotEquals(c(42), C(2)) assertNotEquals(C(42), c(2)) assertNotEquals(C(42), C(2)) assertNotEquals(c(42), c(2), '') assertNotEquals(c(42), C(2), '') assertNotEquals(C(42), c(2), '') assertNotEquals(C(42), C(2), '') assertNotEquals(c(42), c(2), supplier) assertNotEquals(c(42), C(2), supplier) assertNotEquals(C(42), c(2), supplier) assertNotEquals(C(42), C(2), supplier) } @Test void "longs can be passed to assertNotEquals"() { assertNotEquals(l(42), l(2)) assertNotEquals(l(42), L(2)) assertNotEquals(L(42), l(2)) assertNotEquals(L(42), L(2)) assertNotEquals(l(42), l(2), '') assertNotEquals(l(42), L(2), '') assertNotEquals(L(42), l(2), '') assertNotEquals(L(42), L(2), '') assertNotEquals(l(42), l(2), supplier) assertNotEquals(l(42), L(2), supplier) assertNotEquals(L(42), l(2), supplier) assertNotEquals(L(42), L(2), supplier) } @Test void "shorts can be passed to assertNotEquals"() { assertNotEquals(s(42), s(2)) assertNotEquals(s(42), S(2)) assertNotEquals(S(42), s(2)) assertNotEquals(S(42), S(2)) assertNotEquals(s(42), s(2), '') assertNotEquals(s(42), S(2), '') assertNotEquals(S(42), s(2), '') assertNotEquals(S(42), S(2), '') assertNotEquals(s(42), s(2), supplier) assertNotEquals(s(42), S(2), supplier) assertNotEquals(S(42), s(2), supplier) assertNotEquals(S(42), S(2), supplier) } } ================================================ FILE: jupiter-tests/src/test/groovy/org/junit/jupiter/api/PrimitiveAndWrapperTypeHelpers.groovy ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api class PrimitiveAndWrapperTypeHelpers { static char c(int number) { return (char) number } static Character C(int number) { return Character.valueOf((char) number) } static byte b(int number) { return (byte) number } static Byte B(int number) { return Byte.valueOf((byte) number) } static double d(int number) { return (double) number } static Double D(int number) { return Double.valueOf((double) number) } static float f(int number) { return (float) number } static Float F(int number) { return Float.valueOf((float) number) } static long l(int number) { return (long) number } static Long L(int number) { return Long.valueOf( (long) number) } static short s(int number) { return (short) number } static Short S(int number) { return Short.valueOf( (short) number) } static int i(int number) { return number } static Integer I(int number) { return Integer.valueOf( number) } } ================================================ FILE: jupiter-tests/src/test/java/DefaultPackageTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; /** * Simple test case that is used to verify proper support for classpath scanning * within the default package. * * @since 5.0 */ @Disabled("Only used reflectively by other tests") class DefaultPackageTestCase { @Test void test() { // do nothing } } ================================================ FILE: jupiter-tests/src/test/java/example/B_TestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package example; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; /** * @since 5.8 */ public class B_TestCase { public static List callSequence; @BeforeEach void trackInvocations(TestInfo testInfo) { if (callSequence != null) { callSequence.add(testInfo.getTestClass().get().getName()); } } @Test void a() { } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/JupiterTestSuite.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter; import org.junit.platform.suite.api.IncludeClassNamePatterns; import org.junit.platform.suite.api.IncludeEngines; import org.junit.platform.suite.api.SelectPackages; import org.junit.platform.suite.api.Suite; /** * Test suite for the JUnit Jupiter programming model, extension model, and * {@code TestEngine} implementation. * *

Logging Configuration

* *

In order for our log4j2 configuration to be used in an IDE, you must * set the following system property before running any tests — for * example, in Run Configurations in Eclipse. * *

 * -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager
 * 
* * @since 5.0 */ @Suite @SelectPackages("org.junit.jupiter") @IncludeClassNamePatterns(".*Tests?") @IncludeEngines("junit-jupiter") class JupiterTestSuite { } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/AssertAllAssertionsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; 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.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullFor; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullOrEmptyFor; import java.io.IOException; import java.util.Collection; import java.util.stream.Stream; import org.junit.jupiter.api.function.Executable; import org.opentest4j.AssertionFailedError; import org.opentest4j.MultipleFailuresError; /** * Unit tests for JUnit Jupiter {@link Assertions}. * * @since 5.0 */ class AssertAllAssertionsTests { @SuppressWarnings("DataFlowIssue") @Test void assertAllWithNullExecutableArray() { assertPreconditionViolationNotNullOrEmptyFor("executables array", () -> assertAll((Executable[]) null)); } @SuppressWarnings("DataFlowIssue") @Test void assertAllWithNullExecutableCollection() { assertPreconditionViolationNotNullFor("executables collection", () -> assertAll((Collection) null)); } @SuppressWarnings("DataFlowIssue") @Test void assertAllWithNullExecutableStream() { assertPreconditionViolationNotNullFor("executables stream", () -> assertAll((Stream) null)); } @SuppressWarnings("DataFlowIssue") @Test void assertAllWithNullInExecutableArray() { assertPreconditionViolationNotNullFor("individual executables", () -> assertAll((Executable) null)); } @Test void assertAllWithNullInExecutableCollection() { assertPreconditionViolationNotNullFor("individual executables", () -> assertAll(asList((Executable) null))); } @SuppressWarnings("DataFlowIssue") @Test void assertAllWithNullInExecutableStream() { assertPreconditionViolationNotNullFor("individual executables", () -> assertAll(Stream.of((Executable) null))); } @Test void assertAllWithExecutablesThatDoNotThrowExceptions() { // @formatter:off assertAll( () -> assertTrue(true), () -> assertFalse(false) ); assertAll("heading", () -> assertTrue(true), () -> assertFalse(false) ); assertAll(asList( () -> assertTrue(true), () -> assertFalse(false) )); assertAll("heading", asList( () -> assertTrue(true), () -> assertFalse(false) )); assertAll(Stream.of( () -> assertTrue(true), () -> assertFalse(false) )); assertAll("heading", Stream.of( () -> assertTrue(true), () -> assertFalse(false) )); // @formatter:on } @Test void assertAllWithExecutablesThatThrowAssertionErrors() { // @formatter:off MultipleFailuresError multipleFailuresError = assertThrows(MultipleFailuresError.class, () -> assertAll( Assertions::fail, Assertions::fail ) ); // @formatter:on assertExpectedExceptionTypes(multipleFailuresError, AssertionFailedError.class, AssertionFailedError.class); } @Test void assertAllWithCollectionOfExecutablesThatThrowAssertionErrors() { // @formatter:off MultipleFailuresError multipleFailuresError = assertThrows(MultipleFailuresError.class, () -> assertAll(asList( Assertions::fail, Assertions::fail )) ); // @formatter:on assertExpectedExceptionTypes(multipleFailuresError, AssertionFailedError.class, AssertionFailedError.class); } @Test void assertAllWithStreamOfExecutablesThatThrowAssertionErrors() { // @formatter:off MultipleFailuresError multipleFailuresError = assertThrows(MultipleFailuresError.class, () -> assertAll(Stream.of( Assertions::fail, Assertions::fail )) ); // @formatter:on assertExpectedExceptionTypes(multipleFailuresError, AssertionFailedError.class, AssertionFailedError.class); } @Test void assertAllWithExecutableThatThrowsThrowable() { MultipleFailuresError multipleFailuresError = assertThrows(MultipleFailuresError.class, () -> assertAll(() -> { throw new EnigmaThrowable(); })); assertExpectedExceptionTypes(multipleFailuresError, EnigmaThrowable.class); } @Test void assertAllWithExecutableThatThrowsCheckedException() { MultipleFailuresError multipleFailuresError = assertThrows(MultipleFailuresError.class, () -> assertAll(() -> { throw new IOException(); })); assertExpectedExceptionTypes(multipleFailuresError, IOException.class); } @Test void assertAllWithExecutableThatThrowsRuntimeException() { MultipleFailuresError multipleFailuresError = assertThrows(MultipleFailuresError.class, () -> assertAll(() -> { throw new IllegalStateException(); })); assertExpectedExceptionTypes(multipleFailuresError, IllegalStateException.class); } @Test void assertAllWithExecutableThatThrowsError() { MultipleFailuresError multipleFailuresError = assertThrows(MultipleFailuresError.class, () -> assertAll(AssertionTestUtils::recurseIndefinitely)); assertExpectedExceptionTypes(multipleFailuresError, StackOverflowError.class); } @Test void assertAllWithExecutableThatThrowsUnrecoverableException() { OutOfMemoryError outOfMemoryError = assertThrows(OutOfMemoryError.class, () -> assertAll(AssertionTestUtils::runOutOfMemory)); assertEquals("boom", outOfMemoryError.getMessage()); } @Test void assertAllWithParallelStream() { Executable executable = () -> { throw new RuntimeException(); }; MultipleFailuresError multipleFailuresError = assertThrows(MultipleFailuresError.class, () -> assertAll(Stream.generate(() -> executable).parallel().limit(100))); assertThat(multipleFailuresError.getFailures()).hasSize(100).doesNotContainNull(); } @SuppressWarnings("serial") private static class EnigmaThrowable extends Throwable { } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/AssertArrayEqualsAssertionsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEndsWith; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import org.opentest4j.AssertionFailedError; /** * Unit tests for JUnit Jupiter {@link Assertions}. * * @since 5.0 */ class AssertArrayEqualsAssertionsTests { @Test void assertArrayEqualsWithNulls() { assertArrayEquals(null, (boolean[]) null); assertArrayEquals(null, (char[]) null); assertArrayEquals(null, (byte[]) null); assertArrayEquals(null, (int[]) null); assertArrayEquals(null, (long[]) null); assertArrayEquals(null, (float[]) null); assertArrayEquals(null, (double[]) null); assertArrayEquals(null, (Object[]) null); } @Test void assertArrayEqualsBooleanArrays() { boolean[] array = {}; assertArrayEquals(array, array); assertArrayEquals(new boolean[] {}, new boolean[] {}); assertArrayEquals(new boolean[] {}, new boolean[] {}, "message"); assertArrayEquals(new boolean[] {}, new boolean[] {}, () -> "message"); assertArrayEquals(new boolean[] { true }, new boolean[] { true }); assertArrayEquals(new boolean[] { false, true, false, false }, new boolean[] { false, true, false, false }); } @Test void assertArrayEqualsBooleanArrayVsNull() { try { assertArrayEquals(null, new boolean[] { true, false }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected array was "); } try { assertArrayEquals(new boolean[] { true, false }, null); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "actual array was "); } } @Test void assertArrayEqualsBooleanArrayVsNullAndMessage() { try { assertArrayEquals(null, new boolean[] { true, false }, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected array was "); } try { assertArrayEquals(new boolean[] { true, false }, null, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "actual array was "); } } @Test void assertArrayEqualsBooleanArrayVsNullAndMessageSupplier() { try { assertArrayEquals(null, new boolean[] { true, false }, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected array was "); } try { assertArrayEquals(new boolean[] { true, false }, null, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "actual array was "); } } @Test void assertArrayEqualsBooleanArraysOfDifferentLength() { try { assertArrayEquals(new boolean[] { true, false }, new boolean[] { true, false, true }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "array lengths differ, expected: <2> but was: <3>"); } } @Test void assertArrayEqualsBooleanArraysOfDifferentLengthAndMessage() { try { assertArrayEquals(new boolean[] { true, false, false }, new boolean[] { true }, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array lengths differ, expected: <3> but was: <1>"); } } @Test void assertArrayEqualsBooleanArraysOfDifferentLengthAndMessageSupplier() { try { assertArrayEquals(new boolean[] { true }, new boolean[] { true, false }, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array lengths differ, expected: <1> but was: <2>"); } } @Test void assertArrayEqualsDifferentBooleanArrays() { try { assertArrayEquals(new boolean[] { true, false, false }, new boolean[] { true, false, true }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "array contents differ at index [2], expected: but was: "); } } @Test void assertArrayEqualsDifferentBooleanArraysAndMessage() { try { assertArrayEquals(new boolean[] { true, true }, new boolean[] { false, true }, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array contents differ at index [0], expected: but was: "); } } @Test void assertArrayEqualsDifferentBooleanArraysAndMessageSupplier() { try { assertArrayEquals(new boolean[] { false, false, false }, new boolean[] { false, true, true }, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array contents differ at index [1], expected: but was: "); } } @Test void assertArrayEqualsCharArrays() { char[] array = {}; assertArrayEquals(array, array); assertArrayEquals(new char[] {}, new char[] {}); assertArrayEquals(new char[] {}, new char[] {}, "message"); assertArrayEquals(new char[] {}, new char[] {}, () -> "message"); assertArrayEquals(new char[] { 'a' }, new char[] { 'a' }); assertArrayEquals(new char[] { 'j', 'u', 'n', 'i', 't' }, new char[] { 'j', 'u', 'n', 'i', 't' }); } @Test void assertArrayEqualsCharArrayVsNull() { try { assertArrayEquals(null, new char[] { 'a', 'z' }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected array was "); } try { assertArrayEquals(new char[] { 'a', 'z' }, null); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "actual array was "); } } @Test void assertArrayEqualsCharArrayVsNullAndMessage() { try { assertArrayEquals(null, new char[] { 'a', 'b', 'z' }, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected array was "); } try { assertArrayEquals(new char[] { 'a', 'b', 'z' }, null, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "actual array was "); } } @Test void assertArrayEqualsCharArrayVsNullAndMessageSupplier() { try { assertArrayEquals(null, new char[] { 'z', 'x', 'y' }, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected array was "); } try { assertArrayEquals(new char[] { 'z', 'x', 'y' }, null, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "actual array was "); } } @Test void assertArrayEqualsCharArraysOfDifferentLength() { try { assertArrayEquals(new char[] { 'q', 'w', 'e' }, new char[] { 'q', 'w' }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "array lengths differ, expected: <3> but was: <2>"); } } @Test void assertArrayEqualsCharArraysOfDifferentLengthAndMessage() { try { assertArrayEquals(new char[] { 'a', 's', 'd' }, new char[] { 'd' }, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array lengths differ, expected: <3> but was: <1>"); } } @Test void assertArrayEqualsCharArraysOfDifferentLengthAndMessageSupplier() { try { assertArrayEquals(new char[] { 'q' }, new char[] { 't', 'u' }, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array lengths differ, expected: <1> but was: <2>"); } } @Test void assertArrayEqualsDifferentCharArrays() { try { assertArrayEquals(new char[] { 'a', 'b', 'c' }, new char[] { 'a', 'b', 'a' }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "array contents differ at index [2], expected: but was: "); } } @Test void assertArrayEqualsDifferentCharArraysAndMessage() { try { assertArrayEquals(new char[] { 'z', 'x', 'c', 'v' }, new char[] { 'x', 'x', 'c', 'v' }, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array contents differ at index [0], expected: but was: "); } } @Test void assertArrayEqualsDifferentCharArraysAndMessageSupplier() { try { assertArrayEquals(new char[] { 'r', 't', 'y' }, new char[] { 'r', 'y', 'u' }, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array contents differ at index [1], expected: but was: "); } } @Test void assertArrayEqualsByteArrays() { byte[] array = {}; assertArrayEquals(array, array); assertArrayEquals(new byte[] {}, new byte[] {}); assertArrayEquals(new byte[] {}, new byte[] {}, "message"); assertArrayEquals(new byte[] {}, new byte[] {}, () -> "message"); assertArrayEquals(new byte[] { 42 }, new byte[] { 42 }); assertArrayEquals(new byte[] { 1, 2, 3, 42 }, new byte[] { 1, 2, 3, 42 }); } @Test void assertArrayEqualsByteArrayVsNull() { try { assertArrayEquals(null, new byte[] { 7, 8, 9 }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected array was "); } try { assertArrayEquals(new byte[] { 7, 8, 9 }, null); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "actual array was "); } } @Test void assertArrayEqualsByteArrayVsNullAndMessage() { try { assertArrayEquals(null, new byte[] { 9, 8, 7 }, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected array was "); } try { assertArrayEquals(new byte[] { 9, 8, 7 }, null, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "actual array was "); } } @Test void assertArrayEqualsByteArrayVsNullAndMessageSupplier() { try { assertArrayEquals(null, new byte[] { 10, 20, 30 }, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected array was "); } try { assertArrayEquals(new byte[] { 10, 20, 30 }, null, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "actual array was "); } } @Test void assertArrayEqualsByteArraysOfDifferentLength() { try { assertArrayEquals(new byte[] { 1, 2, 100 }, new byte[] { 1, 2, 100, 101 }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "array lengths differ, expected: <3> but was: <4>"); } } @Test void assertArrayEqualsByteArraysOfDifferentLengthAndMessage() { try { assertArrayEquals(new byte[] { 1, 2 }, new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array lengths differ, expected: <2> but was: <9>"); } } @Test void assertArrayEqualsByteArraysOfDifferentLengthAndMessageSupplier() { try { assertArrayEquals(new byte[] { 88, 99 }, new byte[] { 99, 88, 77 }, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array lengths differ, expected: <2> but was: <3>"); } } @Test void assertArrayEqualsDifferentByteArrays() { try { assertArrayEquals(new byte[] { 12, 13, 12, 13 }, new byte[] { 12, 13, 12, 14 }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "array contents differ at index [3], expected: <13> but was: <14>"); } } @Test void assertArrayEqualsDifferentByteArraysAndMessage() { try { assertArrayEquals(new byte[] { 1, 2, 3, 4, 5 }, new byte[] { 1, 2, 3, 5, 5 }, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array contents differ at index [3], expected: <4> but was: <5>"); } } @Test void assertArrayEqualsDifferentByteArraysAndMessageSupplier() { try { assertArrayEquals(new byte[] { 127, 126, -128, +127 }, new byte[] { 127, 126, -128, -127 }, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array contents differ at index [3], expected: <127> but was: <-127>"); } } @Test void assertArrayEqualsShortArrays() { short[] array = {}; assertArrayEquals(array, array); assertArrayEquals(new short[] {}, new short[] {}); assertArrayEquals(new short[] {}, new short[] {}, "message"); assertArrayEquals(new short[] {}, new short[] {}, () -> "message"); assertArrayEquals(new short[] { 999 }, new short[] { 999 }); assertArrayEquals(new short[] { 111, 222, 333, 444, 999 }, new short[] { 111, 222, 333, 444, 999 }); } @Test void assertArrayEqualsShortArrayVsNull() { try { assertArrayEquals(null, new short[] { 5, 10, 12 }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected array was "); } try { assertArrayEquals(new short[] { 5, 10, 12 }, null); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "actual array was "); } } @Test void assertArrayEqualsShortArrayVsNullAndMessage() { try { assertArrayEquals(null, new short[] { 128, 129, 130 }, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected array was "); } try { assertArrayEquals(new short[] { -129, -130, -131 }, null, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "actual array was "); } } @Test void assertArrayEqualsShortArrayVsNullAndMessageSupplier() { try { assertArrayEquals(null, new short[] { 1, 2, 3, 4 }, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected array was "); } try { assertArrayEquals(new short[] { -2000, 1, 2, 3, 4 }, null, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "actual array was "); } } @Test void assertArrayEqualsShortArraysOfDifferentLength() { try { assertArrayEquals(new short[] { 1, 2, 3, 4, 5, 6 }, new short[] { 1, 2, 3 }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "array lengths differ, expected: <6> but was: <3>"); } } @Test void assertArrayEqualsShortArraysOfDifferentLengthAndMessage() { try { assertArrayEquals(new short[] { 1, 2, 3, 10_000 }, new short[] { 10_000, 1, 2, 3, 4, 5, 6, 7 }, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array lengths differ, expected: <4> but was: <8>"); } } @Test void assertArrayEqualsShortArraysOfDifferentLengthAndMessageSupplier() { try { assertArrayEquals(new short[] { 150, 151 }, new short[] { 150, 151, 152 }, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array lengths differ, expected: <2> but was: <3>"); } } @Test void assertArrayEqualsDifferentShortArrays() { try { assertArrayEquals(new short[] { 10, 100, 1000, 10000 }, new short[] { 1, 10, 100, 1000 }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "array contents differ at index [0], expected: <10> but was: <1>"); } } @Test void assertArrayEqualsDifferentShortArraysAndMessage() { try { assertArrayEquals(new short[] { 1, 2, 100, -200 }, new short[] { 1, 2, 100, -500 }, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array contents differ at index [3], expected: <-200> but was: <-500>"); } } @Test void assertArrayEqualsDifferentShortArraysAndMessageSupplier() { try { assertArrayEquals(new short[] { 1000, 2000, +3000, 42 }, new short[] { 1000, 2000, -3000, 42 }, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array contents differ at index [2], expected: <3000> but was: <-3000>"); } } @Test void assertArrayEqualsIntArrays() { int[] array = {}; assertArrayEquals(array, array); assertArrayEquals(new int[] {}, new int[] {}); assertArrayEquals(new int[] {}, new int[] {}, "message"); assertArrayEquals(new int[] {}, new int[] {}, () -> "message"); assertArrayEquals(new int[] { Integer.MAX_VALUE }, new int[] { Integer.MAX_VALUE }); assertArrayEquals(new int[] { 1, 2, 3, 4, 5, 99_999 }, new int[] { 1, 2, 3, 4, 5, 99_999 }); } @Test void assertArrayEqualsIntArrayVsNull() { try { assertArrayEquals(null, new int[] { Integer.MIN_VALUE, 2, 10 }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected array was "); } try { assertArrayEquals(new int[] { Integer.MIN_VALUE, 2, 10 }, null); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "actual array was "); } } @Test void assertArrayEqualsIntArrayVsNullAndMessage() { try { assertArrayEquals(null, new int[] { 99_999, 88_888, 1 }, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected array was "); } try { assertArrayEquals(new int[] { 99_999, 77_7777, 2 }, null, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "actual array was "); } } @Test void assertArrayEqualsIntArrayVsNullAndMessageSupplier() { try { assertArrayEquals(null, new int[] { 1, 10, 100, 1000, 10000, 100000 }, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected array was "); } try { assertArrayEquals(new int[] { 100000, 10000, 1000, 100, 10, 1 }, null, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "actual array was "); } } @Test void assertArrayEqualsIntArraysOfDifferentLength() { try { assertArrayEquals(new int[] { 1, 2, 3, Integer.MIN_VALUE, 4 }, new int[] { 1, Integer.MAX_VALUE, 2 }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "array lengths differ, expected: <5> but was: <3>"); } } @Test void assertArrayEqualsIntArraysOfDifferentLengthAndMessage() { try { assertArrayEquals(new int[] { 100_000, 200_000, 1, 2 }, new int[] { 1, 2, 3 }, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array lengths differ, expected: <4> but was: <3>"); } } @Test void assertArrayEqualsIntArraysOfDifferentLengthAndMessageSupplier() { try { assertArrayEquals(new int[] { Integer.MAX_VALUE, Integer.MIN_VALUE }, new int[] { 1, 2, 3 }, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array lengths differ, expected: <2> but was: <3>"); } } @Test void assertArrayEqualsDifferentIntArrays() { try { assertArrayEquals(new int[] { Integer.MIN_VALUE, 1, 2, 10 }, new int[] { Integer.MIN_VALUE, 1, 10, 10 }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "array contents differ at index [2], expected: <2> but was: <10>"); } } @Test void assertArrayEqualsDifferentIntArraysAndMessage() { try { assertArrayEquals(new int[] { 9, 10, 100, 100_000, 7 }, new int[] { 9, 10, 100, 100_000, 200_000 }, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array contents differ at index [4], expected: <7> but was: <200000>"); } } @Test void assertArrayEqualsDifferentIntArraysAndMessageSupplier() { try { assertArrayEquals(new int[] { 1, Integer.MIN_VALUE, 2 }, new int[] { 1, Integer.MAX_VALUE, 2 }, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array contents differ at index [1], expected: <-2147483648> but was: <2147483647>"); } } @Test void assertArrayEqualsLongArrays() { long[] array = {}; assertArrayEquals(array, array); assertArrayEquals(new long[] {}, new long[] {}); assertArrayEquals(new long[] {}, new long[] {}, "message"); assertArrayEquals(new long[] {}, new long[] {}, () -> "message"); assertArrayEquals(new long[] { Long.MAX_VALUE }, new long[] { Long.MAX_VALUE }); assertArrayEquals(new long[] { Long.MIN_VALUE, 10, 20, 30 }, new long[] { Long.MIN_VALUE, 10, 20, 30 }); } @Test void assertArrayEqualsLongArrayVsNull() { try { assertArrayEquals(null, new long[] { Long.MAX_VALUE, 2, 10 }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected array was "); } try { assertArrayEquals(new long[] { Long.MAX_VALUE, 2, 10 }, null); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "actual array was "); } } @Test void assertArrayEqualsLongArrayVsNullAndMessage() { try { assertArrayEquals(null, new long[] { 42, 4242, 424242, 4242424242L }, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected array was "); } try { assertArrayEquals(new long[] { 4242424242L, 424242, 4242, 42 }, null, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "actual array was "); } } @Test void assertArrayEqualsLongArrayVsNullAndMessageSupplier() { try { assertArrayEquals(null, new long[] { 12345678910L, 10, 9, 8 }, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected array was "); } try { assertArrayEquals(new long[] { 8, 9, 10, 12345678910L }, null, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "actual array was "); } } @Test void assertArrayEqualsLongArraysOfDifferentLength() { try { assertArrayEquals(new long[] { 1, 2, 3, Long.MIN_VALUE, 4 }, new long[] { 1, Long.MAX_VALUE, 2 }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "array lengths differ, expected: <5> but was: <3>"); } } @Test void assertArrayEqualsLongArraysOfDifferentLengthAndMessage() { try { assertArrayEquals(new long[] { 100_000L, 200_000L, 1L, 2L }, new long[] { 1L, 2L, 3L }, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array lengths differ, expected: <4> but was: <3>"); } } @Test void assertArrayEqualsLongArraysOfDifferentLengthAndMessageSupplier() { try { assertArrayEquals(new long[] { Long.MAX_VALUE, Long.MIN_VALUE }, new long[] { 1L, 2L, 42L }, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array lengths differ, expected: <2> but was: <3>"); } } @Test void assertArrayEqualsDifferentLongArrays() { try { assertArrayEquals(new long[] { Long.MIN_VALUE, 17, 18L, 19 }, new long[] { Long.MIN_VALUE, 17, 18, 20 }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "array contents differ at index [3], expected: <19> but was: <20>"); } } @Test void assertArrayEqualsDifferentLongArraysAndMessage() { try { assertArrayEquals(new long[] { 6, 5, 4, 3, 2, Long.MIN_VALUE }, new long[] { 6, 5, 4, 3, 2, 1 }, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array contents differ at index [5], expected: <-9223372036854775808> but was: <1>"); } } @Test void assertArrayEqualsDifferentLongArraysAndMessageSupplier() { try { assertArrayEquals(new long[] { 42, -9999L, 2 }, new long[] { 42L, +9999L, 2L }, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array contents differ at index [1], expected: <-9999> but was: <9999>"); } } @Test void assertArrayEqualsFloatArrays() { float[] array = {}; assertArrayEquals(array, array); assertArrayEquals(new float[] {}, new float[] {}); assertArrayEquals(new float[] {}, new float[] {}, "message"); assertArrayEquals(new float[] {}, new float[] {}, () -> "message"); assertArrayEquals(new float[] { Float.MAX_VALUE }, new float[] { Float.MAX_VALUE }); assertArrayEquals(new float[] { Float.MIN_VALUE, 5F, 5.5F, 1.00F }, new float[] { Float.MIN_VALUE, 5F, 5.5F, 1.00F }); assertArrayEquals(new float[] { Float.NaN }, new float[] { Float.NaN }); assertArrayEquals(new float[] { 10.18F, Float.NaN, 42.9F }, new float[] { 10.18F, Float.NaN, 42.9F }); } @Test void assertArrayEqualsFloatArrayVsNull() { try { assertArrayEquals(null, new float[] { Float.MAX_VALUE, 4.2F, 9.0F }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected array was "); } try { assertArrayEquals(new float[] { Float.MIN_VALUE, 2.3F, 10.10F }, null); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "actual array was "); } } @Test void assertArrayEqualsFloatArrayVsNullAndMessage() { try { assertArrayEquals(null, new float[] { 42.42F, 42.4242F, 19.20F }, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected array was "); } try { assertArrayEquals(new float[] { 11.101F, 12.101F, 99.9F }, null, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "actual array was "); } } @Test void assertArrayEqualsFloatArrayVsNullAndMessageSupplier() { try { assertArrayEquals(null, new float[] { 5F, 6F, 7.77F, 8.88F }, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected array was "); } try { assertArrayEquals(new float[] { 1F, 1.1F, 1.11F, 1.111F }, null, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "actual array was "); } } @Test void assertArrayEqualsFloatArraysOfDifferentLength() { try { assertArrayEquals(new float[] { Float.MIN_VALUE, 1F, 2F, 3F }, new float[] { Float.MAX_VALUE, 7.1F }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "array lengths differ, expected: <4> but was: <2>"); } } @Test void assertArrayEqualsFloatArraysOfDifferentLengthAndMessage() { try { assertArrayEquals(new float[] { 19.1F, 12.77F, 18.F }, new float[] { .9F, .8F, 5.123F, .10F }, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array lengths differ, expected: <3> but was: <4>"); } } @Test void assertArrayEqualsFloatArraysOfDifferentLengthAndMessageSupplier() { try { assertArrayEquals(new float[] { 1.1F, 1.2F, 1.3F }, new float[] { 1F, 2F, 3F, 4F, 5F, 6F }, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array lengths differ, expected: <3> but was: <6>"); } } @Test void assertArrayEqualsDifferentFloatArrays() { try { assertArrayEquals(new float[] { 5.5F, 6.5F, 7.5F, 8.5F }, new float[] { 5.5F, 6.5F, 7.4F, 8.5F }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "array contents differ at index [2], expected: <7.5> but was: <7.4>"); } try { assertArrayEquals(new float[] { 1.0F, 2.0F, 3.0F, Float.NaN }, new float[] { 1.0F, 2.0F, 3.0F, 4.0F }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "array contents differ at index [3], expected: but was: <4.0>"); } } @Test void assertArrayEqualsDifferentFloatArraysAndMessage() { try { assertArrayEquals(new float[] { 1.9F, 0.5F, 0.4F, 0.3F }, new float[] { 1.9F, 0.5F, 0.4F, -0.333F }, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array contents differ at index [3], expected: <0.3> but was: <-0.333>"); } } @Test void assertArrayEqualsDifferentFloatArraysAndMessageSupplier() { try { assertArrayEquals(new float[] { 0.3F, 0.9F, 8F }, new float[] { 0.3F, Float.MIN_VALUE, 8F }, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array contents differ at index [1], expected: <0.9> but was: <1.4E-45>"); } } @Test void assertArrayEqualsDeltaFloatArrays() { float[] array = {}; assertArrayEquals(array, array, 0.001F); assertArrayEquals(new float[] {}, new float[] {}, 0.001F); assertArrayEquals(new float[] {}, new float[] {}, 0.001F, "message"); assertArrayEquals(new float[] {}, new float[] {}, 0.001F, () -> "message"); assertArrayEquals(new float[] { Float.MAX_VALUE }, new float[] { Float.MAX_VALUE }, 0.0001F); assertArrayEquals(new float[] { Float.MIN_VALUE, 2.111F, 2.521F, 1.01F }, new float[] { Float.MIN_VALUE, 2.119F, 2.523F, 1.01001F }, 0.01F); assertArrayEquals(new float[] { Float.NaN }, new float[] { Float.NaN }, 0.1F); assertArrayEquals(new float[] { 10.18F, Float.NaN, 42.9F }, new float[] { 10.98F, Float.NaN, 43.9F }, 1F); } @Test void assertArrayEqualsDeltaFloatArraysThrowsForIllegalDelta() { try { assertArrayEquals(new float[] {}, new float[] {}, -0.5F); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "positive delta expected but was: <-0.5>"); } try { assertArrayEquals(new float[] {}, new float[] {}, Float.NaN); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "positive delta expected but was: "); } try { assertArrayEquals(new float[] { 12.9F, 7F, 13F }, new float[] { 12.9F, 7F, 13F }, -0.5F); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "positive delta expected but was: <-0.5>"); } try { assertArrayEquals(new float[] { 1.11F, 1.11F, 9F }, new float[] { 1.11F, 1.11F, 9F, 10F }, Float.NaN); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "positive delta expected but was: "); } } @Test void assertArrayEqualsDeltaFloatArrayVsNull() { try { assertArrayEquals(null, new float[] { Float.MAX_VALUE, 4.2F, 9.0F }, 0.001F); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected array was "); } try { assertArrayEquals(new float[] { Float.MIN_VALUE, 2.3F, 10.10F }, null, 0.01F); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "actual array was "); } } @Test void assertArrayEqualsDeltaFloatArrayVsNullAndMessage() { try { assertArrayEquals(null, new float[] { 42.42F, 42.4242F, 19.20F }, 0.0001F, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected array was "); } try { assertArrayEquals(new float[] { 11.101F, 12.101F, 99.9F }, null, 0.01F, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "actual array was "); } } @Test void assertArrayEqualsDeltaFloatArrayVsNullAndMessageSupplier() { try { assertArrayEquals(null, new float[] { 5F, 6F, 7.77F, 8.88F }, 0.1F, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected array was "); } try { assertArrayEquals(new float[] { 1F, 1.1F, 1.11F, 1.111F }, null, 0.1F, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "actual array was "); } } @Test void assertArrayEqualsDeltaFloatArraysOfDifferentLength() { try { assertArrayEquals(new float[] { Float.MIN_VALUE, 1F, 2F, 3F }, new float[] { Float.MAX_VALUE, 7.1F }, 0.1F); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "array lengths differ, expected: <4> but was: <2>"); } } @Test void assertArrayEqualsDeltaFloatArraysOfDifferentLengthAndMessage() { try { assertArrayEquals(new float[] { 19.1F, 12.77F }, new float[] { .9F, .8F, 5.123F }, 0.1F, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array lengths differ, expected: <2> but was: <3>"); } } @Test void assertArrayEqualsDeltaFloatArraysOfDifferentLengthAndMessageSupplier() { try { assertArrayEquals(new float[] { 1.1F, 1.2F, 1.3F }, new float[] { 1F, 2F, 3F, 4F }, 0.1F, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array lengths differ, expected: <3> but was: <4>"); } } @Test void assertArrayEqualsDeltaDifferentFloatArrays() { try { assertArrayEquals(new float[] { 5.6F, 3.2F, 9.1F, 0.5F }, new float[] { 5.55F, 3.3F, 9.201F, 0.51F }, 0.1F); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "array contents differ at index [2], expected: <9.1> but was: <9.201>"); } try { assertArrayEquals(new float[] { 1.0F, 2.0F, 3.0F, Float.NaN }, new float[] { 1.5F, 1.5F, 2.9F, 4.0F }, 0.5F); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "array contents differ at index [3], expected: but was: <4.0>"); } } @Test void assertArrayEqualsDeltaDifferentFloatArraysAndMessage() { try { assertArrayEquals(new float[] { 1.91F, 0.5F, .4F, 0.3F }, new float[] { 2F, 0.509F, .499F, -0.333F }, 0.1F, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array contents differ at index [3], expected: <0.3> but was: <-0.333>"); } } @Test void assertArrayEqualsDeltaDifferentFloatArraysAndMessageSupplier() { try { assertArrayEquals(new float[] { 0.3F, 0.9F, 8F }, new float[] { 0.6F, Float.MIN_VALUE, 8.4F }, 0.5F, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array contents differ at index [1], expected: <0.9> but was: <1.4E-45>"); } } @Test void assertArrayEqualsDoubleArrays() { double[] array = {}; assertArrayEquals(array, array); assertArrayEquals(new double[] {}, new double[] {}); assertArrayEquals(new double[] {}, new double[] {}, "message"); assertArrayEquals(new double[] {}, new double[] {}, () -> "message"); assertArrayEquals(new double[] { Double.MAX_VALUE }, new double[] { Double.MAX_VALUE }); assertArrayEquals(new double[] { Double.MIN_VALUE, 2.1, 5.5, 1.0 }, new double[] { Double.MIN_VALUE, 2.1, 5.5, 1.0 }); assertArrayEquals(new double[] { Double.NaN }, new double[] { Double.NaN }); assertArrayEquals(new double[] { 1.2, 10.8, Double.NaN, 42.9 }, new double[] { 1.2, 10.8, Double.NaN, 42.9 }); } @Test void assertArrayEqualsDoubleArrayVsNull() { try { assertArrayEquals(null, new double[] { Double.MAX_VALUE, 17.4, 98.7654321 }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected array was "); } try { assertArrayEquals(new double[] { Double.MIN_VALUE, 93.0, 92.000001 }, null); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "actual array was "); } } @Test void assertArrayEqualsDoubleArrayVsNullAndMessage() { try { assertArrayEquals(null, new double[] { 33.3, 34.9, 20.1, 11.0011 }, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected array was "); } try { assertArrayEquals(new double[] { 44.4, 20.19, 11.3, 0.11 }, null, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "actual array was "); } } @Test void assertArrayEqualsDoubleArrayVsNullAndMessageSupplier() { try { assertArrayEquals(null, new double[] { 1.2, 1.3, 1.4, 2.2002 }, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected array was "); } try { assertArrayEquals(new double[] { 13.13, 43.33, 100 }, null, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "actual array was "); } } @Test void assertArrayEqualsDoubleArraysOfDifferentLength() { try { assertArrayEquals(new double[] { Double.MIN_VALUE, 1.0, 2.0, 3.0 }, new double[] { Double.MAX_VALUE, 1.1, 1.0 }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "array lengths differ, expected: <4> but was: <3>"); } } @Test void assertArrayEqualsDoubleArraysOfDifferentLengthAndMessage() { try { assertArrayEquals(new double[] { 11.1, 99.1, 2 }, new double[] { .9, .1, .0, .1, .3 }, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array lengths differ, expected: <3> but was: <5>"); } } @Test void assertArrayEqualsDoubleArraysOfDifferentLengthAndMessageSupplier() { try { assertArrayEquals(new double[] { 1.15D, 2.2, 2.3 }, new double[] { 1.15D, 1.15D }, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array lengths differ, expected: <3> but was: <2>"); } } @Test void assertArrayEqualsDifferentDoubleArrays() { try { assertArrayEquals(new double[] { 1.17, 1.19, 1.21, 5 }, new double[] { 1.17, 1.00019, 1.21, 5 }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "array contents differ at index [1], expected: <1.19> but was: <1.00019>"); } try { assertArrayEquals(new double[] { 0.1, 0.2, 0.3, 0.4, 0.5 }, new double[] { 0.1, 0.2, 0.3, 0.4, Double.NaN }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "array contents differ at index [4], expected: <0.5> but was: "); } } @Test void assertArrayEqualsDifferentDoubleArraysAndMessage() { try { assertArrayEquals(new double[] { 1.01, 9.031, .123, 4.23 }, new double[] { 1.01, 9.099, .123, 4.23 }, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array contents differ at index [1], expected: <9.031> but was: <9.099>"); } } @Test void assertArrayEqualsDifferentDoubleArraysAndMessageSupplier() { try { assertArrayEquals(new double[] { 0.7, .1, 8 }, new double[] { 0.7, Double.MIN_VALUE, 8 }, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array contents differ at index [1], expected: <0.1> but was: <4.9E-324>"); } } @Test void assertArrayEqualsDeltaDoubleArrays() { double[] array = {}; assertArrayEquals(array, array, 0.5); assertArrayEquals(new double[] {}, new double[] {}, 0.5); assertArrayEquals(new double[] {}, new double[] {}, 0.5, "message"); assertArrayEquals(new double[] {}, new double[] {}, 0.5, () -> "message"); assertArrayEquals(new double[] { Double.MAX_VALUE, 0.1 }, new double[] { Double.MAX_VALUE, 0.2 }, 0.2); assertArrayEquals(new double[] { Double.MIN_VALUE, 3.1, 1.3, 2.7 }, new double[] { Double.MIN_VALUE, 3.4, 1.7, 2.4 }, 0.5); assertArrayEquals(new double[] { Double.NaN }, new double[] { Double.NaN }, 0.01); assertArrayEquals(new double[] { 1.2, 1.8, Double.NaN, 4.9 }, new double[] { 1.25, 1.7, Double.NaN, 4.8 }, 0.2); } @Test void assertArrayEqualsDeltaDoubleArraysThrowsForIllegalDelta() { try { assertArrayEquals(new double[] {}, new double[] {}, -0.5F); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "positive delta expected but was: <-0.5>"); } try { assertArrayEquals(new double[] {}, new double[] {}, Float.NaN); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "positive delta expected but was: "); } try { assertArrayEquals(new double[] { 1.2, 1.3, 10 }, new double[] { 1.2, 1.3, 10 }, -0.5F); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "positive delta expected but was: <-0.5>"); } try { assertArrayEquals(new double[] { 0.1, 10 }, new double[] { 0.1, 10, 11 }, Float.NaN); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "positive delta expected but was: "); } } @Test void assertArrayEqualsDeltaDoubleArrayVsNull() { try { assertArrayEquals(null, new double[] { Double.MAX_VALUE, 11.1, 12.12 }, 0.5); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected array was "); } try { assertArrayEquals(new double[] { Double.MIN_VALUE, 90, 91.9 }, null, 0.1); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "actual array was "); } } @Test void assertArrayEqualsDeltaDoubleArrayVsNullAndMessage() { try { assertArrayEquals(null, new double[] { 33.3, 34.9, 20.1, 11.0011 }, 0.1, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected array was "); } try { assertArrayEquals(new double[] { 44.4, 20.19, 11.3, 0.11 }, null, 0.5, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "actual array was "); } } @Test void assertArrayEqualsDeltaDoubleArrayVsNullAndMessageSupplier() { try { assertArrayEquals(null, new double[] { 1.2, 1.3, 1.4, 2.2002 }, 1, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected array was "); } try { assertArrayEquals(new double[] { 13.13, 43.33, 100 }, null, 1.5, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "actual array was "); } } @Test void assertArrayEqualsDeltaDoubleArraysOfDifferentLength() { try { assertArrayEquals(new double[] { Double.MIN_VALUE, 2.0, 3.0, 4.0 }, new double[] { Double.MAX_VALUE, 2.1, 3.1 }, 0.001); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "array lengths differ, expected: <4> but was: <3>"); } } @Test void assertArrayEqualsDeltaDoubleArraysOfDifferentLengthAndMessage() { try { assertArrayEquals(new double[] { 1.1, 99.1, 3.1 }, new double[] { .9, .1, .0, .1, .3 }, 0.1, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array lengths differ, expected: <3> but was: <5>"); } } @Test void assertArrayEqualsDeltaDoubleArraysOfDifferentLengthAndMessageSupplier() { try { assertArrayEquals(new double[] { 1.77D, 2.1, 3 }, new double[] { 8.8, 0.11 }, 1, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array lengths differ, expected: <3> but was: <2>"); } } @Test void assertArrayEqualsDeltaDifferentDoubleArrays() { try { assertArrayEquals(new double[] { 1.12, 2.92, 1.201 }, new double[] { 1.1201, 2.94, 1.201 }, 0.01); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "array contents differ at index [1], expected: <2.92> but was: <2.94>"); } try { assertArrayEquals(new double[] { 0.6, 0.12, 19.9, 5.5 }, new double[] { 1.0, 0.42, 20, Double.NaN }, 0.5); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "array contents differ at index [3], expected: <5.5> but was: "); } } @Test void assertArrayEqualsDeltaDifferentDoubleArraysAndMessage() { try { assertArrayEquals(new double[] { 1.01, 9.031, .123, 4.23 }, new double[] { 1.1, 9.231, .13, 4.3 }, 0.1, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array contents differ at index [1], expected: <9.031> but was: <9.231>"); } } @Test void assertArrayEqualsDeltaDifferentDoubleArraysAndMessageSupplier() { try { assertArrayEquals(new double[] { 0.7, 0.3001, 8 }, new double[] { 0.7, 0.4002, 8 }, 0.1, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array contents differ at index [1], expected: <0.3001> but was: <0.4002>"); } } @Test void assertArrayEqualsObjectArrays() { Object[] array = { "a", 'b', 1, 2 }; assertArrayEquals(array, array); assertArrayEquals(new Object[] {}, new Object[] {}); assertArrayEquals(new Object[] {}, new Object[] {}, "message"); assertArrayEquals(new Object[] {}, new Object[] {}, () -> "message"); assertArrayEquals(new Object[] { "abc" }, new Object[] { "abc" }); assertArrayEquals(new Object[] { "abc", 1, 2L, 3D }, new Object[] { "abc", 1, 2L, 3D }); assertArrayEquals(new Object[] { new Object[] { new Object[] {} } }, new Object[] { new Object[] { new Object[] {} } }); assertArrayEquals( new Object[] { null, new Object[] { null, new Object[] { null, null } }, null, new Object[] { null } }, new Object[] { null, new Object[] { null, new Object[] { null, null } }, null, new Object[] { null } }); assertArrayEquals(new Object[] { "a", new Object[] { new Object[] { "b", new Object[] { "c", "d" } } }, "e" }, new Object[] { "a", new Object[] { new Object[] { "b", new Object[] { "c", "d" } } }, "e" }); assertArrayEquals( new Object[] { new Object[] { 1 }, new Object[] { 2 }, new Object[] { new Object[] { 3, new Object[] { 4 } } } }, new Object[] { new Object[] { 1 }, new Object[] { 2 }, new Object[] { new Object[] { 3, new Object[] { 4 } } } }); assertArrayEquals( new Object[] { new Object[] { new Object[] { new Object[] { new Object[] { new Object[] { new Object[] { "abc" } } } } } } }, new Object[] { new Object[] { new Object[] { new Object[] { new Object[] { new Object[] { new Object[] { "abc" } } } } } } }); assertArrayEquals( new Object[] { null, new Object[] { null, Double.NaN, new Object[] { Float.NaN, null, new Object[] {} } } }, new Object[] { null, new Object[] { null, Double.NaN, new Object[] { Float.NaN, null, new Object[] {} } } }); assertArrayEquals( new Object[] { new String("a"), Integer.valueOf(1), new Object[] { Double.parseDouble("1.1"), "b" } }, new Object[] { new String("a"), Integer.valueOf(1), new Object[] { Double.parseDouble("1.1"), "b" } }); assertArrayEquals( new Object[] { 1, 2, new Object[] { 3, new int[] { 4, 5 }, new long[] { 6 }, new Object[] { new Object[] { new int[] { 7 } } } }, new int[] { 8 }, new Object[] { new long[] { 9 } } }, new Object[] { 1, 2, new Object[] { 3, new int[] { 4, 5 }, new long[] { 6 }, new Object[] { new Object[] { new int[] { 7 } } } }, new int[] { 8 }, new Object[] { new long[] { 9 } } }); assertArrayEquals( new Object[] { "a", new byte[] { 42 }, new short[] { 42 }, new char[] { 'b', 'c' }, new float[] { 42.23f }, new double[] { 42.23 }, new boolean[] { true }, new Object[] { new Object[] { new String[] { "ef" }, new Object[] { new String[] { "ghi" } } } } }, new Object[] { "a", new byte[] { 42 }, new short[] { 42 }, new char[] { 'b', 'c' }, new float[] { 42.23f }, new double[] { 42.23 }, new boolean[] { true }, new Object[] { new Object[] { new String[] { "ef" }, new Object[] { new String[] { "ghi" } } } } }); } @Test void assertArrayEqualsObjectArrayVsNull() { try { assertArrayEquals(null, new Object[] { "a", "b", 1, new Object() }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected array was "); } try { assertArrayEquals(new Object[] { 'a', 1, new Object(), 10L }, null); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "actual array was "); } } @Test void assertArrayEqualsNestedObjectArrayVsNull() { try { assertArrayEquals(// new Object[] { new Object[] {}, 1, "2", new Object[] { '3', new Object[] { null } } }, // new Object[] { new Object[] {}, 1, "2", new Object[] { '3', new Object[] { new Object[] { "4" } } } }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected array was at index [3][1][0]"); } try { assertArrayEquals( new Object[] { 1, 2, new Object[] { 3, new Object[] { "4", new Object[] { 5, new Object[] { 6 } } } }, "7" }, new Object[] { 1, 2, new Object[] { 3, new Object[] { "4", new Object[] { 5, null } } }, "7" }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "actual array was at index [2][1][1][1]"); } } @Test void assertArrayEqualsObjectArrayVsNullAndMessage() { try { assertArrayEquals(null, new Object[] { 'a', "b", 10, 20D }, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected array was "); } try { assertArrayEquals(new Object[] { "hello", 42 }, null, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "actual array was "); } } @Test void assertArrayEqualsNestedObjectArrayVsNullAndMessage() { try { assertArrayEquals(new Object[] { 1, new Object[] { 2, 3, new Object[] { 4, 5, new Object[] { null } } } }, new Object[] { 1, new Object[] { 2, 3, new Object[] { 4, 5, new Object[] { new Object[] { 6 } } } } }, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected array was at index [1][2][2][0]"); } try { assertArrayEquals( new Object[] { 1, new Object[] { 2, new Object[] { 3, new Object[] { new Object[] { 4 } } } } }, new Object[] { 1, new Object[] { 2, new Object[] { 3, new Object[] { null } } } }, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "actual array was at index [1][1][1][0]"); } } @Test void assertArrayEqualsObjectArrayVsNullAndMessageSupplier() { try { assertArrayEquals(null, new Object[] { 42, "42", new float[] { 42F }, 42D }, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected array was "); } try { assertArrayEquals(new Object[] { new Object[] { "a" }, new Object() }, null, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "actual array was "); } } @Test void assertArrayEqualsNestedObjectArrayVsNullAndMessageSupplier() { try { assertArrayEquals(new Object[] { "1", "2", "3", new Object[] { "4", new Object[] { null } } }, new Object[] { "1", "2", "3", new Object[] { "4", new Object[] { new int[] { 5 } } } }, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected array was at index [3][1][0]"); } try { assertArrayEquals( new Object[] { 1, 2, new Object[] { "3", new Object[] { '4', new Object[] { 5, 6, new long[] {} } } } }, new Object[] { 1, 2, new Object[] { "3", new Object[] { '4', new Object[] { 5, 6, null } } } }, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "actual array was at index [2][1][1][2]"); } } @Test void assertArrayEqualsObjectArraysOfDifferentLength() { try { assertArrayEquals(new Object[] { 'a', "b", 'c' }, new Object[] { 'a', "b", 'c', 1 }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "array lengths differ, expected: <3> but was: <4>"); } } @Test void assertArrayEqualsNestedObjectArraysOfDifferentLength() { try { assertArrayEquals( new Object[] { "a", new Object[] { "b", new Object[] { "c", "d", new Object[] { "e", 1, 2, 3 } } } }, new Object[] { "a", new Object[] { "b", new Object[] { "c", "d", new Object[] { "e", 1, 2, 3, 4, 5 } } } }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "array lengths differ at index [1][1][2], expected: <4> but was: <6>"); } try { assertArrayEquals( new Object[] { new Object[] { new Object[] { new Object[] { new Object[] { new Object[] { new char[] { 'a' } } } } } } }, new Object[] { new Object[] { new Object[] { new Object[] { new Object[] { new Object[] { new char[] { 'a', 'b' } } } } } } }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "array lengths differ at index [0][0][0][0][0][0], expected: <1> but was: <2>"); } } @Test void assertArrayEqualsObjectArraysOfDifferentLengthAndMessage() { try { assertArrayEquals(new Object[] { 'a', 1 }, new Object[] { 'a', 1, new Object() }, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array lengths differ, expected: <2> but was: <3>"); } } @Test void assertArrayEqualsNestedObjectArraysOfDifferentLengthAndMessage() { try { assertArrayEquals(// new Object[] { 'a', 1, new Object[] { 2, 3 } }, // new Object[] { 'a', 1, new Object[] { 2, 3, 4, 5 } }, // "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array lengths differ at index [2], expected: <2> but was: <4>"); } } @Test void assertArrayEqualsObjectArraysOfDifferentLengthAndMessageSupplier() { try { assertArrayEquals(new Object[] { "a", "b", "c" }, new Object[] { "a", "b", "c", "d", "e", "f" }, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array lengths differ, expected: <3> but was: <6>"); } } @Test void assertArrayEqualsNestedObjectArraysOfDifferentLengthAndMessageSupplier() { try { assertArrayEquals(// new Object[] { "a", new Object[] { 1, 2, 3, new double[] { 4.0, 5.1, 6.1 }, 7 } }, // new Object[] { "a", new Object[] { 1, 2, 3, new double[] { 4.0, 5.1, 6.1, 7.0 }, 8 } }, // () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array lengths differ at index [1][3], expected: <3> but was: <4>"); } } @Test void assertArrayEqualsDifferentObjectArrays() { try { assertArrayEquals(new Object[] { 1L, "2", '3', 4, 5D }, new Object[] { 1L, "2", '9', 4, 5D }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "array contents differ at index [2], expected: <3> but was: <9>"); } try { assertArrayEquals(new Object[] { "a", 10, 11, 12, Double.NaN }, new Object[] { "a", 10, 11, 12, 13.55D }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "array contents differ at index [4], expected: but was: <13.55>"); } } @Test void assertArrayEqualsDifferentNestedObjectArrays() { try { assertArrayEquals( new Object[] { 1, 2, new Object[] { 3, new Object[] { 4, new boolean[] { false, true } } } }, new Object[] { 1, 2, new Object[] { 3, new Object[] { 4, new boolean[] { true, false } } } }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "array contents differ at index [2][1][1][0], expected: but was: "); } try { assertArrayEquals(new Object[] { 1, 2, 3, new Object[] { new Object[] { 4, new Object[] { 5 } } } }, new Object[] { 1, 2, 3, new Object[] { new Object[] { 4, new Object[] { new Object[] {} } } } }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "array contents differ at index [3][0][1][0], expected: <5> but was: <[]>"); } } @Test void assertArrayEqualsDifferentObjectArraysAndMessage() { try { assertArrayEquals(new Object[] { 1.1D, 2L, "3" }, new Object[] { 1D, 2L, "3" }, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array contents differ at index [0], expected: <1.1> but was: <1.0>"); } } @Test void assertArrayEqualsDifferentNestedObjectArraysAndMessage() { try { assertArrayEquals(new Object[] { 9, 8, '6', new Object[] { 5, 4, "3", new Object[] { "2", '1' } } }, new Object[] { 9, 8, '6', new Object[] { 5, 4, "3", new Object[] { "99", '1' } } }, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array contents differ at index [3][3][0], expected: <2> but was: <99>"); } try { assertArrayEquals(new Object[] { 9, 8, '6', new Object[] { 5, 4, "3", new String[] { "2", "1" } } }, new Object[] { 9, 8, '6', new Object[] { 5, 4, "3", new String[] { "99", "1" } } }, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array contents differ at index [3][3][0], expected: <2> but was: <99>"); } } @Test void assertArrayEqualsDifferentObjectArraysAndMessageSupplier() { try { assertArrayEquals(new Object[] { "one", 1L, Double.MIN_VALUE, "abc" }, new Object[] { "one", 1L, 42.42, "abc" }, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array contents differ at index [2], expected: <4.9E-324> but was: <42.42>"); } } @Test void assertArrayEqualsDifferentNestedObjectArraysAndMessageSupplier() { try { assertArrayEquals( new Object[] { "one", 1L, new Object[] { "a", 'b', new Object[] { 1, new Object[] { 2, 3 } } }, "abc" }, new Object[] { "one", 1L, new Object[] { "a", 'b', new Object[] { 1, new Object[] { 2, 4 } } }, "abc" }, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array contents differ at index [2][2][1][1], expected: <3> but was: <4>"); } try { assertArrayEquals( new Object[] { "j", new String[] { "a" }, new int[] { 42 }, "ab", new Object[] { 1, new int[] { 3 } } }, new Object[] { "j", new String[] { "a" }, new int[] { 42 }, "ab", new Object[] { 1, new int[] { 5 } } }, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "array contents differ at index [4][1][0], expected: <3> but was: <5>"); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/AssertDoesNotThrowAssertionsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.IOException; import java.util.concurrent.FutureTask; import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.api.function.ThrowingSupplier; import org.opentest4j.AssertionFailedError; /** * Unit tests for JUnit Jupiter {@link Assertions}. * * @since 5.2 */ class AssertDoesNotThrowAssertionsTests { private static final Executable nix = () -> { }; private static final ThrowingSupplier something = () -> "enigma"; @Test void assertDoesNotThrowWithMethodReferenceForNonVoidReturnType() { FutureTask future = new FutureTask<>(() -> "foo"); future.run(); String result; // Current compiler's type inference: does NOT compile since the compiler // cannot figure out which overloaded variant of assertDoesNotThrow() to // invoke (i.e., Executable vs. ThrowingSupplier). // // result = assertDoesNotThrow(future::get); // Explicitly as an Executable assertDoesNotThrow((Executable) future::get); // Explicitly as a ThrowingSupplier result = assertDoesNotThrow((ThrowingSupplier) future::get); assertEquals("foo", result); } @Test void assertDoesNotThrowWithMethodReferenceForVoidReturnType() { var foo = new Foo(); // Note: the following does not compile since the compiler cannot properly // perform type inference for a method reference for an overloaded method // that has a void return type such as Foo.overloaded(...), IFF the // compiler is simultaneously trying to pick which overloaded variant // of assertDoesNotThrow() to invoke. // // assertDoesNotThrow(foo::overloaded); // Current compiler's type inference assertDoesNotThrow(foo::normalMethod); // Explicitly as an Executable assertDoesNotThrow(foo::normalMethod); assertDoesNotThrow((Executable) foo::overloaded); } // --- executable ---------------------------------------------------------- @Test void assertDoesNotThrowAnythingWithExecutable() { assertDoesNotThrow(nix); } @Test void assertDoesNotThrowAnythingWithExecutableAndMessage() { assertDoesNotThrow(nix, "message"); } @Test void assertDoesNotThrowAnythingWithExecutableAndMessageSupplier() { assertDoesNotThrow(nix, () -> "message"); } @Test void assertDoesNotThrowWithExecutableThatThrowsACheckedException() { try { assertDoesNotThrow((Executable) () -> { throw new IOException(); }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "Unexpected exception thrown: " + IOException.class.getName()); } } @Test void assertDoesNotThrowWithExecutableThatThrowsACheckedExceptionWithMessage() { String message = "Checked exception message"; try { assertDoesNotThrow((Executable) () -> { throw new IOException(message); }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "Unexpected exception thrown: " + IOException.class.getName() + ": " + message); } } @Test void assertDoesNotThrowWithExecutableThatThrowsARuntimeException() { try { assertDoesNotThrow((Executable) () -> { throw new IllegalStateException(); }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "Unexpected exception thrown: " + IllegalStateException.class.getName()); } } @Test void assertDoesNotThrowWithExecutableThatThrowsARuntimeExceptionWithMessage() { String message = "Runtime exception message"; try { assertDoesNotThrow((Executable) () -> { throw new IllegalStateException(message); }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "Unexpected exception thrown: " + IllegalStateException.class.getName() + ": " + message); } } @Test void assertDoesNotThrowWithExecutableThatThrowsAnError() { try { assertDoesNotThrow(AssertionTestUtils::recurseIndefinitely); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "Unexpected exception thrown: " + StackOverflowError.class.getName()); } } @Test void assertDoesNotThrowWithExecutableThatThrowsAnExceptionWithMessageString() { try { assertDoesNotThrow((Executable) () -> { throw new IllegalStateException(); }, "Custom message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "Custom message ==> Unexpected exception thrown: " + IllegalStateException.class.getName()); } } @Test void assertDoesNotThrowWithExecutableThatThrowsAnExceptionWithMessageWithMessageString() { String message = "Runtime exception message"; try { assertDoesNotThrow((Executable) () -> { throw new IllegalStateException(message); }, "Custom message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "Custom message ==> Unexpected exception thrown: " + IllegalStateException.class.getName() + ": " + message); } } @Test void assertDoesNotThrowWithExecutableThatThrowsAnExceptionWithMessageSupplier() { try { assertDoesNotThrow((Executable) () -> { throw new IllegalStateException(); }, () -> "Custom message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "Custom message ==> Unexpected exception thrown: " + IllegalStateException.class.getName()); } } @Test void assertDoesNotThrowWithExecutableThatThrowsAnExceptionWithMessageWithMessageSupplier() { String message = "Runtime exception message"; try { assertDoesNotThrow((Executable) () -> { throw new IllegalStateException(message); }, () -> "Custom message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "Custom message ==> Unexpected exception thrown: " + IllegalStateException.class.getName() + ": " + message); } } // --- supplier ------------------------------------------------------------ @Test void assertDoesNotThrowAnythingWithSupplier() { assertEquals("enigma", assertDoesNotThrow(something)); } @Test void assertDoesNotThrowAnythingWithSupplierAndMessage() { assertEquals("enigma", assertDoesNotThrow(something, "message")); } @Test void assertDoesNotThrowAnythingWithSupplierAndMessageSupplier() { assertEquals("enigma", assertDoesNotThrow(something, () -> "message")); } @Test void assertDoesNotThrowWithSupplierThatThrowsACheckedException() { try { assertDoesNotThrow((ThrowingSupplier) () -> { throw new IOException(); }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "Unexpected exception thrown: " + IOException.class.getName()); } } @Test void assertDoesNotThrowWithSupplierThatThrowsARuntimeException() { try { assertDoesNotThrow((ThrowingSupplier) () -> { throw new IllegalStateException(); }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "Unexpected exception thrown: " + IllegalStateException.class.getName()); } } @Test void assertDoesNotThrowWithSupplierThatThrowsAnError() { try { assertDoesNotThrow((ThrowingSupplier) () -> { throw new StackOverflowError(); }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "Unexpected exception thrown: " + StackOverflowError.class.getName()); } } @Test void assertDoesNotThrowWithSupplierThatThrowsAnExceptionWithMessageString() { try { assertDoesNotThrow((ThrowingSupplier) () -> { throw new IllegalStateException(); }, "Custom message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "Custom message ==> Unexpected exception thrown: " + IllegalStateException.class.getName()); } } @Test void assertDoesNotThrowWithSupplierThatThrowsAnExceptionWithMessageSupplier() { try { assertDoesNotThrow((ThrowingSupplier) () -> { throw new IllegalStateException(); }, () -> "Custom message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "Custom message ==> Unexpected exception thrown: " + IllegalStateException.class.getName()); } } // ------------------------------------------------------------------------- private static class Foo { void normalMethod() { } void overloaded() { } @SuppressWarnings("unused") void overloaded(int i) { } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/AssertEqualsAssertionsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.junit.jupiter.api.AssertionTestUtils.assertExpectedAndActualValues; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEndsWith; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.function.Executable; import org.opentest4j.AssertionFailedError; /** * Unit tests for JUnit Jupiter {@link Assertions}. * * @since 5.0 */ class AssertEqualsAssertionsTests { @Test void assertEqualsByte() { byte expected = 1; byte actual = 1; assertEquals(expected, actual); assertEquals(expected, actual, "message"); assertEquals(expected, actual, () -> "message"); } @Test void assertEqualsByteWithUnequalValues() { byte expected = 1; byte actual = 2; try { assertEquals(expected, actual); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected: <1> but was: <2>"); assertExpectedAndActualValues(ex, expected, actual); } } @Test void assertEqualsByteWithUnequalValuesAndMessage() { byte expected = 1; byte actual = 2; try { assertEquals(expected, actual, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected: <1> but was: <2>"); assertExpectedAndActualValues(ex, expected, actual); } } @Test void assertEqualsByteWithUnequalValuesAndMessageSupplier() { byte expected = 1; byte actual = 2; try { assertEquals(expected, actual, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected: <1> but was: <2>"); assertExpectedAndActualValues(ex, expected, actual); } } @Test void assertEqualsShort() { short expected = 1; short actual = 1; assertEquals(expected, actual); assertEquals(expected, actual, "message"); assertEquals(expected, actual, () -> "message"); } @Test void assertEqualsShortWithUnequalValues() { short expected = 1; short actual = 2; try { assertEquals(expected, actual); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected: <1> but was: <2>"); assertExpectedAndActualValues(ex, expected, actual); } } @Test void assertEqualsShortWithUnequalValuesAndMessage() { short expected = 1; short actual = 2; try { assertEquals(expected, actual, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected: <1> but was: <2>"); assertExpectedAndActualValues(ex, expected, actual); } } @Test void assertEqualsShortWithUnequalValuesAndMessageSupplier() { short expected = 1; short actual = 2; try { assertEquals(expected, actual, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected: <1> but was: <2>"); assertExpectedAndActualValues(ex, expected, actual); } } @Test void assertEqualsInt() { assertEquals(1, 1); assertEquals(1, 1, "message"); assertEquals(1, 1, () -> "message"); } @Test void assertEqualsIntWithUnequalValues() { try { assertEquals(1, 2); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected: <1> but was: <2>"); assertExpectedAndActualValues(ex, 1, 2); } } @Test void assertEqualsIntWithUnequalValuesAndMessage() { try { assertEquals(1, 2, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected: <1> but was: <2>"); assertExpectedAndActualValues(ex, 1, 2); } } @Test void assertEqualsIntWithUnequalValuesAndMessageSupplier() { try { assertEquals(1, 2, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected: <1> but was: <2>"); assertExpectedAndActualValues(ex, 1, 2); } } @Test void assertEqualsLong() { assertEquals(1L, 1L); assertEquals(1L, 1L, "message"); assertEquals(1L, 1L, () -> "message"); } @Test void assertEqualsLongWithUnequalValues() { try { assertEquals(1L, 2L); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected: <1> but was: <2>"); assertExpectedAndActualValues(ex, 1L, 2L); } } @Test void assertEqualsLongWithUnequalValuesAndMessage() { try { assertEquals(1L, 2L, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected: <1> but was: <2>"); assertExpectedAndActualValues(ex, 1L, 2L); } } @Test void assertEqualsLongWithUnequalValuesAndMessageSupplier() { try { assertEquals(1L, 2L, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected: <1> but was: <2>"); assertExpectedAndActualValues(ex, 1L, 2L); } } @Test void assertEqualsChar() { assertEquals('a', 'a'); assertEquals('a', 'a', "message"); assertEquals('a', 'a', () -> "message"); } @Test void assertEqualsCharWithUnequalValues() { try { assertEquals('a', 'b'); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected: but was: "); assertExpectedAndActualValues(ex, 'a', 'b'); } } @Test void assertEqualsCharWithUnequalValuesAndMessage() { try { assertEquals('a', 'b', "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected: but was: "); assertExpectedAndActualValues(ex, 'a', 'b'); } } @Test void assertEqualsCharWithUnequalValuesAndMessageSupplier() { try { assertEquals('a', 'b', () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected: but was: "); assertExpectedAndActualValues(ex, 'a', 'b'); } } @Test void assertEqualsFloat() { assertEquals(1.0f, 1.0f); assertEquals(1.0f, 1.0f, "message"); assertEquals(1.0f, 1.0f, () -> "message"); assertEquals(Float.NaN, Float.NaN); assertEquals(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY); assertEquals(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY); assertEquals(Float.MIN_VALUE, Float.MIN_VALUE); assertEquals(Float.MAX_VALUE, Float.MAX_VALUE); assertEquals(Float.MIN_NORMAL, Float.MIN_NORMAL); assertEquals(Double.NaN, Float.NaN); } @Test void assertEqualsFloatWithUnequalValues() { try { assertEquals(1.0f, 1.1f); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected: <1.0> but was: <1.1>"); assertExpectedAndActualValues(ex, 1.0f, 1.1f); } } @Test void assertEqualsFloatWithUnequalValuesAndMessage() { try { assertEquals(1.0f, 1.1f, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected: <1.0> but was: <1.1>"); assertExpectedAndActualValues(ex, 1.0f, 1.1f); } } @Test void assertEqualsFloatWithUnequalValuesAndMessageSupplier() { try { assertEquals(1.0f, 1.1f, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected: <1.0> but was: <1.1>"); assertExpectedAndActualValues(ex, 1.0f, 1.1f); } } @Test void assertEqualsFloatWithDelta() { assertEquals(0.0f, 0.0f, 0.1f); assertEquals(0.0f, 0.0f, 0.1f, "message"); assertEquals(0.0f, 0.0f, 0.1f, () -> "message"); assertEquals(0.56f, 0.6f, 0.05f); assertEquals(0.01f, 0.011f, 0.002f); assertEquals(Float.NaN, Float.NaN, 0.5f); assertEquals(0.1f, 0.1f, 0.0f); } @Test void assertEqualsFloatWithIllegalDelta() { AssertionFailedError e1 = assertThrows(AssertionFailedError.class, () -> assertEquals(0.1f, 0.2f, -0.9f)); assertMessageEndsWith(e1, "positive delta expected but was: <-0.9>"); AssertionFailedError e2 = assertThrows(AssertionFailedError.class, () -> assertEquals(.0f, .0f, -10.5f)); assertMessageEndsWith(e2, "positive delta expected but was: <-10.5>"); AssertionFailedError e3 = assertThrows(AssertionFailedError.class, () -> assertEquals(4.5f, 4.6f, Float.NaN)); assertMessageEndsWith(e3, "positive delta expected but was: "); } @Test void assertEqualsFloatWithDeltaWithUnequalValues() { AssertionFailedError e1 = assertThrows(AssertionFailedError.class, () -> assertEquals(0.5f, 0.2f, 0.2f)); assertMessageEndsWith(e1, "expected: <0.5> but was: <0.2>"); AssertionFailedError e2 = assertThrows(AssertionFailedError.class, () -> assertEquals(0.1f, 0.2f, 0.000001f)); assertMessageEndsWith(e2, "expected: <0.1> but was: <0.2>"); AssertionFailedError e3 = assertThrows(AssertionFailedError.class, () -> assertEquals(100.0f, 50.0f, 10.0f)); assertMessageEndsWith(e3, "expected: <100.0> but was: <50.0>"); AssertionFailedError e4 = assertThrows(AssertionFailedError.class, () -> assertEquals(-3.5f, -3.3f, 0.01f)); assertMessageEndsWith(e4, "expected: <-3.5> but was: <-3.3>"); AssertionFailedError e5 = assertThrows(AssertionFailedError.class, () -> assertEquals(+0.0f, -0.001f, .00001f)); assertMessageEndsWith(e5, "expected: <0.0> but was: <-0.001>"); } @Test void assertEqualsFloatWithDeltaWithUnequalValuesAndMessage() { Executable assertion = () -> assertEquals(0.5f, 0.45f, 0.03f, "message"); AssertionFailedError e = assertThrows(AssertionFailedError.class, assertion); assertMessageStartsWith(e, "message"); assertMessageEndsWith(e, "expected: <0.5> but was: <0.45>"); assertExpectedAndActualValues(e, 0.5f, 0.45f); } @Test void assertEqualsFloatWithDeltaWithUnequalValuesAndMessageSupplier() { Executable assertion = () -> assertEquals(0.5f, 0.45f, 0.03f, () -> "message"); AssertionFailedError e = assertThrows(AssertionFailedError.class, assertion); assertMessageStartsWith(e, "message"); assertMessageEndsWith(e, "expected: <0.5> but was: <0.45>"); assertExpectedAndActualValues(e, 0.5f, 0.45f); } @Test void assertEqualsDouble() { assertEquals(1.0d, 1.0d); assertEquals(1.0d, 1.0d, "message"); assertEquals(1.0d, 1.0d, () -> "message"); assertEquals(Double.NaN, Double.NaN); assertEquals(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY); assertEquals(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); assertEquals(Double.MIN_VALUE, Double.MIN_VALUE); assertEquals(Double.MAX_VALUE, Double.MAX_VALUE); assertEquals(Double.MIN_NORMAL, Double.MIN_NORMAL); } @Test void assertEqualsDoubleWithUnequalValues() { try { assertEquals(1.0d, 1.1d); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected: <1.0> but was: <1.1>"); assertExpectedAndActualValues(ex, 1.0d, 1.1d); } } @Test void assertEqualsDoubleWithUnequalValuesAndMessage() { try { assertEquals(1.0d, 1.1d, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected: <1.0> but was: <1.1>"); assertExpectedAndActualValues(ex, 1.0d, 1.1d); } } @Test void assertEqualsDoubleWithUnequalValuesAndMessageSupplier() { try { assertEquals(1.0d, 1.1d, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected: <1.0> but was: <1.1>"); assertExpectedAndActualValues(ex, 1.0d, 1.1d); } } @Test void assertEqualsDoubleWithDelta() { assertEquals(0.0d, 0.0d, 0.1d); assertEquals(0.0d, 0.0d, 0.1d, "message"); assertEquals(0.0d, 0.0d, 0.1d, () -> "message"); assertEquals(0.42d, 0.24d, 0.19d); assertEquals(0.02d, 0.011d, 0.01d); assertEquals(Double.NaN, Double.NaN, 0.2d); assertEquals(0.001d, 0.001d, 0.0d); } @Test void assertEqualsDoubleWithIllegalDelta() { AssertionFailedError e1 = assertThrows(AssertionFailedError.class, () -> assertEquals(1.1d, 1.11d, -0.5d)); assertMessageEndsWith(e1, "positive delta expected but was: <-0.5>"); AssertionFailedError e2 = assertThrows(AssertionFailedError.class, () -> assertEquals(.55d, .56d, -10.5d)); assertMessageEndsWith(e2, "positive delta expected but was: <-10.5>"); AssertionFailedError e3 = assertThrows(AssertionFailedError.class, () -> assertEquals(1.1d, 1.1d, Double.NaN)); assertMessageEndsWith(e3, "positive delta expected but was: "); } @Test void assertEqualsDoubleWithDeltaWithUnequalValues() { AssertionFailedError e1 = assertThrows(AssertionFailedError.class, () -> assertEquals(9.9d, 9.7d, 0.1d)); assertMessageEndsWith(e1, "expected: <9.9> but was: <9.7>"); assertExpectedAndActualValues(e1, 9.9d, 9.7d); AssertionFailedError e2 = assertThrows(AssertionFailedError.class, () -> assertEquals(0.1d, 0.05d, 0.001d)); assertMessageEndsWith(e2, "expected: <0.1> but was: <0.05>"); assertExpectedAndActualValues(e2, 0.1d, 0.05d); AssertionFailedError e3 = assertThrows(AssertionFailedError.class, () -> assertEquals(17.11d, 15.11d, 1.1d)); assertMessageEndsWith(e3, "expected: <17.11> but was: <15.11>"); assertExpectedAndActualValues(e3, 17.11d, 15.11d); AssertionFailedError e4 = assertThrows(AssertionFailedError.class, () -> assertEquals(-7.2d, -5.9d, 1.1d)); assertMessageEndsWith(e4, "expected: <-7.2> but was: <-5.9>"); assertExpectedAndActualValues(e4, -7.2d, -5.9d); AssertionFailedError e5 = assertThrows(AssertionFailedError.class, () -> assertEquals(+0.0d, -0.001d, .00001d)); assertMessageEndsWith(e5, "expected: <0.0> but was: <-0.001>"); assertExpectedAndActualValues(e5, +0.0d, -0.001d); } @Test void assertEqualsDoubleWithDeltaWithUnequalValuesAndMessage() { Executable assertion = () -> assertEquals(42.42d, 42.4d, 0.001d, "message"); AssertionFailedError e = assertThrows(AssertionFailedError.class, assertion); assertMessageStartsWith(e, "message"); assertMessageEndsWith(e, "expected: <42.42> but was: <42.4>"); assertExpectedAndActualValues(e, 42.42d, 42.4d); } @Test void assertEqualsDoubleWithDeltaWithUnequalValuesAndMessageSupplier() { Executable assertion = () -> assertEquals(0.9d, 10.12d, 5.001d, () -> "message"); AssertionFailedError e = assertThrows(AssertionFailedError.class, assertion); assertMessageStartsWith(e, "message"); assertMessageEndsWith(e, "expected: <0.9> but was: <10.12>"); assertExpectedAndActualValues(e, 0.9d, 10.12d); } @Test void assertEqualsWithNullReferences() { Object null1 = null; Object null2 = null; assertEquals(null1, null); assertEquals(null, null2); assertEquals(null1, null2); } @Test void assertEqualsWithSameObject() { Object foo = new Object(); assertEquals(foo, foo); assertEquals(foo, foo, "message"); assertEquals(foo, foo, () -> "message"); } @Test void assertEqualsWithEquivalentStrings() { assertEquals(new String("foo"), new String("foo")); } @Test void assertEqualsWithNullVsObject() { try { assertEquals(null, "foo"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected: but was: "); assertExpectedAndActualValues(ex, null, "foo"); } } @Test void assertEqualsWithObjectVsNull() { try { assertEquals("foo", null); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected: but was: "); assertExpectedAndActualValues(ex, "foo", null); } } @Test void assertEqualsWithObjectWithNullStringReturnedFromToStringVsNull() { try { assertEquals("null", null); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "expected: java.lang.String@"); assertMessageEndsWith(ex, " but was: "); assertExpectedAndActualValues(ex, "null", null); } } @Test void assertEqualsWithNullVsObjectWithNullStringReturnedFromToString() { try { assertEquals(null, "null"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "expected: but was: java.lang.String@"); assertMessageEndsWith(ex, ""); assertExpectedAndActualValues(ex, null, "null"); } } @Test void assertEqualsWithNullVsObjectAndMessageSupplier() { try { assertEquals(null, "foo", () -> "test"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "test"); assertMessageEndsWith(ex, "expected: but was: "); assertExpectedAndActualValues(ex, null, "foo"); } } @Test void assertEqualsWithObjectVsNullAndMessageSupplier() { try { assertEquals("foo", null, () -> "test"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "test"); assertMessageEndsWith(ex, "expected: but was: "); assertExpectedAndActualValues(ex, "foo", null); } } @Test void assertEqualsInvokesEqualsMethodForIdenticalObjects() { Object obj = new EqualsThrowsException(); assertThrows(NumberFormatException.class, () -> assertEquals(obj, obj)); } @Test void assertEqualsWithUnequalObjectWhoseToStringImplementationThrowsAnException() { try { assertEquals(new ToStringThrowsException(), "foo"); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "expected: <" + ToStringThrowsException.class.getName() + "@"); assertMessageEndsWith(ex, "but was: "); } } // ------------------------------------------------------------------------- @Nested class MixedBoxedAndUnboxedPrimitivesTests { @Test void bytes() { byte primitive = (byte) 42; Byte wrapper = Byte.valueOf("42"); assertEquals(primitive, wrapper); assertEquals(primitive, wrapper, "message"); assertEquals(primitive, wrapper, () -> "message"); assertEquals(wrapper, primitive); assertEquals(wrapper, primitive, "message"); assertEquals(wrapper, primitive, () -> "message"); } @Test void shorts() { short primitive = (short) 42; Short wrapper = Short.valueOf("42"); assertEquals(primitive, wrapper); assertEquals(primitive, wrapper, "message"); assertEquals(primitive, wrapper, () -> "message"); assertEquals(wrapper, primitive); assertEquals(wrapper, primitive, "message"); assertEquals(wrapper, primitive, () -> "message"); } @Test void integers() { int primitive = 42; Integer wrapper = Integer.valueOf("42"); assertEquals(primitive, wrapper); assertEquals(primitive, wrapper, "message"); assertEquals(primitive, wrapper, () -> "message"); assertEquals(wrapper, primitive); assertEquals(wrapper, primitive, "message"); assertEquals(wrapper, primitive, () -> "message"); } @Test void longs() { long primitive = 42L; Long wrapper = Long.valueOf("42"); assertEquals(primitive, wrapper); assertEquals(primitive, wrapper, "message"); assertEquals(primitive, wrapper, () -> "message"); assertEquals(wrapper, primitive); assertEquals(wrapper, primitive, "message"); assertEquals(wrapper, primitive, () -> "message"); } @Test void floats() { float primitive = 42.0f; Float wrapper = Float.valueOf("42.0"); assertEquals(primitive, wrapper); assertEquals(primitive, wrapper, 0.0f); assertEquals(primitive, wrapper, "message"); assertEquals(primitive, wrapper, 0.0f, "message"); assertEquals(primitive, wrapper, () -> "message"); assertEquals(primitive, wrapper, 0.0f, () -> "message"); assertEquals(wrapper, primitive); assertEquals(wrapper, primitive, 0.0f); assertEquals(wrapper, primitive, "message"); assertEquals(wrapper, primitive, 0.0f, "message"); assertEquals(wrapper, primitive, () -> "message"); assertEquals(wrapper, primitive, 0.0f, () -> "message"); } @Test void doubles() { double primitive = 42.0d; Double wrapper = Double.valueOf("42.0"); assertEquals(primitive, wrapper); assertEquals(primitive, wrapper, 0.0d); assertEquals(primitive, wrapper, "message"); assertEquals(primitive, wrapper, 0.0d, "message"); assertEquals(primitive, wrapper, () -> "message"); assertEquals(primitive, wrapper, 0.0d, () -> "message"); assertEquals(wrapper, primitive); assertEquals(wrapper, primitive, 0.0d); assertEquals(wrapper, primitive, "message"); assertEquals(wrapper, primitive, 0.0d, "message"); assertEquals(wrapper, primitive, () -> "message"); assertEquals(wrapper, primitive, 0.0d, () -> "message"); } @Test void booleans() { boolean primitive = true; Boolean wrapper = Boolean.valueOf("true"); assertEquals(primitive, wrapper); assertEquals(primitive, wrapper, "message"); assertEquals(primitive, wrapper, () -> "message"); assertEquals(wrapper, primitive); assertEquals(wrapper, primitive, "message"); assertEquals(wrapper, primitive, () -> "message"); } @Test void chars() { char primitive = 'a'; Character wrapper = Character.valueOf('a'); assertEquals(primitive, wrapper); assertEquals(primitive, wrapper, "message"); assertEquals(primitive, wrapper, () -> "message"); assertEquals(wrapper, primitive); assertEquals(wrapper, primitive, "message"); assertEquals(wrapper, primitive, () -> "message"); } } // ------------------------------------------------------------------------- @SuppressWarnings("overrides") private static class EqualsThrowsException { @Override public boolean equals(Object obj) { throw new NumberFormatException(); } } private static class ToStringThrowsException { @Override public String toString() { throw new NumberFormatException(); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/AssertFalseAssertionsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.junit.jupiter.api.AssertionTestUtils.assertExpectedAndActualValues; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; import static org.junit.jupiter.api.Assertions.assertFalse; import org.opentest4j.AssertionFailedError; /** * Unit tests for JUnit Jupiter {@link Assertions}. * * @since 5.0 */ class AssertFalseAssertionsTests { @Test void assertFalseWithBooleanFalse() { assertFalse(false); assertFalse(false, "test"); assertFalse(false, () -> "test"); } @Test void assertFalseWithBooleanSupplierFalse() { assertFalse(() -> false); assertFalse(() -> false, "test"); assertFalse(() -> false, () -> "test"); } @Test void assertFalseWithBooleanFalseAndMessageSupplier() { assertFalse(false, () -> "test"); } @Test void assertFalseWithBooleanSupplierFalseAndMessageSupplier() { assertFalse(() -> false, () -> "test"); } @Test void assertFalseWithBooleanTrueAndDefaultMessageWithExpectedAndActualValues() { try { assertFalse(true); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected: but was: "); assertExpectedAndActualValues(ex, false, true); } } @Test void assertFalseWithBooleanTrueAndString() { try { assertFalse(true, "test"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "test ==> expected: but was: "); assertExpectedAndActualValues(ex, false, true); } } @Test void assertFalseWithBooleanSupplierTrueAndString() { try { assertFalse(() -> true, "test"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "test ==> expected: but was: "); assertExpectedAndActualValues(ex, false, true); } } @Test void assertFalseWithBooleanTrueAndMessageSupplier() { try { assertFalse(true, () -> "test"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "test ==> expected: but was: "); assertExpectedAndActualValues(ex, false, true); } } @Test void assertFalseWithBooleanSupplierTrueAndMessageSupplier() { try { assertFalse(() -> true, () -> "test"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "test ==> expected: but was: "); assertExpectedAndActualValues(ex, false, true); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/AssertInstanceOfAssertionsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import java.io.IOException; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.function.Executable; import org.opentest4j.AssertionFailedError; /** * Unit tests for JUnit Jupiter {@link Assertions#assertInstanceOf(Class, Object)}. * * @since 5.8 */ class AssertInstanceOfAssertionsTests { @Test void assertInstanceOfFailsNullValue() { assertInstanceOfFails(String.class, null, "null value"); } @Test void assertInstanceOfFailsWrongTypeValue() { assertInstanceOfFails(String.class, 1, "type"); } @Test void assertInstanceOfFailsWrongExceptionValue() { assertInstanceOfFails(RuntimeException.class, new IOException(), "type"); } @Test void assertInstanceOfFailsSuperTypeExceptionValue() { assertInstanceOfFails(IllegalArgumentException.class, new RuntimeException(), "type"); } private static class BaseClass { } private static class SubClass extends BaseClass { } @Test void assertInstanceOfFailsSuperTypeValue() { assertInstanceOfFails(SubClass.class, new BaseClass(), "type"); } @Test void assertInstanceOfSucceedsSameTypeValue() { assertInstanceOfSucceeds(String.class, "indeed a String"); assertInstanceOfSucceeds(BaseClass.class, new BaseClass()); assertInstanceOfSucceeds(SubClass.class, new SubClass()); } @Test void assertInstanceOfSucceedsExpectSuperClassOfValue() { assertInstanceOfSucceeds(CharSequence.class, "indeed a CharSequence"); assertInstanceOfSucceeds(BaseClass.class, new SubClass()); } @Test void assertInstanceOfSucceedsSameTypeExceptionValue() { assertInstanceOfSucceeds(UnsupportedOperationException.class, new UnsupportedOperationException()); } @Test void assertInstanceOfSucceedsExpectSuperClassOfExceptionValue() { assertInstanceOfSucceeds(RuntimeException.class, new IllegalArgumentException("is a RuntimeException")); } private void assertInstanceOfSucceeds(Class expectedType, Object actualValue) { T res = assertInstanceOf(expectedType, actualValue); assertSame(res, actualValue); res = assertInstanceOf(expectedType, actualValue, "extra"); assertSame(res, actualValue); res = assertInstanceOf(expectedType, actualValue, () -> "extra"); assertSame(res, actualValue); } private void assertInstanceOfFails(Class expectedType, @Nullable Object actualValue, String unexpectedSort) { String valueType = actualValue == null ? "null" : actualValue.getClass().getCanonicalName(); String expectedMessage = "Unexpected %s, expected: <%s> but was: <%s>".formatted(unexpectedSort, expectedType.getCanonicalName(), valueType); Throwable expectedCause = actualValue instanceof Throwable throwable ? throwable : null; assertThrowsWithMessage(expectedMessage, expectedCause, () -> assertInstanceOf(expectedType, actualValue)); assertThrowsWithMessage("extra ==> " + expectedMessage, expectedCause, () -> assertInstanceOf(expectedType, actualValue, "extra")); assertThrowsWithMessage("extra ==> " + expectedMessage, expectedCause, () -> assertInstanceOf(expectedType, actualValue, () -> "extra")); } private void assertThrowsWithMessage(String expectedMessage, @Nullable Throwable expectedCause, Executable executable) { Throwable throwable = assertThrows(AssertionFailedError.class, executable); assertEquals(expectedMessage, throwable.getMessage()); assertSame(expectedCause, throwable.getCause()); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/AssertIterableEqualsAssertionsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEndsWith; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertIterableEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.IterableFactory.listOf; import static org.junit.jupiter.api.IterableFactory.setOf; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Set; import org.opentest4j.AssertionFailedError; /** * Unit tests for JUnit Jupiter {@link Assertions}. * * @since 5.0 */ class AssertIterableEqualsAssertionsTests { @Test void assertIterableEqualsEqualToSelf() { List list = listOf("a", 'b', 1, 2); assertIterableEquals(list, list); assertIterableEquals(list, list, "message"); assertIterableEquals(list, list, () -> "message"); Set set = setOf("a", 'b', 1, 2); assertIterableEquals(set, set); } @Test void assertIterableEqualsEqualObjectsOfSameType() { assertIterableEquals(listOf(), listOf()); assertIterableEquals(listOf("abc"), listOf("abc")); assertIterableEquals(listOf("abc", 1, 2L, 3D), listOf("abc", 1, 2L, 3D)); assertIterableEquals(setOf(), setOf()); assertIterableEquals(setOf("abc"), setOf("abc")); assertIterableEquals(setOf("abc", 1, 2L, 3D), setOf("abc", 1, 2L, 3D)); } @Test void assertIterableEqualsNestedIterables() { assertIterableEquals(listOf(listOf(listOf())), listOf(listOf(listOf()))); assertIterableEquals(setOf(setOf(setOf())), setOf(setOf(setOf()))); } @Test void assertIterableEqualsNestedIterablesWithNull() { assertIterableEquals(listOf(null, listOf(null, listOf(null, null)), null, listOf((List) null)), listOf(null, listOf(null, listOf(null, null)), null, listOf((List) null))); assertIterableEquals(setOf(null, setOf(null, setOf(null, null)), null, setOf((Set) null)), setOf(null, setOf(null, setOf(null, null)), null, setOf((Set) null))); } @Test void assertIterableEqualsNestedIterablesWithStrings() { assertIterableEquals(listOf("a", listOf(listOf("b", listOf("c", "d"))), "e"), listOf("a", listOf(listOf("b", listOf("c", "d"))), "e")); assertIterableEquals(setOf("a", setOf(setOf("b", setOf("c", "d"))), "e"), setOf("a", setOf(setOf("b", setOf("c", "d"))), "e")); } @Test void assertIterableEqualsNestedIterablesWithIntegers() { assertIterableEquals(listOf(listOf(1), listOf(2), listOf(listOf(3, listOf(4)))), listOf(listOf(1), listOf(2), listOf(listOf(3, listOf(4))))); assertIterableEquals(setOf(setOf(1), setOf(2), setOf(setOf(3, setOf(4)))), setOf(setOf(1), setOf(2), setOf(setOf(3, setOf(4))))); assertIterableEquals(listOf(listOf(1), listOf(listOf(1))), setOf(setOf(1), setOf(setOf(1)))); } @Test void assertIterableEqualsNestedIterablesWithDeeplyNestedObject() { assertIterableEquals(listOf(listOf(listOf(listOf(listOf(listOf(listOf("abc"))))))), listOf(listOf(listOf(listOf(listOf(listOf(listOf("abc")))))))); assertIterableEquals(setOf(setOf(setOf(setOf(setOf(setOf(setOf("abc"))))))), setOf(setOf(setOf(setOf(setOf(setOf(setOf("abc")))))))); } @Test void assertIterableEqualsNestedIterablesWithNaN() { assertIterableEquals(listOf(null, listOf(null, Double.NaN, listOf(Float.NaN, null, listOf()))), listOf(null, listOf(null, Double.NaN, listOf(Float.NaN, null, listOf())))); assertIterableEquals(setOf(null, setOf(null, Double.NaN, setOf(Float.NaN, null, setOf()))), setOf(null, setOf(null, Double.NaN, setOf(Float.NaN, null, setOf())))); } @Test void assertIterableEqualsNestedIterablesWithObjectsOfDifferentTypes() { assertIterableEquals(listOf(new String("a"), Integer.valueOf(1), listOf(Double.parseDouble("1.1"), "b")), listOf(new String("a"), Integer.valueOf(1), listOf(Double.parseDouble("1.1"), "b"))); assertIterableEquals(setOf(new String("a"), Integer.valueOf(1), setOf(Double.parseDouble("1.1"), "b")), setOf(new String("a"), Integer.valueOf(1), setOf(Double.parseDouble("1.1"), "b"))); } @Test void assertIterableEqualsNestedIterablesOfMixedSubtypes() { assertIterableEquals( listOf(1, 2, listOf(3, setOf(4, 5), setOf(6L), listOf(listOf(setOf(7)))), setOf(8), listOf(setOf(9L))), listOf(1, 2, listOf(3, setOf(4, 5), setOf(6L), listOf(listOf(setOf(7)))), setOf(8), listOf(setOf(9L)))); assertIterableEquals( listOf("a", setOf('b', 'c'), setOf((int) 'd'), listOf(listOf(listOf("ef"), listOf(listOf("ghi"))))), setOf("a", listOf('b', 'c'), listOf((int) 'd'), setOf(setOf(setOf("ef"), setOf(setOf("ghi")))))); } @Test void assertIterableEqualsIterableVsNull() { try { assertIterableEquals(null, listOf("a", "b", 1, listOf())); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected iterable was "); } try { assertIterableEquals(listOf('a', 1, new Object(), 10L), null); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "actual iterable was "); } } @Test void assertIterableEqualsNestedIterableVsNull() { try { assertIterableEquals(listOf(listOf(), 1, "2", setOf('3', listOf((List) null))), listOf(listOf(), 1, "2", setOf('3', listOf(listOf("4"))))); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected iterable was at index [3][1][0]"); } try { assertIterableEquals(setOf(1, 2, listOf(3, listOf("4", setOf(5, setOf(6)))), "7"), setOf(1, 2, listOf(3, listOf("4", setOf(5, null))), "7")); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "actual iterable was at index [2][1][1][1]"); } } @Test void assertIterableEqualsIterableVsNullAndMessage() { try { assertIterableEquals(null, listOf('a', "b", 10, 20D), "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected iterable was "); } try { assertIterableEquals(listOf("hello", 42), null, "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "actual iterable was "); } } @Test void assertIterableEqualsNestedIterableVsNullAndMessage() { try { assertIterableEquals(listOf(1, listOf(2, 3, listOf(4, 5, listOf((List) null)))), listOf(1, listOf(2, 3, listOf(4, 5, listOf(listOf(6))))), "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected iterable was at index [1][2][2][0]"); } try { assertIterableEquals(listOf(1, listOf(2, listOf(3, listOf(listOf(4))))), listOf(1, listOf(2, listOf(3, listOf((List) null)))), "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "actual iterable was at index [1][1][1][0]"); } } @Test void assertIterableEqualsIterableVsNullAndMessageSupplier() { try { assertIterableEquals(null, setOf(42, "42", listOf(42F), 42D), () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected iterable was "); } try { assertIterableEquals(listOf(listOf("a"), listOf()), null, () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "actual iterable was "); } } @Test void assertIterableEqualsNestedIterableVsNullAndMessageSupplier() { try { assertIterableEquals(listOf("1", "2", "3", listOf("4", listOf((List) null))), listOf("1", "2", "3", listOf("4", listOf(listOf(5)))), () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "expected iterable was at index [3][1][0]"); } try { assertIterableEquals(setOf(1, 2, setOf("3", setOf('4', setOf(5, 6, setOf())))), setOf(1, 2, setOf("3", setOf('4', setOf(5, 6, null)))), () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "actual iterable was at index [2][1][1][2]"); } } @Test void assertIterableEqualsIterablesOfDifferentLength() { try { assertIterableEquals(listOf('a', "b", 'c'), listOf('a', "b", 'c', 1)); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "iterable lengths differ, expected: <3> but was: <4>"); } } @Test void assertIterableEqualsNestedIterablesOfDifferentLength() { try { assertIterableEquals(listOf("a", setOf("b", listOf("c", "d", setOf("e", 1, 2, 3)))), listOf("a", setOf("b", listOf("c", "d", setOf("e", 1, 2, 3, 4, 5))))); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "iterable lengths differ at index [1][1][2], expected: <4> but was: <6>"); } try { assertIterableEquals(listOf(listOf(listOf(listOf(listOf(listOf(listOf('a'))))))), listOf(listOf(listOf(listOf(listOf(listOf(listOf('a', 'b')))))))); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "iterable lengths differ at index [0][0][0][0][0][0], expected: <1> but was: <2>"); } } @Test void assertIterableEqualsIterablesOfDifferentLengthAndMessage() { try { assertIterableEquals(setOf('a', 1), setOf('a', 1, new Object()), "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "iterable lengths differ, expected: <2> but was: <3>"); } } @Test void assertIterableEqualsNestedIterablesOfDifferentLengthAndMessage() { try { assertIterableEquals(listOf('a', 1, listOf(2, 3)), listOf('a', 1, listOf(2, 3, 4, 5)), "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "iterable lengths differ at index [2], expected: <2> but was: <4>"); } } @Test void assertIterableEqualsIterablesOfDifferentLengthAndMessageSupplier() { try { assertIterableEquals(setOf("a", "b", "c"), setOf("a", "b", "c", "d", "e", "f"), () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "iterable lengths differ, expected: <3> but was: <6>"); } } @Test void assertIterableEqualsNestedIterablesOfDifferentLengthAndMessageSupplier() { try { assertIterableEquals(listOf("a", setOf(1, 2, 3, listOf(4.0, 5.1, 6.1), 7)), listOf("a", setOf(1, 2, 3, listOf(4.0, 5.1, 6.1, 7.0), 8)), () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "iterable lengths differ at index [1][3], expected: <3> but was: <4>"); } } @Test void assertIterableEqualsDifferentIterables() { try { assertIterableEquals(listOf(1L, "2", '3', 4, 5D), listOf(1L, "2", '9', 4, 5D)); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "iterable contents differ at index [2], expected: <3> but was: <9>"); } try { assertIterableEquals(listOf("a", 10, 11, 12, Double.NaN), listOf("a", 10, 11, 12, 13.55D)); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "iterable contents differ at index [4], expected: but was: <13.55>"); } } @Test void assertIterableEqualsDifferentNestedIterables() { try { assertIterableEquals(listOf(1, 2, listOf(3, listOf(4, listOf(false, true)))), listOf(1, 2, listOf(3, listOf(4, listOf(true, false))))); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "iterable contents differ at index [2][1][1][0], expected: but was: "); } List differentElement = listOf(); try { assertIterableEquals(listOf(1, 2, 3, listOf(listOf(4, listOf(5)))), listOf(1, 2, 3, listOf(listOf(4, listOf(differentElement))))); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "iterable contents differ at index [3][0][1][0], expected: <5> but was: <" + differentElement + ">"); } } @Test void assertIterableEqualsDifferentIterablesAndMessage() { try { assertIterableEquals(listOf(1.1D, 2L, "3"), listOf(1D, 2L, "3"), "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "iterable contents differ at index [0], expected: <1.1> but was: <1.0>"); } } @Test void assertIterableEqualsDifferentNestedIterablesAndMessage() { try { assertIterableEquals(listOf(9, 8, '6', listOf(5, 4, "3", listOf("2", '1'))), listOf(9, 8, '6', listOf(5, 4, "3", listOf("99", '1'))), "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "iterable contents differ at index [3][3][0], expected: <2> but was: <99>"); } try { assertIterableEquals(listOf(9, 8, '6', listOf(5, 4, "3", listOf("2", "1"))), listOf(9, 8, '6', listOf(5, 4, "3", listOf("99", "1"))), "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "iterable contents differ at index [3][3][0], expected: <2> but was: <99>"); } } @Test void assertIterableEqualsDifferentIterablesAndMessageSupplier() { try { assertIterableEquals(setOf("one", 1L, Double.MIN_VALUE, "abc"), setOf("one", 1L, 42.42, "abc"), () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "iterable contents differ at index [2], expected: <4.9E-324> but was: <42.42>"); } } @Test void assertIterableEqualsDifferentNestedIterablesAndMessageSupplier() { try { assertIterableEquals(setOf("one", 1L, setOf("a", 'b', setOf(1, setOf(2, 3))), "abc"), setOf("one", 1L, setOf("a", 'b', setOf(1, setOf(2, 4))), "abc"), () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "iterable contents differ at index [2][2][1][1], expected: <3> but was: <4>"); } try { assertIterableEquals(listOf("j", listOf("a"), setOf(42), "ab", setOf(1, listOf(3))), listOf("j", listOf("a"), setOf(42), "ab", setOf(1, listOf(5))), () -> "message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "message"); assertMessageEndsWith(ex, "iterable contents differ at index [4][1][0], expected: <3> but was: <5>"); } } @Test // https://github.com/junit-team/junit-framework/issues/2157 void assertIterableEqualsWithListOfPath() { var expected = listOf(Path.of("1")); var actual = listOf(Path.of("1")); assertDoesNotThrow(() -> assertIterableEquals(expected, actual)); } @Test void assertIterableEqualsThrowsStackOverflowErrorForInterlockedRecursiveStructures() { var expected = new ArrayList<>(); var actual = new ArrayList<>(); actual.add(expected); expected.add(actual); assertThrows(StackOverflowError.class, () -> assertIterableEquals(expected, actual)); } @Test // https://github.com/junit-team/junit-framework/issues/2915 void assertIterableEqualsWithDifferentListOfPath() { try { var expected = listOf(Path.of("1").resolve("2")); var actual = listOf(Path.of("1").resolve("3")); assertIterableEquals(expected, actual); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "iterable contents differ at index [0][1], expected: <2> but was: <3>"); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/AssertLinesMatchAssertionsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.junit.jupiter.api.AssertLinesMatch.isFastForwardLine; import static org.junit.jupiter.api.AssertLinesMatch.parseFastForwardLimit; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertLinesMatch; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullFor; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Random; import java.util.function.Supplier; import java.util.stream.IntStream; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; import org.opentest4j.AssertionFailedError; /** * Unit tests for JUnit Jupiter {@link Assertions}. * * @since 5.0 */ class AssertLinesMatchAssertionsTests { @Test void assertLinesMatchEmptyLists() { assertLinesMatch(Collections.emptyList(), new ArrayList<>()); } @Test void assertLinesMatchSameListInstance() { List list = List.of("first line", "second line", "third line", "last line"); assertLinesMatch(list, list); } @Test void assertLinesMatchPlainEqualLists() { List expected = List.of("first line", "second line", "third line", "last line"); List actual = List.of("first line", "second line", "third line", "last line"); assertLinesMatch(expected, actual); } @Test void assertLinesMatchUsingRegexPatterns() { List expected = List.of("^first.+line", "second\\s*line", "th.rd l.ne", "last line$"); List actual = List.of("first line", "second line", "third line", "last line"); assertLinesMatch(expected, actual); } @Test void assertLinesMatchUsingFastForwardMarkerAtEndOfExpectedLines() { List expected = List.of("first line", ">> ignore all following lines >>"); List actual = List.of("first line", "I", "II", "III", "IV", "V", "VI", "last line"); assertLinesMatch(expected, actual); } @Test void assertLinesMatchUsingFastForwardMarker() { List expected = List.of("first line", ">> skip lines until next matches >>", "V", "last line"); List actual = List.of("first line", "I", "II", "III", "IV", "V", "last line"); assertLinesMatch(expected, actual); } @Test void assertLinesMatchUsingFastForwardMarkerWithLimit1() { List expected = List.of("first line", ">> 1 >>", "last line"); List actual = List.of("first line", "skipped", "last line"); assertLinesMatch(expected, actual); } @Test void assertLinesMatchUsingFastForwardMarkerWithLimit3() { List expected = List.of(">> 3 >>"); List actual = List.of("first line", "skipped", "last line"); assertLinesMatch(expected, actual); } @Test @SuppressWarnings({ "unchecked", "rawtypes", "DataFlowIssue" }) void assertLinesMatchWithNullFails() { assertPreconditionViolationFor(() -> assertLinesMatch(null, (List) null)); assertPreconditionViolationFor(() -> assertLinesMatch(null, Collections.emptyList())); assertPreconditionViolationFor(() -> assertLinesMatch(Collections.emptyList(), null)); } @Test void assertLinesMatchWithNullElementsFails() { var list = List.of("1", "2", "3"); var withNullElement = Arrays.asList("1", null, "3"); // List.of() doesn't permit null values. assertDoesNotThrow(() -> assertLinesMatch(withNullElement, withNullElement)); assertPreconditionViolationNotNullFor("expected line", () -> assertLinesMatch(withNullElement, list)); assertPreconditionViolationNotNullFor("actual line", () -> assertLinesMatch(list, withNullElement)); } private void assertError(AssertionFailedError error, String expectedMessage, List expectedLines, List actualLines) { assertEquals(expectedMessage, error.getMessage()); assertEquals(String.join(System.lineSeparator(), expectedLines), error.getExpected().getStringRepresentation()); assertEquals(String.join(System.lineSeparator(), actualLines), error.getActual().getStringRepresentation()); } @Test void assertLinesMatchMoreExpectedThanActualAvailableFails() { var expected = List.of("first line", "second line", "third line"); var actual = List.of("first line", "third line"); var error = assertThrows(AssertionFailedError.class, () -> assertLinesMatch(expected, actual)); assertError(error, "expected 3 lines, but only got 2", expected, actual); } @Test void assertLinesMatchFailsWithDescriptiveErrorMessage() { var expected = List.of("first line", "second line", "third line"); var actual = List.of("first line", "sec0nd line", "third line"); var error = assertThrows(AssertionFailedError.class, () -> assertLinesMatch(expected, actual)); var expectedMessage = String.join(System.lineSeparator(), List.of( // "expected line #2 doesn't match actual line #2", // "\texpected: `second line`", // "\t actual: `sec0nd line`")); assertError(error, expectedMessage, expected, actual); } @Test void assertLinesMatchMoreActualLinesThenExpectedFails() { var expected = List.of("first line", "second line", "third line"); var actual = List.of("first line", "second line", "third line", "last line"); var error = assertThrows(AssertionFailedError.class, () -> assertLinesMatch(expected, actual)); assertError(error, "more actual lines than expected: 1", expected, actual); } @Test void assertLinesMatchUsingFastForwardMarkerWithTooLowLimitFails() { var expected = List.of("first line", ">> 1 >>"); var actual = List.of("first line", "skipped", "last line"); var error = assertThrows(AssertionFailedError.class, () -> assertLinesMatch(expected, actual)); assertError(error, "terminal fast-forward(1) error: fast-forward(2) expected", expected, actual); } @Test void assertLinesMatchUsingFastForwardMarkerWithTooHighLimitFails() { var expected = List.of("first line", ">> 100 >>"); var actual = List.of("first line", "skipped", "last line"); var error = assertThrows(AssertionFailedError.class, () -> assertLinesMatch(expected, actual)); assertError(error, "terminal fast-forward(100) error: fast-forward(2) expected", expected, actual); } @Test void assertLinesMatchUsingFastForwardMarkerWithTooHighLimitAndFollowingLineFails() { /* * It is important here that the line counts are expected <= actual, that the * fast-forward exceeds the available actual lines and that it is not a * terminal fast-forward. */ var expected = List.of("first line", ">> 3 >>", "not present"); var actual = List.of("first line", "first skipped", "second skipped"); var error = assertThrows(AssertionFailedError.class, () -> assertLinesMatch(expected, actual)); assertError(error, "fast-forward(3) error: not enough actual lines remaining (2)", expected, actual); } @Test void assertLinesMatchUsingFastForwardMarkerWithoutMatchingNextLineFails() { var expected = List.of("first line", ">> fails, because next line is >>", "not present"); var actual = List.of("first line", "skipped", "last line"); var error = assertThrows(AssertionFailedError.class, () -> assertLinesMatch(expected, actual)); assertError(error, "fast-forward(∞) didn't find: `not present`", expected, actual); } @Test void assertLinesMatchUsingFastForwardMarkerWithExtraExpectLineFails() { var expected = List.of("first line", ">> fails, because final line is missing >>", "last line", "not present"); var actual = List.of("first line", "first skipped", "second skipped", "last line"); var error = assertThrows(AssertionFailedError.class, () -> assertLinesMatch(expected, actual)); assertError(error, "expected line #4:`not present` not found - actual lines depleted", expected, actual); } @Test void assertLinesMatchIsFastForwardLine() { assertAll("valid fast-forward lines", // () -> assertTrue(isFastForwardLine(">>>>")), () -> assertTrue(isFastForwardLine(">> >>")), () -> assertTrue(isFastForwardLine(">> stacktrace >>")), () -> assertTrue(isFastForwardLine(">> single line, non Integer.parse()-able comment >>")), () -> assertTrue(isFastForwardLine(">>9>>")), () -> assertTrue(isFastForwardLine(">> 9 >>")), () -> assertTrue(isFastForwardLine(">> -9 >>")), () -> assertTrue(isFastForwardLine(" >> 9 >> ")), () -> assertTrue(isFastForwardLine(" >> 9 >> "))); } @Test void assertLinesMatchParseFastForwardLimit() { assertAll("valid fast-forward limits", // () -> assertEquals(Integer.MAX_VALUE, parseFastForwardLimit(">>>>")), () -> assertEquals(Integer.MAX_VALUE, parseFastForwardLimit(">> >>")), () -> assertEquals(Integer.MAX_VALUE, parseFastForwardLimit(">> stacktrace >>")), () -> assertEquals(Integer.MAX_VALUE, parseFastForwardLimit(">> non Integer.parse()-able comment >>")), () -> assertEquals(9, parseFastForwardLimit(">>9>>")), () -> assertEquals(9, parseFastForwardLimit(">> 9 >>")), () -> assertEquals(9, parseFastForwardLimit(" >> 9 >> ")), () -> assertEquals(9, parseFastForwardLimit(" >> 9 >> "))); assertPreconditionViolationFor(() -> parseFastForwardLimit(">>0>>"))// .withMessage("fast-forward(0) limit must be greater than zero"); assertPreconditionViolationFor(() -> parseFastForwardLimit(">>-1>>"))// .withMessage("fast-forward(-1) limit must be greater than zero"); assertPreconditionViolationFor(() -> parseFastForwardLimit(">>-2147483648>>"))// .withMessage("fast-forward(-2147483648) limit must be greater than zero"); } @Test void assertLinesMatchMatches() { Random random = new Random(); assertAll("do match", // () -> assertTrue( AssertLinesMatch.matches("duration: [\\d]+ ms", "duration: " + random.nextInt(1000) + " ms")), () -> assertTrue(AssertLinesMatch.matches("123", "123")), () -> assertTrue(AssertLinesMatch.matches(".*", "123")), () -> assertTrue(AssertLinesMatch.matches("\\d+", "123"))); assertAll("don't match", // () -> assertFalse( AssertLinesMatch.matches("duration: [\\d]+ ms", "duration: " + random.nextGaussian() + " ms")), () -> assertFalse(AssertLinesMatch.matches("12", "123")), () -> assertFalse(AssertLinesMatch.matches("..+", "1")), () -> assertFalse(AssertLinesMatch.matches("\\d\\d+", "1"))); } @Test void largeListsThatDoNotMatchAreTruncated() { var expected = IntStream.range(1, 999).boxed().map(Object::toString).toList(); var actual = IntStream.range(0, 1000).boxed().map(Object::toString).toList(); var error = assertThrows(AssertionFailedError.class, () -> assertLinesMatch(expected, actual, "custom message")); var expectedMessage = String.join(System.lineSeparator(), List.of( // "custom message ==> expected line #1 doesn't match actual line #1", // "\texpected: `1`", // "\t actual: `0`")); assertError(error, expectedMessage, expected, actual); } /** * @since 5.5 */ @Nested class WithCustomFailureMessage { @Test void simpleStringMessage() { String message = "XXX"; var expected = List.of("a", "b", "c"); var actual = List.of("a", "d", "c"); var error = assertThrows(AssertionFailedError.class, () -> assertLinesMatch(expected, actual, message)); var expectedMessage = String.join(System.lineSeparator(), List.of( // message + " ==> expected line #2 doesn't match actual line #2", // "\texpected: `b`", // "\t actual: `d`")); assertError(error, expectedMessage, expected, actual); } @Test void stringSupplierWithMultiLineMessage() { var message = "XXX\nYYY"; Supplier<@Nullable String> supplier = () -> message; var expected = List.of("a", "b", "c"); var actual = List.of("a", "d", "c"); var error = assertThrows(AssertionFailedError.class, () -> assertLinesMatch(expected, actual, supplier)); var expectedMessage = String.join(System.lineSeparator(), List.of( // message + " ==> expected line #2 doesn't match actual line #2", // "\texpected: `b`", // "\t actual: `d`")); assertError(error, expectedMessage, expected, actual); } } @Nested class WithStreamsOfStrings { @Test void assertLinesMatchEmptyStreams() { assertLinesMatch(Stream.empty(), Stream.empty()); } @Test void assertLinesMatchSameListInstance() { Stream stream = Stream.of("first line", "second line", "third line", "last line"); assertLinesMatch(stream, stream); } @Test void assertLinesMatchPlainEqualLists() { var expected = """ first line second line third line last line """; var actual = """ first line second line third line last line """; assertLinesMatch(expected.lines(), actual.lines()); } @Test void assertLinesMatchUsingRegexPatterns() { var expected = """ ^first.+line second\\s*line th.rd l.ne last line$ """; var actual = """ first line second line third line last line """; assertLinesMatch(expected.lines(), actual.lines()); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/AssertNotEqualsAssertionsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEndsWith; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import org.opentest4j.AssertionFailedError; /** * Unit tests for JUnit Jupiter {@link Assertions}. * * @since 5.0 */ class AssertNotEqualsAssertionsTests { @Nested class AssertNotEqualsByte { @Test void assertNotEqualsByte() { byte unexpected = 1; byte actual = 2; assertNotEquals(unexpected, actual); assertNotEquals(unexpected, actual, "message"); assertNotEquals(unexpected, actual, () -> "message"); } @Test void withEqualValues() { byte unexpected = 1; byte actual = 1; try { assertNotEquals(unexpected, actual); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected: not equal but was: <1>"); } } @Test void withEqualValuesWithMessage() { byte unexpected = 1; byte actual = 1; try { assertNotEquals(unexpected, actual, "custom message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "custom message"); assertMessageEndsWith(ex, "expected: not equal but was: <1>"); } } @Test void withEqualValuesWithMessageSupplier() { byte unexpected = 1; byte actual = 1; try { assertNotEquals(unexpected, actual, () -> "custom message from supplier"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "custom message from supplier"); assertMessageEndsWith(ex, "expected: not equal but was: <1>"); } } } @Nested class AssertNotEqualsShort { @Test void assertNotEqualsShort() { short unexpected = 1; short actual = 2; assertNotEquals(unexpected, actual); assertNotEquals(unexpected, actual, "message"); assertNotEquals(unexpected, actual, () -> "message"); } @Test void withEqualValues() { short unexpected = 1; short actual = 1; try { assertNotEquals(unexpected, actual); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected: not equal but was: <1>"); } } @Test void withEqualValuesWithMessage() { short unexpected = 1; short actual = 1; try { assertNotEquals(unexpected, actual, "custom message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "custom message"); assertMessageEndsWith(ex, "expected: not equal but was: <1>"); } } @Test void withEqualValuesWithMessageSupplier() { short unexpected = 1; short actual = 1; try { assertNotEquals(unexpected, actual, () -> "custom message from supplier"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "custom message from supplier"); assertMessageEndsWith(ex, "expected: not equal but was: <1>"); } } } @Nested class AssertNotEqualsChar { @Test void assertNotEqualsChar() { char unexpected = 'a'; char actual = 'b'; assertNotEquals(unexpected, actual); assertNotEquals(unexpected, actual, "message"); assertNotEquals(unexpected, actual, () -> "message"); } @Test void withEqualValues() { char unexpected = 'a'; char actual = 'a'; try { assertNotEquals(unexpected, actual); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected: not equal but was: "); } } @Test void withEqualValuesWithMessage() { char unexpected = 'a'; char actual = 'a'; try { assertNotEquals(unexpected, actual, "custom message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "custom message"); assertMessageEndsWith(ex, "expected: not equal but was: "); } } @Test void withEqualValuesWithMessageSupplier() { char unexpected = 'a'; char actual = 'a'; try { assertNotEquals(unexpected, actual, () -> "custom message from supplier"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "custom message from supplier"); assertMessageEndsWith(ex, "expected: not equal but was: "); } } } @Nested class AssertNotEqualsInt { @Test void assertNotEqualsInt() { int unexpected = 1; int actual = 2; assertNotEquals(unexpected, actual); assertNotEquals(unexpected, actual, "message"); assertNotEquals(unexpected, actual, () -> "message"); } @Test void withEqualValues() { int unexpected = 1; int actual = 1; try { assertNotEquals(unexpected, actual); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected: not equal but was: <1>"); } } @Test void withEqualValuesWithMessage() { int unexpected = 1; int actual = 1; try { assertNotEquals(unexpected, actual, "custom message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "custom message"); assertMessageEndsWith(ex, "expected: not equal but was: <1>"); } } @Test void withEqualValuesWithMessageSupplier() { int unexpected = 1; int actual = 1; try { assertNotEquals(unexpected, actual, () -> "custom message from supplier"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "custom message from supplier"); assertMessageEndsWith(ex, "expected: not equal but was: <1>"); } } } @Nested class AssertNotEqualsLong { @Test void assertNotEqualsLong() { long unexpected = 1L; long actual = 2L; assertNotEquals(unexpected, actual); assertNotEquals(unexpected, actual, "message"); assertNotEquals(unexpected, actual, () -> "message"); } @Test void withEqualValues() { long unexpected = 1L; long actual = 1L; try { assertNotEquals(unexpected, actual); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected: not equal but was: <1>"); } } @Test void withEqualValuesWithMessage() { long unexpected = 1L; long actual = 1L; try { assertNotEquals(unexpected, actual, "custom message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "custom message"); assertMessageEndsWith(ex, "expected: not equal but was: <1>"); } } @Test void withEqualValuesWithMessageSupplier() { long unexpected = 1L; long actual = 1L; try { assertNotEquals(unexpected, actual, () -> "custom message from supplier"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "custom message from supplier"); assertMessageEndsWith(ex, "expected: not equal but was: <1>"); } } } @Nested class AssertNotEqualsFloatWithoutDelta { @Test void assertNotEqualsFloat() { float unexpected = 1.0f; float actual = 2.0f; assertNotEquals(unexpected, actual); assertNotEquals(unexpected, actual, "message"); assertNotEquals(unexpected, actual, () -> "message"); } @Test void assertNotEqualsForTwoNaNFloat() { try { assertNotEquals(Float.NaN, Float.NaN); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected: not equal but was: "); } } @Test void assertNotEqualsForPositiveInfinityFloat() { try { assertNotEquals(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected: not equal but was: "); } } @Test void assertNotEqualsForNegativeInfinityFloat() { try { assertNotEquals(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected: not equal but was: <-Infinity>"); } } @Test void withEqualValues() { float unexpected = 1.0f; float actual = 1.0f; try { assertNotEquals(unexpected, actual); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected: not equal but was: <1.0>"); } } @Test void withEqualValuesWithMessage() { float unexpected = 1.0f; float actual = 1.0f; try { assertNotEquals(unexpected, actual, "custom message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "custom message"); assertMessageEndsWith(ex, "expected: not equal but was: <1.0>"); } } @Test void withEqualValuesWithMessageSupplier() { float unexpected = 1.0f; float actual = 1.0f; try { assertNotEquals(unexpected, actual, () -> "custom message from supplier"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "custom message from supplier"); assertMessageEndsWith(ex, "expected: not equal but was: <1.0>"); } } } @Nested class AssertNotEqualsFloatWithDelta { @Test void assertNotEqualsFloat() { assertNotEquals(1.0f, 1.5f, 0.4f); assertNotEquals(1.0f, 1.5f, 0.4f, "message"); assertNotEquals(1.0f, 1.5f, 0.4f, () -> "message"); } @Test void withEqualValues() { float unexpected = 1.0f; float actual = 1.5f; float delta = 0.5f; try { assertNotEquals(unexpected, actual, delta); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected: not equal but was: <1.5>"); } } @Test void withEqualValuesWithMessage() { float unexpected = 1.0f; float actual = 1.5f; float delta = 0.5f; try { assertNotEquals(unexpected, actual, delta, "custom message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "custom message"); assertMessageEndsWith(ex, "expected: not equal but was: <1.5>"); } } @Test void withEqualValuesWithMessageSupplier() { float unexpected = 1.0f; float actual = 1.5f; float delta = 0.5f; try { assertNotEquals(unexpected, actual, delta, () -> "custom message from supplier"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "custom message from supplier"); assertMessageEndsWith(ex, "expected: not equal but was: <1.5>"); } } } @Nested class AssertNotEqualsDoubleWithoutDelta { @Test void assertNotEqualsDouble() { double unexpected = 1.0d; double actual = 2.0d; assertNotEquals(unexpected, actual); assertNotEquals(unexpected, actual, "message"); assertNotEquals(unexpected, actual, () -> "message"); } @Test void assertNotEqualsForTwoNaNDouble() { try { assertNotEquals(Double.NaN, Double.NaN); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected: not equal but was: "); } } @Test void withEqualValues() { double unexpected = 1.0d; double actual = 1.0d; try { assertNotEquals(unexpected, actual); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected: not equal but was: <1.0>"); } } @Test void withEqualValuesWithMessage() { double unexpected = 1.0d; double actual = 1.0d; try { assertNotEquals(unexpected, actual, "custom message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "custom message"); assertMessageEndsWith(ex, "expected: not equal but was: <1.0>"); } } @Test void withEqualValuesWithMessageSupplier() { double unexpected = 1.0d; double actual = 1.0d; try { assertNotEquals(unexpected, actual, () -> "custom message from supplier"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "custom message from supplier"); assertMessageEndsWith(ex, "expected: not equal but was: <1.0>"); } } } @Nested class AssertNotEqualsDoubleWithDelta { @Test void assertNotEqualsDouble() { assertNotEquals(1.0d, 1.5d, 0.4d); assertNotEquals(1.0d, 1.5d, 0.4d, "message"); assertNotEquals(1.0d, 1.5d, 0.4d, () -> "message"); } @Test void withEqualValues() { double unexpected = 1.0d; double actual = 1.5d; double delta = 0.5d; try { assertNotEquals(unexpected, actual, delta); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected: not equal but was: <1.5>"); } } @Test void withEqualValuesWithMessage() { double unexpected = 1.0d; double actual = 1.5d; double delta = 0.5d; try { assertNotEquals(unexpected, actual, delta, "custom message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "custom message"); assertMessageEndsWith(ex, "expected: not equal but was: <1.5>"); } } @Test void withEqualValuesWithMessageSupplier() { double unexpected = 1.0d; double actual = 1.5d; double delta = 0.5d; try { assertNotEquals(unexpected, actual, delta, () -> "custom message from supplier"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "custom message from supplier"); assertMessageEndsWith(ex, "expected: not equal but was: <1.5>"); } } } @Nested class AssertNotEqualsObject { @Test void assertNotEqualsWithNullVsObject() { assertNotEquals(null, "foo"); } @Test void assertNotEqualsWithObjectVsNull() { assertNotEquals("foo", null); } @Test void assertNotEqualsWithDifferentObjects() { assertNotEquals(new Object(), new Object()); assertNotEquals(new Object(), new Object(), "message"); assertNotEquals(new Object(), new Object(), () -> "message"); } @Test void assertNotEqualsWithNullVsObjectAndMessageSupplier() { assertNotEquals(null, "foo", () -> "test"); } @Test void assertNotEqualsWithEquivalentStringsAndMessage() { try { assertNotEquals(new String("foo"), new String("foo"), "test"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "test"); assertMessageEndsWith(ex, "expected: not equal but was: "); } } @Test void assertNotEqualsWithEquivalentStringsAndMessageSupplier() { try { assertNotEquals(new String("foo"), new String("foo"), () -> "test"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "test"); assertMessageEndsWith(ex, "expected: not equal but was: "); } } @Test void assertNotEqualsInvokesEqualsMethodForIdenticalObjects() { Object obj = new EqualsThrowsExceptionClass(); assertThrows(NumberFormatException.class, () -> assertNotEquals(obj, obj)); } } // ------------------------------------------------------------------------- @Nested class MixedBoxedAndUnboxedPrimitivesTests { @Test void bytes() { byte primitive = (byte) 42; Byte wrapper = Byte.valueOf("99"); assertNotEquals(primitive, wrapper); assertNotEquals(primitive, wrapper, "message"); assertNotEquals(primitive, wrapper, () -> "message"); assertNotEquals(wrapper, primitive); assertNotEquals(wrapper, primitive, "message"); assertNotEquals(wrapper, primitive, () -> "message"); } @Test void shorts() { short primitive = (short) 42; Short wrapper = Short.valueOf("99"); assertNotEquals(primitive, wrapper); assertNotEquals(primitive, wrapper, "message"); assertNotEquals(primitive, wrapper, () -> "message"); assertNotEquals(wrapper, primitive); assertNotEquals(wrapper, primitive, "message"); assertNotEquals(wrapper, primitive, () -> "message"); } @Test void integers() { int primitive = 42; Integer wrapper = Integer.valueOf("99"); assertNotEquals(primitive, wrapper); assertNotEquals(primitive, wrapper, "message"); assertNotEquals(primitive, wrapper, () -> "message"); assertNotEquals(wrapper, primitive); assertNotEquals(wrapper, primitive, "message"); assertNotEquals(wrapper, primitive, () -> "message"); } @Test void longs() { long primitive = 42L; Long wrapper = Long.valueOf("99"); assertNotEquals(primitive, wrapper); assertNotEquals(primitive, wrapper, "message"); assertNotEquals(primitive, wrapper, () -> "message"); assertNotEquals(wrapper, primitive); assertNotEquals(wrapper, primitive, "message"); assertNotEquals(wrapper, primitive, () -> "message"); } @Test void floats() { float primitive = 42.0f; Float wrapper = Float.valueOf("99.0"); assertNotEquals(primitive, wrapper); assertNotEquals(primitive, wrapper, 0.0f); assertNotEquals(primitive, wrapper, "message"); assertNotEquals(primitive, wrapper, 0.0f, "message"); assertNotEquals(primitive, wrapper, () -> "message"); assertNotEquals(primitive, wrapper, 0.0f, () -> "message"); assertNotEquals(wrapper, primitive); assertNotEquals(wrapper, primitive, 0.0f); assertNotEquals(wrapper, primitive, "message"); assertNotEquals(wrapper, primitive, 0.0f, "message"); assertNotEquals(wrapper, primitive, () -> "message"); assertNotEquals(wrapper, primitive, 0.0f, () -> "message"); } @Test void doubles() { double primitive = 42.0d; Double wrapper = Double.valueOf("99.0"); assertNotEquals(primitive, wrapper); assertNotEquals(primitive, wrapper, 0.0d); assertNotEquals(primitive, wrapper, "message"); assertNotEquals(primitive, wrapper, 0.0d, "message"); assertNotEquals(primitive, wrapper, () -> "message"); assertNotEquals(primitive, wrapper, 0.0d, () -> "message"); assertNotEquals(wrapper, primitive); assertNotEquals(wrapper, primitive, 0.0d); assertNotEquals(wrapper, primitive, "message"); assertNotEquals(wrapper, primitive, 0.0d, "message"); assertNotEquals(wrapper, primitive, () -> "message"); assertNotEquals(wrapper, primitive, 0.0d, () -> "message"); } @Test void booleans() { boolean primitive = true; Boolean wrapper = Boolean.valueOf("false"); assertNotEquals(primitive, wrapper); assertNotEquals(primitive, wrapper, "message"); assertNotEquals(primitive, wrapper, () -> "message"); assertNotEquals(wrapper, primitive); assertNotEquals(wrapper, primitive, "message"); assertNotEquals(wrapper, primitive, () -> "message"); } @Test void chars() { char primitive = 'a'; Character wrapper = Character.valueOf('z'); assertNotEquals(primitive, wrapper); assertNotEquals(primitive, wrapper, "message"); assertNotEquals(primitive, wrapper, () -> "message"); assertNotEquals(wrapper, primitive); assertNotEquals(wrapper, primitive, "message"); assertNotEquals(wrapper, primitive, () -> "message"); } } // ------------------------------------------------------------------------- @SuppressWarnings("overrides") private static class EqualsThrowsExceptionClass { @Override public boolean equals(Object obj) { throw new NumberFormatException(); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/AssertNotNullAssertionsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEndsWith; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; import static org.junit.jupiter.api.Assertions.assertNotNull; import org.opentest4j.AssertionFailedError; /** * Unit tests for JUnit Jupiter {@link Assertions}. * * @since 5.0 */ class AssertNotNullAssertionsTests { @Test void assertNotNullWithNonNullObject() { assertNotNull("foo"); assertNotNull("foo", "message"); assertNotNull("foo", () -> "message"); } @Test void assertNotNullWithNonNullObjectAndMessageSupplier() { assertNotNull("foo", () -> "should not fail"); } @Test @SuppressWarnings("unused") void assertNotNullWithNull() { try { assertNotNull(null); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected: not "); } } @Test @SuppressWarnings("unused") void assertNotNullWithNullAndMessageSupplier() { try { assertNotNull(null, () -> "test"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "test"); assertMessageEndsWith(ex, "expected: not "); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/AssertNotSameAssertionsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageContains; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; import static org.junit.jupiter.api.Assertions.assertNotSame; import org.opentest4j.AssertionFailedError; /** * Unit tests for JUnit Jupiter {@link Assertions}. * * @since 5.0 */ class AssertNotSameAssertionsTests { @Test void assertNotSameWithDifferentObjects() { assertNotSame(new Object(), new Object()); assertNotSame(new Object(), new Object(), "message"); assertNotSame(new Object(), new Object(), () -> "message"); } @Test void assertNotSameWithDifferentObjectsAndMessageSupplier() { assertNotSame(new Object(), new Object(), () -> "should not fail"); } @Test void assertNotSameWithObjectVsNull() { assertNotSame(new Object(), null); } @Test void assertNotSameWithNullVsObject() { assertNotSame(null, new Object()); } @Test void assertNotSameWithTwoNulls() { try { assertNotSame(null, null); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected: not same but was: "); } } @Test void assertNotSameWithSameObjectAndMessage() { try { Object foo = new Object(); assertNotSame(foo, foo, "test"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "test"); assertMessageContains(ex, "expected: not same but was: "test"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "test"); assertMessageContains(ex, "expected: not same but was: "message"); } @Test void assertNullWithNullAndMessageSupplier() { assertNull(null, () -> "test"); } @Test @SuppressWarnings("unused") void assertNullWithNonNullObject() { try { assertNull("foo"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected: but was: "); assertExpectedAndActualValues(ex, null, "foo"); } } @Test void assertNullWithNonNullObjectWithNullStringReturnedFromToString() { assertNullWithNonNullObjectWithNullStringReturnedFromToString(null); } @Test void assertNullWithNonNullObjectWithNullStringReturnedFromToStringAndMessageSupplier() { assertNullWithNonNullObjectWithNullStringReturnedFromToString(() -> "boom"); } @SuppressWarnings("unused") private void assertNullWithNonNullObjectWithNullStringReturnedFromToString( @Nullable Supplier<@Nullable String> messageSupplier) { String actual = "null"; try { if (messageSupplier == null) { assertNull(actual); } else { assertNull(actual, messageSupplier); } expectAssertionFailedError(); } catch (AssertionFailedError ex) { // Should look something like: // expected: but was: java.lang.String@264b3504 String prefix = (messageSupplier != null ? messageSupplier.get() + " ==> " : ""); assertMessageMatches(ex, prefix + "expected: but was: java\\.lang\\.String@.+"); assertExpectedAndActualValues(ex, null, actual); } } @Test void assertNullWithNonNullObjectWithNullReferenceReturnedFromToString() { assertNullWithNonNullObjectWithNullReferenceReturnedFromToString(null); } @Test void assertNullWithNonNullObjectWithNullReferenceReturnedFromToStringAndMessageSupplier() { assertNullWithNonNullObjectWithNullReferenceReturnedFromToString(() -> "boom"); } @SuppressWarnings("unused") private void assertNullWithNonNullObjectWithNullReferenceReturnedFromToString( @Nullable Supplier<@Nullable String> messageSupplier) { Object actual = new NullToString(); try { if (messageSupplier == null) { assertNull(actual); } else { assertNull(actual, messageSupplier); } expectAssertionFailedError(); } catch (AssertionFailedError ex) { // Should look something like: // expected: but was: org.junit.jupiter.api.AssertNullAssertionsTests$NullToString@4e7912d8 String prefix = (messageSupplier != null ? messageSupplier.get() + " ==> " : ""); assertMessageMatches(ex, prefix + "expected: but was: org\\.junit\\.jupiter\\.api\\.AssertNullAssertionsTests\\$NullToString@.+"); assertExpectedAndActualValues(ex, null, actual); } } @Test @SuppressWarnings("unused") void assertNullWithNonNullObjectAndMessage() { try { assertNull("foo", "a message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "a message ==> expected: but was: "); assertExpectedAndActualValues(ex, null, "foo"); } } @Test @SuppressWarnings("unused") void assertNullWithNonNullObjectAndMessageSupplier() { try { assertNull("foo", () -> "test"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "test ==> expected: but was: "); assertExpectedAndActualValues(ex, null, "foo"); } } @NullUnmarked private static class NullToString { @Override public String toString() { return null; } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/AssertSameAssertionsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.junit.jupiter.api.AssertionTestUtils.assertExpectedAndActualValues; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageContains; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageMatches; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; import static org.junit.jupiter.api.Assertions.assertSame; import org.opentest4j.AssertionFailedError; /** * Unit tests for JUnit Jupiter {@link Assertions}. * * @since 5.0 */ class AssertSameAssertionsTests { @Test void assertSameWithTwoNulls() { assertSame(null, null); assertSame(null, null, () -> "should not fail"); } @Test void assertSameWithSameObject() { Object foo = new Object(); assertSame(foo, foo); assertSame(foo, foo, "message"); assertSame(foo, foo, () -> "message"); } @Test void assertSameWithObjectVsNull() { Object expected = new Object(); try { assertSame(expected, null); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageContains(ex, "expected: "); assertExpectedAndActualValues(ex, expected, null); } } @Test void assertSameWithNullVsObject() { Object actual = new Object(); try { assertSame(null, actual); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageContains(ex, "expected: "); assertMessageContains(ex, "but was: but was: java\\.lang\\.Integer@.+?<999>"); assertExpectedAndActualValues(ex, 999, 999); } } @Test void assertSameWithEquivalentStringsAndMessageSupplier() { String expected = new String("foo"); String actual = new String("foo"); try { assertSame(expected, actual, () -> "test"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "test"); assertMessageContains(ex, "expected: java.lang.String@"); assertMessageContains(ex, "but was: java.lang.String@"); assertExpectedAndActualValues(ex, expected, actual); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/AssertThrowsAssertionsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageContains; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import java.io.IOException; import java.io.Serial; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; import org.junit.jupiter.api.function.Executable; import org.junit.platform.commons.test.TestClassLoader; import org.opentest4j.AssertionFailedError; /** * Unit tests for JUnit Jupiter {@link Assertions}. * * @since 5.0 */ @SuppressWarnings("ExcessiveLambdaUsage") class AssertThrowsAssertionsTests { private static final Executable nix = () -> { }; @Test void assertThrowsWithMethodReferenceForNonVoidReturnType() { FutureTask future = new FutureTask<>(() -> { throw new RuntimeException("boom"); }); future.run(); ExecutionException exception = assertThrows(ExecutionException.class, future::get); assertNotNull(exception.getCause()); assertEquals("boom", exception.getCause().getMessage()); } @Test void assertThrowsWithMethodReferenceForVoidReturnType() { var object = new Object(); IllegalMonitorStateException exception; exception = assertThrows(IllegalMonitorStateException.class, object::notify); assertNotNull(exception); // Note that Object.wait(...) is an overloaded method with a void return type exception = assertThrows(IllegalMonitorStateException.class, object::wait); assertNotNull(exception); } @Test void assertThrowsWithExecutableThatThrowsThrowable() { EnigmaThrowable enigmaThrowable = assertThrows(EnigmaThrowable.class, () -> { throw new EnigmaThrowable(); }); assertNotNull(enigmaThrowable); } @Test void assertThrowsWithExecutableThatThrowsThrowableWithMessage() { EnigmaThrowable enigmaThrowable = assertThrows(EnigmaThrowable.class, () -> { throw new EnigmaThrowable(); }, "message"); assertNotNull(enigmaThrowable); } @Test void assertThrowsWithExecutableThatThrowsThrowableWithMessageSupplier() { EnigmaThrowable enigmaThrowable = assertThrows(EnigmaThrowable.class, () -> { throw new EnigmaThrowable(); }, () -> "message"); assertNotNull(enigmaThrowable); } @Test void assertThrowsWithExecutableThatThrowsCheckedException() { IOException exception = assertThrows(IOException.class, () -> { throw new IOException(); }); assertNotNull(exception); } @Test void assertThrowsWithExecutableThatThrowsRuntimeException() { IllegalStateException illegalStateException = assertThrows(IllegalStateException.class, () -> { throw new IllegalStateException(); }); assertNotNull(illegalStateException); } @Test void assertThrowsWithExecutableThatThrowsError() { StackOverflowError stackOverflowError = assertThrows(StackOverflowError.class, AssertionTestUtils::recurseIndefinitely); assertNotNull(stackOverflowError); } @Test void assertThrowsWithExecutableThatDoesNotThrowAnException() { try { assertThrows(IllegalStateException.class, nix); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "Expected java.lang.IllegalStateException to be thrown, but nothing was thrown."); } } @Test void assertThrowsWithExecutableThatDoesNotThrowAnExceptionWithMessageString() { try { assertThrows(IOException.class, nix, "Custom message"); expectAssertionFailedError(); } catch (AssertionError ex) { assertMessageEquals(ex, "Custom message ==> Expected java.io.IOException to be thrown, but nothing was thrown."); } } @Test void assertThrowsWithExecutableThatDoesNotThrowAnExceptionWithMessageSupplier() { try { assertThrows(IOException.class, nix, () -> "Custom message"); expectAssertionFailedError(); } catch (AssertionError ex) { assertMessageEquals(ex, "Custom message ==> Expected java.io.IOException to be thrown, but nothing was thrown."); } } @Test void assertThrowsWithExecutableThatThrowsAnUnexpectedException() { try { assertThrows(IllegalStateException.class, () -> { throw new NumberFormatException(); }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "Unexpected exception type thrown, "); assertMessageContains(ex, "expected: "); assertMessageContains(ex, "but was: "); assertThat(ex).hasCauseInstanceOf(NumberFormatException.class); } } @Test void assertThrowsWithExecutableThatThrowsAnUnexpectedExceptionWithMessageString() { try { assertThrows(IllegalStateException.class, () -> { throw new NumberFormatException(); }, "Custom message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { // Should look something like this: // Custom message ==> Unexpected exception type thrown, expected: but was: assertMessageStartsWith(ex, "Custom message ==> "); assertMessageContains(ex, "Unexpected exception type thrown, "); assertMessageContains(ex, "expected: "); assertMessageContains(ex, "but was: "); assertThat(ex).hasCauseInstanceOf(NumberFormatException.class); } } @Test void assertThrowsWithExecutableThatThrowsAnUnexpectedExceptionWithMessageSupplier() { try { assertThrows(IllegalStateException.class, () -> { throw new NumberFormatException(); }, () -> "Custom message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { // Should look something like this: // Custom message ==> Unexpected exception type thrown, expected: but was: assertMessageStartsWith(ex, "Custom message ==> "); assertMessageContains(ex, "Unexpected exception type thrown, "); assertMessageContains(ex, "expected: "); assertMessageContains(ex, "but was: "); assertThat(ex).hasCauseInstanceOf(NumberFormatException.class); } } @Test @SuppressWarnings("serial") void assertThrowsWithExecutableThatThrowsInstanceOfAnonymousInnerClassAsUnexpectedException() { try { assertThrows(IllegalStateException.class, () -> { throw new NumberFormatException() { }; }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "Unexpected exception type thrown, "); assertMessageContains(ex, "expected: "); // As of the time of this writing, the class name of the above anonymous inner // class is org.junit.jupiter.api.AssertionsAssertThrowsTests$2; however, hard // coding "$2" is fragile. So we just check for the presence of the "$" // appended to this class's name. assertMessageContains(ex, "but was: <" + getClass().getName() + "$"); assertThat(ex).hasCauseInstanceOf(NumberFormatException.class); } } @Test void assertThrowsWithExecutableThatThrowsInstanceOfStaticNestedClassAsUnexpectedException() { try { assertThrows(IllegalStateException.class, () -> { throw new LocalException(); }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "Unexpected exception type thrown, "); assertMessageContains(ex, "expected: "); // The following verifies that the canonical name is used (i.e., "." instead of "$"). assertMessageContains(ex, "but was: <" + LocalException.class.getName().replace("$", ".") + ">"); assertThat(ex).hasCauseInstanceOf(LocalException.class); } } @Test @SuppressWarnings("unchecked") void assertThrowsWithExecutableThatThrowsSameExceptionTypeFromDifferentClassLoader() throws Exception { try (var testClassLoader = TestClassLoader.forClasses(EnigmaThrowable.class)) { // Load expected exception type from different class loader Class enigmaThrowableClass = (Class) testClassLoader.loadClass( EnigmaThrowable.class.getName()); try { assertThrows(enigmaThrowableClass, () -> { throw new EnigmaThrowable(); }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { // Example Output: // // Unexpected exception type thrown, // expected: // but was: assertMessageStartsWith(ex, "Unexpected exception type thrown, "); // The presence of the "@" sign is sufficient to indicate that the hash was // generated to disambiguate between the two identical class names. assertMessageContains(ex, "expected: { }; @Test void assertThrowsExactlyTheSpecifiedExceptionClass() { var actual = assertThrowsExactly(EnigmaThrowable.class, () -> { throw new EnigmaThrowable(); }); assertNotNull(actual); } @Test void assertThrowsExactlyWithTheExpectedChildException() { try { assertThrowsExactly(RuntimeException.class, () -> { throw new Exception(); }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "Unexpected exception type thrown, "); assertMessageContains(ex, "expected: "); assertMessageContains(ex, "but was: "); assertThat(ex).hasCauseExactlyInstanceOf(Exception.class); } } @Test void assertThrowsExactlyWithTheExpectedParentException() { try { assertThrowsExactly(RuntimeException.class, () -> { throw new NumberFormatException(); }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "Unexpected exception type thrown, "); assertMessageContains(ex, "expected: "); assertMessageContains(ex, "but was: "); assertThat(ex).hasCauseExactlyInstanceOf(NumberFormatException.class); } } @Test void assertThrowsWithMethodReferenceForNonVoidReturnType() { FutureTask future = new FutureTask<>(() -> { throw new RuntimeException("boom"); }); future.run(); ExecutionException exception = assertThrowsExactly(ExecutionException.class, future::get); assertNotNull(exception.getCause()); assertEquals("boom", exception.getCause().getMessage()); } @Test void assertThrowsWithMethodReferenceForVoidReturnType() { var object = new Object(); IllegalMonitorStateException exception; exception = assertThrowsExactly(IllegalMonitorStateException.class, object::notify); assertNotNull(exception); // Note that Object.wait(...) is an overloaded method with a void return type exception = assertThrowsExactly(IllegalMonitorStateException.class, object::wait); assertNotNull(exception); } @Test void assertThrowsWithExecutableThatThrowsThrowable() { EnigmaThrowable enigmaThrowable = assertThrowsExactly(EnigmaThrowable.class, () -> { throw new EnigmaThrowable(); }); assertNotNull(enigmaThrowable); } @Test void assertThrowsWithExecutableThatThrowsThrowableWithMessage() { EnigmaThrowable enigmaThrowable = assertThrowsExactly(EnigmaThrowable.class, () -> { throw new EnigmaThrowable(); }, "message"); assertNotNull(enigmaThrowable); } @Test void assertThrowsWithExecutableThatThrowsThrowableWithMessageSupplier() { EnigmaThrowable enigmaThrowable = assertThrowsExactly(EnigmaThrowable.class, () -> { throw new EnigmaThrowable(); }, () -> "message"); assertNotNull(enigmaThrowable); } @Test void assertThrowsWithExecutableThatThrowsCheckedException() { IOException exception = assertThrowsExactly(IOException.class, () -> { throw new IOException(); }); assertNotNull(exception); } @Test void assertThrowsWithExecutableThatThrowsRuntimeException() { IllegalStateException illegalStateException = assertThrowsExactly(IllegalStateException.class, () -> { throw new IllegalStateException(); }); assertNotNull(illegalStateException); } @Test void assertThrowsWithExecutableThatThrowsError() { StackOverflowError stackOverflowError = assertThrowsExactly(StackOverflowError.class, AssertionTestUtils::recurseIndefinitely); assertNotNull(stackOverflowError); } @Test void assertThrowsWithExecutableThatDoesNotThrowAnException() { try { assertThrowsExactly(IllegalStateException.class, nix); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "Expected java.lang.IllegalStateException to be thrown, but nothing was thrown."); } } @Test void assertThrowsWithExecutableThatDoesNotThrowAnExceptionWithMessageString() { try { assertThrowsExactly(IOException.class, nix, "Custom message"); expectAssertionFailedError(); } catch (AssertionError ex) { assertMessageEquals(ex, "Custom message ==> Expected java.io.IOException to be thrown, but nothing was thrown."); } } @Test void assertThrowsWithExecutableThatDoesNotThrowAnExceptionWithMessageSupplier() { try { assertThrowsExactly(IOException.class, nix, () -> "Custom message"); expectAssertionFailedError(); } catch (AssertionError ex) { assertMessageEquals(ex, "Custom message ==> Expected java.io.IOException to be thrown, but nothing was thrown."); } } @Test void assertThrowsWithExecutableThatThrowsAnUnexpectedException() { try { assertThrowsExactly(IllegalStateException.class, () -> { throw new NumberFormatException(); }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "Unexpected exception type thrown, "); assertMessageContains(ex, "expected: "); assertMessageContains(ex, "but was: "); assertThat(ex).hasCauseExactlyInstanceOf(NumberFormatException.class); } } @Test void assertThrowsWithExecutableThatThrowsAnUnexpectedExceptionWithMessageString() { try { assertThrowsExactly(IllegalStateException.class, () -> { throw new NumberFormatException(); }, "Custom message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { // Should look something like this: // Custom message ==> Unexpected exception type thrown, expected: but was: assertMessageStartsWith(ex, "Custom message ==> "); assertMessageContains(ex, "Unexpected exception type thrown, "); assertMessageContains(ex, "expected: "); assertMessageContains(ex, "but was: "); assertThat(ex).hasCauseExactlyInstanceOf(NumberFormatException.class); } } @Test void assertThrowsWithExecutableThatThrowsAnUnexpectedExceptionWithMessageSupplier() { try { assertThrowsExactly(IllegalStateException.class, () -> { throw new NumberFormatException(); }, () -> "Custom message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { // Should look something like this: // Custom message ==> Unexpected exception type thrown, expected: but was: assertMessageStartsWith(ex, "Custom message ==> "); assertMessageContains(ex, "Unexpected exception type thrown, "); assertMessageContains(ex, "expected: "); assertMessageContains(ex, "but was: "); assertThat(ex).hasCauseExactlyInstanceOf(NumberFormatException.class); } } @Test @SuppressWarnings("serial") void assertThrowsWithExecutableThatThrowsInstanceOfAnonymousInnerClassAsUnexpectedException() { try { assertThrowsExactly(IllegalStateException.class, () -> { throw new NumberFormatException() { }; }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "Unexpected exception type thrown, "); assertMessageContains(ex, "expected: "); // As of the time of this writing, the class name of the above anonymous inner // class is org.junit.jupiter.api.AssertThrowsExactlyAssertionsTests$2; however, hard // coding "$2" is fragile. So we just check for the presence of the "$" // appended to this class's name. assertMessageContains(ex, "but was: <" + getClass().getName() + "$"); assertThat(ex).hasCauseInstanceOf(NumberFormatException.class); } } @Test void assertThrowsWithExecutableThatThrowsInstanceOfStaticNestedClassAsUnexpectedException() { try { assertThrowsExactly(IllegalStateException.class, () -> { throw new LocalException(); }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageStartsWith(ex, "Unexpected exception type thrown, "); assertMessageContains(ex, "expected: "); // The following verifies that the canonical name is used (i.e., "." instead of "$"). assertMessageContains(ex, "but was: <" + LocalException.class.getName().replace("$", ".") + ">"); assertThat(ex).hasCauseExactlyInstanceOf(LocalException.class); } } @Test @SuppressWarnings("unchecked") void assertThrowsWithExecutableThatThrowsSameExceptionTypeFromDifferentClassLoader() throws Exception { try (var testClassLoader = TestClassLoader.forClasses(EnigmaThrowable.class)) { // Load expected exception type from different class loader Class enigmaThrowableClass = (Class) testClassLoader.loadClass( EnigmaThrowable.class.getName()); try { assertThrowsExactly(enigmaThrowableClass, () -> { throw new EnigmaThrowable(); }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { // Example Output: // // Unexpected exception type thrown, // expected: // but was: assertMessageStartsWith(ex, "Unexpected exception type thrown, "); // The presence of the "@" sign is sufficient to indicate that the hash was // generated to disambiguate between the two identical class names. assertMessageContains(ex, "expected: changed = ThreadLocal.withInitial(() -> new AtomicBoolean(false)); private final Executable nix = () -> { }; // --- executable ---------------------------------------------------------- @Test void assertTimeoutForExecutableThatCompletesBeforeTheTimeout() { changed.get().set(false); assertTimeout(ofMillis(500), () -> changed.get().set(true)); assertTrue(changed.get().get(), "should have executed in the same thread"); assertTimeout(ofMillis(500), nix, "message"); assertTimeout(ofMillis(500), nix, () -> "message"); } @Test void assertTimeoutForExecutableThatThrowsAnException() { RuntimeException exception = assertThrows(RuntimeException.class, () -> assertTimeout(ofMillis(500), () -> { throw new RuntimeException("not this time"); })); assertMessageEquals(exception, "not this time"); } @Test void assertTimeoutForExecutableThatThrowsAnAssertionFailedError() { AssertionFailedError exception = assertThrows(AssertionFailedError.class, () -> assertTimeout(ofMillis(500), () -> fail("enigma"))); assertMessageEquals(exception, "enigma"); } @Test void assertTimeoutForExecutableThatCompletesAfterTheTimeout() { AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> assertTimeout(ofMillis(10), this::nap)); assertMessageStartsWith(error, "execution exceeded timeout of 10 ms by"); } @Test void assertTimeoutWithMessageForExecutableThatCompletesAfterTheTimeout() { AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> assertTimeout(ofMillis(10), this::nap, "Tempus Fugit")); assertMessageStartsWith(error, "Tempus Fugit ==> execution exceeded timeout of 10 ms by"); } @Test void assertTimeoutWithMessageSupplierForExecutableThatCompletesAfterTheTimeout() { AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> assertTimeout(ofMillis(10), this::nap, () -> "Tempus" + " " + "Fugit")); assertMessageStartsWith(error, "Tempus Fugit ==> execution exceeded timeout of 10 ms by"); } // --- supplier ------------------------------------------------------------ @Test void assertTimeoutForSupplierThatCompletesBeforeTheTimeout() { changed.get().set(false); String result = assertTimeout(ofMillis(500), () -> { changed.get().set(true); return "Tempus Fugit"; }); assertTrue(changed.get().get(), "should have executed in the same thread"); assertEquals("Tempus Fugit", result); assertEquals("Tempus Fugit", assertTimeout(ofMillis(500), () -> "Tempus Fugit", "message")); assertEquals("Tempus Fugit", assertTimeout(ofMillis(500), () -> "Tempus Fugit", () -> "message")); } @Test void assertTimeoutForSupplierThatThrowsAnException() { RuntimeException exception = assertThrows(RuntimeException.class, () -> { assertTimeout(ofMillis(500), () -> ExceptionUtils.throwAsUncheckedException(new RuntimeException("not this time"))); }); assertMessageEquals(exception, "not this time"); } @Test void assertTimeoutForSupplierThatThrowsAnAssertionFailedError() { AssertionFailedError exception = assertThrows(AssertionFailedError.class, () -> { assertTimeout(ofMillis(500), () -> fail("enigma")); }); assertMessageEquals(exception, "enigma"); } @Test void assertTimeoutForSupplierThatCompletesAfterTheTimeout() { AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> { assertTimeout(ofMillis(10), () -> { nap(); return "Tempus Fugit"; }); }); assertMessageStartsWith(error, "execution exceeded timeout of 10 ms by"); } @Test void assertTimeoutWithMessageForSupplierThatCompletesAfterTheTimeout() { AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> { assertTimeout(ofMillis(10), () -> { nap(); return "Tempus Fugit"; }, "Tempus Fugit"); }); assertMessageStartsWith(error, "Tempus Fugit ==> execution exceeded timeout of 10 ms by"); } @Test void assertTimeoutWithMessageSupplierForSupplierThatCompletesAfterTheTimeout() { AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> { assertTimeout(ofMillis(10), () -> { nap(); return "Tempus Fugit"; }, () -> "Tempus" + " " + "Fugit"); }); assertMessageStartsWith(error, "Tempus Fugit ==> execution exceeded timeout of 10 ms by"); } /** * Take a nap for 100 milliseconds. */ private void nap() throws InterruptedException { long start = System.nanoTime(); // workaround for imprecise clocks (yes, Windows, I'm talking about you) do { Thread.sleep(100); } while (System.nanoTime() - start < 100_000_000L); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/AssertTimeoutPreemptivelyAssertionsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static java.time.Duration.ofMillis; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.condition.OS.WINDOWS; import java.time.Duration; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.function.Executable; import org.junit.platform.commons.util.ExceptionUtils; import org.opentest4j.AssertionFailedError; /** * Unit tests for {@link AssertTimeoutPreemptively}. * * @since 5.0 */ class AssertTimeoutPreemptivelyAssertionsTests { private static final Duration PREEMPTIVE_TIMEOUT = ofMillis(WINDOWS.isCurrentOs() ? 1000 : 100); private static final ThreadLocal changed = ThreadLocal.withInitial(() -> new AtomicBoolean(false)); private final Executable nix = () -> { }; // --- executable ---------------------------------------------------------- @Test void assertTimeoutPreemptivelyForExecutableThatCompletesBeforeTheTimeout() { changed.get().set(false); assertTimeoutPreemptively(ofMillis(500), () -> changed.get().set(true)); assertFalse(changed.get().get(), "should have executed in a different thread"); assertTimeoutPreemptively(ofMillis(500), nix, "message"); assertTimeoutPreemptively(ofMillis(500), nix, () -> "message"); } @Test void assertTimeoutPreemptivelyForExecutableThatThrowsAnException() { RuntimeException exception = assertThrows(RuntimeException.class, () -> assertTimeoutPreemptively(ofMillis(500), () -> { throw new RuntimeException("not this time"); })); assertMessageEquals(exception, "not this time"); } @Test void assertTimeoutPreemptivelyForExecutableThatThrowsAnAssertionFailedError() { AssertionFailedError exception = assertThrows(AssertionFailedError.class, () -> assertTimeoutPreemptively(ofMillis(500), () -> fail("enigma"))); assertMessageEquals(exception, "enigma"); } @Test void assertTimeoutPreemptivelyForExecutableThatCompletesAfterTheTimeout() { AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, this::waitForInterrupt)); assertMessageEquals(error, "execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); assertMessageStartsWith(error.getCause(), "Execution timed out in "); assertStackTraceContains(error.getCause(), "CountDownLatch", "await"); } @Test void assertTimeoutPreemptivelyWithMessageForExecutableThatCompletesAfterTheTimeout() { AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, this::waitForInterrupt, "Tempus Fugit")); assertMessageEquals(error, "Tempus Fugit ==> execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); assertMessageStartsWith(error.getCause(), "Execution timed out in "); assertStackTraceContains(error.getCause(), "CountDownLatch", "await"); } @Test void assertTimeoutPreemptivelyWithMessageSupplierForExecutableThatCompletesAfterTheTimeout() { AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, this::waitForInterrupt, () -> "Tempus" + " " + "Fugit")); assertMessageEquals(error, "Tempus Fugit ==> execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); assertMessageStartsWith(error.getCause(), "Execution timed out in "); assertStackTraceContains(error.getCause(), "CountDownLatch", "await"); } @Test void assertTimeoutPreemptivelyWithMessageSupplierForExecutableThatCompletesBeforeTheTimeout() { assertTimeoutPreemptively(ofMillis(500), nix, () -> "Tempus" + " " + "Fugit"); } // --- supplier ------------------------------------------------------------ @Test void assertTimeoutPreemptivelyForSupplierThatCompletesBeforeTheTimeout() { changed.get().set(false); String result = assertTimeoutPreemptively(ofMillis(500), () -> { changed.get().set(true); return "Tempus Fugit"; }); assertFalse(changed.get().get(), "should have executed in a different thread"); assertEquals("Tempus Fugit", result); assertEquals("Tempus Fugit", assertTimeoutPreemptively(ofMillis(500), () -> "Tempus Fugit", "message")); assertEquals("Tempus Fugit", assertTimeoutPreemptively(ofMillis(500), () -> "Tempus Fugit", () -> "message")); } @Test void assertTimeoutPreemptivelyForSupplierThatThrowsAnException() { RuntimeException exception = assertThrows(RuntimeException.class, () -> { assertTimeoutPreemptively(ofMillis(500), () -> ExceptionUtils.throwAsUncheckedException(new RuntimeException("not this time"))); }); assertMessageEquals(exception, "not this time"); } @Test void assertTimeoutPreemptivelyForSupplierThatThrowsAnAssertionFailedError() { AssertionFailedError exception = assertThrows(AssertionFailedError.class, () -> { assertTimeoutPreemptively(ofMillis(500), () -> { fail("enigma"); return "Tempus Fugit"; }); }); assertMessageEquals(exception, "enigma"); } @Test void assertTimeoutPreemptivelyForSupplierThatCompletesAfterTheTimeout() { AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> { assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, () -> { waitForInterrupt(); return "Tempus Fugit"; }); }); assertMessageEquals(error, "execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); assertMessageStartsWith(error.getCause(), "Execution timed out in "); assertStackTraceContains(error.getCause(), "CountDownLatch", "await"); } @Test void assertTimeoutPreemptivelyWithMessageForSupplierThatCompletesAfterTheTimeout() { AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> { assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, () -> { waitForInterrupt(); return "Tempus Fugit"; }, "Tempus Fugit"); }); assertMessageEquals(error, "Tempus Fugit ==> execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); assertMessageStartsWith(error.getCause(), "Execution timed out in "); assertStackTraceContains(error.getCause(), "CountDownLatch", "await"); } @Test void assertTimeoutPreemptivelyWithMessageSupplierForSupplierThatCompletesAfterTheTimeout() { AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> { assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, () -> { waitForInterrupt(); return "Tempus Fugit"; }, () -> "Tempus" + " " + "Fugit"); }); assertMessageEquals(error, "Tempus Fugit ==> execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); assertMessageStartsWith(error.getCause(), "Execution timed out in "); assertStackTraceContains(error.getCause(), "CountDownLatch", "await"); } @Test void assertTimeoutPreemptivelyUsesThreadsWithSpecificNamePrefix() { AtomicReference threadName = new AtomicReference<>(""); assertTimeoutPreemptively(ofMillis(1000), () -> threadName.set(Thread.currentThread().getName())); assertThat(threadName.get()) // .withFailMessage("Thread name does not match the expected prefix") // .startsWith("junit-timeout-thread-"); } private void waitForInterrupt() { try { assertFalse(Thread.interrupted(), "Already interrupted"); new CountDownLatch(1).await(); } catch (InterruptedException ignore) { // ignore } } /** * Assert the given stack trace elements contain an element with the given class name and method name. */ private static void assertStackTraceContains(@Nullable Throwable throwable, String className, String methodName) { assertThat(throwable).isNotNull(); assertThat(throwable.getStackTrace()).anySatisfy(element -> { assertThat(element.getClassName()).endsWith(className); assertThat(element.getMethodName()).isEqualTo(methodName); }); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/AssertTrueAssertionsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.junit.jupiter.api.AssertionTestUtils.assertExpectedAndActualValues; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; import static org.junit.jupiter.api.Assertions.assertTrue; import org.opentest4j.AssertionFailedError; /** * Unit tests for JUnit Jupiter {@link Assertions}. * * @since 5.0 */ class AssertTrueAssertionsTests { @Test void assertTrueWithBooleanTrue() { assertTrue(true); assertTrue(true, "test"); assertTrue(true, () -> "test"); } @Test void assertTrueWithBooleanSupplierTrue() { assertTrue(() -> true); assertTrue(() -> true, "test"); assertTrue(() -> true, () -> "test"); } @Test void assertTrueWithBooleanTrueAndMessageSupplier() { assertTrue(true, () -> "test"); } @Test void assertTrueWithBooleanSupplierTrueAndMessageSupplier() { assertTrue(() -> true, () -> "test"); } @Test void assertTrueWithBooleanFalseAndDefaultMessageWithExpectedAndActualValues() { try { assertTrue(false); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "expected: but was: "); assertExpectedAndActualValues(ex, true, false); } } @Test void assertTrueWithBooleanFalseAndString() { try { assertTrue(false, "test"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "test ==> expected: but was: "); assertExpectedAndActualValues(ex, true, false); } } @Test void assertTrueWithBooleanFalseAndMessageSupplier() { try { assertTrue(false, () -> "test"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "test ==> expected: but was: "); assertExpectedAndActualValues(ex, true, false); } } @Test void assertTrueWithBooleanSupplierFalseAndString() { try { assertTrue(() -> false, "test"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "test ==> expected: but was: "); assertExpectedAndActualValues(ex, true, false); } } @Test void assertTrueWithBooleanSupplierFalseAndMessageSupplier() { try { assertTrue(() -> false, () -> "test"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "test ==> expected: but was: "); assertExpectedAndActualValues(ex, true, false); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/AssertionFailureBuilderTest.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static java.util.concurrent.Executors.newSingleThreadExecutor; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import static org.junit.jupiter.api.Assertions.assertLinesMatch; import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.Arrays; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import org.junit.platform.commons.PreconditionViolationException; import org.opentest4j.AssertionFailedError; class AssertionFailureBuilderTest { @Test void doesNotTrimByDefault() { var error = AssertionsFacade.fail(); assertStackTraceMatch(error, """ \\Qorg.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:\\E.+ \\Qorg.junit.jupiter.api.AssertionFailureBuilderTest$AssertionsFacade.fail(AssertionFailureBuilderTest.java:\\E.+ \\Qorg.junit.jupiter.api.AssertionFailureBuilderTest.doesNotTrimByDefault(AssertionFailureBuilderTest.java:\\E.+ >>>> """); } @Test void trimsUpToAssertionsFacade() { var error = AssertionsFacade.failWithTrimmedStacktrace(AssertionsFacade.class, 0); assertStackTraceMatch(error, """ \\Qorg.junit.jupiter.api.AssertionFailureBuilderTest.trimsUpToAssertionsFacade(AssertionFailureBuilderTest.java:\\E.+ >>>> """); } @Test void trimsUpToAssertionsFacadeKeepingOne() { var error = AssertionsFacade.failWithTrimmedStacktrace(AssertionsFacade.class, 1); assertStackTraceMatch(error, """ \\Qorg.junit.jupiter.api.AssertionFailureBuilderTest$AssertionsFacade.failWithTrimmedStacktrace(AssertionFailureBuilderTest.java:\\E.+ \\Qorg.junit.jupiter.api.AssertionFailureBuilderTest.trimsUpToAssertionsFacadeKeepingOne(AssertionFailureBuilderTest.java:\\E.+ >>>> """); } @Test void trimsUpToAssertionFailureBuilder() { var error = AssertionsFacade.failWithTrimmedStacktrace(AssertionFailureBuilder.class, 0); assertStackTraceMatch(error, """ \\Qorg.junit.jupiter.api.AssertionFailureBuilderTest$AssertionsFacade.failWithTrimmedStacktrace(AssertionFailureBuilderTest.java:\\E.+ \\Qorg.junit.jupiter.api.AssertionFailureBuilderTest.trimsUpToAssertionFailureBuilder(AssertionFailureBuilderTest.java:\\E.+ >>>> """); } @Test void ignoresClassNotInStackTrace() { var error = AssertionsFacade.failWithTrimmedStacktrace(String.class, 0); assertStackTraceMatch(error, """ \\Qorg.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:\\E.+ \\Qorg.junit.jupiter.api.AssertionFailureBuilderTest$AssertionsFacade.failWithTrimmedStacktrace(AssertionFailureBuilderTest.java:\\E.+ \\Qorg.junit.jupiter.api.AssertionFailureBuilderTest.ignoresClassNotInStackTrace(AssertionFailureBuilderTest.java:\\E.+ >>>> """); } @Test void canTrimToEmptyStacktrace() throws ExecutionException, InterruptedException { try (ExecutorService service = newSingleThreadExecutor()) { // Ensure that the stacktrace starts at Thread. var error = service.submit(() -> AssertionsFacade.failWithTrimmedStacktrace(Thread.class, 0)).get(); assertThat(error.getStackTrace()).isEmpty(); } } @Test void mustRetainNonNegativeNumberOfFrames() { var exception = assertThrows(PreconditionViolationException.class, // () -> assertionFailure().retainStackTraceElements(-1)); assertThat(exception).hasMessage("retainStackTraceElements must have a non-negative value"); } private static void assertStackTraceMatch(AssertionFailedError assertionFailedError, String expectedLines) { List stackStraceAsLines = Arrays.stream(assertionFailedError.getStackTrace()) // .map(StackTraceElement::toString) // .toList(); assertLinesMatch(expectedLines.lines().toList(), stackStraceAsLines); } static class AssertionsFacade { static AssertionFailedError fail() { return assertionFailure().build(); } static AssertionFailedError failWithTrimmedStacktrace(Class to, int retain) { return AssertionFailureBuilder.assertionFailure() // .trimStacktrace(to) // .retainStackTraceElements(retain) // .build(); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/AssumptionsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assumptions.abort; import static org.junit.jupiter.api.Assumptions.assumeFalse; import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.junit.jupiter.api.Assumptions.assumingThat; import java.util.ArrayList; import java.util.List; import java.util.Objects; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.function.Executable; import org.opentest4j.TestAbortedException; /** * Unit tests for JUnit Jupiter {@link Assumptions}. * * @since 5.0 */ class AssumptionsTests { // --- assumeTrue ---------------------------------------------------- @Test void assumeTrueWithBooleanTrue() { String foo = null; try { assumeTrue(true); assumeTrue(true, "message"); assumeTrue(true, () -> "message"); foo = "foo"; } finally { assertNotNull(foo); } } @Test void assumeTrueWithBooleanSupplierTrue() { String foo = null; try { assumeTrue(() -> true); assumeTrue(() -> true, "message"); assumeTrue(() -> true, () -> "message"); foo = "foo"; } finally { assertNotNull(foo); } } @Test void assumeTrueWithBooleanFalse() { assertAssumptionFailure("assumption is not true", () -> assumeTrue(false)); } @Test void assumeTrueWithBooleanSupplierFalse() { assertAssumptionFailure("assumption is not true", () -> assumeTrue(() -> false)); } @Test void assumeTrueWithBooleanFalseAndStringMessage() { assertAssumptionFailure("test", () -> assumeTrue(false, "test")); } @Test void assumeTrueWithBooleanFalseAndNullStringMessage() { assertAssumptionFailure(null, () -> assumeTrue(false, (String) null)); } @Test void assumeTrueWithBooleanSupplierFalseAndStringMessage() { assertAssumptionFailure("test", () -> assumeTrue(() -> false, "test")); } @Test void assumeTrueWithBooleanSupplierFalseAndMessageSupplier() { assertAssumptionFailure("test", () -> assumeTrue(() -> false, () -> "test")); } @Test void assumeTrueWithBooleanFalseAndMessageSupplier() { assertAssumptionFailure("test", () -> assumeTrue(false, () -> "test")); } // --- assumeFalse ---------------------------------------------------- @Test void assumeFalseWithBooleanFalse() { String foo = null; try { assumeFalse(false); assumeFalse(false, "message"); assumeFalse(false, () -> "message"); foo = "foo"; } finally { assertNotNull(foo); } } @Test void assumeFalseWithBooleanSupplierFalse() { String foo = null; try { assumeFalse(() -> false); assumeFalse(() -> false, "message"); assumeFalse(() -> false, () -> "message"); foo = "foo"; } finally { assertNotNull(foo); } } @Test void assumeFalseWithBooleanTrue() { assertAssumptionFailure("assumption is not false", () -> assumeFalse(true)); } @Test void assumeFalseWithBooleanSupplierTrue() { assertAssumptionFailure("assumption is not false", () -> assumeFalse(() -> true)); } @Test void assumeFalseWithBooleanTrueAndStringMessage() { assertAssumptionFailure("test", () -> assumeFalse(true, "test")); } @Test void assumeFalseWithBooleanSupplierTrueAndMessage() { assertAssumptionFailure("test", () -> assumeFalse(() -> true, "test")); } @Test void assumeFalseWithBooleanSupplierTrueAndMessageSupplier() { assertAssumptionFailure("test", () -> assumeFalse(() -> true, () -> "test")); } @Test void assumeFalseWithBooleanTrueAndMessageSupplier() { assertAssumptionFailure("test", () -> assumeFalse(true, () -> "test")); } // --- assumingThat -------------------------------------------------- @Test void assumingThatWithBooleanTrue() { List list = new ArrayList<>(); assumingThat(true, () -> list.add("test")); assertEquals(1, list.size()); assertEquals("test", list.getFirst()); } @Test void assumingThatWithBooleanSupplierTrue() { List list = new ArrayList<>(); assumingThat(() -> true, () -> list.add("test")); assertEquals(1, list.size()); assertEquals("test", list.getFirst()); } @Test void assumingThatWithBooleanFalse() { List list = new ArrayList<>(); assumingThat(false, () -> list.add("test")); assertEquals(0, list.size()); } @Test void assumingThatWithBooleanSupplierFalse() { List list = new ArrayList<>(); assumingThat(() -> false, () -> list.add("test")); assertEquals(0, list.size()); } @Test void assumingThatWithFailingExecutable() { assertThrows(EnigmaThrowable.class, () -> assumingThat(true, () -> { throw new EnigmaThrowable(); })); } // --- abort --------------------------------------------------------- @Test void abortWithNoArguments() { assertTestAbortedException(null, Assumptions::abort); } @Test void abortWithStringMessage() { assertTestAbortedException("test", () -> abort("test")); } @Test void abortWithStringSupplier() { assertTestAbortedException("test", () -> abort(() -> "test")); } // ------------------------------------------------------------------- private static void assertAssumptionFailure(@Nullable String msg, Executable executable) { assertTestAbortedException(msg == null ? "Assumption failed" : "Assumption failed: " + msg, executable); } private static void assertTestAbortedException(@Nullable String expectedMessage, Executable executable) { try { executable.execute(); expectTestAbortedException(); } catch (Throwable ex) { assertInstanceOf(TestAbortedException.class, ex); assertMessageEquals(ex, expectedMessage); } } private static void expectTestAbortedException() { throw new AssertionError("Should have thrown a " + TestAbortedException.class.getName()); } private static void assertMessageEquals(Throwable t, @Nullable String expectedMessage) throws AssertionError { if (!Objects.equals(expectedMessage, t.getMessage())) { throw new AssertionError("Message in TestAbortedException should be [" + expectedMessage + "], but was [" + t.getMessage() + "]."); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/ConstantTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.assertj.core.api.Assertions.assertThat; import org.junit.platform.commons.util.ClassNamePatternFilterUtils; import org.junit.platform.engine.support.hierarchical.DefaultParallelExecutionConfigurationStrategy; import org.junit.platform.engine.support.hierarchical.ParallelHierarchicalTestExecutorServiceFactory; public class ConstantTests { @Test void constantsAreConsistent() { assertThat(Constants.PARALLEL_CONFIG_EXECUTOR_SERVICE_PROPERTY_NAME).isEqualTo(Constants.PARALLEL_CONFIG_PREFIX + ParallelHierarchicalTestExecutorServiceFactory.EXECUTOR_SERVICE_PROPERTY_NAME); assertThat(Constants.PARALLEL_CONFIG_STRATEGY_PROPERTY_NAME).isEqualTo(Constants.PARALLEL_CONFIG_PREFIX + DefaultParallelExecutionConfigurationStrategy.CONFIG_STRATEGY_PROPERTY_NAME); assertThat(Constants.PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME).isEqualTo(Constants.PARALLEL_CONFIG_PREFIX + DefaultParallelExecutionConfigurationStrategy.CONFIG_FIXED_PARALLELISM_PROPERTY_NAME); assertThat(Constants.PARALLEL_CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME).isEqualTo( Constants.PARALLEL_CONFIG_PREFIX + DefaultParallelExecutionConfigurationStrategy.CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME); assertThat(Constants.PARALLEL_CONFIG_FIXED_SATURATE_PROPERTY_NAME).isEqualTo(Constants.PARALLEL_CONFIG_PREFIX + DefaultParallelExecutionConfigurationStrategy.CONFIG_FIXED_SATURATE_PROPERTY_NAME); assertThat(Constants.PARALLEL_CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME).isEqualTo(Constants.PARALLEL_CONFIG_PREFIX + DefaultParallelExecutionConfigurationStrategy.CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME); assertThat(Constants.PARALLEL_CONFIG_CUSTOM_CLASS_PROPERTY_NAME).isEqualTo(Constants.PARALLEL_CONFIG_PREFIX + DefaultParallelExecutionConfigurationStrategy.CONFIG_CUSTOM_CLASS_PROPERTY_NAME); assertThat(Constants.DEACTIVATE_ALL_CONDITIONS_PATTERN).isEqualTo(ClassNamePatternFilterUtils.ALL_PATTERN); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationInheritanceTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; /** * @since 5.8 */ @DisplayNameGeneration(ReplaceUnderscores.class) class DisplayNameGenerationInheritanceTestCase { @Nested class InnerNestedTestCase { @Test void this_is_a_test() { } } static class StaticNestedTestCase { @Test void this_is_a_test() { } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import java.lang.reflect.Method; import java.util.EmptyStackException; import java.util.List; import java.util.Stack; import java.util.stream.Stream; import org.jspecify.annotations.NullMarked; import org.junit.jupiter.api.DisplayNameGenerator.IndicativeSentences.SentenceFragment; import org.junit.jupiter.api.extension.ClassTemplateInvocationContext; import org.junit.jupiter.api.extension.ClassTemplateInvocationContextProvider; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.testkit.engine.EngineExecutionResults; import org.junit.platform.testkit.engine.Event; /** * Check generated display names. * * @see DisplayName * @see DisplayNameGenerator * @see DisplayNameGeneration * @since 5.4 */ class DisplayNameGenerationTests extends AbstractJupiterTestEngineTests { @Test void standardGenerator() { check(DefaultStyleTestCase.class, // "CONTAINER: DisplayNameGenerationTests$DefaultStyleTestCase", // "TEST: @DisplayName prevails", // "TEST: test()", // "TEST: test(TestInfo)", // "TEST: testUsingCamelCaseStyle()", // "TEST: testUsingCamelCase_and_also_UnderScores()", // "TEST: testUsingCamelCase_and_also_UnderScores_keepingParameterTypeNamesIntact(TestInfo)", // "TEST: test_with_underscores()" // ); } @Test void simpleGenerator() { check(SimpleStyleTestCase.class, // "CONTAINER: DisplayNameGenerationTests$SimpleStyleTestCase", // "TEST: @DisplayName prevails", // "TEST: test", // "TEST: test (TestInfo)", // "TEST: testUsingCamelCaseStyle", // "TEST: testUsingCamelCase_and_also_UnderScores", // "TEST: testUsingCamelCase_and_also_UnderScores_keepingParameterTypeNamesIntact (TestInfo)", // "TEST: test_with_underscores" // ); } @Test void underscoreGenerator() { var expectedDisplayNames = new String[] { // "", // "TEST: @DisplayName prevails", // "TEST: test", // "TEST: test (TestInfo)", // "TEST: test with underscores", // "TEST: testUsingCamelCase and also UnderScores", // "TEST: testUsingCamelCase and also UnderScores keepingParameterTypeNamesIntact (TestInfo)", // "TEST: testUsingCamelCaseStyle" // }; expectedDisplayNames[0] = "CONTAINER: DisplayNameGenerationTests$UnderscoreStyleTestCase"; check(UnderscoreStyleTestCase.class, expectedDisplayNames); expectedDisplayNames[0] = "CONTAINER: DisplayNameGenerationTests$UnderscoreStyleInheritedFromSuperClassTestCase"; check(UnderscoreStyleInheritedFromSuperClassTestCase.class, expectedDisplayNames); } @Test void indicativeSentencesGeneratorOnStaticNestedClass() { check(IndicativeStyleTestCase.class, // "CONTAINER: DisplayNameGenerationTests$IndicativeStyleTestCase", // "TEST: @DisplayName prevails", // "TEST: DisplayNameGenerationTests$IndicativeStyleTestCase -> test", // "TEST: DisplayNameGenerationTests$IndicativeStyleTestCase -> test (TestInfo)", // "TEST: DisplayNameGenerationTests$IndicativeStyleTestCase -> test with underscores", // "TEST: DisplayNameGenerationTests$IndicativeStyleTestCase -> testUsingCamelCase and also UnderScores", // "TEST: DisplayNameGenerationTests$IndicativeStyleTestCase -> testUsingCamelCase and also UnderScores keepingParameterTypeNamesIntact (TestInfo)", // "TEST: DisplayNameGenerationTests$IndicativeStyleTestCase -> testUsingCamelCaseStyle" // ); } @Test void indicativeSentencesGeneratorOnTopLevelClass() { check(IndicativeSentencesTopLevelTestCase.class, // "CONTAINER: IndicativeSentencesTopLevelTestCase", // "CONTAINER: IndicativeSentencesTopLevelTestCase -> A year is a leap year", // "TEST: IndicativeSentencesTopLevelTestCase -> A year is a leap year -> if it is divisible by 4 but not by 100" // ); } @Test void indicativeSentencesGeneratorOnNestedClass() { check(IndicativeSentencesNestedTestCase.class, // "CONTAINER: IndicativeSentencesNestedTestCase", // "CONTAINER: A year is a leap year", // "TEST: A year is a leap year -> if it is divisible by 4 but not by 100" // ); } @Test void noNameGenerator() { check(NoNameStyleTestCase.class, // "CONTAINER: nn", // "TEST: @DisplayName prevails", // "TEST: nn", // "TEST: nn", // "TEST: nn", // "TEST: nn", // "TEST: nn", // "TEST: nn" // ); } @Test void checkDisplayNameGeneratedForTestingAStackDemo() { check(StackTestCase.class, // "CONTAINER: A stack", // "TEST: is instantiated using its noarg constructor", // "CONTAINER: A new stack", // "TEST: throws an EmptyStackException when peeked", // "TEST: throws an EmptyStackException when popped", // "TEST: is empty", // "CONTAINER: After pushing an element to an empty stack", // "TEST: peek returns that element without removing it from the stack", // "TEST: pop returns that element and leaves an empty stack", // "TEST: the stack is no longer empty" // ); } @Test void checkDisplayNameGeneratedForIndicativeGenerator() { check(IndicativeGeneratorTestCase.class, // "CONTAINER: A stack", // "TEST: A stack, is instantiated with its constructor", // "CONTAINER: A stack, when new", // "TEST: A stack, when new, throws EmptyStackException when peeked", // "CONTAINER: A stack, when new, after pushing an element to an empty stack", // "TEST: A stack, when new, after pushing an element to an empty stack, is no longer empty" // ); } @Test void checkDisplayNameGeneratedForIndicativeGeneratorWithCustomSeparator() { check(IndicativeGeneratorWithCustomSeparatorTestCase.class, // "CONTAINER: A stack", // "TEST: A stack >> is instantiated with its constructor", // "CONTAINER: A stack >> when new", // "TEST: A stack >> when new >> throws EmptyStackException when peeked", // "CONTAINER: A stack >> when new >> after pushing an element to an empty stack", // "TEST: A stack >> when new >> after pushing an element to an empty stack >> is no longer empty" // ); } @Test void checkDisplayNameGeneratedForIndicativeGeneratorWithCustomSentenceFragments() { check(IndicativeGeneratorWithCustomSentenceFragmentsTestCase.class, // "CONTAINER: A stack", // "TEST: A stack, is instantiated with its constructor", // "CONTAINER: A stack, when new", // "TEST: A stack, when new, throws EmptyStackException when peeked", // "CONTAINER: A stack, when new, after pushing an element to an empty stack", // "TEST: A stack, when new, after pushing an element to an empty stack, is no longer empty" // ); } @Test void blankSentenceFragmentOnClassYieldsError() { var results = discoverTests(selectClass(BlankSentenceFragmentOnClassTestCase.class)); var discoveryIssues = results.getDiscoveryIssues(); assertThat(discoveryIssues).hasSize(1); assertThat(discoveryIssues.getFirst().severity()).isEqualTo(Severity.ERROR); assertThat(discoveryIssues.getFirst().cause().orElseThrow()) // .hasMessage("@SentenceFragment on [%s] must be declared with a non-blank value.", BlankSentenceFragmentOnClassTestCase.class); } @Test void blankSentenceFragmentOnMethodYieldsError() throws Exception { var results = discoverTests(selectMethod(BlankSentenceFragmentOnMethodTestCase.class, "test")); var discoveryIssues = results.getDiscoveryIssues(); assertThat(discoveryIssues).hasSize(1); assertThat(discoveryIssues.getFirst().severity()).isEqualTo(Severity.ERROR); assertThat(discoveryIssues.getFirst().cause().orElseThrow()) // .hasMessage("@SentenceFragment on [%s] must be declared with a non-blank value.", BlankSentenceFragmentOnMethodTestCase.class.getDeclaredMethod("test")); } @Test void displayNameGenerationInheritance() { check(DisplayNameGenerationInheritanceTestCase.InnerNestedTestCase.class, // "CONTAINER: DisplayNameGenerationInheritanceTestCase", // "CONTAINER: InnerNestedTestCase", // "TEST: this is a test"// ); check(DisplayNameGenerationInheritanceTestCase.StaticNestedTestCase.class, // "CONTAINER: DisplayNameGenerationInheritanceTestCase$StaticNestedTestCase", // "TEST: this_is_a_test()"// ); } @Test void indicativeSentencesGenerationInheritance() { check(IndicativeSentencesGenerationInheritanceTestCase.InnerNestedTestCase.class, // "CONTAINER: IndicativeSentencesGenerationInheritanceTestCase", // "CONTAINER: IndicativeSentencesGenerationInheritanceTestCase -> InnerNestedTestCase", // "TEST: IndicativeSentencesGenerationInheritanceTestCase -> InnerNestedTestCase -> this is a test"// ); check(IndicativeSentencesGenerationInheritanceTestCase.StaticNestedTestCase.class, // "CONTAINER: IndicativeSentencesGenerationInheritanceTestCase$StaticNestedTestCase", // "TEST: this_is_a_test()"// ); } @Test void indicativeSentencesRuntimeEnclosingType() { check(IndicativeSentencesRuntimeEnclosingTypeScenarioOneTestCase.class, // "CONTAINER: Scenario 1", // "CONTAINER: Scenario 1 -> Level 1", // "CONTAINER: Scenario 1 -> Level 1 -> Level 2", // "TEST: Scenario 1 -> Level 1 -> Level 2 -> this is a test"// ); check(IndicativeSentencesRuntimeEnclosingTypeScenarioTwoTestCase.class, // "CONTAINER: Scenario 2", // "CONTAINER: Scenario 2 -> Level 1", // "CONTAINER: Scenario 2 -> Level 1 -> Level 2", // "TEST: Scenario 2 -> Level 1 -> Level 2 -> this is a test"// ); } @Test void indicativeSentencesOnSubClass() { check(IndicativeSentencesOnSubClassScenarioOneTestCase.class, // "CONTAINER: IndicativeSentencesOnSubClassScenarioOneTestCase", // "CONTAINER: IndicativeSentencesOnSubClassScenarioOneTestCase -> Level 1", // "CONTAINER: IndicativeSentencesOnSubClassScenarioOneTestCase -> Level 1 -> Level 2", // "TEST: IndicativeSentencesOnSubClassScenarioOneTestCase -> Level 1 -> Level 2 -> this is a test"// ); } @Test void indicativeSentencesOnClassTemplate() { check(ClassTemplateTestCase.class, // "CONTAINER: Class template", // "CONTAINER: [1] Class template", // "TEST: Class template, some test", // "CONTAINER: Class template, Regular Nested Test Case", // "TEST: Class template, Regular Nested Test Case, some nested test", // "CONTAINER: Class template, Nested Class Template", // "CONTAINER: [1] Class template, Nested Class Template", // "TEST: Class template, Nested Class Template, some nested test" // ); assertThat(executeTestsForClass(ClassTemplateTestCase.class).allEvents().started().stream()) // .map(event -> event.getTestDescriptor().getDisplayName()) // .containsExactly( // "JUnit Jupiter", // "Class template", // "[1] Class template", // "Class template, some test", // "Class template, Regular Nested Test Case", // "Class template, Regular Nested Test Case, some nested test", // "Class template, Nested Class Template", // "[1] Class template, Nested Class Template", // "Class template, Nested Class Template, some nested test" // ); } private void check(Class testClass, String... expectedDisplayNames) { var results = executeTestsForClass(testClass); check(results, expectedDisplayNames); } private void check(EngineExecutionResults results, String[] expectedDisplayNames) { var descriptors = results.allEvents().started().stream() // .map(Event::getTestDescriptor) // .skip(1); // Skip engine descriptor assertThat(descriptors) // .map(it -> it.getType() + ": " + it.getDisplayName()) // .containsExactlyInAnyOrder(expectedDisplayNames); } // ------------------------------------------------------------------------- @NullMarked static class NoNameGenerator implements DisplayNameGenerator { @Override public String generateDisplayNameForClass(Class testClass) { return "nn"; } @Override public String generateDisplayNameForNestedClass(List> enclosingInstanceTypes, Class nestedClass) { return "nn"; } @Override public String generateDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, Method testMethod) { return "nn"; } } @DisplayNameGeneration(NoNameGenerator.class) static abstract class AbstractTestCase { @Test void test() { } @Test void test(TestInfo testInfo) { testInfo.getDisplayName(); } @Test void testUsingCamelCaseStyle() { } @Test void testUsingCamelCase_and_also_UnderScores() { } @Test void testUsingCamelCase_and_also_UnderScores_keepingParameterTypeNamesIntact(TestInfo testInfo) { testInfo.getDisplayName(); } @Test void test_with_underscores() { } @DisplayName("@DisplayName prevails") @Test void testDisplayNamePrevails() { } } @DisplayNameGeneration(DisplayNameGenerator.Standard.class) static class DefaultStyleTestCase extends AbstractTestCase { } @DisplayNameGeneration(DisplayNameGenerator.Simple.class) static class SimpleStyleTestCase extends AbstractTestCase { } @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) static class UnderscoreStyleTestCase extends AbstractTestCase { } @IndicativeSentencesGeneration(separator = " -> ", generator = DisplayNameGenerator.ReplaceUnderscores.class) static class IndicativeStyleTestCase extends AbstractTestCase { } @DisplayNameGeneration(NoNameGenerator.class) static class NoNameStyleTestCase extends AbstractTestCase { } // No annotation here! @DisplayNameGeneration is inherited from super class static class UnderscoreStyleInheritedFromSuperClassTestCase extends UnderscoreStyleTestCase { } // ------------------------------------------------------------------------- @DisplayName("A stack") @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) static class StackTestCase { @Test void is_instantiated_using_its_noarg_constructor() { new Stack<>(); } @Nested class A_new_stack { Stack stack; @BeforeEach void createNewStack() { stack = new Stack<>(); } @Test void is_empty() { assertTrue(stack.isEmpty()); } @Test void throws_an_EmptyStackException_when_popped() { assertThrows(EmptyStackException.class, () -> stack.pop()); } @Test void throws_an_EmptyStackException_when_peeked() { assertThrows(EmptyStackException.class, () -> stack.peek()); } @Nested class After_pushing_an_element_to_an_empty_stack { String anElement = "an element"; @BeforeEach void pushAnElement() { stack.push(anElement); } @Test void the_stack_is_no_longer_empty() { assertFalse(stack.isEmpty()); } @Test void pop_returns_that_element_and_leaves_an_empty_stack() { assertEquals(anElement, stack.pop()); assertTrue(stack.isEmpty()); } @Test void peek_returns_that_element_without_removing_it_from_the_stack() { assertEquals(anElement, stack.peek()); assertFalse(stack.isEmpty()); } } } } // ------------------------------------------------------------------------- @DisplayName("A stack") @IndicativeSentencesGeneration(generator = DisplayNameGenerator.ReplaceUnderscores.class) static class IndicativeGeneratorTestCase { @Test void is_instantiated_with_its_constructor() { new Stack<>(); } @Nested class when_new { Stack stack; @BeforeEach void create_with_new_stack() { stack = new Stack<>(); } @Test void throws_EmptyStackException_when_peeked() { assertThrows(EmptyStackException.class, () -> stack.peek()); } @Nested class after_pushing_an_element_to_an_empty_stack { String anElement = "an element"; @BeforeEach void push_an_element() { stack.push(anElement); } @Test void is_no_longer_empty() { assertFalse(stack.isEmpty()); } } } } // ------------------------------------------------------------------------- @DisplayName("A stack") @IndicativeSentencesGeneration(separator = " >> ", generator = DisplayNameGenerator.ReplaceUnderscores.class) static class IndicativeGeneratorWithCustomSeparatorTestCase { @Test void is_instantiated_with_its_constructor() { new Stack<>(); } @Nested class when_new { Stack stack; @BeforeEach void create_with_new_stack() { stack = new Stack<>(); } @Test void throws_EmptyStackException_when_peeked() { assertThrows(EmptyStackException.class, () -> stack.peek()); } @Nested class after_pushing_an_element_to_an_empty_stack { String anElement = "an element"; @BeforeEach void push_an_element() { stack.push(anElement); } @Test void is_no_longer_empty() { assertFalse(stack.isEmpty()); } } } } // ------------------------------------------------------------------------- @SentenceFragment("A stack") @IndicativeSentencesGeneration static class IndicativeGeneratorWithCustomSentenceFragmentsTestCase { @SentenceFragment("is instantiated with its constructor") @Test void instantiateViaConstructor() { new Stack<>(); } @SentenceFragment("when new") @Nested class NewStackTestCase { Stack stack; @BeforeEach void createNewStack() { stack = new Stack<>(); } @SentenceFragment("throws EmptyStackException when peeked") @Test void throwsExceptionWhenPeeked() { assertThrows(EmptyStackException.class, () -> stack.peek()); } @SentenceFragment("after pushing an element to an empty stack") @Nested class ElementPushedOntoStackTestCase { String anElement = "an element"; @BeforeEach void pushElementOntoStack() { stack.push(anElement); } @SentenceFragment("is no longer empty") @Test void nonEmptyStack() { assertFalse(stack.isEmpty()); } } } } // ------------------------------------------------------------------------- @ClassTemplate @ExtendWith(ClassTemplateTestCase.Once.class) @DisplayName("Class template") @IndicativeSentencesGeneration(generator = DisplayNameGenerator.ReplaceUnderscores.class) @TestClassOrder(ClassOrderer.OrderAnnotation.class) static class ClassTemplateTestCase { @Test void some_test() { } @Nested @Order(1) class Regular_Nested_Test_Case { @Test void some_nested_test() { } } @Nested @Order(2) @ClassTemplate class Nested_Class_Template { @Test void some_nested_test() { } } @NullMarked private static class Once implements ClassTemplateInvocationContextProvider { @Override public boolean supportsClassTemplate(ExtensionContext context) { return true; } @Override public Stream provideClassTemplateInvocationContexts( ExtensionContext context) { return Stream.of(new ClassTemplateInvocationContext() { @Override public String getDisplayName(int invocationIndex) { return "%s %s".formatted(ClassTemplateInvocationContext.super.getDisplayName(invocationIndex), context.getDisplayName()); } }); } } } @IndicativeSentencesGeneration @SentenceFragment("") static class BlankSentenceFragmentOnClassTestCase { @Test void test() { } } @IndicativeSentencesGeneration static class BlankSentenceFragmentOnMethodTestCase { @SentenceFragment("\t") @Test void test() { } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/DynamicContainerTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullFor; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullOrBlankFor; import java.net.URI; import java.util.Collection; import java.util.List; import java.util.stream.Stream; /** * @since 6.1 */ class DynamicContainerTests { @Test void appliesConfiguration() { var child = dynamicTest("Test", Assertions::fail); var container = dynamicContainer(config -> config // .displayName("Container") // .testSourceUri(URI.create("https://junit.org")) // .executionMode(CONCURRENT) // .childExecutionMode(SAME_THREAD) // .children(child)); assertThat(container.getDisplayName()).isEqualTo("Container"); assertThat(container.getTestSourceUri()).contains(URI.create("https://junit.org")); assertThat(container.getExecutionMode()).contains(CONCURRENT); assertThat(container.getChildExecutionMode()).contains(SAME_THREAD); assertThat(container.getChildren().map(DynamicNode.class::cast)).containsExactly(child); } @Test void displayNameMustNotBeBlank() { assertPreconditionViolationNotNullOrBlankFor("displayName", () -> dynamicContainer(__ -> { })); assertPreconditionViolationNotNullOrBlankFor("displayName", () -> dynamicContainer(config -> config.displayName(""))); } @SuppressWarnings("DataFlowIssue") @Test void executionModeMustNotBeNull() { assertPreconditionViolationNotNullFor("executionMode", () -> dynamicContainer(config -> config.executionMode(null))); } @SuppressWarnings("DataFlowIssue") @Test void childExecutionModeMustNotBeNull() { assertPreconditionViolationNotNullFor("executionMode", () -> dynamicContainer(config -> config.childExecutionMode(null))); } @SuppressWarnings("DataFlowIssue") @Test void childrenMustBeConfigured() { assertPreconditionViolationNotNullFor("children", () -> dynamicContainer(config -> config.children((DynamicNode[]) null))); assertPreconditionViolationNotNullFor("children", () -> dynamicContainer(config -> config.children((Stream) null))); assertPreconditionViolationNotNullFor("children", () -> dynamicContainer(config -> config.children((Collection) null))); assertPreconditionViolationNotNullFor("children", () -> dynamicContainer(config -> config.displayName("container"))); assertPreconditionViolationFor(() -> dynamicContainer(config -> config.children((DynamicNode) null))) // .withMessage("children must not contain null elements"); } @Test void childrenMustNotBeConfiguredMoreThanOnce() { assertPreconditionViolationFor(() -> dynamicContainer(config -> config.children().children())) // .withMessage("children can only be set once"); assertPreconditionViolationFor(() -> dynamicContainer(config -> config.children().children(Stream.empty()))) // .withMessage("children can only be set once"); assertPreconditionViolationFor(() -> dynamicContainer(config -> config.children().children(List.of()))) // .withMessage("children can only be set once"); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/DynamicTestTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static java.util.Collections.emptyIterator; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullFor; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullOrBlankFor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URI; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.function.Function; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.api.function.ThrowingConsumer; import org.junit.platform.commons.support.ReflectionSupport; import org.opentest4j.AssertionFailedError; /** * @since 5.0 */ class DynamicTestTests { private static final Executable nix = () -> { }; private final List<@Nullable String> assertedValues = new ArrayList<>(); @SuppressWarnings("DataFlowIssue") @Test void streamFromStreamPreconditions() { ThrowingConsumer testExecutor = input -> { }; Function displayNameGenerator = Object::toString; assertPreconditionViolationFor(() -> DynamicTest.stream((Stream) null, displayNameGenerator, testExecutor)); assertPreconditionViolationFor(() -> DynamicTest.stream(Stream.empty(), null, testExecutor)); assertPreconditionViolationFor(() -> DynamicTest.stream(Stream.empty(), displayNameGenerator, null)); } @SuppressWarnings("DataFlowIssue") @Test void streamFromIteratorPreconditions() { ThrowingConsumer testExecutor = input -> { }; Function displayNameGenerator = Object::toString; assertPreconditionViolationFor( () -> DynamicTest.stream((Iterator) null, displayNameGenerator, testExecutor)); assertPreconditionViolationFor(() -> DynamicTest.stream(emptyIterator(), null, testExecutor)); assertPreconditionViolationFor(() -> DynamicTest.stream(emptyIterator(), displayNameGenerator, null)); } @SuppressWarnings("DataFlowIssue") @Test void streamFromStreamWithNamesPreconditions() { ThrowingConsumer testExecutor = input -> { }; assertPreconditionViolationFor(() -> DynamicTest.stream((Stream>) null, testExecutor)); assertPreconditionViolationFor(() -> DynamicTest.stream(Stream.empty(), null)); } @SuppressWarnings("DataFlowIssue") @Test void streamFromIteratorWithNamesPreconditions() { ThrowingConsumer testExecutor = input -> { }; assertPreconditionViolationFor( () -> DynamicTest.stream((Iterator>) null, testExecutor)); assertPreconditionViolationFor(() -> DynamicTest.stream(emptyIterator(), null)); } @SuppressWarnings("DataFlowIssue") @Test void streamFromStreamWithNamedExecutablesPreconditions() { assertPreconditionViolationFor(() -> DynamicTest.stream((Stream) null)); } @SuppressWarnings("DataFlowIssue") @Test void streamFromIteratorWithNamedExecutablesPreconditions() { assertPreconditionViolationFor(() -> DynamicTest.stream((Iterator) null)); } @Test void streamFromStream() throws Throwable { Stream stream = DynamicTest.stream(Stream.of("foo", "bar", "baz"), String::toUpperCase, this::throwingConsumer); assertStream(stream); } @Test void streamFromIterator() throws Throwable { Stream stream = DynamicTest.stream(List.of("foo", "bar", "baz").iterator(), String::toUpperCase, this::throwingConsumer); assertStream(stream); } @Test void streamFromStreamWithNames() throws Throwable { Stream stream = DynamicTest.stream( Stream.of(Named.of("FOO", "foo"), Named.of("BAR", "bar"), Named.of("BAZ", "baz")), this::throwingConsumer); assertStream(stream); } @Test void streamFromIteratorWithNames() throws Throwable { Stream stream = DynamicTest.stream( List.of(Named.of("FOO", "foo"), Named.of("BAR", "bar"), Named.of("BAZ", "baz")).iterator(), this::throwingConsumer); assertStream(stream); } @Test void streamFromStreamWithNamedExecutables() throws Throwable { Stream stream = DynamicTest.stream( Stream.of(new DummyNamedExecutableForTests("foo", this::throwingConsumer), new DummyNamedExecutableForTests("bar", this::throwingConsumer), new DummyNamedExecutableForTests("baz", this::throwingConsumer))); assertStream(stream); } @Test void streamFromIteratorWithNamedExecutables() throws Throwable { Stream stream = DynamicTest.stream( List.of(new DummyNamedExecutableForTests("foo", this::throwingConsumer), new DummyNamedExecutableForTests("bar", this::throwingConsumer), new DummyNamedExecutableForTests("baz", this::throwingConsumer)).iterator()); assertStream(stream); } private void assertStream(Stream stream) throws Throwable { List dynamicTests = stream.toList(); assertThat(dynamicTests).extracting(DynamicTest::getDisplayName).containsExactly("FOO", "BAR", "BAZ"); assertThat(assertedValues).isEmpty(); dynamicTests.get(0).getExecutable().execute(); assertThat(assertedValues).containsExactly("foo"); dynamicTests.get(1).getExecutable().execute(); assertThat(assertedValues).containsExactly("foo", "bar"); Throwable t = assertThrows(Throwable.class, () -> dynamicTests.get(2).getExecutable().execute()); assertThat(t).hasMessage("Baz!"); assertThat(assertedValues).containsExactly("foo", "bar"); } private void throwingConsumer(@Nullable String str) throws Throwable { if ("baz".equals(str)) { throw new Throwable("Baz!"); } this.assertedValues.add(str); } @Test void reflectiveOperationsThrowingAssertionFailedError() { Throwable t48 = assertThrows(AssertionFailedError.class, () -> dynamicTest("1 == 48", this::assert1Equals48Directly).getExecutable().execute()); assertThat(t48).hasMessage("expected: <1> but was: <48>"); Throwable t49 = assertThrows(AssertionFailedError.class, () -> dynamicTest("1 == 49", this::assert1Equals49ReflectivelyAndUnwrapInvocationTargetException).getExecutable().execute()); assertThat(t49).hasMessage("expected: <1> but was: <49>"); } @Test void reflectiveOperationThrowingInvocationTargetException() { Throwable t50 = assertThrows(InvocationTargetException.class, () -> dynamicTest("1 == 50", this::assert1Equals50Reflectively).getExecutable().execute()); assertThat(t50.getCause()).hasMessage("expected: <1> but was: <50>"); } @Test void sourceUriIsNotPresentByDefault() { DynamicTest test = dynamicTest("foo", nix); assertThat(test.getTestSourceUri()).isNotPresent(); assertThat(test.toString()).isEqualTo("DynamicTest [displayName = 'foo', testSourceUri = null]"); DynamicContainer container = dynamicContainer("bar", Stream.of(test)); assertThat(container.getTestSourceUri()).isNotPresent(); assertThat(container.toString()).isEqualTo("DynamicContainer [displayName = 'bar', testSourceUri = null]"); } @Test void sourceUriIsReturnedWhenSupplied() { URI testSourceUri = URI.create("any://test"); DynamicTest test = dynamicTest("foo", testSourceUri, nix); URI containerSourceUri = URI.create("other://container"); DynamicContainer container = dynamicContainer("bar", containerSourceUri, Stream.of(test)); assertThat(test.getTestSourceUri()).containsSame(testSourceUri); assertThat(test.toString()).isEqualTo("DynamicTest [displayName = 'foo', testSourceUri = any://test]"); assertThat(container.getTestSourceUri()).containsSame(containerSourceUri); assertThat(container.toString()).isEqualTo( "DynamicContainer [displayName = 'bar', testSourceUri = other://container]"); } @Test void appliesConfiguration() { Executable executable = Assertions::fail; var test = dynamicTest(config -> config // .displayName("Container") // .testSourceUri(URI.create("https://junit.org")) // .executionMode(CONCURRENT).executable(executable)); assertThat(test.getDisplayName()).isEqualTo("Container"); assertThat(test.getTestSourceUri()).contains(URI.create("https://junit.org")); assertThat(test.getExecutionMode()).contains(CONCURRENT); assertThat(test.getExecutable()).isSameAs(executable); } @Test void displayNameMustNotBeBlank() { assertPreconditionViolationNotNullOrBlankFor("displayName", () -> dynamicTest(__ -> { })); assertPreconditionViolationNotNullOrBlankFor("displayName", () -> dynamicTest(config -> config.displayName(""))); } @SuppressWarnings("DataFlowIssue") @Test void executionModeMustNotBeNull() { assertPreconditionViolationNotNullFor("executionMode", () -> dynamicTest(config -> config.executionMode(null))); } @SuppressWarnings("DataFlowIssue") @Test void executableModeMustNotBeNull() { assertPreconditionViolationNotNullFor("executable", () -> dynamicTest(config -> config.displayName("test"))); assertPreconditionViolationNotNullFor("executable", () -> dynamicTest(config -> config.executable(null))); } private void assert1Equals48Directly() { Assertions.assertEquals(1, 48); } private void assert1Equals49ReflectivelyAndUnwrapInvocationTargetException() throws Throwable { Method method = Assertions.class.getMethod("assertEquals", int.class, int.class); ReflectionSupport.invokeMethod(method, null, 1, 49); } private void assert1Equals50Reflectively() throws Throwable { Method method = Assertions.class.getMethod("assertEquals", int.class, int.class); method.invoke(null, 1, 50); } record DummyNamedExecutableForTests(String name, ThrowingConsumer consumer) implements NamedExecutable { @Override public String getName() { return name.toUpperCase(Locale.ROOT); } @Override public void execute() throws Throwable { consumer.accept(name); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/EnigmaThrowable.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; /** * This is a top-level type in order to avoid issues with * {@link Class#getCanonicalName()} when using different class * loaders in tests. * * @since 5.0 */ @SuppressWarnings("serial") class EnigmaThrowable extends Throwable { } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/FailAssertionsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.junit.jupiter.api.AssertionTestUtils.assertEmptyMessage; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageContains; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; import java.util.function.Supplier; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; import org.opentest4j.AssertionFailedError; /** * Unit tests for JUnit Jupiter {@link Assertions}. * * @since 5.0 */ class FailAssertionsTests { @Test void failWithoutArgument() { try { fail(); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertEmptyMessage(ex); } } @Test void failWithString() { try { fail("test"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "test"); } } @Test void failWithMessageSupplier() { try { fail(() -> "test"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "test"); } } @Test void failWithNullString() { try { fail((String) null); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertEmptyMessage(ex); } } @SuppressWarnings("DataFlowIssue") @Test void failWithNullMessageSupplier() { try { fail((Supplier<@Nullable String>) null); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertEmptyMessage(ex); } } @Test void failWithStringAndThrowable() { try { fail("message", new Throwable("cause")); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "message"); Throwable cause = ex.getCause(); assertMessageContains(cause, "cause"); } } @Test void failWithThrowable() { try { fail(new Throwable("cause")); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertEmptyMessage(ex); Throwable cause = ex.getCause(); assertMessageContains(cause, "cause"); } } @Test void failWithStringAndNullThrowable() { try { fail("message", null); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertMessageEquals(ex, "message"); if (ex.getCause() != null) { throw new AssertionError("Cause should have been null"); } } } @Test void failWithNullStringAndThrowable() { try { fail(null, new Throwable("cause")); expectAssertionFailedError(); } catch (AssertionFailedError ex) { assertEmptyMessage(ex); Throwable cause = ex.getCause(); assertMessageContains(cause, "cause"); } } @Test void failUsableAsAnExpression() { // @formatter:off long count = Stream.empty() .peek(element -> fail("peek should never be called")) .filter(element -> fail("filter should never be called", new Throwable("cause"))) .map(element -> Assertions. fail(new Throwable("map should never be called"))) .sorted((e1, e2) -> fail(() -> "sorted should never be called")) .count(); // @formatter:on assertEquals(0L, count); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesGenerationInheritanceTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; /** * @since 5.8 */ @IndicativeSentencesGeneration(separator = " -> ", generator = ReplaceUnderscores.class) class IndicativeSentencesGenerationInheritanceTestCase { @Nested class InnerNestedTestCase { @Test void this_is_a_test() { } } static class StaticNestedTestCase { @Test void this_is_a_test() { } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesNestedTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; /** * This test case declares {@link IndicativeSentencesGeneration} on a test class * that is nested directly within a top-level test class. * * @see IndicativeSentencesTopLevelTestCase * @since 5.8 */ class IndicativeSentencesNestedTestCase { @Nested @IndicativeSentencesGeneration(separator = " -> ", generator = ReplaceUnderscores.class) class A_year_is_a_leap_year { @Test void if_it_is_divisible_by_4_but_not_by_100() { } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesOnSubClassScenarioOneTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; /** * @since 5.12 */ @IndicativeSentencesGeneration(separator = " -> ", generator = DisplayNameGenerator.ReplaceUnderscores.class) class IndicativeSentencesOnSubClassScenarioOneTestCase extends IndicativeSentencesOnSubClassTestCase { } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesOnSubClassTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; /** * @since 5.12 */ @DisplayName("Base Scenario") abstract class IndicativeSentencesOnSubClassTestCase { @Nested class Level_1 { @Nested class Level_2 { @Test void this_is_a_test() { } } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeScenarioOneTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; /** * @since 5.12 */ @DisplayName("Scenario 1") class IndicativeSentencesRuntimeEnclosingTypeScenarioOneTestCase extends IndicativeSentencesRuntimeEnclosingTypeTestCase { } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeScenarioTwoTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; /** * @since 5.12 */ @DisplayName("Scenario 2") class IndicativeSentencesRuntimeEnclosingTypeScenarioTwoTestCase extends IndicativeSentencesRuntimeEnclosingTypeTestCase { } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; /** * @since 5.12 */ @DisplayName("Base Scenario") @IndicativeSentencesGeneration(separator = " -> ", generator = ReplaceUnderscores.class) abstract class IndicativeSentencesRuntimeEnclosingTypeTestCase { @Nested class Level_1 { @Nested class Level_2 { @Test void this_is_a_test() { } } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesTopLevelTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; /** * This test case declares {@link IndicativeSentencesGeneration} on a top-level * test class that contains a nested test class. * * @see IndicativeSentencesNestedTestCase * @since 5.8 */ @IndicativeSentencesGeneration(separator = " -> ", generator = ReplaceUnderscores.class) class IndicativeSentencesTopLevelTestCase { @Nested class A_year_is_a_leap_year { @Test void if_it_is_divisible_by_4_but_not_by_100() { } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/IterableFactory.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import java.util.Arrays; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import org.jspecify.annotations.Nullable; final class IterableFactory { @SuppressWarnings("NullableProblems") static List listOf(@Nullable Object... objects) { return Arrays.asList(objects); } static Set setOf(@Nullable Object... objects) { return new LinkedHashSet<>(listOf(objects)); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/MediaTypeTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullFor; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullOrBlankFor; import org.jspecify.annotations.Nullable; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.NullAndEmptySource; import org.junit.jupiter.params.provider.ValueSource; /** * Unit tests for {@link MediaType}. * * @since 1.14 * @see org.junit.jupiter.api.extension.DeprecatedMediaTypeTests */ class MediaTypeTests { @Test void equals() { var mediaType1 = MediaType.TEXT_PLAIN; var mediaType2 = MediaType.parse(mediaType1.toString()); var mediaType3 = MediaType.APPLICATION_JSON; assertEqualsAndHashCode(mediaType1, mediaType2, mediaType3); } @Nested class ParseTests { @ParameterizedTest @NullAndEmptySource @ValueSource(strings = { " ", " \t " }) @SuppressWarnings("DataFlowIssue") // MediaType.create() parameters are not @Nullable void parseWithNullOrBlankMediaType(@Nullable String mediaType) { assertPreconditionViolationNotNullOrBlankFor("value", () -> MediaType.parse(mediaType)); } @ParameterizedTest @ValueSource(strings = { "/", " / ", "type", "type/", "/subtype" }) void parseWithInvalidMediaType(String mediaType) { assertPreconditionViolationFor(() -> MediaType.parse(mediaType))// .withMessage("Invalid media type: '%s'", mediaType.strip()); } @ParameterizedTest @ValueSource(strings = { "text/plain", " text/plain ", "text/plain; charset=UTF-8", "\t text/plain; charset=UTF-8 \t" }) void parse(String value) { assertThat(MediaType.parse(value)).hasToString(value.strip()); } } @Nested class CreateTests { @ParameterizedTest @NullAndEmptySource @ValueSource(strings = { " ", " \t " }) @SuppressWarnings("DataFlowIssue") // MediaType.create() parameters are not @Nullable void createWithNullOrBlankType(@Nullable String type) { assertPreconditionViolationNotNullOrBlankFor("type", () -> MediaType.create(type, "json")); } @ParameterizedTest @NullAndEmptySource @ValueSource(strings = { " ", " \t " }) @SuppressWarnings("DataFlowIssue") // MediaType.create() parameters are not @Nullable void createWithNullOrBlankTypeAndCharset(@Nullable String type) { assertPreconditionViolationNotNullOrBlankFor("type", () -> MediaType.create(type, "json", UTF_8)); } @ParameterizedTest @NullAndEmptySource @ValueSource(strings = { " ", " \t " }) @SuppressWarnings("DataFlowIssue") // MediaType.create() parameters are not @Nullable void createWithNullOrBlankSubtype(@Nullable String subtype) { assertPreconditionViolationNotNullOrBlankFor("subtype", () -> MediaType.create("application", subtype)); } @ParameterizedTest @NullAndEmptySource @ValueSource(strings = { " ", " \t " }) @SuppressWarnings("DataFlowIssue") // MediaType.create() parameters are not @Nullable void createWithNullOrBlankSubtypeAndCharset(@Nullable String subtype) { assertPreconditionViolationNotNullOrBlankFor("subtype", () -> MediaType.create("application", subtype, UTF_8)); } @Test @SuppressWarnings("DataFlowIssue") // MediaType.create() parameters are not @Nullable void createWithNullCharset() { assertPreconditionViolationNotNullFor("charset", () -> MediaType.create("application", "json", null)); } @ParameterizedTest @ValueSource(strings = { "/", " / ", "type/", "/subtype" }) void createWithInvalidType(String type) { assertPreconditionViolationFor(() -> MediaType.create(type, "json"))// .withMessage("Invalid media type: '%s/json'", type.strip()); } @ParameterizedTest @ValueSource(strings = { "/", " / ", "type/", "/subtype" }) void createWithInvalidSubtype(String subtype) { assertPreconditionViolationFor(() -> MediaType.create("application", subtype))// .withMessage("Invalid media type: 'application/%s'", subtype.strip()); } @Test void create() { assertThat(MediaType.create("application", "json")).hasToString("application/json"); } @Test void createWithCharset() { assertThat(MediaType.create("text", "plain", UTF_8)).hasToString("text/plain; charset=UTF-8"); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/RandomlyOrderedTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Constants.DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasses; import java.util.Collections; import java.util.LinkedHashSet; import java.util.Set; import java.util.stream.IntStream; import org.junit.platform.testkit.engine.EngineTestKit; import org.junit.platform.testkit.engine.Events; /** * @since 5.8 */ class RandomlyOrderedTests { private static final Set callSequence = Collections.synchronizedSet(new LinkedHashSet<>()); @Test void randomSeedForClassAndMethodOrderingIsDeterministic() { IntStream.range(0, 20).forEach(i -> { callSequence.clear(); var tests = executeTests(1618034); tests.assertStatistics(stats -> stats.succeeded(callSequence.size())); assertThat(callSequence).containsExactlyInAnyOrder("B_TestCase#b", "B_TestCase#c", "B_TestCase#a", "C_TestCase#b", "C_TestCase#c", "C_TestCase#a", "A_TestCase#b", "A_TestCase#c", "A_TestCase#a"); }); } private Events executeTests(@SuppressWarnings("SameParameterValue") long randomSeed) { // @formatter:off return EngineTestKit .engine("junit-jupiter") .configurationParameter(DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME, ClassOrderer.Random.class.getName()) .configurationParameter(DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME, MethodOrderer.Random.class.getName()) .configurationParameter(MethodOrderer.Random.RANDOM_SEED_PROPERTY_NAME, String.valueOf(randomSeed)) .selectors(selectClasses(A_TestCase.class, B_TestCase.class, C_TestCase.class)) .execute() .testEvents(); // @formatter:on } abstract static class BaseTestCase { @BeforeEach void trackInvocations(TestInfo testInfo) { var testClass = testInfo.getTestClass().orElseThrow(); var testMethod = testInfo.getTestMethod().orElseThrow(); callSequence.add(testClass.getSimpleName() + "#" + testMethod.getName()); } @Test void a() { } @Test void b() { } @Test void c() { } } @SuppressWarnings("NewClassNamingConvention") static class A_TestCase extends BaseTestCase { } @SuppressWarnings("NewClassNamingConvention") static class B_TestCase extends BaseTestCase { } @SuppressWarnings("NewClassNamingConvention") static class C_TestCase extends BaseTestCase { } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/condition/AbstractExecutionConditionTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.commons.support.HierarchyTraversalMode.TOP_DOWN; import static org.junit.platform.commons.support.ReflectionSupport.findMethod; import static org.junit.platform.commons.support.ReflectionSupport.findMethods; import static org.junit.platform.commons.support.ReflectionSupport.newInstance; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.util.List; import java.util.Optional; import java.util.function.Predicate; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.ExtensionContext; /** * Abstract base class for unit testing a concrete {@link ExecutionCondition} * implementation. * *

WARNING: this abstract base class currently does not * support tests in {@code @Nested} test classes within the * {@linkplain #getTestClass() test class}, since {@link #beforeEach(TestInfo)} * instantiates the test class using the no-args default constructor. * * @since 5.1 */ @TestInstance(Lifecycle.PER_CLASS) abstract class AbstractExecutionConditionTests { private final ExtensionContext context = mock(); private @Nullable ConditionEvaluationResult result; @BeforeAll void ensureAllTestMethodsAreCovered() { Predicate isTestMethod = method -> method.isAnnotationPresent(Test.class); List methodsToTest = findMethods(getTestClass(), isTestMethod, TOP_DOWN).stream()// .map(Method::getName).sorted().toList(); List localTestMethods = findMethods(getClass(), isTestMethod, TOP_DOWN).stream()// .map(Method::getName).sorted().toList(); assertThat(localTestMethods).containsExactlyElementsOf(methodsToTest); } @BeforeEach void beforeEach(TestInfo testInfo) { when(this.context.getElement()).thenReturn(method(testInfo)); when(this.context.getTestInstance()).thenReturn(Optional.of(newInstance(getTestClass()))); doReturn(getTestClass()).when(this.context).getRequiredTestClass(); } protected abstract ExecutionCondition getExecutionCondition(); protected abstract Class getTestClass(); protected void evaluateCondition() { this.result = getExecutionCondition().evaluateExecutionCondition(this.context); } protected void assertEnabled() { assertNotNull(this.result); assertFalse(this.result.isDisabled(), "Should be enabled"); } protected void assertDisabled() { assertNotNull(this.result); assertTrue(this.result.isDisabled(), "Should be disabled"); } protected void assertReasonContains(String text) { assertNotNull(this.result); assertThat(this.result.getReason()).hasValueSatisfying(reason -> assertThat(reason).contains(text)); } protected void assertCustomDisabledReasonIs(String text) { assertNotNull(this.result); if (this.result.isDisabled()) { assertThat(this.result.getReason()).hasValueSatisfying( reason -> assertThat(reason).contains(" ==> " + text)); } } private Optional method(TestInfo testInfo) { return method(getTestClass(), testInfo.getTestMethod().orElseThrow().getName()); } private Optional method(Class testClass, String methodName) { return Optional.of(findMethod(testClass, methodName).orElseThrow()); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/condition/ConditionEvaluationResultTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.assertj.core.api.Assertions.assertThat; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.NullSource; import org.junit.jupiter.params.provider.ValueSource; /** * Unit tests for {@link ConditionEvaluationResult}. * * @since 5.13.3 */ class ConditionEvaluationResultTests { @Test void enabledWithReason() { var result = ConditionEvaluationResult.enabled("reason"); assertThat(result.isDisabled()).isFalse(); assertThat(result.getReason()).contains("reason"); assertThat(result).asString()// .isEqualTo("ConditionEvaluationResult [enabled = true, reason = 'reason']"); } @BlankReasonsTest void enabledWithBlankReason(@Nullable String reason) { var result = ConditionEvaluationResult.enabled(reason); assertThat(result.isDisabled()).isFalse(); assertThat(result.getReason()).isEmpty(); assertThat(result).asString()// .isEqualTo("ConditionEvaluationResult [enabled = true, reason = '']"); } @Test void disabledWithDefaultReason() { var result = ConditionEvaluationResult.disabled("default"); assertThat(result.isDisabled()).isTrue(); assertThat(result.getReason()).contains("default"); assertThat(result).asString()// .isEqualTo("ConditionEvaluationResult [enabled = false, reason = 'default']"); } @BlankReasonsTest void disabledWithBlankDefaultReason(@Nullable String reason) { var result = ConditionEvaluationResult.disabled(reason); assertThat(result.isDisabled()).isTrue(); assertThat(result.getReason()).isEmpty(); assertThat(result).asString()// .isEqualTo("ConditionEvaluationResult [enabled = false, reason = '']"); } @BlankReasonsTest void disabledWithDefaultReasonAndBlankCustomReason(@Nullable String customReason) { var result = ConditionEvaluationResult.disabled("default", customReason); assertThat(result.isDisabled()).isTrue(); assertThat(result.getReason()).contains("default"); assertThat(result).asString()// .isEqualTo("ConditionEvaluationResult [enabled = false, reason = 'default']"); } @BlankReasonsTest void disabledWithBlankDefaultReasonAndCustomReason(@Nullable String reason) { var result = ConditionEvaluationResult.disabled(reason, "custom"); assertThat(result.isDisabled()).isTrue(); assertThat(result.getReason()).contains("custom"); assertThat(result).asString().isEqualTo("ConditionEvaluationResult [enabled = false, reason = 'custom']"); } @BlankReasonsTest void disabledWithBlankDefaultReasonAndBlankCustomReason(@Nullable String reason) { // We intentionally use the reason as both the default and custom reason. var result = ConditionEvaluationResult.disabled(reason, reason); assertThat(result.isDisabled()).isTrue(); assertThat(result.getReason()).isEmpty(); assertThat(result).asString()// .isEqualTo("ConditionEvaluationResult [enabled = false, reason = '']"); } @Test void disabledWithDefaultReasonAndCustomReason() { disabledWithDefaultReasonAndCustomReason("default", "custom"); } @Test void disabledWithDefaultReasonAndCustomReasonWithLeadingAndTrailingWhitespace() { disabledWithDefaultReasonAndCustomReason(" default ", " custom "); } private static void disabledWithDefaultReasonAndCustomReason(String defaultReason, String customReason) { var result = ConditionEvaluationResult.disabled(defaultReason, customReason); assertThat(result.isDisabled()).isTrue(); assertThat(result.getReason()).contains("default ==> custom"); assertThat(result).asString()// .isEqualTo("ConditionEvaluationResult [enabled = false, reason = 'default ==> custom']"); } @Retention(RetentionPolicy.RUNTIME) @ParameterizedTest @NullSource @ValueSource(strings = { "", " ", " ", "\t", "\f", "\r", "\n", "\r\n" }) @interface BlankReasonsTest { } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledForJreRangeConditionTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava17; import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava18; import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava19; import static org.junit.jupiter.api.condition.JavaVersionPredicates.onKnownVersion; import static org.junit.jupiter.api.condition.JavaVersionPredicates.onOtherVersion; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExecutionCondition; /** * Unit tests for {@link DisabledForJreRange}. * *

Note that test method names MUST match the test method names in * {@link DisabledForJreRangeIntegrationTests}. * * @since 5.6 */ class DisabledForJreRangeConditionTests extends AbstractExecutionConditionTests { private static final String JAVA_VERSION = System.getProperty("java.version"); @Override protected ExecutionCondition getExecutionCondition() { return new DisabledForJreRangeCondition(); } @Override protected Class getTestClass() { return DisabledForJreRangeIntegrationTests.class; } /** * @see DisabledForJreRangeIntegrationTests#enabledBecauseAnnotationIsNotPresent() */ @Test void enabledBecauseAnnotationIsNotPresent() { evaluateCondition(); assertEnabled(); assertReasonContains("@DisabledForJreRange is not present"); } /** * @see DisabledForJreRangeIntegrationTests#defaultValues() */ @Test void defaultValues() { assertPreconditionViolationFor(this::evaluateCondition)// .withMessage( "You must declare a non-default value for the minimum or maximum value in @DisabledForJreRange"); } /** * @see DisabledForJreRangeIntegrationTests#effectiveJreDefaultValues() */ @Test void effectiveJreDefaultValues() { defaultValues(); } /** * @see DisabledForJreRangeIntegrationTests#effectiveVersionDefaultValues() */ @Test void effectiveVersionDefaultValues() { defaultValues(); } /** * @see DisabledForJreRangeIntegrationTests#min17() */ @Test void min17() { defaultValues(); } /** * @see DisabledForJreRangeIntegrationTests#minVersion17() */ @Test void minVersion17() { defaultValues(); } /** * @see DisabledForJreRangeIntegrationTests#maxOther() */ @Test void maxOther() { defaultValues(); } /** * @see DisabledForJreRangeIntegrationTests#maxVersionMaxInteger() */ @Test void maxVersionMaxInteger() { defaultValues(); } /** * @see DisabledForJreRangeIntegrationTests#minVersion7() */ @Test void minVersion7() { assertPreconditionViolationFor(this::evaluateCondition)// .withMessage("@DisabledForJreRange's minVersion [7] must be greater than or equal to 8"); } /** * @see DisabledForJreRangeIntegrationTests#maxVersion16() */ @Test void maxVersion16() { assertPreconditionViolationFor(this::evaluateCondition)// .withMessage( "@DisabledForJreRange's minimum value [17] must be less than or equal to its maximum value [16]"); } /** * @see DisabledForJreRangeIntegrationTests#minAndMinVersion() */ @Test void minAndMinVersion() { assertPreconditionViolationFor(this::evaluateCondition)// .withMessage( "@DisabledForJreRange's minimum value must be configured with either a JRE enum constant or numeric version, but not both"); } /** * @see DisabledForJreRangeIntegrationTests#maxAndMaxVersion() */ @Test void maxAndMaxVersion() { assertPreconditionViolationFor(this::evaluateCondition)// .withMessage( "@DisabledForJreRange's maximum value must be configured with either a JRE enum constant or numeric version, but not both"); } /** * @see DisabledForJreRangeIntegrationTests#minGreaterThanMax() */ @Test void minGreaterThanMax() { assertPreconditionViolationFor(this::evaluateCondition)// .withMessage( "@DisabledForJreRange's minimum value [21] must be less than or equal to its maximum value [17]"); } /** * @see DisabledForJreRangeIntegrationTests#minGreaterThanMaxVersion() */ @Test void minGreaterThanMaxVersion() { minGreaterThanMax(); } /** * @see DisabledForJreRangeIntegrationTests#minVersionGreaterThanMaxVersion() */ @Test void minVersionGreaterThanMaxVersion() { minGreaterThanMax(); } /** * @see DisabledForJreRangeIntegrationTests#minVersionGreaterThanMax() */ @Test void minVersionGreaterThanMax() { minGreaterThanMax(); } /** * @see DisabledForJreRangeIntegrationTests#min18() */ @Test void min18() { evaluateCondition(); assertDisabledOnCurrentJreIf(!onJava17()); } /** * @see DisabledForJreRangeIntegrationTests#minVersion18() */ @Test void minVersion18() { min18(); } /** * @see DisabledForJreRangeIntegrationTests#max18() */ @Test void max18() { evaluateCondition(); assertDisabledOnCurrentJreIf(onJava17() || onJava18()); } /** * @see DisabledForJreRangeIntegrationTests#maxVersion18() */ @Test void maxVersion18() { max18(); } /** * @see DisabledForJreRangeIntegrationTests#min17Max17() */ @Test void min17Max17() { evaluateCondition(); assertDisabledOnCurrentJreIf(onJava17()); } /** * @see DisabledForJreRangeIntegrationTests#minVersion17MaxVersion17() */ @Test void minVersion17MaxVersion17() { min17Max17(); } /** * @see DisabledForJreRangeIntegrationTests#min18Max19() */ @Test void min18Max19() { evaluateCondition(); assertDisabledOnCurrentJreIf(onJava18() || onJava19()); assertCustomDisabledReasonIs("Disabled on Java 18 & 19"); } /** * @see DisabledForJreRangeIntegrationTests#minVersion18MaxVersion19() */ @Test void minVersion18MaxVersion19() { min18Max19(); } /** * @see DisabledForJreRangeIntegrationTests#minOtherMaxOther() */ @Test void minOtherMaxOther() { evaluateCondition(); assertDisabledOnCurrentJreIf(!(onKnownVersion() || onOtherVersion())); } /** * @see DisabledForJreRangeIntegrationTests#minMaxIntegerMaxMaxInteger() */ @Test void minMaxIntegerMaxMaxInteger() { minOtherMaxOther(); } private void assertDisabledOnCurrentJreIf(boolean condition) { if (condition) { assertDisabled(); assertReasonContains("Disabled on JRE version: " + JAVA_VERSION); } else { assertEnabled(); assertReasonContains("Enabled on JRE version: " + JAVA_VERSION); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledForJreRangeIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.condition.JRE.JAVA_17; import static org.junit.jupiter.api.condition.JRE.JAVA_18; import static org.junit.jupiter.api.condition.JRE.JAVA_19; import static org.junit.jupiter.api.condition.JRE.JAVA_21; import static org.junit.jupiter.api.condition.JRE.OTHER; import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava17; import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava18; import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava19; import static org.junit.jupiter.api.condition.JavaVersionPredicates.onKnownVersion; import static org.junit.jupiter.api.condition.JavaVersionPredicates.onOtherVersion; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; /** * Integration tests for {@link DisabledForJreRange @DisabledForJreRange}. * * @since 5.6 */ class DisabledForJreRangeIntegrationTests { @Test void enabledBecauseAnnotationIsNotPresent() { } @Test @Disabled("Only used in a unit test via reflection") @DisabledForJreRange void defaultValues() { fail("should result in a configuration exception"); } @SuppressWarnings("deprecation") @Test @Disabled("Only used in a unit test via reflection") @DisabledForJreRange(min = JAVA_17, max = OTHER) void effectiveJreDefaultValues() { fail("should result in a configuration exception"); } @Test @Disabled("Only used in a unit test via reflection") @DisabledForJreRange(minVersion = 17, maxVersion = Integer.MAX_VALUE) void effectiveVersionDefaultValues() { fail("should result in a configuration exception"); } @Test @Disabled("Only used in a unit test via reflection") @DisabledForJreRange(min = JAVA_17) void min17() { fail("should result in a configuration exception"); } @Test @Disabled("Only used in a unit test via reflection") @DisabledForJreRange(minVersion = 17) void minVersion17() { fail("should result in a configuration exception"); } @Test @Disabled("Only used in a unit test via reflection") @SuppressWarnings("deprecation") @DisabledForJreRange(max = OTHER) void maxOther() { fail("should result in a configuration exception"); } @Test @Disabled("Only used in a unit test via reflection") @DisabledForJreRange(maxVersion = Integer.MAX_VALUE) void maxVersionMaxInteger() { fail("should result in a configuration exception"); } @Test @Disabled("Only used in a unit test via reflection") @DisabledForJreRange(minVersion = 7) void minVersion7() { fail("should result in a configuration exception"); } @Test @Disabled("Only used in a unit test via reflection") @DisabledForJreRange(maxVersion = 16) void maxVersion16() { fail("should result in a configuration exception"); } @Test @Disabled("Only used in a unit test via reflection") @DisabledForJreRange(min = JAVA_18, minVersion = 21) void minAndMinVersion() { fail("should result in a configuration exception"); } @Test @Disabled("Only used in a unit test via reflection") @DisabledForJreRange(max = JAVA_18, maxVersion = 21) void maxAndMaxVersion() { fail("should result in a configuration exception"); } @Test @Disabled("Only used in a unit test via reflection") @DisabledForJreRange(min = JAVA_21, max = JAVA_17) void minGreaterThanMax() { fail("should result in a configuration exception"); } @Test @Disabled("Only used in a unit test via reflection") @DisabledForJreRange(min = JAVA_21, maxVersion = 17) void minGreaterThanMaxVersion() { fail("should result in a configuration exception"); } @Test @Disabled("Only used in a unit test via reflection") @DisabledForJreRange(minVersion = 21, maxVersion = 17) void minVersionGreaterThanMaxVersion() { fail("should result in a configuration exception"); } @Test @Disabled("Only used in a unit test via reflection") @DisabledForJreRange(minVersion = 21, max = JAVA_17) void minVersionGreaterThanMax() { fail("should result in a configuration exception"); } @Test @DisabledForJreRange(min = JAVA_18) void min18() { assertFalse(onJava18() || onJava19()); assertTrue(onJava17()); } @Test @DisabledForJreRange(minVersion = 18) void minVersion18() { min18(); } @Test @DisabledForJreRange(max = JAVA_18) void max18() { assertFalse(onJava17() || onJava18()); } @Test @DisabledForJreRange(maxVersion = 18) void maxVersion18() { max18(); } @Test @DisabledForJreRange(min = JAVA_17, max = JAVA_17) void min17Max17() { assertFalse(onJava17()); } @Test @DisabledForJreRange(minVersion = 17, maxVersion = 17) void minVersion17MaxVersion17() { min17Max17(); } @Test @DisabledForJreRange(min = JAVA_18, max = JAVA_19, disabledReason = "Disabled on Java 18 & 19") void min18Max19() { assertFalse(onJava18() || onJava19()); } @Test @DisabledForJreRange(minVersion = 18, maxVersion = 19, disabledReason = "Disabled on Java 18 & 19") void minVersion18MaxVersion19() { min18Max19(); } @Test @SuppressWarnings("deprecation") @DisabledForJreRange(min = OTHER, max = OTHER) void minOtherMaxOther() { assertTrue(onKnownVersion() || onOtherVersion()); } @Test @DisabledForJreRange(minVersion = Integer.MAX_VALUE, maxVersion = Integer.MAX_VALUE) void minMaxIntegerMaxMaxInteger() { minOtherMaxOther(); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledIfConditionClassLoaderTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.lang.reflect.Method; import java.util.Optional; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.test.TestClassLoader; /** * Tests for {@link DisabledIfCondition} using custom {@link ClassLoader} arrangements. * * @since 5.10 */ public class DisabledIfConditionClassLoaderTests { @Test // No need to introduce a "disabled" version of this test, since it would simply be the // logical inverse of this method and would therefore not provide any further benefit. void enabledWithStaticMethodInTypeFromDifferentClassLoader() throws Exception { try (var testClassLoader = TestClassLoader.forClasses(getClass(), StaticConditionMethods.class)) { var testClass = testClassLoader.loadClass(getClass().getName()); assertThat(testClass.getClassLoader()).isSameAs(testClassLoader); ExtensionContext context = mock(); Method annotatedMethod = ReflectionSupport.findMethod(getClass(), "enabledMethod").get(); when(context.getElement()).thenReturn(Optional.of(annotatedMethod)); doReturn(testClass).when(context).getRequiredTestClass(); DisabledIfCondition condition = new DisabledIfCondition(); ConditionEvaluationResult result = condition.evaluateExecutionCondition(context); assertThat(result).isNotNull(); assertThat(result.isDisabled()).isFalse(); Method conditionMethod = condition.getConditionMethod( "org.junit.jupiter.api.condition.StaticConditionMethods#returnsFalse", context); assertThat(conditionMethod).isNotNull(); Class declaringClass = conditionMethod.getDeclaringClass(); assertThat(declaringClass.getClassLoader()).isSameAs(testClassLoader); assertThat(declaringClass.getName()).isEqualTo(StaticConditionMethods.class.getName()); assertThat(declaringClass).isNotEqualTo(StaticConditionMethods.class); } } @DisabledIf("org.junit.jupiter.api.condition.StaticConditionMethods#returnsFalse") private void enabledMethod() { } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledIfConditionTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExecutionCondition; /** * Unit tests for {@link DisabledIf}. * *

Note that test method names MUST match the test method names in * {@link DisabledIfIntegrationTests}. * * @since 5.7 */ public class DisabledIfConditionTests extends AbstractExecutionConditionTests { @Override protected ExecutionCondition getExecutionCondition() { return new DisabledIfCondition(); } @Override protected Class getTestClass() { return DisabledIfIntegrationTests.class; } /** * @see DisabledIfIntegrationTests#enabledBecauseAnnotationIsNotPresent() */ @Test void enabledBecauseAnnotationIsNotPresent() { evaluateCondition(); assertEnabled(); assertReasonContains("@DisabledIf is not present"); } /** * @see DisabledIfIntegrationTests#disabledBecauseStaticConditionMethodReturnsTrue() */ @Test void disabledBecauseStaticConditionMethodReturnsTrue() { evaluateCondition(); assertDisabled(); assertReasonContains("Disabled for some reason"); } /** * @see DisabledIfIntegrationTests#enabledBecauseStaticConditionMethodReturnsFalse() */ @Test void enabledBecauseStaticConditionMethodReturnsFalse() { evaluateCondition(); assertEnabled(); assertReasonContains(""" @DisabledIf("staticMethodThatReturnsFalse") evaluated to false"""); } /** * @see DisabledIfIntegrationTests#disabledBecauseConditionMethodReturnsTrue() */ @Test void disabledBecauseConditionMethodReturnsTrue() { evaluateCondition(); assertDisabled(); assertReasonContains(""" @DisabledIf("methodThatReturnsTrue") evaluated to true"""); } /** * @see DisabledIfIntegrationTests#enabledBecauseConditionMethodReturnsFalse() */ @Test void enabledBecauseConditionMethodReturnsFalse() { evaluateCondition(); assertEnabled(); assertReasonContains(""" @DisabledIf("methodThatReturnsFalse") evaluated to false"""); } /** * @see DisabledIfIntegrationTests.ExternalConditionMethod#disabledBecauseStaticExternalConditionMethodReturnsTrue() */ @Test void disabledBecauseStaticExternalConditionMethodReturnsTrue() { evaluateCondition(); assertDisabled(); assertReasonContains(""" @DisabledIf("org.junit.jupiter.api.condition.StaticConditionMethods#returnsTrue") evaluated to true"""); } /** * @see DisabledIfIntegrationTests.ExternalConditionMethod#enabledBecauseStaticExternalConditionMethodReturnsFalse() */ @Test void enabledBecauseStaticExternalConditionMethodReturnsFalse() { evaluateCondition(); assertEnabled(); assertReasonContains( """ @DisabledIf("org.junit.jupiter.api.condition.StaticConditionMethods#returnsFalse") evaluated to false"""); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableConditionTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.junit.jupiter.api.condition.DisabledIfEnvironmentVariableIntegrationTests.ENIGMA; import static org.junit.jupiter.api.condition.DisabledIfEnvironmentVariableIntegrationTests.KEY1; import static org.junit.jupiter.api.condition.DisabledIfEnvironmentVariableIntegrationTests.KEY2; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotBlankFor; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExecutionCondition; /** * Unit tests for {@link DisabledIfEnvironmentVariableCondition}. * *

Note that test method names MUST match the test method names in * {@link DisabledIfEnvironmentVariableIntegrationTests}. * * @since 5.1 */ class DisabledIfEnvironmentVariableConditionTests extends AbstractExecutionConditionTests { /** * Stubbed subclass of {@link DisabledIfEnvironmentVariableCondition}. */ private ExecutionCondition condition = new DisabledIfEnvironmentVariableCondition() { @Override protected @Nullable String getEnvironmentVariable(String name) { return KEY1.equals(name) ? ENIGMA : null; } }; @Override protected ExecutionCondition getExecutionCondition() { return this.condition; } @Override protected Class getTestClass() { return DisabledIfEnvironmentVariableIntegrationTests.class; } /** * @see DisabledIfEnvironmentVariableIntegrationTests#enabledBecauseAnnotationIsNotPresent() */ @Test void enabledBecauseAnnotationIsNotPresent() { evaluateCondition(); assertEnabled(); assertReasonContains( "No @DisabledIfEnvironmentVariable conditions resulting in 'disabled' execution encountered"); } /** * @see DisabledIfEnvironmentVariableIntegrationTests#blankNamedAttribute() */ @Test void blankNamedAttribute() { assertPreconditionViolationNotBlankFor("The 'named' attribute", this::evaluateCondition); } /** * @see DisabledIfEnvironmentVariableIntegrationTests#blankMatchesAttribute() */ @Test void blankMatchesAttribute() { assertPreconditionViolationNotBlankFor("The 'matches' attribute", this::evaluateCondition); } /** * @see DisabledIfEnvironmentVariableIntegrationTests#disabledBecauseEnvironmentVariableMatchesExactly() */ @Test void disabledBecauseEnvironmentVariableMatchesExactly() { evaluateCondition(); assertDisabled(); assertReasonContains("matches regular expression"); assertCustomDisabledReasonIs("That's an enigma"); } /** * @see DisabledIfEnvironmentVariableIntegrationTests#disabledBecauseEnvironmentVariableForComposedAnnotationMatchesExactly() */ @Test void disabledBecauseEnvironmentVariableForComposedAnnotationMatchesExactly() { this.condition = new DisabledIfEnvironmentVariableCondition() { @Override protected @Nullable String getEnvironmentVariable(String name) { return KEY1.equals(name) || KEY2.equals(name) ? ENIGMA : null; } }; evaluateCondition(); assertDisabled(); assertReasonContains("matches regular expression"); } /** * @see DisabledIfEnvironmentVariableIntegrationTests#disabledBecauseEnvironmentVariableMatchesPattern() */ @Test void disabledBecauseEnvironmentVariableMatchesPattern() { evaluateCondition(); assertDisabled(); assertReasonContains("matches regular expression"); } /** * @see DisabledIfEnvironmentVariableIntegrationTests#enabledBecauseEnvironmentVariableDoesNotMatch() */ @Test void enabledBecauseEnvironmentVariableDoesNotMatch() { evaluateCondition(); assertEnabled(); assertReasonContains( "No @DisabledIfEnvironmentVariable conditions resulting in 'disabled' execution encountered"); } /** * @see DisabledIfEnvironmentVariableIntegrationTests#enabledBecauseEnvironmentVariableDoesNotExist() */ @Test void enabledBecauseEnvironmentVariableDoesNotExist() { evaluateCondition(); assertEnabled(); assertReasonContains( "No @DisabledIfEnvironmentVariable conditions resulting in 'disabled' execution encountered"); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.fail; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; /** * Integration tests for {@link DisabledIfEnvironmentVariable}. * * @since 5.1 */ @Disabled("Disabled since the required environment variables are not set") // Tests (except those with intentional configuration errors) will pass if you set // the following environment variables: // DisabledIfEnvironmentVariableTests.key1 = enigma // DisabledIfEnvironmentVariableTests.key2 = enigma class DisabledIfEnvironmentVariableIntegrationTests { static final String KEY1 = "DisabledIfEnvironmentVariableTests.key1"; static final String KEY2 = "DisabledIfEnvironmentVariableTests.key2"; static final String ENIGMA = "enigma"; static final String BOGUS = "bogus"; @Test void enabledBecauseAnnotationIsNotPresent() { } @Test @DisabledIfEnvironmentVariable(named = " ", matches = ENIGMA) void blankNamedAttribute() { } @Test @DisabledIfEnvironmentVariable(named = KEY1, matches = " ") void blankMatchesAttribute() { } @Test @DisabledIfEnvironmentVariable(named = KEY1, matches = ENIGMA, disabledReason = "That's an enigma") void disabledBecauseEnvironmentVariableMatchesExactly() { fail("should be disabled"); } @Test @DisabledIfEnvironmentVariable(named = KEY1, matches = BOGUS) @CustomDisabled void disabledBecauseEnvironmentVariableForComposedAnnotationMatchesExactly() { fail("should be disabled"); } @Test @DisabledIfEnvironmentVariable(named = KEY1, matches = ".*e.+gma$") void disabledBecauseEnvironmentVariableMatchesPattern() { fail("should be disabled"); } @Test @DisabledIfEnvironmentVariable(named = KEY1, matches = BOGUS) void enabledBecauseEnvironmentVariableDoesNotMatch() { assertNotEquals(BOGUS, System.getenv(KEY1)); } @Test @DisabledIfEnvironmentVariable(named = BOGUS, matches = "doesn't matter") void enabledBecauseEnvironmentVariableDoesNotExist() { assertNull(System.getenv(BOGUS)); } @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @DisabledIfEnvironmentVariable(named = KEY2, matches = ENIGMA) @interface CustomDisabled { } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledIfIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.junit.jupiter.api.Assertions.fail; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; /** * Integration tests for {@link DisabledIf}. * * @since 5.7 */ public class DisabledIfIntegrationTests { @Test @Disabled("Only used in a unit test via reflection") void enabledBecauseAnnotationIsNotPresent() { } @Test @DisabledIf(value = "staticMethodThatReturnsTrue", disabledReason = "Disabled for some reason") void disabledBecauseStaticConditionMethodReturnsTrue() { fail("Should be disabled"); } @Test @DisabledIf("staticMethodThatReturnsFalse") void enabledBecauseStaticConditionMethodReturnsFalse() { } @Test @DisabledIf("methodThatReturnsTrue") void disabledBecauseConditionMethodReturnsTrue() { fail("Should be disabled"); } @Test @DisabledIf("methodThatReturnsFalse") void enabledBecauseConditionMethodReturnsFalse() { } @Test @DisabledIf("org.junit.jupiter.api.condition.StaticConditionMethods#returnsTrue") void disabledBecauseStaticExternalConditionMethodReturnsTrue() { fail("Should be disabled"); } @Test @DisabledIf("org.junit.jupiter.api.condition.StaticConditionMethods#returnsFalse") void enabledBecauseStaticExternalConditionMethodReturnsFalse() { } @Nested @DisabledIf("org.junit.jupiter.api.condition.StaticConditionMethods#returnsTrue") class ConditionallyDisabledClass { @Test void disabledBecauseConditionMethodReturnsTrue() { fail("Should be disabled"); } } // ------------------------------------------------------------------------- @SuppressWarnings("unused") private static boolean staticMethodThatReturnsTrue() { return true; } @SuppressWarnings("unused") private static boolean staticMethodThatReturnsFalse() { return false; } @SuppressWarnings("unused") private boolean methodThatReturnsTrue() { return true; } @SuppressWarnings("unused") private boolean methodThatReturnsFalse() { return false; } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyConditionTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotBlankFor; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.util.SetSystemProperty; /** * Unit tests for {@link DisabledIfSystemPropertyCondition}. * *

Note that test method names MUST match the test method names in * {@link DisabledIfSystemPropertyIntegrationTests}. * * @since 5.1 */ @SetSystemProperty(key = DisabledIfSystemPropertyIntegrationTests.KEY1, value = DisabledIfSystemPropertyIntegrationTests.ENIGMA) @SetSystemProperty(key = DisabledIfSystemPropertyIntegrationTests.KEY2, value = DisabledIfSystemPropertyIntegrationTests.ENIGMA) class DisabledIfSystemPropertyConditionTests extends AbstractExecutionConditionTests { @Override protected ExecutionCondition getExecutionCondition() { return new DisabledIfSystemPropertyCondition(); } @Override protected Class getTestClass() { return DisabledIfSystemPropertyIntegrationTests.class; } /** * @see DisabledIfSystemPropertyIntegrationTests#enabledBecauseAnnotationIsNotPresent() */ @Test void enabledBecauseAnnotationIsNotPresent() { evaluateCondition(); assertEnabled(); assertReasonContains("No @DisabledIfSystemProperty conditions resulting in 'disabled' execution encountered"); } /** * @see DisabledIfSystemPropertyIntegrationTests#blankNamedAttribute() */ @Test void blankNamedAttribute() { assertPreconditionViolationNotBlankFor("The 'named' attribute", this::evaluateCondition); } /** * @see DisabledIfSystemPropertyIntegrationTests#blankMatchesAttribute() */ @Test void blankMatchesAttribute() { assertPreconditionViolationNotBlankFor("The 'matches' attribute", this::evaluateCondition); } /** * @see DisabledIfSystemPropertyIntegrationTests#disabledBecauseSystemPropertyMatchesExactly() */ @Test void disabledBecauseSystemPropertyMatchesExactly() { evaluateCondition(); assertDisabled(); assertReasonContains("matches regular expression"); assertCustomDisabledReasonIs("That's an enigma"); } /** * @see DisabledIfSystemPropertyIntegrationTests#disabledBecauseSystemPropertyForComposedAnnotationMatchesExactly() */ @Test void disabledBecauseSystemPropertyForComposedAnnotationMatchesExactly() { evaluateCondition(); assertDisabled(); assertReasonContains("matches regular expression"); } /** * @see DisabledIfSystemPropertyIntegrationTests#disabledBecauseSystemPropertyMatchesPattern() */ @Test void disabledBecauseSystemPropertyMatchesPattern() { evaluateCondition(); assertDisabled(); assertReasonContains("matches regular expression"); } /** * @see DisabledIfSystemPropertyIntegrationTests#enabledBecauseSystemPropertyDoesNotMatch() */ @Test void enabledBecauseSystemPropertyDoesNotMatch() { evaluateCondition(); assertEnabled(); assertReasonContains("No @DisabledIfSystemProperty conditions resulting in 'disabled' execution encountered"); } /** * @see DisabledIfSystemPropertyIntegrationTests#enabledBecauseSystemPropertyDoesNotExist() */ @Test void enabledBecauseSystemPropertyDoesNotExist() { evaluateCondition(); assertEnabled(); assertReasonContains("No @DisabledIfSystemProperty conditions resulting in 'disabled' execution encountered"); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.fail; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.util.SetSystemProperty; /** * Integration tests for {@link DisabledIfSystemProperty}. * * @since 5.1 */ @SetSystemProperty(key = DisabledIfSystemPropertyIntegrationTests.KEY1, value = DisabledIfSystemPropertyIntegrationTests.ENIGMA) @SetSystemProperty(key = DisabledIfSystemPropertyIntegrationTests.KEY2, value = DisabledIfSystemPropertyIntegrationTests.ENIGMA) class DisabledIfSystemPropertyIntegrationTests { static final String KEY1 = "DisabledIfSystemPropertyTests.key1"; static final String KEY2 = "DisabledIfSystemPropertyTests.key2"; static final String ENIGMA = "enigma"; private static final String BOGUS = "bogus"; @Test void enabledBecauseAnnotationIsNotPresent() { // no-op } @Test @Disabled("Only used in a unit test via reflection") @DisabledIfSystemProperty(named = " ", matches = ENIGMA) void blankNamedAttribute() { fail("should be disabled"); } @Test @Disabled("Only used in a unit test via reflection") @DisabledIfSystemProperty(named = KEY1, matches = " ") void blankMatchesAttribute() { fail("should be disabled"); } @Test @DisabledIfSystemProperty(named = KEY1, matches = ENIGMA, disabledReason = "That's an enigma") void disabledBecauseSystemPropertyMatchesExactly() { fail("should be disabled"); } @Test @DisabledIfSystemProperty(named = KEY1, matches = BOGUS) @CustomDisabled void disabledBecauseSystemPropertyForComposedAnnotationMatchesExactly() { fail("should be disabled"); } @Test @DisabledIfSystemProperty(named = KEY1, matches = ".*e.+gma$") void disabledBecauseSystemPropertyMatchesPattern() { fail("should be disabled"); } @Test @DisabledIfSystemProperty(named = KEY1, matches = BOGUS) void enabledBecauseSystemPropertyDoesNotMatch() { assertNotEquals(BOGUS, System.getProperty(KEY1)); } @Test @DisabledIfSystemProperty(named = BOGUS, matches = "doesn't matter") void enabledBecauseSystemPropertyDoesNotExist() { assertNull(System.getProperty(BOGUS)); } @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @DisabledIfSystemProperty(named = KEY2, matches = ENIGMA) @interface CustomDisabled { } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledOnOsConditionTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onAix; import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onArchitecture; import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onFreebsd; import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onLinux; import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onMac; import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onOpenbsd; import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onSolaris; import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onWindows; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExecutionCondition; /** * Unit tests for {@link DisabledOnOsCondition}. * *

Note that test method names MUST match the test method names in * {@link DisabledOnOsIntegrationTests}. * * @since 5.1 */ class DisabledOnOsConditionTests extends AbstractExecutionConditionTests { private static final String OS_NAME = System.getProperty("os.name"); private static final String ARCH = System.getProperty("os.arch"); @Override protected ExecutionCondition getExecutionCondition() { return new DisabledOnOsCondition(); } @Override protected Class getTestClass() { return DisabledOnOsIntegrationTests.class; } /** * @see DisabledOnOsIntegrationTests#enabledBecauseAnnotationIsNotPresent() */ @Test void enabledBecauseAnnotationIsNotPresent() { evaluateCondition(); assertEnabled(); assertReasonContains("@DisabledOnOs is not present"); } /** * @see DisabledOnOsIntegrationTests#missingOsAndArchitectureDeclaration() */ @Test void missingOsAndArchitectureDeclaration() { assertPreconditionViolationFor(this::evaluateCondition)// .withMessageContaining("You must declare at least one OS or architecture"); } /** * @see DisabledOnOsIntegrationTests#disabledOnEveryOs() */ @Test void disabledOnEveryOs() { evaluateCondition(); assertDisabled(); assertReasonContains("Disabled on operating system: %s ==> Disabled on every OS".formatted(OS_NAME)); } /** * @see DisabledOnOsIntegrationTests#aix() */ @Test void aix() { evaluateCondition(); assertDisabledOnCurrentOsIf(onAix()); } /** * @see DisabledOnOsIntegrationTests#freebsd() */ @Test void freebsd() { evaluateCondition(); assertDisabledOnCurrentOsIf(onFreebsd()); } /** * @see DisabledOnOsIntegrationTests#linux() */ @Test void linux() { evaluateCondition(); assertDisabledOnCurrentOsIf(onLinux()); } /** * @see DisabledOnOsIntegrationTests#macOs() */ @Test void macOs() { evaluateCondition(); assertDisabledOnCurrentOsIf(onMac()); } /** * @see DisabledOnOsIntegrationTests#macOsWithComposedAnnotation() */ @Test void macOsWithComposedAnnotation() { evaluateCondition(); assertDisabledOnCurrentOsIf(onMac()); } /** * @see DisabledOnOsIntegrationTests#openbsd() */ @Test void openbsd() { evaluateCondition(); assertDisabledOnCurrentOsIf(onOpenbsd()); } /** * @see DisabledOnOsIntegrationTests#windows() */ @Test void windows() { evaluateCondition(); assertDisabledOnCurrentOsIf(onWindows()); } /** * @see DisabledOnOsIntegrationTests#solaris() */ @Test void solaris() { evaluateCondition(); assertDisabledOnCurrentOsIf(onSolaris()); } /** * @see DisabledOnOsIntegrationTests#other() */ @Test void other() { evaluateCondition(); assertDisabledOnCurrentOsIf( !(onAix() || onFreebsd() || onLinux() || onMac() || onOpenbsd() || onSolaris() || onWindows())); } /** * @see DisabledOnOsIntegrationTests#architectureX86_64() */ @Test void architectureX86_64() { evaluateCondition(); assertDisabledOnCurrentArchitectureIf(onArchitecture("x86_64")); } /** * @see DisabledOnOsIntegrationTests#architectureAarch64() */ @Test void architectureAarch64() { evaluateCondition(); assertDisabledOnCurrentArchitectureIf(onArchitecture("aarch64")); } /** * @see EnabledOnOsIntegrationTests#architectureX86_64WithMacOs() */ @Test void architectureX86_64WithMacOs() { evaluateCondition(); assertDisabledOnCurrentOsAndArchitectureIf(onMac() && onArchitecture("x86_64")); } /** * @see EnabledOnOsIntegrationTests#architectureX86_64WithWindows() */ @Test void architectureX86_64WithWindows() { evaluateCondition(); assertDisabledOnCurrentOsAndArchitectureIf(onWindows() && onArchitecture("x86_64")); } /** * @see EnabledOnOsIntegrationTests#architectureX86_64WithLinux() */ @Test void architectureX86_64WithLinux() { evaluateCondition(); assertDisabledOnCurrentOsAndArchitectureIf(onLinux() && onArchitecture("x86_64")); } /** * @see EnabledOnOsIntegrationTests#aarch64WithMacOs() */ @Test void aarch64WithMacOs() { evaluateCondition(); assertDisabledOnCurrentOsAndArchitectureIf(onMac() && onArchitecture("aarch64")); } /** * @see EnabledOnOsIntegrationTests#aarch64WithWindows() */ @Test void aarch64WithWindows() { evaluateCondition(); assertDisabledOnCurrentOsAndArchitectureIf(onWindows() && onArchitecture("aarch64")); } /** * @see EnabledOnOsIntegrationTests#aarch64WithLinux() */ @Test void aarch64WithLinux() { evaluateCondition(); assertDisabledOnCurrentOsAndArchitectureIf(onLinux() && onArchitecture("aarch64")); } private void assertDisabledOnCurrentOsIf(boolean condition) { if (condition) { assertDisabled(); assertReasonContains("Disabled on operating system: %s".formatted(OS_NAME)); } else { assertEnabled(); assertReasonContains("Enabled on operating system: %s".formatted(OS_NAME)); } } private void assertDisabledOnCurrentArchitectureIf(boolean condition) { if (condition) { assertDisabled(); assertReasonContains("Disabled on architecture: %s".formatted(ARCH)); } else { assertEnabled(); assertReasonContains("Enabled on architecture: %s".formatted(ARCH)); } } private void assertDisabledOnCurrentOsAndArchitectureIf(boolean condition) { if (condition) { assertDisabled(); assertReasonContains("Disabled on operating system: %s (%s)".formatted(OS_NAME, ARCH)); } else { assertEnabled(); assertReasonContains("Enabled on operating system: %s (%s)".formatted(OS_NAME, ARCH)); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledOnOsIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onAix; import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onArchitecture; import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onFreebsd; import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onLinux; import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onMac; import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onOpenbsd; import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onSolaris; import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onWindows; import static org.junit.jupiter.api.condition.OS.AIX; import static org.junit.jupiter.api.condition.OS.FREEBSD; import static org.junit.jupiter.api.condition.OS.LINUX; import static org.junit.jupiter.api.condition.OS.MAC; import static org.junit.jupiter.api.condition.OS.OPENBSD; import static org.junit.jupiter.api.condition.OS.OTHER; import static org.junit.jupiter.api.condition.OS.SOLARIS; import static org.junit.jupiter.api.condition.OS.WINDOWS; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; /** * Integration tests for {@link DisabledOnOs}. * * @since 5.1 */ class DisabledOnOsIntegrationTests { @Test @Disabled("Only used in a unit test via reflection") void enabledBecauseAnnotationIsNotPresent() { } @Test @Disabled("Only used in a unit test via reflection") @DisabledOnOs({}) void missingOsAndArchitectureDeclaration() { } @Test @DisabledOnOs(value = { AIX, FREEBSD, LINUX, MAC, OPENBSD, WINDOWS, SOLARIS, OTHER }, disabledReason = "Disabled on every OS") void disabledOnEveryOs() { fail("should be disabled"); } @Test @DisabledOnOs(AIX) void aix() { assertFalse(onAix()); } @Test @DisabledOnOs(FREEBSD) void freebsd() { assertFalse(onFreebsd()); } @Test @DisabledOnOs(LINUX) void linux() { assertFalse(onLinux()); } @Test @DisabledOnOs(MAC) void macOs() { assertFalse(onMac()); } @Test @DisabledOnMac void macOsWithComposedAnnotation() { assertFalse(onMac()); } @Test @DisabledOnOs(OPENBSD) void openbsd() { assertFalse(onOpenbsd()); } @Test @DisabledOnOs(WINDOWS) void windows() { assertFalse(onWindows()); } @Test @DisabledOnOs(SOLARIS) void solaris() { assertFalse(onSolaris()); } @Test @DisabledOnOs(OTHER) void other() { assertTrue(onAix() || onFreebsd() || onLinux() || onMac() || onOpenbsd() || onSolaris() || onWindows()); } @Test @DisabledOnOs(architectures = "x86_64") void architectureX86_64() { assertFalse(onArchitecture("x_86_64")); } @Test @DisabledOnOs(architectures = "aarch64") void architectureAarch64() { assertFalse(onArchitecture("aarch64")); } @Test @DisabledOnOs(value = MAC, architectures = "x86_64") void architectureX86_64WithMacOs() { assertFalse(onMac() && onArchitecture("x_86_64")); } @Test @DisabledOnOs(value = WINDOWS, architectures = "x86_64") void architectureX86_64WithWindows() { assertFalse(onWindows() && onArchitecture("x86_64")); } @Test @DisabledOnOs(value = LINUX, architectures = "x86_64") void architectureX86_64WithLinux() { assertFalse(onLinux() && onArchitecture("x86_64")); } @Test @DisabledOnOs(value = MAC, architectures = "aarch64") void aarch64WithMacOs() { assertFalse(onMac() && onArchitecture("aarch64")); } @Test @DisabledOnOs(value = WINDOWS, architectures = "aarch64") void aarch64WithWindows() { assertFalse(onWindows() && onArchitecture("aarch64")); } @Test @DisabledOnOs(value = LINUX, architectures = "aarch64") void aarch64WithLinux() { assertFalse(onLinux() && onArchitecture("aarch64")); } // ------------------------------------------------------------------------- @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @DisabledOnOs(MAC) @interface DisabledOnMac { } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledForJreRangeConditionTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava17; import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava18; import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava19; import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava20; import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava21; import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava22; import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava23; import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava24; import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava25; import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava26; import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava27; import static org.junit.jupiter.api.condition.JavaVersionPredicates.onKnownVersion; import static org.junit.jupiter.api.condition.JavaVersionPredicates.onOtherVersion; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExecutionCondition; /** * Unit tests for {@link EnabledForJreRange @EnabledForJreRange}. * *

Note that test method names MUST match the test method names in * {@link EnabledForJreRangeIntegrationTests}. * * @since 5.6 */ class EnabledForJreRangeConditionTests extends AbstractExecutionConditionTests { private static final String JAVA_VERSION = System.getProperty("java.version"); @Override protected ExecutionCondition getExecutionCondition() { return new EnabledForJreRangeCondition(); } @Override protected Class getTestClass() { return EnabledForJreRangeIntegrationTests.class; } /** * @see EnabledForJreRangeIntegrationTests#enabledBecauseAnnotationIsNotPresent() */ @Test void enabledBecauseAnnotationIsNotPresent() { evaluateCondition(); assertEnabled(); assertReasonContains("@EnabledForJreRange is not present"); } /** * @see EnabledForJreRangeIntegrationTests#defaultValues() */ @Test void defaultValues() { assertPreconditionViolationFor(this::evaluateCondition)// .withMessage( "You must declare a non-default value for the minimum or maximum value in @EnabledForJreRange"); } /** * @see EnabledForJreRangeIntegrationTests#effectiveJreDefaultValues() */ @Test void effectiveJreDefaultValues() { defaultValues(); } /** * @see EnabledForJreRangeIntegrationTests#effectiveVersionDefaultValues() */ @Test void effectiveVersionDefaultValues() { defaultValues(); } /** * @see EnabledForJreRangeIntegrationTests#min17() */ @Test void min17() { defaultValues(); } /** * @see EnabledForJreRangeIntegrationTests#minVersion17() */ @Test void minVersion17() { defaultValues(); } /** * @see EnabledForJreRangeIntegrationTests#maxOther() */ @Test void maxOther() { defaultValues(); } /** * @see EnabledForJreRangeIntegrationTests#maxVersionMaxInteger() */ @Test void maxVersionMaxInteger() { defaultValues(); } /** * @see EnabledForJreRangeIntegrationTests#minVersion7() */ @Test void minVersion7() { assertPreconditionViolationFor(this::evaluateCondition)// .withMessage("@EnabledForJreRange's minVersion [7] must be greater than or equal to 8"); } /** * @see EnabledForJreRangeIntegrationTests#maxVersion16() */ @Test void maxVersion16() { assertPreconditionViolationFor(this::evaluateCondition)// .withMessage( "@EnabledForJreRange's minimum value [17] must be less than or equal to its maximum value [16]"); } /** * @see EnabledForJreRangeIntegrationTests#minAndMinVersion() */ @Test void minAndMinVersion() { assertPreconditionViolationFor(this::evaluateCondition)// .withMessage( "@EnabledForJreRange's minimum value must be configured with either a JRE enum constant or numeric version, but not both"); } /** * @see EnabledForJreRangeIntegrationTests#maxAndMaxVersion() */ @Test void maxAndMaxVersion() { assertPreconditionViolationFor(this::evaluateCondition)// .withMessage( "@EnabledForJreRange's maximum value must be configured with either a JRE enum constant or numeric version, but not both"); } /** * @see EnabledForJreRangeIntegrationTests#minGreaterThanMax() */ @Test void minGreaterThanMax() { assertPreconditionViolationFor(this::evaluateCondition)// .withMessage( "@EnabledForJreRange's minimum value [21] must be less than or equal to its maximum value [17]"); } /** * @see EnabledForJreRangeIntegrationTests#minGreaterThanMaxVersion() */ @Test void minGreaterThanMaxVersion() { minGreaterThanMax(); } /** * @see EnabledForJreRangeIntegrationTests#minVersionGreaterThanMaxVersion() */ @Test void minVersionGreaterThanMaxVersion() { minGreaterThanMax(); } /** * @see EnabledForJreRangeIntegrationTests#minVersionGreaterThanMax() */ @Test void minVersionGreaterThanMax() { minGreaterThanMax(); } /** * @see EnabledForJreRangeIntegrationTests#min20() */ @Test void min20() { evaluateCondition(); assertEnabledOnCurrentJreIf(onJava20() || onJava21() || onJava22() || onJava23() || onJava24() || onJava25() || onJava26() || onJava27() || onOtherVersion()); } /** * @see EnabledForJreRangeIntegrationTests#minVersion20() */ @Test void minVersion20() { min20(); } /** * @see EnabledForJreRangeIntegrationTests#max21() */ @Test void max21() { evaluateCondition(); assertEnabledOnCurrentJreIf(onJava17() || onJava18() || onJava19() || onJava20() || onJava21()); } /** * @see EnabledForJreRangeIntegrationTests#maxVersion21() */ @Test void maxVersion21() { max21(); } /** * @see EnabledForJreRangeIntegrationTests#min17Max21() */ @Test void min17Max21() { max21(); } /** * @see EnabledForJreRangeIntegrationTests#min17Max17() */ @Test void min17Max17() { evaluateCondition(); assertEnabledOnCurrentJreIf(onJava17()); } /** * @see EnabledForJreRangeIntegrationTests#min17MaxVersion17() */ @Test void min17MaxVersion17() { min17Max17(); } /** * @see EnabledForJreRangeIntegrationTests#minVersion17Max17() */ @Test void minVersion17Max17() { min17Max17(); } /** * @see EnabledForJreRangeIntegrationTests#minVersion17MaxVersion17() */ @Test void minVersion17MaxVersion17() { min17Max17(); } /** * @see EnabledForJreRangeIntegrationTests#min20Max21() */ @Test void min20Max21() { evaluateCondition(); assertEnabledOnCurrentJreIf(onJava20() || onJava21()); } /** * @see EnabledForJreRangeIntegrationTests#min20MaxVersion21() */ @Test void min20MaxVersion21() { min20Max21(); } /** * @see EnabledForJreRangeIntegrationTests#minVersion20Max21() */ @Test void minVersion20Max21() { min20Max21(); } /** * @see EnabledForJreRangeIntegrationTests#minVersion20MaxVersion21() */ @Test void minVersion20MaxVersion21() { min20Max21(); } /** * @see EnabledForJreRangeIntegrationTests#minVersion21MaxVersionMaxInteger() */ @Test void minVersion21MaxVersionMaxInteger() { evaluateCondition(); assertEnabledOnCurrentJreIf(onJava21() || onJava22() || onJava23() || onJava24() || onJava25() || onJava26() || onJava27() || onOtherVersion()); } /** * @see EnabledForJreRangeIntegrationTests#minOtherMaxOther() */ @Test void minOtherMaxOther() { evaluateCondition(); assertEnabledOnCurrentJreIf(!(onKnownVersion() || onOtherVersion())); } /** * @see EnabledForJreRangeIntegrationTests#minMaxIntegerMaxMaxInteger() */ @Test void minMaxIntegerMaxMaxInteger() { minOtherMaxOther(); } private void assertEnabledOnCurrentJreIf(boolean condition) { if (condition) { assertEnabled(); assertReasonContains("Enabled on JRE version: " + JAVA_VERSION); } else { assertDisabled(); assertReasonContains("Disabled on JRE version: " + JAVA_VERSION); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledForJreRangeIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.condition.JRE.JAVA_17; import static org.junit.jupiter.api.condition.JRE.JAVA_18; import static org.junit.jupiter.api.condition.JRE.JAVA_20; import static org.junit.jupiter.api.condition.JRE.JAVA_21; import static org.junit.jupiter.api.condition.JRE.OTHER; import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava17; import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava18; import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava19; import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava20; import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava21; import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava22; import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava23; import static org.junit.jupiter.api.condition.JavaVersionPredicates.onKnownVersion; import static org.junit.jupiter.api.condition.JavaVersionPredicates.onOtherVersion; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; /** * Integration tests for {@link EnabledForJreRange @EnabledForJreRange}. * * @since 5.6 */ class EnabledForJreRangeIntegrationTests { private static final JRE CURRENT_JRE = JRE.currentJre(); @Test @Disabled("Only used in a unit test via reflection") void enabledBecauseAnnotationIsNotPresent() { } @Test @Disabled("Only used in a unit test via reflection") @EnabledForJreRange void defaultValues() { fail("should result in a configuration exception"); } @SuppressWarnings("deprecation") @Test @Disabled("Only used in a unit test via reflection") @EnabledForJreRange(min = JAVA_17, max = OTHER) void effectiveJreDefaultValues() { fail("should result in a configuration exception"); } @Test @Disabled("Only used in a unit test via reflection") @EnabledForJreRange(minVersion = 17, maxVersion = Integer.MAX_VALUE) void effectiveVersionDefaultValues() { fail("should result in a configuration exception"); } @Test @Disabled("Only used in a unit test via reflection") @EnabledForJreRange(min = JAVA_17) void min17() { fail("should result in a configuration exception"); } @Test @Disabled("Only used in a unit test via reflection") @EnabledForJreRange(minVersion = 17) void minVersion17() { fail("should result in a configuration exception"); } @Test @Disabled("Only used in a unit test via reflection") @SuppressWarnings("deprecation") @EnabledForJreRange(max = OTHER) void maxOther() { fail("should result in a configuration exception"); } @Test @Disabled("Only used in a unit test via reflection") @EnabledForJreRange(maxVersion = Integer.MAX_VALUE) void maxVersionMaxInteger() { fail("should result in a configuration exception"); } @Test @Disabled("Only used in a unit test via reflection") @EnabledForJreRange(minVersion = 7) void minVersion7() { fail("should result in a configuration exception"); } @Test @Disabled("Only used in a unit test via reflection") @EnabledForJreRange(maxVersion = 16) void maxVersion16() { fail("should result in a configuration exception"); } @Test @Disabled("Only used in a unit test via reflection") @EnabledForJreRange(min = JAVA_18, minVersion = 21) void minAndMinVersion() { fail("should result in a configuration exception"); } @Test @Disabled("Only used in a unit test via reflection") @EnabledForJreRange(max = JAVA_18, maxVersion = 21) void maxAndMaxVersion() { fail("should result in a configuration exception"); } @Test @Disabled("Only used in a unit test via reflection") @EnabledForJreRange(min = JAVA_21, max = JAVA_17) void minGreaterThanMax() { fail("should result in a configuration exception"); } @Test @Disabled("Only used in a unit test via reflection") @EnabledForJreRange(min = JAVA_21, maxVersion = 17) void minGreaterThanMaxVersion() { fail("should result in a configuration exception"); } @Test @Disabled("Only used in a unit test via reflection") @EnabledForJreRange(minVersion = 21, maxVersion = 17) void minVersionGreaterThanMaxVersion() { fail("should result in a configuration exception"); } @Test @Disabled("Only used in a unit test via reflection") @EnabledForJreRange(minVersion = 21, max = JAVA_17) void minVersionGreaterThanMax() { fail("should result in a configuration exception"); } @Test @EnabledForJreRange(min = JAVA_20) void min20() { assertTrue(onKnownVersion() || onOtherVersion()); assertTrue(JRE.currentVersionNumber() >= 20); assertTrue(CURRENT_JRE.compareTo(JAVA_20) >= 0); assertTrue(CURRENT_JRE.version() >= 20); assertFalse(onJava19()); } @Test @EnabledForJreRange(minVersion = 20) void minVersion20() { min20(); } @Test @EnabledForJreRange(max = JAVA_21) void max21() { assertTrue(onKnownVersion()); assertTrue(JRE.currentVersionNumber() <= 21); assertTrue(CURRENT_JRE.compareTo(JAVA_21) <= 0); assertTrue(CURRENT_JRE.version() <= 21); assertTrue(onJava17() || onJava18() || onJava19() || onJava20() || onJava21()); assertFalse(onJava22()); } @Test @EnabledForJreRange(maxVersion = 21) void maxVersion21() { max21(); } @Test @EnabledForJreRange(min = JAVA_17, max = JAVA_21) void min17Max21() { max21(); } @Test @EnabledForJreRange(min = JAVA_17, max = JAVA_17) void min17Max17() { assertTrue(onJava17()); } @Test @EnabledForJreRange(min = JAVA_17, maxVersion = 17) void min17MaxVersion17() { min17Max17(); } @Test @EnabledForJreRange(minVersion = 17, max = JAVA_17) void minVersion17Max17() { min17Max17(); } @Test @EnabledForJreRange(minVersion = 17, maxVersion = 17) void minVersion17MaxVersion17() { min17Max17(); } @Test @EnabledForJreRange(min = JAVA_20, max = JAVA_21) void min20Max21() { assertTrue(onJava20() || onJava21()); assertFalse(onJava17() || onJava23()); } @Test @EnabledForJreRange(min = JAVA_20, maxVersion = 21) void min20MaxVersion21() { min20Max21(); } @Test @EnabledForJreRange(minVersion = 20, max = JAVA_21) void minVersion20Max21() { min20Max21(); } @Test @EnabledForJreRange(minVersion = 20, maxVersion = 21) void minVersion20MaxVersion21() { min20Max21(); } @Test @EnabledForJreRange(minVersion = 21, maxVersion = Integer.MAX_VALUE) void minVersion21MaxVersionMaxInteger() { assertTrue(onKnownVersion() || onOtherVersion()); assertTrue(JRE.currentVersionNumber() >= 21); } @Test @SuppressWarnings("deprecation") @EnabledForJreRange(min = OTHER, max = OTHER) void minOtherMaxOther() { assertFalse(onKnownVersion()); } @Test @EnabledForJreRange(minVersion = Integer.MAX_VALUE, maxVersion = Integer.MAX_VALUE) void minMaxIntegerMaxMaxInteger() { minOtherMaxOther(); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledIfConditionClassLoaderTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.lang.reflect.Method; import java.util.Optional; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.test.TestClassLoader; /** * Tests for {@link EnabledIfCondition} using custom {@link ClassLoader} arrangements. * * @since 5.10 */ public class EnabledIfConditionClassLoaderTests { @Test // No need to introduce a "disabled" version of this test, since it would simply be the // logical inverse of this method and would therefore not provide any further benefit. void enabledWithStaticMethodInTypeFromDifferentClassLoader() throws Exception { try (var testClassLoader = TestClassLoader.forClasses(getClass(), StaticConditionMethods.class)) { var testClass = testClassLoader.loadClass(getClass().getName()); assertThat(testClass.getClassLoader()).isSameAs(testClassLoader); ExtensionContext context = mock(); Method annotatedMethod = ReflectionSupport.findMethod(getClass(), "enabledMethod").get(); when(context.getElement()).thenReturn(Optional.of(annotatedMethod)); doReturn(testClass).when(context).getRequiredTestClass(); EnabledIfCondition condition = new EnabledIfCondition(); ConditionEvaluationResult result = condition.evaluateExecutionCondition(context); assertThat(result).isNotNull(); assertThat(result.isDisabled()).isFalse(); Method conditionMethod = condition.getConditionMethod( "org.junit.jupiter.api.condition.StaticConditionMethods#returnsTrue", context); assertThat(conditionMethod).isNotNull(); Class declaringClass = conditionMethod.getDeclaringClass(); assertThat(declaringClass.getClassLoader()).isSameAs(testClassLoader); assertThat(declaringClass.getName()).isEqualTo(StaticConditionMethods.class.getName()); assertThat(declaringClass).isNotEqualTo(StaticConditionMethods.class); } } @EnabledIf("org.junit.jupiter.api.condition.StaticConditionMethods#returnsTrue") private void enabledMethod() { } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledIfConditionTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExecutionCondition; /** * Unit tests for {@link EnabledIf}. * *

Note that test method names MUST match the test method names in * {@link EnabledIfIntegrationTests}. * * @since 5.7 */ public class EnabledIfConditionTests extends AbstractExecutionConditionTests { @Override protected ExecutionCondition getExecutionCondition() { return new EnabledIfCondition(); } @Override protected Class getTestClass() { return EnabledIfIntegrationTests.class; } /** * @see EnabledIfIntegrationTests#enabledBecauseAnnotationIsNotPresent() */ @Test void enabledBecauseAnnotationIsNotPresent() { evaluateCondition(); assertEnabled(); assertReasonContains("@EnabledIf is not present"); } /** * @see EnabledIfIntegrationTests#enabledBecauseStaticConditionMethodReturnsTrue() */ @Test void enabledBecauseStaticConditionMethodReturnsTrue() { evaluateCondition(); assertEnabled(); assertReasonContains(""" @EnabledIf("staticMethodThatReturnsTrue") evaluated to true"""); } /** * @see EnabledIfIntegrationTests#disabledBecauseStaticConditionMethodReturnsFalse() */ @Test void disabledBecauseStaticConditionMethodReturnsFalse() { evaluateCondition(); assertDisabled(); assertReasonContains("Disabled for some reason"); } /** * @see EnabledIfIntegrationTests#enabledBecauseConditionMethodReturnsTrue() */ @Test void enabledBecauseConditionMethodReturnsTrue() { evaluateCondition(); assertEnabled(); assertReasonContains(""" @EnabledIf("methodThatReturnsTrue") evaluated to true"""); } /** * @see EnabledIfIntegrationTests#disabledBecauseConditionMethodReturnsFalse() */ @Test void disabledBecauseConditionMethodReturnsFalse() { evaluateCondition(); assertDisabled(); assertReasonContains(""" @EnabledIf("methodThatReturnsFalse") evaluated to false"""); } /** * @see EnabledIfIntegrationTests.ExternalConditionMethod#enabledBecauseStaticExternalConditionMethodReturnsTrue() */ @Test void enabledBecauseStaticExternalConditionMethodReturnsTrue() { evaluateCondition(); assertEnabled(); assertReasonContains(""" @EnabledIf("org.junit.jupiter.api.condition.StaticConditionMethods#returnsTrue") evaluated to true"""); } /** * @see EnabledIfIntegrationTests.ExternalConditionMethod#disabledBecauseStaticExternalConditionMethodReturnsFalse() */ @Test void disabledBecauseStaticExternalConditionMethodReturnsFalse() { evaluateCondition(); assertDisabled(); assertReasonContains( """ @EnabledIf("org.junit.jupiter.api.condition.StaticConditionMethods#returnsFalse") evaluated to false"""); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableConditionTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.junit.jupiter.api.condition.EnabledIfEnvironmentVariableIntegrationTests.BOGUS; import static org.junit.jupiter.api.condition.EnabledIfEnvironmentVariableIntegrationTests.ENIGMA; import static org.junit.jupiter.api.condition.EnabledIfEnvironmentVariableIntegrationTests.KEY1; import static org.junit.jupiter.api.condition.EnabledIfEnvironmentVariableIntegrationTests.KEY2; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotBlankFor; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExecutionCondition; /** * Unit tests for {@link EnabledIfEnvironmentVariableCondition}. * *

Note that test method names MUST match the test method names in * {@link EnabledIfEnvironmentVariableIntegrationTests}. * * @since 5.1 */ class EnabledIfEnvironmentVariableConditionTests extends AbstractExecutionConditionTests { /** * Stubbed subclass of {@link EnabledIfEnvironmentVariableCondition}. */ private ExecutionCondition condition = new EnabledIfEnvironmentVariableCondition() { @Override protected @Nullable String getEnvironmentVariable(String name) { return KEY1.equals(name) ? ENIGMA : null; } }; @Override protected ExecutionCondition getExecutionCondition() { return condition; } @Override protected Class getTestClass() { return EnabledIfEnvironmentVariableIntegrationTests.class; } /** * @see EnabledIfEnvironmentVariableIntegrationTests#enabledBecauseAnnotationIsNotPresent() */ @Test void enabledBecauseAnnotationIsNotPresent() { evaluateCondition(); assertEnabled(); assertReasonContains( "No @EnabledIfEnvironmentVariable conditions resulting in 'disabled' execution encountered"); } /** * @see EnabledIfEnvironmentVariableIntegrationTests#blankNamedAttribute() */ @Test void blankNamedAttribute() { assertPreconditionViolationNotBlankFor("The 'named' attribute", this::evaluateCondition); } /** * @see EnabledIfEnvironmentVariableIntegrationTests#blankMatchesAttribute() */ @Test void blankMatchesAttribute() { assertPreconditionViolationNotBlankFor("The 'matches' attribute", this::evaluateCondition); } /** * @see EnabledIfEnvironmentVariableIntegrationTests#enabledBecauseEnvironmentVariableMatchesExactly() */ @Test void enabledBecauseEnvironmentVariableMatchesExactly() { evaluateCondition(); assertEnabled(); assertReasonContains( "No @EnabledIfEnvironmentVariable conditions resulting in 'disabled' execution encountered"); } /** * @see EnabledIfEnvironmentVariableIntegrationTests#enabledBecauseBothEnvironmentVariablesMatchExactly() */ @Test void enabledBecauseBothEnvironmentVariablesMatchExactly() { this.condition = new EnabledIfEnvironmentVariableCondition() { @Override protected @Nullable String getEnvironmentVariable(String name) { return KEY1.equals(name) || KEY2.equals(name) ? ENIGMA : null; } }; evaluateCondition(); assertEnabled(); assertReasonContains( "No @EnabledIfEnvironmentVariable conditions resulting in 'disabled' execution encountered"); } /** * @see EnabledIfEnvironmentVariableIntegrationTests#enabledBecauseEnvironmentVariableMatchesPattern() */ @Test void enabledBecauseEnvironmentVariableMatchesPattern() { evaluateCondition(); assertEnabled(); assertReasonContains( "No @EnabledIfEnvironmentVariable conditions resulting in 'disabled' execution encountered"); } /** * @see EnabledIfEnvironmentVariableIntegrationTests#disabledBecauseEnvironmentVariableDoesNotMatch() */ @Test void disabledBecauseEnvironmentVariableDoesNotMatch() { evaluateCondition(); assertDisabled(); assertReasonContains("does not match regular expression"); assertCustomDisabledReasonIs("Not bogus"); } /** * @see EnabledIfEnvironmentVariableIntegrationTests#disabledBecauseEnvironmentVariableForComposedAnnotationDoesNotMatch() */ @Test void disabledBecauseEnvironmentVariableForComposedAnnotationDoesNotMatch() { this.condition = new EnabledIfEnvironmentVariableCondition() { @Override protected @Nullable String getEnvironmentVariable(String name) { return KEY1.equals(name) ? ENIGMA : KEY2.equals(name) ? BOGUS : null; } }; evaluateCondition(); assertDisabled(); assertReasonContains("does not match regular expression"); } /** * @see EnabledIfEnvironmentVariableIntegrationTests#disabledBecauseEnvironmentVariableDoesNotExist() */ @Test void disabledBecauseEnvironmentVariableDoesNotExist() { evaluateCondition(); assertDisabled(); assertReasonContains("does not exist"); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; /** * Integration tests for {@link EnabledIfEnvironmentVariable}. * * @since 5.1 */ @Disabled("Disabled since the required environment variables are not set") // Tests (except those with intentional configuration errors) will pass if you set // the following environment variables: // EnabledIfEnvironmentVariableTests.key1 = enigma // EnabledIfEnvironmentVariableTests.key2 = enigma class EnabledIfEnvironmentVariableIntegrationTests { static final String KEY1 = "EnabledIfEnvironmentVariableTests.key1"; static final String KEY2 = "EnabledIfEnvironmentVariableTests.key2"; static final String ENIGMA = "enigma"; static final String PUZZLE = "puzzle"; static final String BOGUS = "bogus"; @Test void enabledBecauseAnnotationIsNotPresent() { } @Test @EnabledIfEnvironmentVariable(named = " ", matches = ENIGMA) void blankNamedAttribute() { } @Test @EnabledIfEnvironmentVariable(named = KEY1, matches = " ") void blankMatchesAttribute() { } @Test @EnabledIfEnvironmentVariable(named = KEY1, matches = ENIGMA) void enabledBecauseEnvironmentVariableMatchesExactly() { assertEquals(ENIGMA, System.getenv(KEY1)); } @Test @EnabledIfEnvironmentVariable(named = KEY1, matches = ENIGMA) @EnabledIfEnvironmentVariable(named = KEY2, matches = ENIGMA) void enabledBecauseBothEnvironmentVariablesMatchExactly() { assertEquals(ENIGMA, System.getenv(KEY1)); assertEquals(ENIGMA, System.getenv(KEY2)); } @Test @EnabledIfEnvironmentVariable(named = KEY1, matches = ".*e.+ma$") void enabledBecauseEnvironmentVariableMatchesPattern() { assertEquals(ENIGMA, System.getenv(KEY1)); } @Test @EnabledIfEnvironmentVariable(named = KEY1, matches = BOGUS, disabledReason = "Not bogus") void disabledBecauseEnvironmentVariableDoesNotMatch() { fail("should be disabled"); } @Test @EnabledIfEnvironmentVariable(named = KEY1, matches = ENIGMA) @CustomEnabled void disabledBecauseEnvironmentVariableForComposedAnnotationDoesNotMatch() { fail("should be disabled"); } @Test @EnabledIfEnvironmentVariable(named = BOGUS, matches = "doesn't matter") void disabledBecauseEnvironmentVariableDoesNotExist() { fail("should be disabled"); } @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @EnabledIfEnvironmentVariable(named = KEY2, matches = PUZZLE) @interface CustomEnabled { } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledIfIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.junit.jupiter.api.Assertions.fail; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; /** * Integration tests for {@link EnabledIf}. * * @since 5.7 */ public class EnabledIfIntegrationTests { @Test @Disabled("Only used in a unit test via reflection") void enabledBecauseAnnotationIsNotPresent() { } @Test @EnabledIf("staticMethodThatReturnsTrue") void enabledBecauseStaticConditionMethodReturnsTrue() { } @Test @EnabledIf(value = "staticMethodThatReturnsFalse", disabledReason = "Disabled for some reason") void disabledBecauseStaticConditionMethodReturnsFalse() { fail("Should be disabled"); } @Test @EnabledIf("methodThatReturnsTrue") void enabledBecauseConditionMethodReturnsTrue() { } @Test @EnabledIf("methodThatReturnsFalse") void disabledBecauseConditionMethodReturnsFalse() { fail("Should be disabled"); } @Test @EnabledIf("org.junit.jupiter.api.condition.StaticConditionMethods#returnsTrue") void enabledBecauseStaticExternalConditionMethodReturnsTrue() { } @Test @EnabledIf("org.junit.jupiter.api.condition.StaticConditionMethods#returnsFalse") void disabledBecauseStaticExternalConditionMethodReturnsFalse() { fail("Should be disabled"); } @Nested @EnabledIf("org.junit.jupiter.api.condition.StaticConditionMethods#returnsFalse") class ConditionallyDisabledClass { @Test void disabledBecauseConditionMethodReturnsFalse() { fail("Should be disabled"); } } // ------------------------------------------------------------------------- @SuppressWarnings("unused") private static boolean staticMethodThatReturnsTrue() { return true; } @SuppressWarnings("unused") private static boolean staticMethodThatReturnsFalse() { return false; } @SuppressWarnings("unused") private boolean methodThatReturnsTrue() { return true; } @SuppressWarnings("unused") private boolean methodThatReturnsFalse() { return false; } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyConditionTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotBlankFor; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.util.SetSystemProperty; /** * Unit tests for {@link EnabledIfSystemPropertyCondition}. * *

Note that test method names MUST match the test method names in * {@link EnabledIfSystemPropertyIntegrationTests}. * * @since 5.1 */ @SetSystemProperty(key = EnabledIfSystemPropertyIntegrationTests.KEY1, value = EnabledIfSystemPropertyIntegrationTests.ENIGMA) @SetSystemProperty(key = EnabledIfSystemPropertyIntegrationTests.KEY2, value = EnabledIfSystemPropertyIntegrationTests.ENIGMA) class EnabledIfSystemPropertyConditionTests extends AbstractExecutionConditionTests { @Override protected ExecutionCondition getExecutionCondition() { return new EnabledIfSystemPropertyCondition(); } @Override protected Class getTestClass() { return EnabledIfSystemPropertyIntegrationTests.class; } /** * @see EnabledIfSystemPropertyIntegrationTests#enabledBecauseAnnotationIsNotPresent() */ @Test void enabledBecauseAnnotationIsNotPresent() { evaluateCondition(); assertEnabled(); assertReasonContains("No @EnabledIfSystemProperty conditions resulting in 'disabled' execution encountered"); } /** * @see EnabledIfSystemPropertyIntegrationTests#blankNamedAttribute() */ @Test void blankNamedAttribute() { assertPreconditionViolationNotBlankFor("The 'named' attribute", this::evaluateCondition); } /** * @see EnabledIfSystemPropertyIntegrationTests#blankMatchesAttribute() */ @Test void blankMatchesAttribute() { assertPreconditionViolationNotBlankFor("The 'matches' attribute", this::evaluateCondition); } /** * @see EnabledIfSystemPropertyIntegrationTests#enabledBecauseSystemPropertyMatchesExactly() */ @Test void enabledBecauseSystemPropertyMatchesExactly() { evaluateCondition(); assertEnabled(); assertReasonContains("No @EnabledIfSystemProperty conditions resulting in 'disabled' execution encountered"); } /** * @see EnabledIfSystemPropertyIntegrationTests#enabledBecauseBothSystemPropertiesMatchExactly() */ @Test void enabledBecauseBothSystemPropertiesMatchExactly() { evaluateCondition(); assertEnabled(); assertReasonContains("No @EnabledIfSystemProperty conditions resulting in 'disabled' execution encountered"); } /** * @see EnabledIfSystemPropertyIntegrationTests#enabledBecauseSystemPropertyMatchesPattern() */ @Test void enabledBecauseSystemPropertyMatchesPattern() { evaluateCondition(); assertEnabled(); assertReasonContains("No @EnabledIfSystemProperty conditions resulting in 'disabled' execution encountered"); } /** * @see EnabledIfSystemPropertyIntegrationTests#disabledBecauseSystemPropertyDoesNotMatch() */ @Test void disabledBecauseSystemPropertyDoesNotMatch() { evaluateCondition(); assertDisabled(); assertReasonContains("does not match regular expression"); assertCustomDisabledReasonIs("Not bogus"); } @Test void disabledBecauseSystemPropertyForComposedAnnotationDoesNotMatch() { evaluateCondition(); assertDisabled(); assertReasonContains("does not match regular expression"); } /** * @see EnabledIfSystemPropertyIntegrationTests#disabledBecauseSystemPropertyDoesNotExist() */ @Test void disabledBecauseSystemPropertyDoesNotExist() { evaluateCondition(); assertDisabled(); assertReasonContains("does not exist"); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.util.SetSystemProperty; /** * Integration tests for {@link EnabledIfSystemProperty}. * * @since 5.1 */ @SetSystemProperty(key = EnabledIfSystemPropertyIntegrationTests.KEY1, value = EnabledIfSystemPropertyIntegrationTests.ENIGMA) @SetSystemProperty(key = EnabledIfSystemPropertyIntegrationTests.KEY2, value = EnabledIfSystemPropertyIntegrationTests.ENIGMA) class EnabledIfSystemPropertyIntegrationTests { static final String KEY1 = "EnabledIfSystemPropertyTests.key1"; static final String KEY2 = "EnabledIfSystemPropertyTests.key2"; static final String ENIGMA = "enigma"; private static final String BOGUS = "bogus"; @Test void enabledBecauseAnnotationIsNotPresent() { } @Test @Disabled("Only used in a unit test via reflection") @EnabledIfSystemProperty(named = " ", matches = ENIGMA) void blankNamedAttribute() { fail("should be disabled"); } @Test @Disabled("Only used in a unit test via reflection") @EnabledIfSystemProperty(named = KEY1, matches = " ") void blankMatchesAttribute() { fail("should be disabled"); } @Test @EnabledIfSystemProperty(named = KEY1, matches = ENIGMA) void enabledBecauseSystemPropertyMatchesExactly() { assertEquals(ENIGMA, System.getProperty(KEY1)); } @Test @EnabledIfSystemProperty(named = KEY1, matches = ENIGMA) @EnabledIfSystemProperty(named = KEY2, matches = ENIGMA) void enabledBecauseBothSystemPropertiesMatchExactly() { assertEquals(ENIGMA, System.getProperty(KEY1)); assertEquals(ENIGMA, System.getProperty(KEY2)); } @Test @EnabledIfSystemProperty(named = KEY1, matches = ".*en.+gma$") void enabledBecauseSystemPropertyMatchesPattern() { assertEquals(ENIGMA, System.getProperty(KEY1)); } @Test @EnabledIfSystemProperty(named = KEY1, matches = BOGUS, disabledReason = "Not bogus") void disabledBecauseSystemPropertyDoesNotMatch() { fail("should be disabled"); } @Test @EnabledIfSystemProperty(named = KEY1, matches = ENIGMA) @CustomEnabled void disabledBecauseSystemPropertyForComposedAnnotationDoesNotMatch() { fail("should be disabled"); } @Test @EnabledIfSystemProperty(named = BOGUS, matches = "doesn't matter") void disabledBecauseSystemPropertyDoesNotExist() { fail("should be disabled"); } @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @EnabledIfSystemProperty(named = KEY2, matches = BOGUS) @interface CustomEnabled { } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledOnOsConditionTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onAix; import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onArchitecture; import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onFreebsd; import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onLinux; import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onMac; import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onOpenbsd; import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onSolaris; import static org.junit.jupiter.api.condition.EnabledOnOsIntegrationTests.onWindows; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExecutionCondition; /** * Unit tests for {@link EnabledOnOsCondition}. * *

Note that test method names MUST match the test method names in * {@link EnabledOnOsIntegrationTests}. * * @since 5.1 */ class EnabledOnOsConditionTests extends AbstractExecutionConditionTests { private static final String OS_NAME = System.getProperty("os.name"); private static final String ARCH = System.getProperty("os.arch"); @Override protected ExecutionCondition getExecutionCondition() { return new EnabledOnOsCondition(); } @Override protected Class getTestClass() { return EnabledOnOsIntegrationTests.class; } /** * @see EnabledOnOsIntegrationTests#enabledBecauseAnnotationIsNotPresent() */ @Test void enabledBecauseAnnotationIsNotPresent() { evaluateCondition(); assertEnabled(); assertReasonContains("@EnabledOnOs is not present"); } /** * @see EnabledOnOsIntegrationTests#missingOsAndArchitectureDeclaration() */ @Test void missingOsAndArchitectureDeclaration() { assertPreconditionViolationFor(this::evaluateCondition)// .withMessageContaining("You must declare at least one OS or architecture"); } /** * @see EnabledOnOsIntegrationTests#enabledOnEveryOs() */ @Test void enabledOnEveryOs() { evaluateCondition(); assertEnabledOnCurrentOsIf(true); } /** * @see EnabledOnOsIntegrationTests#aix() */ @Test void aix() { evaluateCondition(); assertEnabledOnCurrentOsIf(onAix()); } /** * @see EnabledOnOsIntegrationTests#freebsd() */ @Test void freebsd() { evaluateCondition(); assertEnabledOnCurrentOsIf(onFreebsd()); } /** * @see EnabledOnOsIntegrationTests#linux() */ @Test void linux() { evaluateCondition(); assertEnabledOnCurrentOsIf(onLinux()); } /** * @see EnabledOnOsIntegrationTests#macOs() */ @Test void macOs() { evaluateCondition(); assertEnabledOnCurrentOsIf(onMac()); } /** * @see EnabledOnOsIntegrationTests#macOsWithComposedAnnotation() */ @Test void macOsWithComposedAnnotation() { evaluateCondition(); assertEnabledOnCurrentOsIf(onMac()); } /** * @see EnabledOnOsIntegrationTests#openbsd() */ @Test void openbsd() { evaluateCondition(); assertEnabledOnCurrentOsIf(onOpenbsd()); } /** * @see EnabledOnOsIntegrationTests#windows() */ @Test void windows() { evaluateCondition(); assertEnabledOnCurrentOsIf(onWindows()); } /** * @see EnabledOnOsIntegrationTests#solaris() */ @Test void solaris() { evaluateCondition(); assertEnabledOnCurrentOsIf(onSolaris()); } /** * @see EnabledOnOsIntegrationTests#other() */ @Test void other() { evaluateCondition(); assertEnabledOnCurrentOsIf( !(onAix() || onFreebsd() || onLinux() || onMac() || onOpenbsd() || onSolaris() || onWindows())); assertCustomDisabledReasonIs("Disabled on almost every OS"); } /** * @see EnabledOnOsIntegrationTests#architectureX86_64() */ @Test void architectureX86_64() { evaluateCondition(); assertEnabledOnCurrentArchitectureIf(onArchitecture("x86_64")); } /** * @see EnabledOnOsIntegrationTests#architectureAarch64() */ @Test void architectureAarch64() { evaluateCondition(); assertEnabledOnCurrentArchitectureIf(onArchitecture("aarch64")); } /** * @see EnabledOnOsIntegrationTests#architectureX86_64WithMacOs() */ @Test void architectureX86_64WithMacOs() { evaluateCondition(); assertEnabledOnCurrentOsAndArchitectureIf(onMac() && onArchitecture("x86_64")); } /** * @see EnabledOnOsIntegrationTests#architectureX86_64WithWindows() */ @Test void architectureX86_64WithWindows() { evaluateCondition(); assertEnabledOnCurrentOsAndArchitectureIf(onWindows() && onArchitecture("x86_64")); } /** * @see EnabledOnOsIntegrationTests#architectureX86_64WithLinux() */ @Test void architectureX86_64WithLinux() { evaluateCondition(); assertEnabledOnCurrentOsAndArchitectureIf(onLinux() && onArchitecture("x86_64")); } /** * @see EnabledOnOsIntegrationTests#aarch64WithMacOs() */ @Test void aarch64WithMacOs() { evaluateCondition(); assertEnabledOnCurrentOsAndArchitectureIf(onMac() && onArchitecture("aarch64")); } /** * @see EnabledOnOsIntegrationTests#aarch64WithWindows() */ @Test void aarch64WithWindows() { evaluateCondition(); assertEnabledOnCurrentOsAndArchitectureIf(onWindows() && onArchitecture("aarch64")); } /** * @see EnabledOnOsIntegrationTests#aarch64WithLinux() */ @Test void aarch64WithLinux() { evaluateCondition(); assertEnabledOnCurrentOsAndArchitectureIf(onLinux() && onArchitecture("aarch64")); } private void assertEnabledOnCurrentOsIf(boolean condition) { if (condition) { assertEnabled(); assertReasonContains("Enabled on operating system: %s".formatted(OS_NAME)); } else { assertDisabled(); assertReasonContains("Disabled on operating system: %s".formatted(OS_NAME)); } } private void assertEnabledOnCurrentArchitectureIf(boolean condition) { if (condition) { assertEnabled(); assertReasonContains("Enabled on architecture: %s".formatted(ARCH)); } else { assertDisabled(); assertReasonContains("Disabled on architecture: %s".formatted(ARCH)); } } private void assertEnabledOnCurrentOsAndArchitectureIf(boolean condition) { if (condition) { assertEnabled(); assertReasonContains("Enabled on operating system: %s (%s)".formatted(OS_NAME, ARCH)); } else { assertDisabled(); assertReasonContains("Disabled on operating system: %s (%s)".formatted(OS_NAME, ARCH)); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledOnOsIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.condition.OS.AIX; import static org.junit.jupiter.api.condition.OS.FREEBSD; import static org.junit.jupiter.api.condition.OS.LINUX; import static org.junit.jupiter.api.condition.OS.MAC; import static org.junit.jupiter.api.condition.OS.OPENBSD; import static org.junit.jupiter.api.condition.OS.OTHER; import static org.junit.jupiter.api.condition.OS.SOLARIS; import static org.junit.jupiter.api.condition.OS.WINDOWS; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.Locale; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; /** * Integration tests for {@link EnabledOnOs}. * * @since 5.1 */ class EnabledOnOsIntegrationTests { private static final String ARCH = System.getProperty("os.arch").toLowerCase(Locale.ENGLISH); private static final String OS_NAME = System.getProperty("os.name").toLowerCase(Locale.ENGLISH); @Test @Disabled("Only used in a unit test via reflection") void enabledBecauseAnnotationIsNotPresent() { } @Test @Disabled("Only used in a unit test via reflection") @EnabledOnOs({}) void missingOsAndArchitectureDeclaration() { } @Test @EnabledOnOs({ AIX, FREEBSD, LINUX, MAC, OPENBSD, WINDOWS, SOLARIS, OTHER }) void enabledOnEveryOs() { } @Test @EnabledOnOs(AIX) void aix() { assertTrue(onAix()); } @Test @EnabledOnOs(FREEBSD) void freebsd() { assertTrue(onFreebsd()); } @Test @EnabledOnOs(LINUX) void linux() { assertTrue(onLinux()); } @Test @EnabledOnOs(MAC) void macOs() { assertTrue(onMac()); } @Test @EnabledOnMac void macOsWithComposedAnnotation() { assertTrue(onMac()); } @Test @EnabledOnOs(OPENBSD) void openbsd() { assertTrue(onOpenbsd()); } @Test @EnabledOnOs(WINDOWS) void windows() { assertTrue(onWindows()); } @Test @EnabledOnOs(SOLARIS) void solaris() { assertTrue(onSolaris()); } @Test @EnabledOnOs(value = OTHER, disabledReason = "Disabled on almost every OS") void other() { assertFalse(onAix() || onFreebsd() || onLinux() || onMac() || onOpenbsd() || onSolaris() || onWindows()); } @Test @EnabledOnOs(architectures = "x86_64") void architectureX86_64() { assertFalse(onArchitecture("x_86_64")); } @Test @EnabledOnOs(architectures = "aarch64") void architectureAarch64() { assertTrue(onArchitecture("aarch64")); } @Test @EnabledOnOs(value = MAC, architectures = "x86_64") void architectureX86_64WithMacOs() { assertTrue(onMac()); assertTrue(onArchitecture("x86_64")); } @Test @EnabledOnOs(value = WINDOWS, architectures = "x86_64") void architectureX86_64WithWindows() { assertTrue(onWindows()); assertTrue(onArchitecture("x86_64")); } @Test @EnabledOnOs(value = LINUX, architectures = "x86_64") void architectureX86_64WithLinux() { assertTrue(onLinux()); assertTrue(onArchitecture("x86_64")); } @Test @EnabledOnOs(value = MAC, architectures = "aarch64") void aarch64WithMacOs() { assertTrue(onMac()); assertTrue(onArchitecture("aarch64")); } @Test @EnabledOnOs(value = WINDOWS, architectures = "aarch64") void aarch64WithWindows() { assertTrue(onWindows()); assertTrue(onArchitecture("aarch64")); } @Test @EnabledOnOs(value = LINUX, architectures = "aarch64") void aarch64WithLinux() { assertTrue(onLinux()); assertTrue(onArchitecture("aarch64")); } static boolean onAix() { return OS_NAME.contains("aix"); } static boolean onArchitecture(String arch) { return ARCH.contains(arch); } static boolean onFreebsd() { return OS_NAME.contains("freebsd"); } static boolean onLinux() { return OS_NAME.contains("linux"); } static boolean onMac() { return OS_NAME.contains("mac"); } static boolean onOpenbsd() { return OS_NAME.contains("openbsd"); } static boolean onSolaris() { return OS_NAME.contains("solaris"); } static boolean onWindows() { return OS_NAME.contains("windows"); } // ------------------------------------------------------------------------- @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @EnabledOnOs(MAC) @interface EnabledOnMac { } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/condition/JRETests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.condition.JRE.JAVA_17; import static org.junit.jupiter.api.condition.JRE.JAVA_18; import static org.junit.jupiter.api.condition.JRE.JAVA_19; import static org.junit.jupiter.api.condition.JRE.JAVA_20; import static org.junit.jupiter.api.condition.JRE.JAVA_21; import static org.junit.jupiter.api.condition.JRE.JAVA_22; import static org.junit.jupiter.api.condition.JRE.JAVA_23; import static org.junit.jupiter.api.condition.JRE.JAVA_24; import static org.junit.jupiter.api.condition.JRE.JAVA_25; import static org.junit.jupiter.api.condition.JRE.OTHER; import org.junit.jupiter.api.Test; /** * Unit tests for {@link JRE} * * @since 5.7 */ public class JRETests { private static final JRE CURRENT_JRE = JRE.currentJre(); @Test @EnabledOnJre(JAVA_17) void jre17() { assertThat(CURRENT_JRE).as("current version").isEqualTo(JAVA_17); assertThat(CURRENT_JRE.version()).as("current feature version").isEqualTo(17); } @Test @EnabledOnJre(JAVA_18) void jre18() { assertThat(CURRENT_JRE).as("current version").isEqualTo(JAVA_18); assertThat(CURRENT_JRE.version()).as("current feature version").isEqualTo(18); } @Test @EnabledOnJre(JAVA_19) void jre19() { assertThat(CURRENT_JRE).as("current version").isEqualTo(JAVA_19); assertThat(CURRENT_JRE.version()).as("current feature version").isEqualTo(19); } @Test @EnabledOnJre(JAVA_20) void jre20() { assertThat(CURRENT_JRE).as("current version").isEqualTo(JAVA_20); assertThat(CURRENT_JRE.version()).as("current feature version").isEqualTo(20); } @Test @EnabledOnJre(JAVA_21) void jre21() { assertThat(CURRENT_JRE).as("current version").isEqualTo(JAVA_21); assertThat(CURRENT_JRE.version()).as("current feature version").isEqualTo(21); } @Test @EnabledOnJre(JAVA_22) void jre22() { assertThat(CURRENT_JRE).as("current version").isEqualTo(JAVA_21); assertThat(CURRENT_JRE.version()).as("current feature version").isEqualTo(22); } @Test @EnabledOnJre(JAVA_23) void jre23() { assertThat(CURRENT_JRE).as("current version").isEqualTo(JAVA_23); assertThat(CURRENT_JRE.version()).as("current feature version").isEqualTo(23); } @Test @EnabledOnJre(JAVA_24) void jre24() { assertThat(CURRENT_JRE).as("current version").isEqualTo(JAVA_24); assertThat(CURRENT_JRE.version()).as("current feature version").isEqualTo(24); } @Test @EnabledOnJre(JAVA_25) void jre25() { assertThat(CURRENT_JRE).as("current version").isEqualTo(JAVA_25); assertThat(CURRENT_JRE.version()).as("current feature version").isEqualTo(25); } @Test @EnabledOnJre(versions = 17) void version17() { jre17(); } @Test @EnabledOnJre(versions = 18) void version18() { jre18(); } @Test @EnabledOnJre(versions = 19) void version19() { jre19(); } @Test @EnabledOnJre(versions = 20) void version20() { jre20(); } @Test @EnabledOnJre(versions = 21) void version21() { jre21(); } @Test @EnabledOnJre(versions = 22) void version22() { jre22(); } @Test @EnabledOnJre(versions = 23) void version23() { jre23(); } @Test @EnabledOnJre(versions = 24) void version24() { jre24(); } @Test @EnabledOnJre(versions = 25) void version25() { jre25(); } @SuppressWarnings("deprecation") @Test @EnabledOnJre(OTHER) void jreOther() { assertThat(CURRENT_JRE).as("current version").isEqualTo(OTHER); assertThat(CURRENT_JRE.version()).as("current feature version").isEqualTo(Integer.MAX_VALUE); } @Test @EnabledOnJre(versions = Integer.MAX_VALUE) void versionMaxInteger() { jreOther(); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/condition/OSTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import org.junit.jupiter.api.Nested; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.NullAndEmptySource; import org.junit.jupiter.params.provider.ValueSource; class OSTests { @ParameterizedTest @NullAndEmptySource @ValueSource(strings = { " ", "\t" }) void blankOsNameYieldsNull(String name) { assertNull(OS.parse(name)); } @ParameterizedTest @ValueSource(strings = { "!", "?", "✨", "MINIX" }) void unknownOsNameYieldsOTHER(String name) { assertEquals(OS.OTHER, OS.parse(name)); } @Nested class ValidNames { @ParameterizedTest @ValueSource(strings = { "AIX", "Aix", "LaIxOS" }) void aix(String name) { assertEquals(OS.AIX, OS.parse(name)); } @ParameterizedTest @ValueSource(strings = { "FREEBSD", "FreeBSD" }) void freebsd(String name) { assertEquals(OS.FREEBSD, OS.parse(name)); } @ParameterizedTest @ValueSource(strings = { "LINUX", "Linux" }) void linux(String name) { assertEquals(OS.LINUX, OS.parse(name)); } @ParameterizedTest @ValueSource(strings = { "MAC", "mac" }) void mac(String name) { assertEquals(OS.MAC, OS.parse(name)); } @ParameterizedTest @ValueSource(strings = { "OPENBSD", "OpenBSD" }) void openbsd(String name) { assertEquals(OS.OPENBSD, OS.parse(name)); } @ParameterizedTest @ValueSource(strings = { "SOLARIS", "SunOS" }) void solaris(String name) { assertEquals(OS.SOLARIS, OS.parse(name)); } @ParameterizedTest @ValueSource(strings = { "WINDOW", "Microsoft Windows [Version 10.?]" }) void windows(String name) { assertEquals(OS.WINDOWS, OS.parse(name)); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/condition/StaticConditionMethods.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; public class StaticConditionMethods { public static boolean returnsTrue() { return true; } public static boolean returnsFalse() { return false; } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/condition/TestDoubleJRETests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.condition; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.condition.JRE.JAVA_23; import org.junit.jupiter.api.Test; /** * Unit tests for {@link JRE} using {@link TestDoubleJRE} * * @since 6.1 */ public class TestDoubleJRETests { @Test @EnabledForJreRange(min = JAVA_23) // "23" because "22" is the maximum available in TestDoubleJRE @SuppressWarnings("deprecation") void currentJreIsOtherForUnsupportedJre() { assertEquals(TestDoubleJRE.OTHER, TestDoubleJRE.currentJre()); assertTrue(TestDoubleJRE.OTHER.isCurrentVersion()); assertTrue(TestDoubleJRE.isCurrentVersion(TestDoubleJRE.OTHER.version())); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/extension/CloseableResourceIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.EventConditions.reportEntry; import static org.junit.platform.testkit.engine.EventConditions.test; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.cause; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.suppressed; import java.util.Map; import org.junit.jupiter.api.Test; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; public class CloseableResourceIntegrationTests extends AbstractJupiterTestEngineTests { @Test void closesCloseableResourcesInReverseInsertOrder() { executeTestsForClass(TestCase.class).allEvents().reportingEntryPublished() // .assertEventsMatchExactly( // reportEntry(Map.of("3", "closed")), // reportEntry(Map.of("2", "closed")), // reportEntry(Map.of("1", "closed"))); } @ExtendWith(ExtensionContextParameterResolver.class) static class TestCase { @Test void closesCloseableResourcesInExtensionContext(ExtensionContext extensionContext) { ExtensionContext.Store store = extensionContext.getStore(GLOBAL); store.put("foo", reportEntryOnClose(extensionContext, "1")); store.put("bar", reportEntryOnClose(extensionContext, "2")); store.put("baz", reportEntryOnClose(extensionContext, "3")); } private AutoCloseable reportEntryOnClose(ExtensionContext extensionContext, String key) { return () -> extensionContext.publishReportEntry(Map.of(key, "closed")); } } @Test void exceptionsDuringCloseAreReportedAsSuppressed() { executeTestsForClass(ExceptionInCloseableResourceTestCase.class).testEvents() // .assertEventsMatchLoosely(event( // test(), // finishedWithFailure( // message("Exception in test"), // suppressed(0, // message("Failed to close extension context"), // cause(message("Exception in onClose")) // )))); } @ExtendWith(ThrowingOnCloseExtension.class) static class ExceptionInCloseableResourceTestCase { @Test void test() { throw new RuntimeException("Exception in test"); } } static class ThrowingOnCloseExtension implements BeforeEachCallback { @Override public void beforeEach(ExtensionContext context) { context.getStore(GLOBAL).put("throwingResource", new ThrowingResource()); } } @SuppressWarnings({ "deprecation", "try" }) static class ThrowingResource implements ExtensionContext.Store.CloseableResource, AutoCloseable { @Override public void close() throws Exception { throw new RuntimeException("Exception in onClose"); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/extension/DeprecatedMediaTypeTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullFor; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullOrBlankFor; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.NullAndEmptySource; import org.junit.jupiter.params.provider.ValueSource; /** * Unit tests for the deprecated {@link MediaType}. * * @see org.junit.jupiter.api.MediaTypeTests * @see MediaTypeInteroperabilityTests */ @SuppressWarnings("removal") class DeprecatedMediaTypeTests { @Test void equals() { var mediaType1 = MediaType.TEXT_PLAIN; var mediaType2 = MediaType.parse(mediaType1.toString()); var mediaType3 = MediaType.APPLICATION_JSON; assertEqualsAndHashCode(mediaType1, mediaType2, mediaType3); } @Nested class ParseTests { @ParameterizedTest @NullAndEmptySource @ValueSource(strings = { " ", " \t " }) @SuppressWarnings("DataFlowIssue") // MediaType.create() parameters are not @Nullable void parseWithNullOrBlankMediaType(@Nullable String mediaType) { assertPreconditionViolationNotNullOrBlankFor("value", () -> MediaType.parse(mediaType)); } @ParameterizedTest @ValueSource(strings = { "/", " / ", "type", "type/", "/subtype" }) void parseWithInvalidMediaType(String mediaType) { assertPreconditionViolationFor(() -> MediaType.parse(mediaType))// .withMessage("Invalid media type: '%s'", mediaType.strip()); } @ParameterizedTest @ValueSource(strings = { "text/plain", " text/plain ", "text/plain; charset=UTF-8", "\t text/plain; charset=UTF-8 \t" }) void parse(String value) { assertThat(MediaType.parse(value)).hasToString(value.strip()); } } @Nested class CreateTests { @ParameterizedTest @NullAndEmptySource @ValueSource(strings = { " ", " \t " }) @SuppressWarnings("DataFlowIssue") // MediaType.create() parameters are not @Nullable void createWithNullOrBlankType(@Nullable String type) { assertPreconditionViolationNotNullOrBlankFor("type", () -> MediaType.create(type, "json")); } @ParameterizedTest @NullAndEmptySource @ValueSource(strings = { " ", " \t " }) @SuppressWarnings("DataFlowIssue") // MediaType.create() parameters are not @Nullable void createWithNullOrBlankTypeAndCharset(@Nullable String type) { assertPreconditionViolationNotNullOrBlankFor("type", () -> MediaType.create(type, "json", UTF_8)); } @ParameterizedTest @NullAndEmptySource @ValueSource(strings = { " ", " \t " }) @SuppressWarnings("DataFlowIssue") // MediaType.create() parameters are not @Nullable void createWithNullOrBlankSubtype(@Nullable String subtype) { assertPreconditionViolationNotNullOrBlankFor("subtype", () -> MediaType.create("application", subtype)); } @ParameterizedTest @NullAndEmptySource @ValueSource(strings = { " ", " \t " }) @SuppressWarnings("DataFlowIssue") // MediaType.create() parameters are not @Nullable void createWithNullOrBlankSubtypeAndCharset(@Nullable String subtype) { assertPreconditionViolationNotNullOrBlankFor("subtype", () -> MediaType.create("application", subtype, UTF_8)); } @Test @SuppressWarnings("DataFlowIssue") // MediaType.create() parameters are not @Nullable void createWithNullCharset() { assertPreconditionViolationNotNullFor("charset", () -> MediaType.create("application", "json", null)); } @ParameterizedTest @ValueSource(strings = { "/", " / ", "type/", "/subtype" }) void createWithInvalidType(String type) { assertPreconditionViolationFor(() -> MediaType.create(type, "json"))// .withMessage("Invalid media type: '%s/json'", type.strip()); } @ParameterizedTest @ValueSource(strings = { "/", " / ", "type/", "/subtype" }) void createWithInvalidSubtype(String subtype) { assertPreconditionViolationFor(() -> MediaType.create("application", subtype))// .withMessage("Invalid media type: 'application/%s'", subtype.strip()); } @Test void create() { assertThat(MediaType.create("application", "json")).hasToString("application/json"); } @Test void createWithCharset() { assertThat(MediaType.create("text", "plain", UTF_8)).hasToString("text/plain; charset=UTF-8"); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/extension/ExecutableInvokerIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import java.lang.reflect.Constructor; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.platform.testkit.engine.EngineExecutionResults; /** * @since 5.9 */ public class ExecutableInvokerIntegrationTests extends AbstractJupiterTestEngineTests { @Test void invokeConstructorViaExtensionContext() { EngineExecutionResults results = executeTestsForClass(ExecuteConstructorTwiceTestCase.class); assertEquals(1, results.testEvents().succeeded().count()); assertEquals(2, ExecuteConstructorTwiceTestCase.constructorInvocations); } @Test void invokeMethodViaExtensionContext() { EngineExecutionResults results = executeTestsForClass(ExecuteTestsTwiceTestCase.class); assertEquals(1, results.testEvents().succeeded().count()); assertEquals(2, ExecuteTestsTwiceTestCase.testInvocations); } @ExtendWith(ExecuteTestsTwiceExtension.class) static class ExecuteTestsTwiceTestCase { static int testInvocations = 0; @SuppressWarnings("JUnitMalformedDeclaration") @Test void testWithResolvedParameter(TestInfo testInfo, @ExtendWith(ExtensionContextParameterResolver.class) ExtensionContext extensionContext) { assertNotNull(testInfo); assertEquals(testInfo.getTestMethod().orElseThrow(), extensionContext.getRequiredTestMethod()); testInvocations++; } } @ExtendWith(ExecuteConstructorTwiceExtension.class) static class ExecuteConstructorTwiceTestCase { static int constructorInvocations = 0; ExecuteConstructorTwiceTestCase(TestInfo testInfo, @ExtendWith(ExtensionContextParameterResolver.class) ExtensionContext extensionContext) { assertNotNull(testInfo); assertEquals(testInfo.getTestClass().orElseThrow(), extensionContext.getRequiredTestClass()); constructorInvocations++; } @Test void test() { } } static class ExecuteTestsTwiceExtension implements AfterTestExecutionCallback { @Override public void afterTestExecution(ExtensionContext context) { context.getExecutableInvoker() // .invoke(context.getRequiredTestMethod(), context.getRequiredTestInstance()); } } static class ExecuteConstructorTwiceExtension implements BeforeAllCallback { @Override public void beforeAll(ExtensionContext context) throws Exception { Constructor constructor = context.getRequiredTestClass() // .getDeclaredConstructor(TestInfo.class, ExtensionContext.class); context.getExecutableInvoker().invoke(constructor); } } static class ExtensionContextParameterResolver implements ParameterResolver { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return ExtensionContext.class.equals(parameterContext.getParameter().getType()); } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return extensionContext; } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/extension/ExtensionComposabilityTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static java.util.function.Predicate.not; import static java.util.stream.Collectors.toCollection; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.junit.platform.commons.util.FunctionUtils.where; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; import org.junit.jupiter.api.DynamicContainer; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; import org.junit.platform.commons.support.ModifierSupport; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.ClassUtils; /** * Unit tests for extension composability in JUnit Jupiter. * *

The purpose of these tests is to ensure that a concrete extension * (a.k.a., the kitchen sink extension) is able to implement all extension * APIs supported by JUnit Jupiter without any naming conflicts or * ambiguities with regard to method names or method signatures. * * @since 5.0 * @see KitchenSinkExtension */ class ExtensionComposabilityTests { @Test void ensureJupiterExtensionApisAreComposable() { // 1) Find all existing top-level Extension APIs List> extensionApis = findExtensionApis(); // 2) Determine which methods we expect the kitchen sink to implement... // @formatter:off List expectedMethods = extensionApis.stream() .map(Class::getDeclaredMethods) .flatMap(Arrays::stream) .filter(not(Method::isSynthetic)) .filter(not(where(Method::getModifiers, Modifier::isStatic))) .toList(); List expectedMethodSignatures = expectedMethods.stream() .map(this::methodSignature) .sorted() .toList(); List expectedMethodNames = expectedMethods.stream() .map(Method::getName) .distinct() .sorted() .toList(); // @formatter:on // 3) Dynamically implement all Extension APIs Object dynamicKitchenSinkExtension = Proxy.newProxyInstance(getClass().getClassLoader(), extensionApis.toArray(Class[]::new), (proxy, method, args) -> null); // 4) Determine what ended up in the kitchen sink... // @formatter:off List actualMethods = Arrays.stream(dynamicKitchenSinkExtension.getClass().getDeclaredMethods()) .filter(ModifierSupport::isNotStatic) .toList(); List actualMethodSignatures = actualMethods.stream() .map(this::methodSignature) .distinct() .sorted() .collect(toCollection(ArrayList::new)); List actualMethodNames = actualMethods.stream() .map(Method::getName) .distinct() .sorted() .collect(toCollection(ArrayList::new)); // @formatter:on // 5) Remove methods from java.lang.Object actualMethodSignatures.remove("equals(Object)"); actualMethodSignatures.remove("hashCode()"); actualMethodSignatures.remove("toString()"); actualMethodNames.remove("equals"); actualMethodNames.remove("hashCode"); actualMethodNames.remove("toString"); // 6) Verify our expectations // @formatter:off assertAll( () -> assertThat(actualMethodSignatures).isEqualTo(expectedMethodSignatures), () -> assertThat(actualMethodNames).isEqualTo(expectedMethodNames) ); // @formatter:on } @TestFactory Stream kitchenSinkExtensionImplementsAllExtensionApis() { var declaredMethods = List.of(KitchenSinkExtension.class.getDeclaredMethods()); return findExtensionApis().stream() // .map(c -> dynamicContainer( // c.getSimpleName(), // Stream.concat( // Stream.of( dynamicTest("implements interface", () -> c.isAssignableFrom(KitchenSinkExtension.class))), // Arrays.stream(c.getMethods()) // .filter(ModifierSupport::isNotStatic).map(m -> dynamicTest( // "overrides " + m.getName(), // () -> assertTrue( // declaredMethods.stream().anyMatch(it -> // it.getName().equals(m.getName()) // && it.getReturnType().equals(m.getReturnType()) // && Arrays.equals(it.getParameterTypes(), m.getParameterTypes()) // ))) // ) // ) // )); } private List> findExtensionApis() { return ReflectionSupport.findAllClassesInPackage(Extension.class.getPackage().getName(), this::isExtensionApi, name -> true); } private boolean isExtensionApi(Class candidate) { return candidate.isInterface() && (candidate != Extension.class) && Extension.class.isAssignableFrom(candidate); } private String methodSignature(Method method) { return "%s(%s)".formatted(method.getName(), ClassUtils.nullSafeToString(Class::getSimpleName, method.getParameterTypes())); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/extension/Heavyweight.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; class Heavyweight implements ParameterResolver, BeforeEachCallback { @Override public void beforeEach(ExtensionContext context) { context.getStore(ExtensionContext.Namespace.GLOBAL).put("once", new CloseableOnlyOnceResource()); } @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext context) { return Resource.class.equals(parameterContext.getParameter().getType()); } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext context) { var engineContext = context.getRoot(); var store = engineContext.getStore(ExtensionContext.Namespace.GLOBAL); var resource = store.computeIfAbsent(ResourceValue.class); resource.usages.incrementAndGet(); return resource; } interface Resource { String ID = "org.junit.jupiter.extensions.Heavyweight.Resource"; int usages(); } /** * Demo resource class. * *

The class implements interface {@link AutoCloseable} * and interface {@link AutoCloseable} to show and ensure that a single * {@link ResourceValue#close()} method implementation is needed to comply * with both interfaces. */ static class ResourceValue implements Resource, AutoCloseable { static final AtomicInteger creations = new AtomicInteger(); private final AtomicInteger usages = new AtomicInteger(); @SuppressWarnings("unused") // used via reflection ResourceValue() { // Open long-living resources here. assertEquals(1, creations.incrementAndGet(), "Singleton pattern failure!"); } @Override public void close() { // Close resources here. assertEquals(9, usages.get(), "Usage counter mismatch!"); } @Override public int usages() { return usages.get(); } } private static class CloseableOnlyOnceResource implements AutoCloseable { private final AtomicBoolean closed = new AtomicBoolean(); @Override public void close() { assertTrue(closed.compareAndSet(false, true), "already closed!"); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/extension/HeavyweightTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import java.util.stream.Stream; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.parallel.ResourceLock; /** * Unit tests for {@link org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource} * stored values. * * @since 1.1 */ class HeavyweightTests { @AfterAll static void afterAll() { Heavyweight.ResourceValue.creations.set(0); } @Nested @TestInstance(PER_CLASS) @ExtendWith(Heavyweight.class) @ResourceLock(Heavyweight.Resource.ID) class Alpha { private int mark; @BeforeAll void setMark(Heavyweight.Resource resource) { assertTrue(resource.usages() > 0); mark = resource.usages(); } @TestFactory Stream alpha1(Heavyweight.Resource resource) { return Stream.of(dynamicTest("foo", () -> assertTrue(resource.usages() > 1))); } @Test void alpha2(Heavyweight.Resource resource) { assertTrue(resource.usages() > 1); } @Test void alpha3(Heavyweight.Resource resource) { assertTrue(resource.usages() > 1); } @AfterAll void checkMark(Heavyweight.Resource resource) { assertEquals(mark, resource.usages() - 4); } } @Nested @TestInstance(PER_CLASS) @ExtendWith(Heavyweight.class) @ResourceLock(Heavyweight.Resource.ID) class Beta { private int mark; @BeforeAll void beforeAll(Heavyweight.Resource resource) { assertTrue(resource.usages() > 0); mark = resource.usages(); } @BeforeEach void beforeEach(Heavyweight.Resource resource) { assertTrue(resource.usages() > 1); } @Test void beta(Heavyweight.Resource resource) { assertTrue(resource.usages() > 2); } @AfterAll void afterAll(Heavyweight.Resource resource) { assertEquals(mark, resource.usages() - 3); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/extension/KitchenSinkExtension.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.Optional; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; /** * Kitchen Sink extension that implements every extension API * supported by JUnit Jupiter. * *

This extension should never actually be registered for any tests. * Rather, its sole purpose is to help ensure (via visual inspection) * that a concrete extension is able to implement all extension APIs * supported by JUnit Jupiter without any naming conflicts or * ambiguities with regard to method names or method signatures. * {@link ExtensionComposabilityTests}, on the other hand, serves * the same purpose in a dynamic and automated fashion. * * @since 5.0 * @see ExtensionComposabilityTests */ // @formatter:off public class KitchenSinkExtension implements // Lifecycle Callbacks BeforeAllCallback, BeforeClassTemplateInvocationCallback, BeforeEachCallback, BeforeTestExecutionCallback, TestExecutionExceptionHandler, AfterTestExecutionCallback, AfterEachCallback, AfterClassTemplateInvocationCallback, AfterAllCallback, // Lifecycle methods exception handling LifecycleMethodExecutionExceptionHandler, // Dependency Injection TestInstancePreConstructCallback, TestInstanceFactory, TestInstancePostProcessor, TestInstancePreDestroyCallback, ParameterResolver, // Conditional Test Execution ExecutionCondition, // @TestTemplate and @ClassTemplate TestTemplateInvocationContextProvider, ClassTemplateInvocationContextProvider, // Miscellaneous TestWatcher, InvocationInterceptor, PreInterruptCallback // @formatter:on { // --- Test Class Instantiation -------------------------------------------- @Override public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { return ExtensionContextScope.TEST_METHOD; } // --- Lifecycle Callbacks ------------------------------------------------- @Override public void beforeAll(ExtensionContext context) { } @Override public void beforeClassTemplateInvocation(ExtensionContext context) { } @Override public void beforeEach(ExtensionContext context) { } @Override public void beforeTestExecution(ExtensionContext context) { } @Override public void handleTestExecutionException(ExtensionContext context, Throwable throwable) { } @Override public void afterTestExecution(ExtensionContext context) { } @Override public void afterEach(ExtensionContext context) { } @Override public void afterClassTemplateInvocation(ExtensionContext context) { } @Override public void afterAll(ExtensionContext context) { } // --- Lifecycle methods exception handling @Override public void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable throwable) { } @Override public void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable throwable) { } @Override public void handleAfterEachMethodExecutionException(ExtensionContext context, Throwable throwable) { } @Override public void handleAfterAllMethodExecutionException(ExtensionContext context, Throwable throwable) { } // --- Dependency Injection ------------------------------------------------ @Override public void preConstructTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext context) { } @Override public Object createTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext) { throw new UnsupportedOperationException(); } @Override public void postProcessTestInstance(Object testInstance, ExtensionContext context) { } @Override public void preDestroyTestInstance(ExtensionContext context) { } @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return false; } @Override public @Nullable Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return null; } // --- Conditional Test Execution ------------------------------------------ @Override public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { throw new UnsupportedOperationException(); } // --- @TestTemplate ------------------------------------------------------- @Override public boolean supportsTestTemplate(ExtensionContext context) { return false; } @Override public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { throw new UnsupportedOperationException(); } @Override public boolean mayReturnZeroTestTemplateInvocationContexts(ExtensionContext context) { return false; } // --- @ClassTemplate ------------------------------------------------------- @Override public boolean supportsClassTemplate(ExtensionContext context) { return false; } @Override public Stream provideClassTemplateInvocationContexts(ExtensionContext context) { throw new UnsupportedOperationException(); } @Override public boolean mayReturnZeroClassTemplateInvocationContexts(ExtensionContext context) { return false; } // --- TestWatcher --------------------------------------------------------- @Override public void testDisabled(ExtensionContext context, Optional reason) { } @Override public void testSuccessful(ExtensionContext context) { } @Override public void testAborted(ExtensionContext context, @Nullable Throwable cause) { } @Override public void testFailed(ExtensionContext context, @Nullable Throwable cause) { } // --- InvocationInterceptor ----------------------------------------------- @Override public T interceptTestClassConstructor(Invocation invocation, ReflectiveInvocationContext> invocationContext, ExtensionContext extensionContext) throws Throwable { return InvocationInterceptor.super.interceptTestClassConstructor(invocation, invocationContext, extensionContext); } @Override public void interceptBeforeAllMethod(Invocation<@Nullable Void> invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { InvocationInterceptor.super.interceptBeforeAllMethod(invocation, invocationContext, extensionContext); } @Override public void interceptBeforeEachMethod(Invocation<@Nullable Void> invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { InvocationInterceptor.super.interceptBeforeEachMethod(invocation, invocationContext, extensionContext); } @Override public void interceptTestMethod(Invocation<@Nullable Void> invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { InvocationInterceptor.super.interceptTestMethod(invocation, invocationContext, extensionContext); } @Override public T interceptTestFactoryMethod(Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { return InvocationInterceptor.super.interceptTestFactoryMethod(invocation, invocationContext, extensionContext); } @Override public void interceptTestTemplateMethod(Invocation<@Nullable Void> invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { InvocationInterceptor.super.interceptTestTemplateMethod(invocation, invocationContext, extensionContext); } @Override public void interceptDynamicTest(Invocation<@Nullable Void> invocation, DynamicTestInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { InvocationInterceptor.super.interceptDynamicTest(invocation, invocationContext, extensionContext); } @Override public void interceptAfterEachMethod(Invocation<@Nullable Void> invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { InvocationInterceptor.super.interceptAfterEachMethod(invocation, invocationContext, extensionContext); } @Override public void interceptAfterAllMethod(Invocation<@Nullable Void> invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { InvocationInterceptor.super.interceptAfterAllMethod(invocation, invocationContext, extensionContext); } // --- PreInterruptCallback ------------------------------------------------ @Override public void beforeThreadInterrupt(PreInterruptContext preInterruptContext, ExtensionContext extensionContext) throws Exception { } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/extension/MediaTypeInteroperabilityTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import org.junit.jupiter.api.Test; /** * Interoperability tests for {@link org.junit.jupiter.api.MediaType} and the * deprecated {@link org.junit.jupiter.api.extension.MediaType}. * * @since 1.14 * @see DeprecatedMediaTypeTests */ class MediaTypeInteroperabilityTests { @Test @SuppressWarnings("removal") void newAndDeprecatedMediaTypesAreLogicallyEquivalent() { var mediaType = org.junit.jupiter.api.MediaType.TEXT_PLAIN_UTF_8; var deprecatedMediaType = MediaType.TEXT_PLAIN_UTF_8; var differentMediaType = MediaType.TEXT_PLAIN; assertThat(mediaType.getClass()).isNotEqualTo(deprecatedMediaType.getClass()); assertEqualsAndHashCode(mediaType, deprecatedMediaType, differentMediaType); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolverTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.extension.support; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.platform.commons.support.ReflectionSupport; /** * @since 5.6 */ class TypeBasedParameterResolverTests { private final ParameterResolver basicTypeBasedParameterResolver = new BasicTypeBasedParameterResolver(); private final ParameterResolver subClassedBasicTypeBasedParameterResolver = new SubClassedBasicTypeBasedParameterResolver(); private final ParameterResolver parametrizedTypeBasedParameterResolver = new ParameterizedTypeBasedParameterResolver(); @Test void missingTypeTypeBasedParameterResolver() { assertPreconditionViolationFor(MissingTypeTypeBasedParameterResolver::new)// .withMessage("Failed to discover parameter type supported by " + MissingTypeTypeBasedParameterResolver.class.getName() + "; potentially caused by lacking parameterized type in class declaration."); } @Test void supportsParameterForBasicTypes() { Parameter parameter1 = findParameterOfMethod("methodWithBasicTypeParameter", String.class); assertTrue(basicTypeBasedParameterResolver.supportsParameter(parameterContext(parameter1), mock())); assertTrue(subClassedBasicTypeBasedParameterResolver.supportsParameter(parameterContext(parameter1), mock())); Parameter parameter2 = findParameterOfMethod("methodWithObjectParameter", Object.class); assertFalse(basicTypeBasedParameterResolver.supportsParameter(parameterContext(parameter2), mock())); } @Test void supportsParameterForParameterizedTypes() { Parameter parameter1 = findParameterOfMethod("methodWithParameterizedTypeParameter", Map.class); assertTrue(parametrizedTypeBasedParameterResolver.supportsParameter(parameterContext(parameter1), mock())); Parameter parameter3 = findParameterOfMethod("methodWithAnotherParameterizedTypeParameter", Map.class); assertFalse(parametrizedTypeBasedParameterResolver.supportsParameter(parameterContext(parameter3), mock())); } @Test void resolve() { ExtensionContext extensionContext = extensionContext(); ParameterContext parameterContext = parameterContext( findParameterOfMethod("methodWithBasicTypeParameter", String.class)); assertEquals("Displaying TestAnnotation", basicTypeBasedParameterResolver.resolveParameter(parameterContext, extensionContext)); Parameter parameter2 = findParameterOfMethod("methodWithParameterizedTypeParameter", Map.class); assertEquals(Map.of("ids", List.of(1, 42)), parametrizedTypeBasedParameterResolver.resolveParameter(parameterContext(parameter2), extensionContext)); } private static ParameterContext parameterContext(Parameter parameter) { ParameterContext parameterContext = mock(); when(parameterContext.getParameter()).thenReturn(parameter); return parameterContext; } private static ExtensionContext extensionContext() { ExtensionContext extensionContext = mock(); when(extensionContext.getDisplayName()).thenReturn("Displaying"); return extensionContext; } private Parameter findParameterOfMethod(String methodName, Class... parameterTypes) { Method method = ReflectionSupport.findMethod(Sample.class, methodName, parameterTypes).orElseThrow(); return method.getParameters()[0]; } // ------------------------------------------------------------------------- @SuppressWarnings("rawtypes") static class MissingTypeTypeBasedParameterResolver extends TypeBasedParameterResolver { @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return "enigma"; } } static class BasicTypeBasedParameterResolver extends TypeBasedParameterResolver { @Override public String resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { Class parameterAnnotation = parameterContext.getParameter().getAnnotations()[0].annotationType(); return "%s %s".formatted(extensionContext.getDisplayName(), parameterAnnotation.getSimpleName()); } } static class SubClassedBasicTypeBasedParameterResolver extends BasicTypeBasedParameterResolver { } static class ParameterizedTypeBasedParameterResolver extends TypeBasedParameterResolver>> { @Override public Map> resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return Map.of("ids", List.of(1, 42)); } } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) @interface TestAnnotation { } static class Sample { void methodWithBasicTypeParameter(@TestAnnotation String string) { } void methodWithObjectParameter(Object nothing) { } void methodWithParameterizedTypeParameter(Map> map) { } void methodWithAnotherParameterizedTypeParameter(Map> nothing) { } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/io/TempDirDeletionStrategyTests.java ================================================ /* * Copyright 2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.io; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.logging.Level; import java.util.logging.LogRecord; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.AnnotatedElementContext; import org.junit.jupiter.api.fixtures.TrackLogRecords; import org.junit.jupiter.api.io.TempDirDeletionStrategy.DeletionException; import org.junit.jupiter.api.io.TempDirDeletionStrategy.IgnoreFailures; import org.junit.platform.commons.logging.LogRecordListener; /** * @since 6.1 */ class TempDirDeletionStrategyTests { @Nested class IgnoreFailuresTests { @Test void logsAndIgnoresFailures(@TempDir Path tempDir, @TrackLogRecords LogRecordListener log) throws Exception { var undeletableDir = Files.createDirectory(tempDir.resolve("undeletable")); var strategy = new IgnoreFailures(new FailingTempDirDeletionStrategy()); AnnotatedElementContext annotatedElementContext = mock(); var testMethod = IgnoreFailuresTests.class.getDeclaredMethod("logsAndIgnoresFailures", Path.class, LogRecordListener.class); when(annotatedElementContext.getAnnotatedElement()).thenReturn(testMethod.getParameters()[0]); var result = strategy.delete(undeletableDir, annotatedElementContext, mock()); assertThat(result.isSuccessful()); var loggedWarnings = log.stream(IgnoreFailures.class, Level.WARNING).toList(); assertThat(loggedWarnings) // .extracting(LogRecord::getMessage) // .containsExactly( "Failed to delete all temporary files for parameter 'tempDir' in method logsAndIgnoresFailures(Path, LogRecordListener)"); var exception = loggedWarnings.getFirst().getThrown(); assertThat(exception).isInstanceOf(DeletionException.class); assertThat(exception).hasMessage( "Failed to delete temp directory %s. The following paths could not be deleted (see suppressed exceptions for details): ".formatted( undeletableDir.toAbsolutePath())); assertThat(exception.getSuppressed()).hasSize(1); assertThat(exception.getSuppressed()[0]) // .isInstanceOf(IOException.class) // .hasMessage("Simulated failure"); } // } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/LockTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.parallel; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ; import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ_WRITE; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.ResourceLocksProvider.Lock; /** * Unit tests for {@link Lock}. * * @since 5.12 */ class LockTests { @Test void readWriteModeSetByDefault() { assertEquals(READ_WRITE, new Lock("a").getAccessMode()); } @Test void equalsAndHashCode() { // @formatter:off assertEqualsAndHashCode( new Lock("a", READ_WRITE), new Lock("a", READ_WRITE), new Lock("b", READ_WRITE) ); assertEqualsAndHashCode( new Lock("a", READ_WRITE), new Lock("a", READ_WRITE), new Lock("a", READ) ); // @formatter:on } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.parallel; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.util.Throwables.getRootCause; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectIteration; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.lang.reflect.Method; import java.util.List; import java.util.Set; import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.ClassTemplate; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; import org.junit.jupiter.engine.descriptor.JupiterTestDescriptor; import org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor; import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.commons.JUnitException; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.hierarchical.ExclusiveResource; import org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode; import org.junit.platform.testkit.engine.Event; /** * Integration tests for {@link ResourceLock} and {@link ResourceLocksProvider}. * * @since 5.12 */ class ResourceLockAnnotationTests extends AbstractJupiterTestEngineTests { private static final UniqueId uniqueId = UniqueId.root("enigma", "foo"); private final JupiterConfiguration configuration = mock(); @BeforeEach void setUp() { when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new DisplayNameGenerator.Standard()); when(configuration.getDefaultExecutionMode()).thenReturn(ExecutionMode.SAME_THREAD); } @Test void noSharedResources() { // @formatter:off var classResources = getClassResources( NoSharedResourcesTestCase.class ); assertThat(classResources).isEmpty(); var methodResources = getMethodResources( NoSharedResourcesTestCase.class ); assertThat(methodResources).isEmpty(); var nestedClassResources = getNestedClassResources( NoSharedResourcesTestCase.NestedClass.class ); assertThat(nestedClassResources).isEmpty(); // @formatter:on } @Test void addSharedResourcesViaAnnotationValue() { // @formatter:off var classResources = getClassResources( SharedResourcesViaAnnotationValueTestCase.class ); assertThat(classResources).containsExactlyInAnyOrder( new ExclusiveResource("a1", LockMode.READ_WRITE), new ExclusiveResource("a2", LockMode.READ_WRITE) ); var methodResources = getMethodResources( SharedResourcesViaAnnotationValueTestCase.class ); assertThat(methodResources).containsExactlyInAnyOrder( new ExclusiveResource("a3", LockMode.READ_WRITE), new ExclusiveResource("b1", LockMode.READ), new ExclusiveResource("b2", LockMode.READ_WRITE) ); var nestedClassResources = getNestedClassResources( SharedResourcesViaAnnotationValueTestCase.NestedClass.class ); assertThat(nestedClassResources).containsExactlyInAnyOrder( new ExclusiveResource("a3", LockMode.READ_WRITE), new ExclusiveResource("c1", LockMode.READ) ); var nestedClassMethodResources = getMethodResources( SharedResourcesViaAnnotationValueTestCase.NestedClass.class ); assertThat(nestedClassMethodResources).containsExactlyInAnyOrder( new ExclusiveResource("c2", LockMode.READ) ); // @formatter:on } @Test void addSharedResourcesViaAnnotationProviders() { // @formatter:off var classResources = getClassResources( SharedResourcesViaAnnotationProvidersTestCase.class ); assertThat(classResources).containsExactlyInAnyOrder( new ExclusiveResource("a1", LockMode.READ), new ExclusiveResource("a2", LockMode.READ) ); var methodResources = getMethodResources( SharedResourcesViaAnnotationProvidersTestCase.class ); assertThat(methodResources).containsExactlyInAnyOrder( new ExclusiveResource("b1", LockMode.READ_WRITE), new ExclusiveResource("b2", LockMode.READ_WRITE) ); var nestedClassResources = getNestedClassResources( SharedResourcesViaAnnotationProvidersTestCase.NestedClass.class ); assertThat(nestedClassResources).containsExactlyInAnyOrder( new ExclusiveResource("c1", LockMode.READ_WRITE), new ExclusiveResource("c2", LockMode.READ) ); // @formatter:on } @Test void addSharedResourcesViaAnnotationValueAndProviders() { // @formatter:off var classResources = getClassResources( SharedResourcesViaAnnotationValueAndProvidersTestCase.class ); assertThat(classResources).containsExactlyInAnyOrder( new ExclusiveResource("a1", LockMode.READ_WRITE), new ExclusiveResource("a3", LockMode.READ) ); var methodResources = getMethodResources( SharedResourcesViaAnnotationValueAndProvidersTestCase.class ); assertThat(methodResources).containsExactlyInAnyOrder( new ExclusiveResource("a2", LockMode.READ_WRITE), new ExclusiveResource("b1", LockMode.READ), new ExclusiveResource("b2", LockMode.READ) ); var nestedClassResources = getNestedClassResources( SharedResourcesViaAnnotationValueAndProvidersTestCase.NestedClass.class ); assertThat(nestedClassResources).containsExactlyInAnyOrder( new ExclusiveResource("a2", LockMode.READ_WRITE), new ExclusiveResource("c1", LockMode.READ_WRITE), new ExclusiveResource("c2", LockMode.READ_WRITE), new ExclusiveResource("c3", LockMode.READ_WRITE) ); // @formatter:on } @Test void addSharedResourcesViaAnnotationValueAndProvidersForClassTemplate() { var selector = selectClass(SharedResourcesViaAnnotationValueAndProvidersClassTemplateTestCase.class); var engineDescriptor = discoverTests(selector).getEngineDescriptor(); engineDescriptor.accept(TestDescriptor::prune); var classTemplateTestDescriptor = (JupiterTestDescriptor) getOnlyElement(engineDescriptor.getChildren()); var expectedResources = List.of( // new ExclusiveResource("a1", LockMode.READ_WRITE), // new ExclusiveResource("a2", LockMode.READ_WRITE), // new ExclusiveResource("a3", LockMode.READ), // new ExclusiveResource("b1", LockMode.READ), // new ExclusiveResource("b2", LockMode.READ), // new ExclusiveResource("c1", LockMode.READ_WRITE), // new ExclusiveResource("c2", LockMode.READ_WRITE), // new ExclusiveResource("c3", LockMode.READ_WRITE), // new ExclusiveResource("d1", LockMode.READ_WRITE), // new ExclusiveResource("d2", LockMode.READ) // ); assertThat(classTemplateTestDescriptor.getExclusiveResources()) // .containsExactlyInAnyOrderElementsOf(expectedResources); } @Test void addSharedResourcesViaAnnotationValueAndProvidersForClassTemplateInvocation() { var selector = selectIteration( selectClass(SharedResourcesViaAnnotationValueAndProvidersClassTemplateTestCase.class), 0); var engineDescriptor = discoverTests(selector).getEngineDescriptor(); engineDescriptor.accept(TestDescriptor::prune); var classTemplateTestDescriptor = (JupiterTestDescriptor) getOnlyElement(engineDescriptor.getChildren()); var expectedResources = List.of( // new ExclusiveResource("a1", LockMode.READ_WRITE), // new ExclusiveResource("a2", LockMode.READ_WRITE), // new ExclusiveResource("a3", LockMode.READ), // new ExclusiveResource("b1", LockMode.READ), // new ExclusiveResource("b2", LockMode.READ), // new ExclusiveResource("c1", LockMode.READ_WRITE), // new ExclusiveResource("c2", LockMode.READ_WRITE), // new ExclusiveResource("c3", LockMode.READ_WRITE), // new ExclusiveResource("d1", LockMode.READ_WRITE), // new ExclusiveResource("d2", LockMode.READ) // ); assertThat(classTemplateTestDescriptor.getExclusiveResources()) // .containsExactlyInAnyOrderElementsOf(expectedResources); } @Test void addSharedResourcesViaAnnotationValueAndProvidersForMethodInClassTemplate() { var selector = selectMethod(SharedResourcesViaAnnotationValueAndProvidersClassTemplateTestCase.class, "test"); var engineDescriptor = discoverTests(selector).getEngineDescriptor(); engineDescriptor.accept(TestDescriptor::prune); var classTemplateTestDescriptor = (JupiterTestDescriptor) getOnlyElement(engineDescriptor.getChildren()); var expectedResources = List.of( // new ExclusiveResource("a1", LockMode.READ_WRITE), // new ExclusiveResource("a2", LockMode.READ_WRITE), // new ExclusiveResource("a3", LockMode.READ), // new ExclusiveResource("b1", LockMode.READ), // new ExclusiveResource("b2", LockMode.READ) // ); assertThat(classTemplateTestDescriptor.getExclusiveResources()) // .containsExactlyInAnyOrderElementsOf(expectedResources); } @Test void sharedResourcesHavingTheSameValueAndModeAreDeduplicated() { // @formatter:off var methodResources = getMethodResources( SharedResourcesHavingTheSameValueAndModeAreDeduplicatedTestCase.class ); assertThat(methodResources).containsExactlyInAnyOrder( new ExclusiveResource("a1", LockMode.READ_WRITE) ); // @formatter:on } @Test void sharedResourcesHavingTheSameValueButDifferentModeAreNotDeduplicated() { // @formatter:off var methodResources = getMethodResources( SharedResourcesHavingTheSameValueButDifferentModeAreNotDeduplicatedTestCase.class ); assertThat(methodResources).containsExactlyInAnyOrder( new ExclusiveResource("a1", LockMode.READ), new ExclusiveResource("a1", LockMode.READ_WRITE) ); // @formatter:on } static Stream> testMethodsCanNotDeclareSharedResourcesForChildrenArguments() { // @formatter:off return Stream.of( TestCanNotDeclareSharedResourcesForChildrenTestCase.class, ParameterizedTestCanNotDeclareSharedResourcesForChildrenTestCase.class, RepeatedTestCanNotDeclareSharedResourcesForChildrenTestCase.class, TestFactoryCanNotDeclareSharedResourcesForChildrenTestCase.class ); // @formatter:on } @ParameterizedTest @MethodSource("testMethodsCanNotDeclareSharedResourcesForChildrenArguments") void testMethodsCanNotDeclareSharedResourcesForChildren(Class testClass) { var messageTemplate = "'ResourceLockTarget.CHILDREN' is not supported for methods. Invalid method: %s"; assertThrowsJunitExceptionWithMessage( // testClass, // messageTemplate.formatted(getDeclaredTestMethod(testClass)) // ); } @Test void emptyAnnotation() { // @formatter:off var classResources = getClassResources( EmptyAnnotationTestCase.class ); assertThat(classResources).isEmpty(); var methodResources = getMethodResources( EmptyAnnotationTestCase.class ); assertThat(methodResources).isEmpty(); var nestedClassResources = getNestedClassResources( EmptyAnnotationTestCase.NestedClass.class ); assertThat(nestedClassResources).isEmpty(); // @formatter:on } private Set getClassResources(Class testClass) { return getClassTestDescriptor(testClass).getExclusiveResources(); } private ClassTestDescriptor getClassTestDescriptor(Class testClass) { return new ClassTestDescriptor(uniqueId, testClass, configuration); } private Set getMethodResources(Class testClass) { var descriptor = new TestMethodTestDescriptor( // uniqueId, testClass, getDeclaredTestMethod(testClass), List::of, configuration // ); descriptor.setParent(getClassTestDescriptor(testClass)); return descriptor.getExclusiveResources(); } private static Method getDeclaredTestMethod(Class testClass) { try { return testClass.getDeclaredMethod("test"); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } } private Set getNestedClassResources(Class testClass) { var descriptor = new NestedClassTestDescriptor(uniqueId, testClass, List::of, configuration); descriptor.setParent(getClassTestDescriptor(testClass.getEnclosingClass())); return descriptor.getExclusiveResources(); } private void assertThrowsJunitExceptionWithMessage(Class testClass, String message) { // @formatter:off var events = executeTestsForClass(testClass).allEvents(); assertThat(events.filter(finishedWithFailure(instanceOf(JUnitException.class))::matches)) .hasSize(1) .map(Event::getPayload) .map(payload -> (TestExecutionResult) payload.orElseThrow()) .map(payload -> getRootCause(payload.getThrowable().orElseThrow())) .first() .is(instanceOf(JUnitException.class)) .has(message(message)); // @formatter:on } // ------------------------------------------------------------------------- static class NoSharedResourcesTestCase { @Test void test() { } @Nested class NestedClass { } } @ResourceLock("a1") @ResourceLock(value = "a2", mode = ResourceAccessMode.READ_WRITE) @ResourceLock(value = "a3", mode = ResourceAccessMode.READ_WRITE, target = ResourceLockTarget.CHILDREN) static class SharedResourcesViaAnnotationValueTestCase { @Test @ResourceLock(value = "b1", mode = ResourceAccessMode.READ) @ResourceLock(value = "b2", target = ResourceLockTarget.SELF) void test() { } @Nested @ResourceLock(value = "c1", mode = ResourceAccessMode.READ) @ResourceLock(value = "c2", mode = ResourceAccessMode.READ, target = ResourceLockTarget.CHILDREN) class NestedClass { @Test void test() { } } } @ResourceLock(providers = { // SharedResourcesViaAnnotationProvidersTestCase.FirstClassLevelProvider.class, // SharedResourcesViaAnnotationProvidersTestCase.SecondClassLevelProvider.class // }) static class SharedResourcesViaAnnotationProvidersTestCase { @Test @ResourceLock(providers = MethodLevelProvider.class) void test() { } @Nested @ResourceLock(providers = NestedClassLevelProvider.class) class NestedClass { } static class FirstClassLevelProvider implements ResourceLocksProvider { @Override public Set provideForClass(Class testClass) { return Set.of(new Lock("a1", ResourceAccessMode.READ)); } } static class SecondClassLevelProvider implements ResourceLocksProvider { @Override public Set provideForClass(Class testClass) { return Set.of(new Lock("a2", ResourceAccessMode.READ)); } @Override public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, Method testMethod) { return Set.of(new Lock("b1")); } } static class MethodLevelProvider implements ResourceLocksProvider { @Override public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, Method testMethod) { return Set.of(new Lock("b2")); } } static class NestedClassLevelProvider implements ResourceLocksProvider { @Override public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { return Set.of(new Lock("c1"), new Lock("c2", ResourceAccessMode.READ)); } } } @ResourceLock( // value = "a1", // providers = SharedResourcesViaAnnotationValueAndProvidersTestCase.FirstClassLevelProvider.class // ) @ResourceLock( // value = "a2", // target = ResourceLockTarget.CHILDREN, // providers = SharedResourcesViaAnnotationValueAndProvidersTestCase.SecondClassLevelProvider.class // ) static class SharedResourcesViaAnnotationValueAndProvidersTestCase { @Test @ResourceLock(value = "b1", mode = ResourceAccessMode.READ) void test() { } @Nested @ResourceLock("c1") @ResourceLock(providers = NestedClassLevelProvider.class) class NestedClass { } static class FirstClassLevelProvider implements ResourceLocksProvider { @Override public Set provideForClass(Class testClass) { return Set.of(new Lock("a3", ResourceAccessMode.READ)); } } static class SecondClassLevelProvider implements ResourceLocksProvider { @Override public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, Method testMethod) { return Set.of(new Lock("b2", ResourceAccessMode.READ)); } @Override public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { return Set.of(new Lock("c2")); } } static class NestedClassLevelProvider implements ResourceLocksProvider { @Override public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { return Set.of(new Lock("c3")); } } } @ResourceLock( // value = "a1", // target = ResourceLockTarget.CHILDREN, // providers = SharedResourcesHavingTheSameValueAndModeAreDeduplicatedTestCase.Provider.class // ) static class SharedResourcesHavingTheSameValueAndModeAreDeduplicatedTestCase { @Test @ResourceLock(value = "a1") void test() { } static class Provider implements ResourceLocksProvider { @Override public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, Method testMethod) { return Set.of(new Lock("a1")); } } } @ResourceLock(value = "a1", mode = ResourceAccessMode.READ_WRITE, target = ResourceLockTarget.CHILDREN) static class SharedResourcesHavingTheSameValueButDifferentModeAreNotDeduplicatedTestCase { @Test @ResourceLock(value = "a1", mode = ResourceAccessMode.READ) void test() { } } static class TestCanNotDeclareSharedResourcesForChildrenTestCase { @Test @ResourceLock(value = "a1", target = ResourceLockTarget.CHILDREN) void test() { } } static class ParameterizedTestCanNotDeclareSharedResourcesForChildrenTestCase { @ParameterizedTest @ValueSource(ints = { 1, 2, 3 }) @ResourceLock(value = "a1", target = ResourceLockTarget.CHILDREN) void test() { } } static class RepeatedTestCanNotDeclareSharedResourcesForChildrenTestCase { @RepeatedTest(5) @ResourceLock(value = "a1", target = ResourceLockTarget.CHILDREN) void test() { } } static class TestFactoryCanNotDeclareSharedResourcesForChildrenTestCase { @TestFactory @ResourceLock(value = "a1", target = ResourceLockTarget.CHILDREN) Stream test() { return Stream.of(DynamicTest.dynamicTest("Dynamic test", () -> { })); } } @ResourceLock static class EmptyAnnotationTestCase { @Test @ResourceLock void test() { } @Nested @ResourceLock class NestedClass { } } @ClassTemplate @ResourceLock( // value = "a1", // providers = SharedResourcesViaAnnotationValueAndProvidersClassTemplateTestCase.FirstClassLevelProvider.class // ) @ResourceLock( // value = "a2", // target = ResourceLockTarget.CHILDREN, // providers = SharedResourcesViaAnnotationValueAndProvidersClassTemplateTestCase.SecondClassLevelProvider.class // ) static class SharedResourcesViaAnnotationValueAndProvidersClassTemplateTestCase { @Test @ResourceLock(value = "b1", mode = ResourceAccessMode.READ) void test() { } @Nested @ResourceLock(providers = NestedClassLevelProvider.class) class NestedClass { @Test @ResourceLock("c1") void test() { } } @Nested @ClassTemplate @ResourceLock(value = "d1", target = ResourceLockTarget.CHILDREN) class NestedClassTemplate { @Test @ResourceLock(value = "d2", mode = ResourceAccessMode.READ) void test() { } } static class FirstClassLevelProvider implements ResourceLocksProvider { @Override public Set provideForClass(Class testClass) { return Set.of(new Lock("a3", ResourceAccessMode.READ)); } } static class SecondClassLevelProvider implements ResourceLocksProvider { @Override public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, Method testMethod) { return Set.of(new Lock("b2", ResourceAccessMode.READ)); } @Override public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { return Set.of(new Lock("c2")); } } static class NestedClassLevelProvider implements ResourceLocksProvider { @Override public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { return Set.of(new Lock("c3")); } } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLocksProviderTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.parallel; import static java.util.Collections.emptySet; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; import static org.junit.platform.testkit.engine.EventConditions.test; import java.lang.reflect.Method; import java.util.List; import java.util.Set; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.platform.testkit.engine.Event; /** * Integration tests for {@link ResourceLocksProvider}. * * @since 5.12 */ class ResourceLocksProviderTests extends AbstractJupiterTestEngineTests { @Test void classLevelProvider() { var events = execute(ClassLevelProviderTestCase.class); assertThat(events.filter(event(test(), finishedSuccessfully())::matches)).hasSize(2); } @Test void nestedClassLevelProvider() { var events = execute(NestedClassLevelProviderTestCase.class); assertThat(events.filter(event(test(), finishedSuccessfully())::matches)).hasSize(2); } @Test void methodLevelProvider() { var events = execute(MethodLevelProviderTestCase.class); assertThat(events.filter(event(test(), finishedSuccessfully())::matches)).hasSize(2); } @Test void methodLevelProviderInNestedClass() { var events = execute(MethodLevelProviderInNestedClassTestCase.class); assertThat(events.filter(event(test(), finishedSuccessfully())::matches)).hasSize(2); } @Test void providesAccessToRuntimeEnclosingInstances() { var events = execute(SubClassLevelProviderTestCase.class); assertThat(events.filter(event(test(), finishedSuccessfully())::matches)).hasSize(2); } private Stream execute(Class testCase) { return executeTestsForClass(testCase).allEvents().stream(); } // ------------------------------------------------------------------------- @ResourceLock(providers = ClassLevelProviderTestCase.Provider.class) static class ClassLevelProviderTestCase { @Test void test() { assertTrue(Provider.isProvideForClassCalled, "'provideForClass' was not called"); assertTrue(Provider.isProvideForTestMethodCalled, "'provideForMethod(test)' was not called"); } @Nested class NestedClass { @Test void nestedTest() { assertTrue(Provider.isProvideForNestedClassCalled, "'provideForNestedClass' was not called"); // @formatter:off assertTrue( Provider.isProvideForNestedTestMethodCalled, "'provideForMethod(nestedTest)' was not called" ); // @formatter:on } } @AfterAll static void afterAll() { Provider.isProvideForClassCalled = false; Provider.isProvideForTestMethodCalled = false; Provider.isProvideForNestedClassCalled = false; Provider.isProvideForNestedTestMethodCalled = false; } static class Provider implements ResourceLocksProvider { private static boolean isProvideForClassCalled = false; private static boolean isProvideForTestMethodCalled = false; private static boolean isProvideForNestedClassCalled = false; private static boolean isProvideForNestedTestMethodCalled = false; @Nullable private Class testClass; @Override public Set provideForClass(Class testClass) { this.testClass = testClass; isProvideForClassCalled = true; assertThat(testClass).isAssignableTo(ClassLevelProviderTestCase.class); return emptySet(); } @Override public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { isProvideForNestedClassCalled = true; assertEquals(List.of(requireNonNull(this.testClass)), enclosingInstanceTypes); assertEquals(ClassLevelProviderTestCase.NestedClass.class, testClass); return emptySet(); } @Override public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, Method testMethod) { if (ClassLevelProviderTestCase.class.isAssignableFrom(testClass)) { assertEquals(List.of(), enclosingInstanceTypes); assertEquals("test", testMethod.getName()); isProvideForTestMethodCalled = true; return emptySet(); } if (testClass == ClassLevelProviderTestCase.NestedClass.class) { assertEquals(List.of(this.testClass), enclosingInstanceTypes); assertEquals("nestedTest", testMethod.getName()); isProvideForNestedTestMethodCalled = true; return emptySet(); } fail("Unexpected test class: " + testClass); return emptySet(); } } } static class SubClassLevelProviderTestCase extends ClassLevelProviderTestCase { } static class NestedClassLevelProviderTestCase { @Test void test() { } @Nested @ResourceLock(providers = NestedClassLevelProviderTestCase.Provider.class) class NestedClass { @Test void nestedTest() { assertTrue(Provider.isProvideForNestedClassCalled, "'provideForNestedClass' was not called"); assertTrue(Provider.isProvideForMethodCalled, "'provideForMethod' was not called"); } } @AfterAll static void afterAll() { Provider.isProvideForNestedClassCalled = false; Provider.isProvideForMethodCalled = false; } static class Provider implements ResourceLocksProvider { private static boolean isProvideForNestedClassCalled = false; private static boolean isProvideForMethodCalled = false; @Override public Set provideForClass(Class testClass) { fail("'provideForClass' should not be called"); return emptySet(); } @Override public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { isProvideForNestedClassCalled = true; assertEquals(List.of(NestedClassLevelProviderTestCase.class), enclosingInstanceTypes); assertEquals(NestedClass.class, testClass); return emptySet(); } @Override public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, Method testMethod) { isProvideForMethodCalled = true; assertEquals(List.of(NestedClassLevelProviderTestCase.class), enclosingInstanceTypes); assertEquals(NestedClassLevelProviderTestCase.NestedClass.class, testClass); assertEquals("nestedTest", testMethod.getName()); return emptySet(); } } } static class MethodLevelProviderTestCase { @Test @ResourceLock(providers = MethodLevelProviderTestCase.Provider.class) void test() { assertTrue(Provider.isProvideForMethodCalled, "'provideForMethod' was not called"); } @Nested class NestedClass { @Test void nestedTest() { } } @AfterAll static void afterAll() { Provider.isProvideForMethodCalled = false; } static class Provider implements ResourceLocksProvider { private static boolean isProvideForMethodCalled = false; @Override public Set provideForClass(Class testClass) { fail("'provideForClass' should not be called"); return emptySet(); } @Override public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { fail("'provideForNestedClass' should not be called"); return emptySet(); } @Override public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, Method testMethod) { isProvideForMethodCalled = true; assertEquals(List.of(), enclosingInstanceTypes); assertEquals(MethodLevelProviderTestCase.class, testClass); assertEquals("test", testMethod.getName()); return emptySet(); } } } static class MethodLevelProviderInNestedClassTestCase { @Test void test() { } @Nested class NestedClass { @Test @ResourceLock(providers = MethodLevelProviderInNestedClassTestCase.Provider.class) void nestedTest() { assertTrue(Provider.isProvideForMethodCalled, "'provideForMethod' was not called"); } } @AfterAll static void afterAll() { Provider.isProvideForMethodCalled = false; } static class Provider implements ResourceLocksProvider { private static boolean isProvideForMethodCalled = false; @Override public Set provideForClass(Class testClass) { fail("'provideForClass' should not be called"); return emptySet(); } @Override public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { fail("'provideForNestedClass' should not be called"); return emptySet(); } @Override public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, Method testMethod) { isProvideForMethodCalled = true; assertEquals(List.of(MethodLevelProviderInNestedClassTestCase.class), enclosingInstanceTypes); assertEquals(MethodLevelProviderInNestedClassTestCase.NestedClass.class, testClass); assertEquals("nestedTest", testMethod.getName()); return emptySet(); } } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/subpackage/SubclassedAssertionsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.subpackage; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.opentest4j.AssertionFailedError; /** * Tests which verify that {@link Assertions} can be subclassed. * * @since 5.3 */ class SubclassedAssertionsTests extends Assertions { @Test void assertTrueWithBooleanTrue() { assertTrue(true); assertTrue(true, "test"); assertTrue(true, () -> "test"); } @Test void assertFalseWithBooleanTrue() { AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> assertFalse(true)); assertEquals("expected: but was: ", error.getMessage()); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/subpackage/SubclassedAssumptionsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.subpackage; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Test; import org.opentest4j.TestAbortedException; /** * Tests which verify that {@link Assumptions} can be subclassed. * * @since 5.3 */ class SubclassedAssumptionsTests extends Assumptions { @Test void assumeTrueWithBooleanTrue() { String foo = null; try { assumeTrue(true); foo = "foo"; } finally { assertEquals("foo", foo); } } @Test void assumeFalseWithBooleanTrue() { TestAbortedException exception = assertThrows(TestAbortedException.class, () -> assumeFalse(true)); assertEquals("Assumption failed: assumption is not false", exception.getMessage()); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/timeout/PreemptiveTimeoutUtilsTest.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.timeout; import static java.time.Duration.ofMillis; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.condition.OS.WINDOWS; import static org.junit.jupiter.api.timeout.PreemptiveTimeoutUtils.executeWithPreemptiveTimeout; import java.time.Duration; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeoutException; import org.junit.jupiter.api.Test; import org.junit.platform.commons.util.ExceptionUtils; import org.opentest4j.AssertionFailedError; class PreemptiveTimeoutUtilsTest { private static final Duration PREEMPTIVE_TIMEOUT = ofMillis(WINDOWS.isCurrentOs() ? 1000 : 100); private static final PreemptiveTimeoutUtils.TimeoutFailureFactory TIMEOUT_EXCEPTION_FACTORY = (__, ___, ____, _____) -> new TimeoutException(); @Test void executeWithPreemptiveTimeoutThrowingTimeoutExceptionWithMessageForSupplierThatCompletesAfterTheTimeout() { assertThrows(TimeoutException.class, () -> executeWithPreemptiveTimeout(PREEMPTIVE_TIMEOUT, () -> { waitForInterrupt(); return "Tempus Fugit"; }, () -> "Tempus Fugit", TIMEOUT_EXCEPTION_FACTORY)); } @Test void executeWithPreemptiveTimeoutThrowingTimeoutExceptionWithMessageForSupplierThatThrowsAnAssertionFailedError() { AssertionFailedError exception = assertThrows(AssertionFailedError.class, () -> executeWithPreemptiveTimeout(ofMillis(500), () -> fail("enigma"), () -> "Tempus Fugit", TIMEOUT_EXCEPTION_FACTORY)); assertThat(exception).hasMessage("enigma"); } @Test void executeWithPreemptiveTimeoutThrowingTimeoutExceptionWithMessageForSupplierThatThrowsAnException() { RuntimeException exception = assertThrows(RuntimeException.class, () -> executeWithPreemptiveTimeout(ofMillis(500), () -> ExceptionUtils.throwAsUncheckedException(new RuntimeException(":(")), () -> "Tempus Fugit", TIMEOUT_EXCEPTION_FACTORY)); assertThat(exception).hasMessage(":("); } @Test void executeWithPreemptiveTimeoutThrowingTimeoutExceptionWithMessageForSupplierThatCompletesBeforeTimeout() throws Exception { var result = executeWithPreemptiveTimeout(PREEMPTIVE_TIMEOUT, () -> "Tempus Fugit", () -> "Tempus Fugit", TIMEOUT_EXCEPTION_FACTORY); assertThat(result).isEqualTo("Tempus Fugit"); } private void waitForInterrupt() { try { assertFalse(Thread.interrupted(), "Already interrupted"); new CountDownLatch(1).await(); } catch (InterruptedException ignore) { // ignore } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/util/DefaultLocaleTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.util; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import java.util.Locale; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.extension.ExtensionConfigurationException; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.platform.testkit.engine.EngineExecutionResults; @DisplayName("DefaultLocale extension") class DefaultLocaleTests extends AbstractJupiterTestEngineTests { private static Locale TEST_DEFAULT_LOCALE; private static Locale DEFAULT_LOCALE_BEFORE_TEST; @BeforeAll static void globalSetUp() { DEFAULT_LOCALE_BEFORE_TEST = Locale.getDefault(); TEST_DEFAULT_LOCALE = JupiterLocaleUtils.createLocale("custom"); Locale.setDefault(TEST_DEFAULT_LOCALE); } @AfterAll static void globalTearDown() { Locale.setDefault(DEFAULT_LOCALE_BEFORE_TEST); } @Nested @DisplayName("applied on the method level") class MethodLevelTests { @Test @ReadsDefaultLocale @DisplayName("does nothing when annotation is not present") void testDefaultLocaleNoAnnotation() { assertThat(Locale.getDefault()).isEqualTo(TEST_DEFAULT_LOCALE); } @Test @DefaultLocale("zh-Hant-TW") @DisplayName("sets the default locale using a language tag") void setsLocaleViaLanguageTag() { assertThat(Locale.getDefault()).isEqualTo(Locale.forLanguageTag("zh-Hant-TW")); } @Test @DefaultLocale(language = "en") @DisplayName("sets the default locale using a language") void setsLanguage() { assertThat(Locale.getDefault()).isEqualTo(JupiterLocaleUtils.createLocale("en")); } @Test @DefaultLocale(language = "en", country = "EN") @DisplayName("sets the default locale using a language and a country") void setsLanguageAndCountry() { assertThat(Locale.getDefault()).isEqualTo(JupiterLocaleUtils.createLocale("en", "EN")); } /** * A valid variant checked by {@link sun.util.locale.LanguageTag#isVariant} against BCP 47 (or more detailed RFC 5646) matches either {@code [0-9a-Z]{5-8}} or {@code [0-9][0-9a-Z]{3}}. * It does NOT check if such a variant exists in real. *
* The Locale-Builder accepts valid variants, concatenated by minus or underscore (minus will be transformed by the builder). * This means "en-EN" is a valid languageTag, but not a valid IETF BCP 47 variant subtag. *
* This is very confusing as the official page for supported locales shows that japanese locales return {@code *} or {@code JP} as a variant. * Even more confusing the enum values {@code Locale.JAPAN} and {@code Locale.JAPANESE} don't return a variant. * * @see RFC 5646 */ @Test @DefaultLocale(language = "ja", country = "JP", variant = "japanese") @DisplayName("sets the default locale using a language, a country and a variant") void setsLanguageAndCountryAndVariant() { assertThat(Locale.getDefault()).isEqualTo(JupiterLocaleUtils.createLocale("ja", "JP", "japanese")); } } @Test @WritesDefaultLocale @DisplayName("applied on the class level, should execute tests with configured Locale") void shouldExecuteTestsWithConfiguredLocale() { EngineExecutionResults results = executeTestsForClass(ClassLevelTestCases.class); results.testEvents().assertThatEvents().haveAtMost(2, finishedSuccessfully()); } @DefaultLocale(language = "fr", country = "FR") static class ClassLevelTestCases { @Test @ReadsDefaultLocale void shouldExecuteWithClassLevelLocale() { assertThat(Locale.getDefault()).isEqualTo(JupiterLocaleUtils.createLocale("fr", "FR")); } @Test @DefaultLocale(language = "de", country = "DE") void shouldBeOverriddenWithMethodLevelLocale() { assertThat(Locale.getDefault()).isEqualTo(JupiterLocaleUtils.createLocale("de", "DE")); } } @Nested @DefaultLocale(language = "en") @DisplayName("with nested classes") class NestedDefaultLocaleTests { @Nested @DisplayName("without DefaultLocale annotation") class NestedClass { @Test @DisplayName("DefaultLocale should be set from enclosed class when it is not provided in nested") void shouldSetLocaleFromEnclosedClass() { assertThat(Locale.getDefault().getLanguage()).isEqualTo("en"); } } @Nested @DefaultLocale(language = "de") @DisplayName("with DefaultLocale annotation") class AnnotatedNestedClass { @Test @DisplayName("DefaultLocale should be set from nested class when it is provided") void shouldSetLocaleFromNestedClass() { assertThat(Locale.getDefault().getLanguage()).isEqualTo("de"); } @Test @DefaultLocale(language = "ch") @DisplayName("DefaultLocale should be set from method when it is provided") void shouldSetLocaleFromMethodOfNestedClass() { assertThat(Locale.getDefault().getLanguage()).isEqualTo("ch"); } } } @Nested @DefaultLocale(language = "fi") @TestInstance(TestInstance.Lifecycle.PER_CLASS) @DisplayName("correctly sets/resets before/after each/all extension points") class ResettingDefaultLocaleTests { @Nested @DefaultLocale(language = "de") @TestInstance(TestInstance.Lifecycle.PER_CLASS) class ResettingDefaultLocaleNestedTests { @Test @DefaultLocale(language = "en") void setForTestMethod() { // only here to set the locale, so another test can verify whether it was reset; // still, better to assert the value was actually set assertThat(Locale.getDefault().getLanguage()).isEqualTo("en"); } @AfterAll @ReadsDefaultLocale void resetAfterTestMethodExecution() { assertThat(Locale.getDefault().getLanguage()).isEqualTo("custom"); } } @AfterAll @ReadsDefaultLocale void resetAfterTestMethodExecution() { assertThat(Locale.getDefault().getLanguage()).isEqualTo("custom"); } } @DefaultLocale(language = "en") static class ClassLevelResetTestCase { @Test void setForTestMethod() { // only here to set the locale, so another test can verify whether it was reset; // still, better to assert the value was actually set assertThat(Locale.getDefault().getLanguage()).isEqualTo("en"); } } @Nested @DisplayName("when configured incorrect") class ConfigurationFailureTests { @Nested @DisplayName("on the method level") class MethodLevel { @Test @DisplayName("should fail when nothing is configured") void shouldFailWhenNothingIsConfigured() { EngineExecutionResults results = executeTests( selectMethod(MethodLevelInitializationFailureTestCases.class, "shouldFailMissingConfiguration")); results.testEvents().assertThatEvents().haveAtMost(1, finishedWithFailure(instanceOf(ExtensionConfigurationException.class))); } @Test @DisplayName("should fail when variant is set but country is not") void shouldFailWhenVariantIsSetButCountryIsNot() { EngineExecutionResults results = executeTests( selectMethod(MethodLevelInitializationFailureTestCases.class, "shouldFailMissingCountry")); results.testEvents().assertThatEvents().haveAtMost(1, finishedWithFailure(instanceOf(ExtensionConfigurationException.class))); } @Test @DisplayName("should fail when languageTag and language is set") void shouldFailWhenLanguageTagAndLanguageIsSet() { EngineExecutionResults results = executeTests( selectMethod(MethodLevelInitializationFailureTestCases.class, "shouldFailLanguageTagAndLanguage")); results.testEvents().assertThatEvents().haveAtMost(1, finishedWithFailure(instanceOf(ExtensionConfigurationException.class))); } @Test @DisplayName("should fail when languageTag and country is set") void shouldFailWhenLanguageTagAndCountryIsSet() { EngineExecutionResults results = executeTests( selectMethod(MethodLevelInitializationFailureTestCases.class, "shouldFailLanguageTagAndCountry")); results.testEvents().assertThatEvents().haveAtMost(1, finishedWithFailure(instanceOf(ExtensionConfigurationException.class))); } @Test @DisplayName("should fail when languageTag and variant is set") void shouldFailWhenLanguageTagAndVariantIsSet() { EngineExecutionResults results = executeTests( selectMethod(MethodLevelInitializationFailureTestCases.class, "shouldFailLanguageTagAndVariant")); results.testEvents().assertThatEvents().haveAtMost(1, finishedWithFailure(instanceOf(ExtensionConfigurationException.class))); } @Test @DisplayName("should fail when invalid BCP 47 variant is set") void shouldFailIfNoValidBCP47VariantIsSet() { EngineExecutionResults results = executeTests( selectMethod(MethodLevelInitializationFailureTestCases.class, "shouldFailNoValidBCP47Variant")); results.testEvents().assertThatEvents().haveAtMost(1, finishedWithFailure(instanceOf(ExtensionConfigurationException.class))); } } @Nested @DisplayName("on the class level") class ClassLevel { @Test @DisplayName("should fail when variant is set but country is not") void shouldFailWhenVariantIsSetButCountryIsNot() { EngineExecutionResults results = executeTestsForClass(ClassLevelInitializationFailureTestCases.class); results.testEvents().assertThatEvents().haveAtMost(1, finishedWithFailure(instanceOf(ExtensionConfigurationException.class))); } } } static class MethodLevelInitializationFailureTestCases { @Test @DefaultLocale void shouldFailMissingConfiguration() { } @Test @DefaultLocale(language = "de", variant = "ch") void shouldFailMissingCountry() { } @Test @DefaultLocale(value = "Something", language = "de") void shouldFailLanguageTagAndLanguage() { } @Test @DefaultLocale(value = "Something", country = "DE") void shouldFailLanguageTagAndCountry() { } @Test @DefaultLocale(value = "Something", variant = "ch") void shouldFailLanguageTagAndVariant() { } @Test @DefaultLocale(variant = "en-GB") void shouldFailNoValidBCP47Variant() { } } @DefaultLocale(language = "de", variant = "ch") static class ClassLevelInitializationFailureTestCases { @Test void shouldFail() { } } @Nested @DisplayName("used with inheritance") class InheritanceTests extends InheritanceBaseTest { @Test @DisplayName("should inherit default locale annotation") void shouldInheritClearAndSetProperty() { assertThat(Locale.getDefault()).isEqualTo(JupiterLocaleUtils.createLocale("fr", "FR")); } } @DefaultLocale(language = "fr", country = "FR") static class InheritanceBaseTest { } @Nested @DisplayName("when used with a locale provider") class LocaleProviderTests { @Test @DisplayName("can get a basic locale from provider") @DefaultLocale(localeProvider = BasicLocaleProvider.class) void canUseProvider() { assertThat(Locale.getDefault()).isEqualTo(Locale.FRENCH); } @Test @ReadsDefaultLocale @DisplayName("throws a NullPointerException with custom message if provider returns null") void providerReturnsNull() { EngineExecutionResults results = executeTests(selectMethod(BadProviderTestCases.class, "returnsNull")); results.testEvents().assertThatEvents().haveAtMost(1, finishedWithFailure(instanceOf(NullPointerException.class), message(it -> it.contains("LocaleProvider instance returned with null")))); } @Test @ReadsDefaultLocale @DisplayName("throws an ExtensionConfigurationException if any other option is present") void mutuallyExclusiveWithValue() { EngineExecutionResults results = executeTests( selectMethod(BadProviderTestCases.class, "mutuallyExclusiveWithValue")); results.testEvents().assertThatEvents().haveAtMost(1, finishedWithFailure(instanceOf(ExtensionConfigurationException.class), message(it -> it.contains( "can only be used with a provider if value, language, country and variant are not set.")))); } @Test @ReadsDefaultLocale @DisplayName("throws an ExtensionConfigurationException if any other option is present") void mutuallyExclusiveWithLanguage() { EngineExecutionResults results = executeTests( selectMethod(BadProviderTestCases.class, "mutuallyExclusiveWithLanguage")); results.testEvents().assertThatEvents().haveAtMost(1, finishedWithFailure(instanceOf(ExtensionConfigurationException.class), message(it -> it.contains("can only be used with language tag if provider is not set.")))); } @Test @ReadsDefaultLocale @DisplayName("throws an ExtensionConfigurationException if any other option is present") void mutuallyExclusiveWithCountry() { EngineExecutionResults results = executeTests( selectMethod(BadProviderTestCases.class, "mutuallyExclusiveWithCountry")); results.testEvents().assertThatEvents().haveAtMost(1, finishedWithFailure(instanceOf(ExtensionConfigurationException.class), message(it -> it.contains( "can only be used with a provider if value, language, country and variant are not set.")))); } @Test @ReadsDefaultLocale @DisplayName("throws an ExtensionConfigurationException if any other option is present") void mutuallyExclusiveWithVariant() { EngineExecutionResults results = executeTests( selectMethod(BadProviderTestCases.class, "mutuallyExclusiveWithVariant")); results.testEvents().assertThatEvents().haveAtMost(1, finishedWithFailure(instanceOf(ExtensionConfigurationException.class), message(it -> it.contains( "can only be used with a provider if value, language, country and variant are not set.")))); } @Test @ReadsDefaultLocale @DisplayName("throws an ExtensionConfigurationException if localeProvider can't be constructed") void badConstructor() { EngineExecutionResults results = executeTests(selectMethod(BadProviderTestCases.class, "badConstructor")); results.testEvents().assertThatEvents().haveAtMost(1, finishedWithFailure(instanceOf(ExtensionConfigurationException.class), message(it -> it.contains("could not be constructed because of an exception")))); } } static class BadProviderTestCases { @Test @DefaultLocale(value = "en", localeProvider = BasicLocaleProvider.class) void mutuallyExclusiveWithValue() { // can't have both a value and a provider } @Test @DefaultLocale(language = "en", localeProvider = BasicLocaleProvider.class) void mutuallyExclusiveWithLanguage() { // can't have both a language property and a provider } @Test @DefaultLocale(country = "EN", localeProvider = BasicLocaleProvider.class) void mutuallyExclusiveWithCountry() { // can't have both a country property and a provider } @Test @DefaultLocale(variant = "japanese", localeProvider = BasicLocaleProvider.class) void mutuallyExclusiveWithVariant() { // can't have both a variant property and a provider } @Test @DefaultLocale(localeProvider = ReturnsNullLocaleProvider.class) void returnsNull() { // provider should not return 'null' } @Test @DefaultLocale(localeProvider = BadConstructorLocaleProvider.class) void badConstructor() { // provider has to have a no-args constructor } } static class BasicLocaleProvider implements LocaleProvider { @Override public Locale get() { return Locale.FRENCH; } } static class ReturnsNullLocaleProvider implements LocaleProvider { @Override @SuppressWarnings("NullAway") public Locale get() { return null; } } static class BadConstructorLocaleProvider implements LocaleProvider { private final String language; BadConstructorLocaleProvider(String language) { this.language = language; } @Override public Locale get() { return Locale.forLanguageTag(language); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/util/DefaultTimeZoneTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.util; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import java.util.TimeZone; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.extension.ExtensionConfigurationException; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.platform.testkit.engine.EngineExecutionResults; @DisplayName("DefaultTimeZone extension") class DefaultTimeZoneTests extends AbstractJupiterTestEngineTests { private static TimeZone TEST_DEFAULT_TIMEZONE; private static TimeZone DEFAULT_TIMEZONE_BEFORE_TEST; @BeforeAll static void globalSetUp() { // we set UTC as test time zone unless it is already // the system's time zone; in that case we use UTC+12 DEFAULT_TIMEZONE_BEFORE_TEST = TimeZone.getDefault(); TimeZone utc = TimeZone.getTimeZone("UTC"); TimeZone utcPlusTwelve = TimeZone.getTimeZone("GMT+12:00"); if (DEFAULT_TIMEZONE_BEFORE_TEST.equals(utc)) TimeZone.setDefault(utcPlusTwelve); else TimeZone.setDefault(utc); TEST_DEFAULT_TIMEZONE = TimeZone.getDefault(); } @AfterAll static void globalTearDown() { TimeZone.setDefault(DEFAULT_TIMEZONE_BEFORE_TEST); } @Nested @DisplayName("when applied on the method level") class MethodLevelTests { @Test @ReadsDefaultTimeZone @DisplayName("does nothing when annotation is not present") void doesNothingWhenAnnotationNotPresent() { assertThat(TimeZone.getDefault()).isEqualTo(TEST_DEFAULT_TIMEZONE); } @Test @DefaultTimeZone("GMT") @DisplayName("does not throw when explicitly set to GMT") void doesNotThrowForExplicitGmt() { assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("GMT")); } @Test @DefaultTimeZone("CET") @DisplayName("sets the default time zone using an abbreviation") void setsTimeZoneFromAbbreviation() { assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("CET")); } @Test @DefaultTimeZone("America/Los_Angeles") @DisplayName("sets the default time zone using a full name") void setsTimeZoneFromFullName() { assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("America/Los_Angeles")); } } @Nested @DefaultTimeZone("GMT-8:00") @DisplayName("when applied on the class level") class ClassLevelTestCases { @Test @ReadsDefaultTimeZone @DisplayName("sets the default time zone") void shouldExecuteWithClassLevelTimeZone() { assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("GMT-8:00")); } @Test @DefaultTimeZone("GMT-12:00") @DisplayName("gets overridden by annotation on the method level") void shouldBeOverriddenWithMethodLevelTimeZone() { assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("GMT-12:00")); } } @Nested @DefaultTimeZone("GMT") @DisplayName("when explicitly set to GMT on the class level") class ExplicitGmtClassLevelTestCases { @Test @DisplayName("does not throw and sets to GMT ") void explicitGmt() { assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("GMT")); } } @Nested @DefaultTimeZone("GMT-8:00") @DisplayName("with nested classes") class NestedTests { @Nested @DisplayName("without DefaultTimeZone annotation") class NestedClass { @Test @ReadsDefaultTimeZone @DisplayName("DefaultTimeZone should be set from enclosed class when it is not provided in nested") public void shouldSetTimeZoneFromEnclosedClass() { assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("GMT-8:00")); } } @Nested @DefaultTimeZone("GMT-12:00") @DisplayName("with DefaultTimeZone annotation") class AnnotatedNestedClass { @Test @ReadsDefaultTimeZone @DisplayName("DefaultTimeZone should be set from nested class when it is provided") public void shouldSetTimeZoneFromNestedClass() { assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("GMT-12:00")); } @Test @DefaultTimeZone("GMT-6:00") @DisplayName("DefaultTimeZone should be set from method when it is provided") public void shouldSetTimeZoneFromMethodOfNestedClass() { assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("GMT-6:00")); } } } @Nested @DefaultTimeZone("GMT-12:00") @TestInstance(TestInstance.Lifecycle.PER_CLASS) class ResettingDefaultTimeZoneTests { @Nested @DefaultTimeZone("GMT-3:00") @TestInstance(TestInstance.Lifecycle.PER_CLASS) class ResettingDefaultTimeZoneNestedTests { @Test @DefaultTimeZone("GMT+6:00") void setForTestMethod() { // only here to set the time zone, so another test can verify whether it was reset; // still, better to assert the value was actually set assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("GMT+6:00")); } @AfterAll @ReadsDefaultTimeZone void resetAfterTestMethodExecution() { assertThat(TimeZone.getDefault()).isEqualTo(TEST_DEFAULT_TIMEZONE); } } @AfterAll @ReadsDefaultTimeZone void resetAfterTestMethodExecution() { assertThat(TimeZone.getDefault()).isEqualTo(TEST_DEFAULT_TIMEZONE); } } @Nested @DisplayName("when misconfigured") class ConfigurationTests { @Test @ReadsDefaultTimeZone @DisplayName("on method level, throws exception") void throwsWhenConfigurationIsBad() { EngineExecutionResults results = executeTests( selectMethod(BadMethodLevelConfigurationTestCases.class, "badConfiguration")); results.testEvents().assertThatEvents().haveAtMost(1, finishedWithFailure(instanceOf(ExtensionConfigurationException.class), message(it -> it.contains("@DefaultTimeZone not configured correctly.")))); } @Test @ReadsDefaultTimeZone @DisplayName("on class level, throws exception") void shouldThrowWithBadConfiguration() { EngineExecutionResults results = executeTestsForClass(BadClassLevelConfigurationTestCases.class); results.testEvents().assertThatEvents().haveAtMost(1, finishedWithFailure(instanceOf(ExtensionConfigurationException.class), message(it -> it.contains("@DefaultTimeZone not configured correctly.")))); } @AfterEach void verifyMisconfigurationSisNotChangeTimeZone() { assertThat(TimeZone.getDefault()).isEqualTo(TEST_DEFAULT_TIMEZONE); } } static class BadMethodLevelConfigurationTestCases { @Test @DefaultTimeZone("Gibberish") void badConfiguration() { } } @DefaultTimeZone("Gibberish") static class BadClassLevelConfigurationTestCases { @Test void badConfiguration() { } } @Nested @DisplayName("used with inheritance") class InheritanceTests extends InheritanceBaseTest { @Test @DisplayName("should inherit default time zone annotation") void shouldInheritClearAndSetProperty() { assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("GMT-8:00")); } } @DefaultTimeZone("GMT-8:00") static class InheritanceBaseTest { } @Nested @DisplayName("used with TimeZoneProvider") class ProviderTests { @Test @DisplayName("can get a basic time zone") @DefaultTimeZone(timeZoneProvider = BasicTimeZoneProvider.class) void canGetBasicTimeZone() { assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("Europe/Prague")); } @Test @DisplayName("defaults to GMT if the provider returns null") @DefaultTimeZone(timeZoneProvider = NullProvider.class) void defaultToGmt() { assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("GMT")); } @Test @ReadsDefaultTimeZone @DisplayName("throws ExtensionConfigurationException if the provider is not the only option") void throwsForMutuallyExclusiveOptions() { EngineExecutionResults results = executeTests( selectMethod(BadTimeZoneProviderTestCases.class, "notExclusive")); results.testEvents().assertThatEvents().haveAtMost(1, finishedWithFailure(instanceOf(ExtensionConfigurationException.class), message(it -> it.contains("Either a valid time zone id or a TimeZoneProvider must be provided")))); } @Test @ReadsDefaultTimeZone @DisplayName("throws ExtensionConfigurationException if properties are empty") void throwsForEmptyOptions() { EngineExecutionResults results = executeTests(selectMethod(BadTimeZoneProviderTestCases.class, "empty")); results.testEvents().assertThatEvents().haveAtMost(1, finishedWithFailure(instanceOf(ExtensionConfigurationException.class), message(it -> it.contains("Either a valid time zone id or a TimeZoneProvider must be provided")))); } @Test @ReadsDefaultTimeZone @DisplayName("throws ExtensionConfigurationException if the provider does not have a suitable constructor") void throwsForBadConstructor() { EngineExecutionResults results = executeTests( selectMethod(BadTimeZoneProviderTestCases.class, "noConstructor")); results.testEvents().assertThatEvents().haveAtMost(1, finishedWithFailure(instanceOf(ExtensionConfigurationException.class), message(it -> it.contains("Could not instantiate TimeZoneProvider because of exception")))); } } static class BadTimeZoneProviderTestCases { @Test @DefaultTimeZone(value = "GMT", timeZoneProvider = BasicTimeZoneProvider.class) void notExclusive() { // can't have both a time zone value and a provider } @Test @DefaultTimeZone void empty() { // must have a provider or a time zone } @Test @DefaultTimeZone(timeZoneProvider = ComplicatedProvider.class) void noConstructor() { // provider has to have a no-args constructor } } static class BasicTimeZoneProvider implements TimeZoneProvider { @Override public TimeZone get() { return TimeZone.getTimeZone("Europe/Prague"); } } static class NullProvider implements TimeZoneProvider { @Override @SuppressWarnings("NullAway") public TimeZone get() { return null; } } static class ComplicatedProvider implements TimeZoneProvider { private final String timeZoneString; ComplicatedProvider(String timeZoneString) { this.timeZoneString = timeZoneString; } @Override public TimeZone get() { return TimeZone.getTimeZone(timeZoneString); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/util/JupiterPropertyUtilsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.util; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.junit.jupiter.api.util.JupiterPropertyUtils.cloneWithoutDefaults; import static org.mockito.Mockito.when; import java.io.Serial; import java.util.Optional; import java.util.Properties; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionConfigurationException; import org.junit.jupiter.api.extension.ExtensionContext; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoSettings; /** * Tests for {@link JupiterPropertyUtils}. */ @MockitoSettings class JupiterPropertyUtilsTests { @Mock ExtensionContext context; @Test void cloneProperties() { var properties = new Properties(); properties.setProperty("a", "a"); var value = new Object(); properties.put("a-obj", value); var clone = cloneWithoutDefaults(context, properties); assertThat(clone.stringPropertyNames()).containsExactly("a"); assertThat(clone.get("a")).isSameAs(properties.get("a")); assertThat(clone.keySet()).containsExactly("a", "a-obj"); assertThat(clone.getProperty("a")).isEqualTo("a"); assertThat(clone.get("a-obj")).isSameAs(value); } @Test void withDefaults() { var defaults = new Properties(); defaults.setProperty("a", "a"); var properties = new Properties(defaults); properties.setProperty("X", "X"); when(context.getElement()).thenReturn(Optional.of(JupiterPropertyUtilsTests.class)); assertThatExceptionOfType(ExtensionConfigurationException.class) // .isThrownBy(() -> cloneWithoutDefaults(context, properties))// .withMessage(""" SystemPropertiesExtension was configured to restore the system properties by [%s]. \ However, it was not possible to create an accurate snapshot of the system properties \ using Properties::clone, because default properties were present: [a]""", JupiterPropertyUtilsTests.class); } @Test void withDefaultsAndSubclass() { var defaults = new CustomProperties(); defaults.setProperty("a", "a"); var properties = new CustomProperties(defaults); properties.setProperty("b", "b"); var clone = cloneWithoutDefaults(context, properties); assertThat(clone.stringPropertyNames()).containsExactly("a", "b"); assertThat(clone.getProperty("a")).isEqualTo("a"); assertThat(clone.getProperty("b")).isEqualTo("b"); } static final class CustomProperties extends Properties { @Serial private static final long serialVersionUID = 1L; CustomProperties() { this(null); } CustomProperties(@Nullable CustomProperties defaults) { super(defaults); } @Override public synchronized Object clone() { CustomProperties clone = (CustomProperties) super.clone(); clone.defaults = this.defaults == null ? null : (Properties) this.defaults.clone(); return clone; } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/api/util/SystemPropertiesExtensionTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.util; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.EventConditions.test; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.Properties; 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.ClassOrderer; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestClassOrder; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.extension.ExtensionConfigurationException; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.platform.testkit.engine.EngineExecutionResults; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoSettings; @DisplayName("System Properties Extension") class SystemPropertiesExtensionTests extends AbstractJupiterTestEngineTests { @BeforeAll static void globalSetUp() { System.setProperty("A", "old A"); System.setProperty("B", "old B"); System.setProperty("C", "old C"); System.clearProperty("clear prop D"); System.clearProperty("clear prop E"); System.clearProperty("clear prop F"); } @AfterAll static void globalTearDown() { System.clearProperty("A"); System.clearProperty("B"); System.clearProperty("C"); assertThat(System.getProperty("clear prop D")).isNull(); assertThat(System.getProperty("clear prop E")).isNull(); assertThat(System.getProperty("clear prop F")).isNull(); } @Nested @DisplayName("with @ClearSystemProperty") @ClearSystemProperty(key = "A") class ClearSystemPropertyTests { @Test @DisplayName("should clear system property") @ClearSystemProperty(key = "B") void shouldClearSystemProperty() { assertThat(System.getProperty("A")).isNull(); assertThat(System.getProperty("B")).isNull(); assertThat(System.getProperty("C")).isEqualTo("old C"); assertThat(System.getProperty("clear prop D")).isNull(); assertThat(System.getProperty("clear prop E")).isNull(); assertThat(System.getProperty("clear prop F")).isNull(); } @Test @DisplayName("should be repeatable") @ClearSystemProperty(key = "B") @ClearSystemProperty(key = "C") void shouldBeRepeatable() { assertThat(System.getProperty("A")).isNull(); assertThat(System.getProperty("B")).isNull(); assertThat(System.getProperty("C")).isNull(); assertThat(System.getProperty("clear prop D")).isNull(); assertThat(System.getProperty("clear prop E")).isNull(); assertThat(System.getProperty("clear prop F")).isNull(); } } @Nested @DisplayName("with @SetSystemProperty") @SetSystemProperty(key = "A", value = "new A") class SetSystemPropertyTests { @Test @DisplayName("should set system property to value") @SetSystemProperty(key = "B", value = "new B") void shouldSetSystemPropertyToValue() { assertThat(System.getProperty("A")).isEqualTo("new A"); assertThat(System.getProperty("B")).isEqualTo("new B"); assertThat(System.getProperty("C")).isEqualTo("old C"); assertThat(System.getProperty("clear prop D")).isNull(); assertThat(System.getProperty("clear prop E")).isNull(); assertThat(System.getProperty("clear prop F")).isNull(); } @Test @DisplayName("should be repeatable") @SetSystemProperty(key = "B", value = "new B") @SetSystemProperty(key = "clear prop D", value = "new D") void shouldBeRepeatable() { assertThat(System.getProperty("A")).isEqualTo("new A"); assertThat(System.getProperty("B")).isEqualTo("new B"); assertThat(System.getProperty("C")).isEqualTo("old C"); assertThat(System.getProperty("clear prop D")).isEqualTo("new D"); assertThat(System.getProperty("clear prop E")).isNull(); assertThat(System.getProperty("clear prop F")).isNull(); } } @Nested @DisplayName("with both @ClearSystemProperty and @SetSystemProperty") @ClearSystemProperty(key = "A") @SetSystemProperty(key = "clear prop D", value = "new D") class CombinedClearAndSetTests { @Test @DisplayName("should be combinable") @ClearSystemProperty(key = "B") @SetSystemProperty(key = "clear prop E", value = "new E") void clearAndSetSystemPropertyShouldBeCombinable() { assertThat(System.getProperty("A")).isNull(); assertThat(System.getProperty("B")).isNull(); assertThat(System.getProperty("C")).isEqualTo("old C"); assertThat(System.getProperty("clear prop D")).isEqualTo("new D"); assertThat(System.getProperty("clear prop E")).isEqualTo("new E"); assertThat(System.getProperty("clear prop F")).isNull(); } @Test @DisplayName("method level should overwrite class level") @ClearSystemProperty(key = "clear prop D") @SetSystemProperty(key = "A", value = "new A") void methodLevelShouldOverwriteClassLevel() { assertThat(System.getProperty("A")).isEqualTo("new A"); assertThat(System.getProperty("B")).isEqualTo("old B"); assertThat(System.getProperty("C")).isEqualTo("old C"); assertThat(System.getProperty("clear prop D")).isNull(); assertThat(System.getProperty("clear prop E")).isNull(); assertThat(System.getProperty("clear prop F")).isNull(); } @Test @DisplayName("method level should not clash (in terms of duplicate entries) with class level") @SetSystemProperty(key = "A", value = "new A") void methodLevelShouldNotClashWithClassLevel() { assertThat(System.getProperty("A")).isEqualTo("new A"); assertThat(System.getProperty("B")).isEqualTo("old B"); assertThat(System.getProperty("C")).isEqualTo("old C"); assertThat(System.getProperty("clear prop D")).isEqualTo("new D"); assertThat(System.getProperty("clear prop E")).isNull(); assertThat(System.getProperty("clear prop F")).isNull(); } } @Nested @DisplayName("with Set, Clear, and Restore") @WritesSystemProperty // Many of these tests write, many also access @Execution(SAME_THREAD) // Uses instance state @TestInstance(TestInstance.Lifecycle.PER_CLASS) // Uses instance state @TestClassOrder(ClassOrderer.OrderAnnotation.class) class CombinedClearSetRestoreTests { Properties initialState; // Stateful @BeforeAll void beforeAll() { initialState = System.getProperties(); } @Nested @Order(1) @DisplayName("Set, Clear & Restore on class") @ClearSystemProperty(key = "A") @SetSystemProperty(key = "clear prop D", value = "new D") @RestoreSystemProperties @TestMethodOrder(OrderAnnotation.class) @TestInstance(TestInstance.Lifecycle.PER_CLASS) // Uses instance state class SetClearRestoreOnClass { @AfterAll void afterAll() { System.setProperties(new Properties()); // Really blow it up after this class } @Test @Order(1) @DisplayName("Set, Clear on method w/ direct set Sys Prop") @ClearSystemProperty(key = "B") @SetSystemProperty(key = "clear prop E", value = "new E") void clearSetRestoreShouldBeCombinable() { assertThat(System.getProperties()).withFailMessage( "Restore should swap out the Sys Properties instance").isNotSameAs(initialState); // Direct modification - shouldn't be visible in next test System.setProperty("Restore", "Restore Me"); System.getProperties().put("XYZ", this); assertThat(System.getProperty("Restore")).isEqualTo("Restore Me"); assertThat(System.getProperties().get("XYZ")).isSameAs(this); // All the others assertThat(System.getProperty("A")).isNull(); assertThat(System.getProperty("B")).isNull(); assertThat(System.getProperty("C")).isEqualTo("old C"); assertThat(System.getProperty("clear prop D")).isEqualTo("new D"); assertThat(System.getProperty("clear prop E")).isEqualTo("new E"); assertThat(System.getProperty("clear prop F")).isNull(); } @Test @DisplayName("Restore from class should restore direct mods") @Order(2) void restoreShouldHaveRevertedDirectModification() { assertThat(System.getProperty("Restore")).isNull(); assertThat(System.getProperties().get("XYZ")).isNull(); } } @Nested @Order(2) @DisplayName("Prior nested class changes should be restored}") class priorNestedChangesRestored { @Test @DisplayName("Restore from class should restore direct mods") void restoreShouldHaveRevertedDirectModification() { assertThat(System.getProperties()).isSameAs(initialState); } } @Nested @Order(3) @DisplayName("Set & Clear on class, Restore on method") @ClearSystemProperty(key = "A") @SetSystemProperty(key = "clear prop D", value = "new D") @TestMethodOrder(OrderAnnotation.class) @TestInstance(TestInstance.Lifecycle.PER_CLASS) // Uses instance state class SetAndClearOnClass { Properties initialState; // Stateful @BeforeAll void beforeAll() { initialState = System.getProperties(); } @Test @Order(1) @DisplayName("Set, Clear & Restore on method w/ direct set Sys Prop") @ClearSystemProperty(key = "B") @SetSystemProperty(key = "clear prop E", value = "new E") @RestoreSystemProperties void clearSetRestoreShouldBeCombinable() { assertThat(System.getProperties()).withFailMessage( "Restore should swap out the Sys Properties instance").isNotSameAs(initialState); // Direct modification - shouldn't be visible in the next test System.setProperty("Restore", "Restore Me"); System.getProperties().put("XYZ", this); // All the others assertThat(System.getProperty("A")).isNull(); assertThat(System.getProperty("B")).isNull(); assertThat(System.getProperty("C")).isEqualTo("old C"); assertThat(System.getProperty("clear prop D")).isEqualTo("new D"); assertThat(System.getProperty("clear prop E")).isEqualTo("new E"); assertThat(System.getProperty("clear prop F")).isNull(); } @Test @DisplayName("Restore from prior method should restore direct mods") @Order(2) void restoreShouldHaveRevertedDirectModification() { assertThat(System.getProperty("Restore")).isNull(); assertThat(System.getProperties().get("XYZ")).isNull(); assertThat(System.getProperties()).isSameAs(initialState); } } } @Nested @DisplayName("@RestoreSystemProperties individual methods tests") @WritesSystemProperty // Many of these tests write, many also access class RestoreSystemPropertiesUnitTests { SystemPropertiesExtension spe; @BeforeEach void beforeEach() { spe = new SystemPropertiesExtension(); } @Nested @DisplayName("Attributes of RestoreSystemProperties Annotation") class BasicAttributesOfRestoreSystemProperties { @Test @DisplayName("Restore annotation has correct markers") void restoreHasCorrectMarkers() { assertThat(RestoreSystemProperties.class).hasAnnotations(Inherited.class, WritesSystemProperty.class); } @Test @DisplayName("Restore annotation has correct retention") void restoreHasCorrectRetention() { assertThat(RestoreSystemProperties.class.getAnnotation(Retention.class).value()).isEqualTo( RetentionPolicy.RUNTIME); } @Test @DisplayName("Restore annotation has correct targets") void restoreHasCorrectTargets() { assertThat(RestoreSystemProperties.class.getAnnotation(Target.class).value()).containsExactlyInAnyOrder( ElementType.METHOD, ElementType.TYPE); } } @Nested @DisplayName("RestorableContext Workflow Tests") @MockitoSettings class RestorableContextWorkflowTests { @Mock ExtensionContext context; @Test @DisplayName("Workflow of RestorableContext") void workflowOfRestorableContexts() { Properties initialState = System.getProperties(); //This is a live reference try { Properties returnedFromPrepareToEnter = spe.prepareToEnterRestorableContext(context); Properties postPrepareToEnterSysProps = System.getProperties(); spe.prepareToExitRestorableContext(initialState); Properties postPrepareToExitSysProps = System.getProperties(); assertThat(returnedFromPrepareToEnter) // .withFailMessage( "prepareToEnterRestorableContext should return actual original or deep copy") // .isSameAs(initialState); assertThat(returnedFromPrepareToEnter) // .withFailMessage("prepareToEnterRestorableContext should replace the actual Sys Props") // .isNotSameAs(postPrepareToEnterSysProps); assertThat(postPrepareToEnterSysProps).isEqualTo(initialState); assertThat(postPrepareToExitSysProps).isSameAs(initialState); } finally { System.setProperties(initialState); // Ensure complete recovery } } } } @Nested @DisplayName("with nested classes") @ClearSystemProperty(key = "A") @SetSystemProperty(key = "B", value = "new B") class NestedSystemPropertyTests { @Nested @TestMethodOrder(OrderAnnotation.class) @DisplayName("without SystemProperty annotations") class NestedClass { @Test @Order(1) @ReadsSystemProperty @DisplayName("system properties should be set from enclosed class when they are not provided in nested") void shouldSetSystemPropertyFromEnclosedClass() { assertThat(System.getProperty("A")).isNull(); assertThat(System.getProperty("B")).isEqualTo("new B"); } @Test @Order(2) @ReadsSystemProperty @DisplayName("system properties should be set from enclosed class after restore") void shouldSetSystemPropertyFromEnclosedClassAfterRestore() { assertThat(System.getProperty("A")).isNull(); assertThat(System.getProperty("B")).isEqualTo("new B"); } } @Nested @SetSystemProperty(key = "B", value = "newer B") @DisplayName("with @SetSystemProperty annotation") class AnnotatedNestedClass { @Test @ReadsSystemProperty @DisplayName("system property should be set from nested class when it is provided") void shouldSetSystemPropertyFromNestedClass() { assertThat(System.getProperty("B")).isEqualTo("newer B"); } @Test @SetSystemProperty(key = "B", value = "newest B") @DisplayName("system property should be set from method when it is provided") void shouldSetSystemPropertyFromMethodOfNestedClass() { assertThat(System.getProperty("B")).isEqualTo("newest B"); } } } @Nested @SetSystemProperty(key = "A", value = "new A") @TestInstance(TestInstance.Lifecycle.PER_CLASS) class ResettingSystemPropertyTests { @Nested @SetSystemProperty(key = "A", value = "newer A") @TestInstance(TestInstance.Lifecycle.PER_CLASS) class ResettingSystemPropertyAfterEachNestedTests { @BeforeEach void changeShouldBeVisible() { // We already see "newest A" because BeforeEachCallBack is invoked before @BeforeEach // See https://junit.org/junit5/docs/current/user-guide/#extensions-execution-order-overview assertThat(System.getProperty("A")).isEqualTo("newest A"); } @Test @SetSystemProperty(key = "A", value = "newest A") void setForTestMethod() { assertThat(System.getProperty("A")).isEqualTo("newest A"); } @AfterEach @ReadsSystemProperty void resetAfterTestMethodExecution() { // We still see "newest A" because AfterEachCallBack is invoked after @AfterEach // See https://junit.org/junit5/docs/current/user-guide/#extensions-execution-order-overview assertThat(System.getProperty("A")).isEqualTo("newest A"); } } @Nested @SetSystemProperty(key = "A", value = "newer A") @TestInstance(TestInstance.Lifecycle.PER_CLASS) class ResettingSystemPropertyAfterAllNestedTests { @BeforeAll void changeShouldBeVisible() { assertThat(System.getProperty("A")).isEqualTo("newer A"); } @Test @SetSystemProperty(key = "A", value = "newest A") void setForTestMethod() { assertThat(System.getProperty("A")).isEqualTo("newest A"); } @AfterAll @ReadsSystemProperty void resetAfterTestMethodExecution() { assertThat(System.getProperty("A")).isEqualTo("newer A"); } } @AfterAll @ReadsSystemProperty void resetAfterTestContainerExecution() { assertThat(System.getProperty("A")).isEqualTo("new A"); } } @Nested @DisplayName("with incorrect configuration") class ConfigurationFailureTests { @Test @DisplayName("should fail when clear and set same system property") void shouldFailWhenClearAndSetSameSystemProperty() { EngineExecutionResults results = executeTests(selectMethod(MethodLevelInitializationFailureTestCases.class, "shouldFailWhenClearAndSetSameSystemProperty")); results.testEvents().assertThatEvents().haveAtMost(1, finishedWithFailure(instanceOf(ExtensionConfigurationException.class), message(it -> it.contains("@DefaultTimeZone not configured correctly.")))); } @Test @DisplayName("should not fail when clear same system property twice") void shouldNotFailWhenClearSameSystemPropertyTwice() { EngineExecutionResults results = executeTests(selectMethod(MethodLevelInitializationFailureTestCases.class, "shouldFailWhenClearSameSystemPropertyTwice")); results.testEvents().assertThatEvents().haveExactly(1, event(test(), finishedSuccessfully())); } @Test @DisplayName("should fail when set same system property twice") void shouldFailWhenSetSameSystemPropertyTwice() { EngineExecutionResults results = executeTests(selectMethod(MethodLevelInitializationFailureTestCases.class, "shouldFailWhenSetSameSystemPropertyTwice")); results.testEvents().assertThatEvents().haveAtMost(1, finishedWithFailure(instanceOf(ExtensionConfigurationException.class))); } } static class MethodLevelInitializationFailureTestCases { @Test @DisplayName("clearing and setting the same property") @ClearSystemProperty(key = "A") @SetSystemProperty(key = "A", value = "new A") void shouldFailWhenClearAndSetSameSystemProperty() { } @Test @ClearSystemProperty(key = "A") @ClearSystemProperty(key = "A") void shouldFailWhenClearSameSystemPropertyTwice() { } @Test @SetSystemProperty(key = "A", value = "new A") @SetSystemProperty(key = "A", value = "new B") void shouldFailWhenSetSameSystemPropertyTwice() { } } @Nested @DisplayName("Clear and Set with inheritance") class InheritanceClearAndSetTests extends InheritanceClearAndSetBaseTest { @Test @DisplayName("should inherit clear and set annotations") void shouldInheritClearAndSetProperty() { assertThat(System.getProperty("A")).isNull(); assertThat(System.getProperty("B")).isNull(); assertThat(System.getProperty("clear prop D")).isEqualTo("new D"); assertThat(System.getProperty("clear prop E")).isEqualTo("new E"); } } @Nested @DisplayName("Clear, Set, and Restore with inheritance") @TestMethodOrder(OrderAnnotation.class) @TestClassOrder(ClassOrderer.OrderAnnotation.class) @Execution(SAME_THREAD) // Uses instance state @TestInstance(TestInstance.Lifecycle.PER_CLASS) // Uses instance state class InheritanceClearSetRestoreTests extends InheritanceClearSetRestoreBaseTest { Properties initialState; // Stateful @BeforeAll void beforeAll() { initialState = System.getProperties(); } @Test @Order(1) @DisplayName("should inherit clear and set annotations") void shouldInheritClearSetRestore() { // Direct modification - shouldn't be visible in the next test System.setProperty("Restore", "Restore Me"); System.getProperties().put("XYZ", this); assertThat(System.getProperty("A")).isNull(); // The rest are checked elsewhere } @Test @Order(2) @DisplayName("Restore from class should restore direct mods") void restoreShouldHaveRevertedDirectModification() { assertThat(System.getProperty("Restore")).isNull(); assertThat(System.getProperties().get("XYZ")).isNull(); assertThat(System.getProperties()) // .withFailMessage("Restore should swap out the Sys Properties instance") // .isNotSameAs(initialState); assertThat(System.getProperties()).isEqualTo(initialState); } @Nested @Order(1) @DisplayName("Set props to ensure inherited restore") @TestMethodOrder(OrderAnnotation.class) @TestInstance(TestInstance.Lifecycle.PER_CLASS) class SetSomeValuesToRestore { @AfterAll void afterAll() { System.setProperty("RestoreAll", "Restore Me"); // This should also be restored } @Test @Order(1) @DisplayName("Inherit values and restore behavior") void shouldInheritInNestedClass() { assertThat(System.getProperty("A")).isNull(); // Shouldn't be visible in the next test System.setProperty("Restore", "Restore Me"); } @Test @Order(2) @DisplayName("Verify restore behavior bt methods") void verifyRestoreBetweenMethods() { assertThat(System.getProperty("Restore")).isNull(); } } @Nested @Order(2) @DisplayName("Verify props are restored") @TestInstance(TestInstance.Lifecycle.PER_CLASS) class VerifyValuesAreRestored { @Test @DisplayName("Inherit values and restore behavior") void shouldInheritInNestedClass() { assertThat(System.getProperty("RestoreAll")).isNull(); // Should be restored } } } @ClearSystemProperty(key = "A") @ClearSystemProperty(key = "B") @SetSystemProperty(key = "clear prop D", value = "new D") @SetSystemProperty(key = "clear prop E", value = "new E") static class InheritanceClearAndSetBaseTest { } @ClearSystemProperty(key = "A") @ClearSystemProperty(key = "B") @SetSystemProperty(key = "clear prop D", value = "new D") @SetSystemProperty(key = "clear prop E", value = "new E") @RestoreSystemProperties static class InheritanceClearSetRestoreBaseTest { } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import static kotlin.jvm.JvmClassMappingKt.getJavaClass; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.launcher.LauncherConstants.CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME; import static org.junit.platform.launcher.LauncherConstants.STACKTRACE_PRUNING_ENABLED_PROPERTY_NAME; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.junit.platform.launcher.core.OutputDirectoryCreators.dummyOutputDirectoryCreator; import java.util.List; import java.util.function.Consumer; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; import org.junit.platform.testkit.engine.EngineDiscoveryResults; import org.junit.platform.testkit.engine.EngineExecutionResults; import org.junit.platform.testkit.engine.EngineTestKit; import kotlin.reflect.KClass; /** * Abstract base class for tests involving the {@link JupiterTestEngine}. * * @since 5.0 */ public abstract class AbstractJupiterTestEngineTests { private final JupiterTestEngine engine = new JupiterTestEngine(); protected EngineExecutionResults executeTestsForClass(KClass testClass) { return executeTestsForClass(getJavaClass(testClass)); } protected EngineExecutionResults executeTestsForClass(Class testClass) { return executeTests(selectClass(testClass)); } protected EngineExecutionResults executeTests(DiscoverySelector... selectors) { return executeTests(List.of(selectors)); } protected EngineExecutionResults executeTests(List selectors) { return executeTests(request -> request.selectors(selectors)); } protected EngineExecutionResults executeTests(Consumer configurer) { var builder = defaultRequest(); configurer.accept(builder); return executeTests(builder); } protected EngineExecutionResults executeTests(LauncherDiscoveryRequestBuilder builder) { return executeTests(builder.build()); } protected EngineExecutionResults executeTests(LauncherDiscoveryRequest request) { return EngineTestKit.execute(this.engine, request); } protected TestDescriptor discoverTestsWithoutIssues(LauncherDiscoveryRequest request) { var results = discoverTests(request); assertThat(results.getDiscoveryIssues()).isEmpty(); return results.getEngineDescriptor(); } protected EngineDiscoveryResults discoverTestsForClass(Class testClass) { return discoverTests(selectClass(testClass)); } protected EngineDiscoveryResults discoverTests(Consumer configurer) { var builder = defaultRequest(); configurer.accept(builder); return discoverTests(builder); } protected EngineDiscoveryResults discoverTests(DiscoverySelector... selectors) { return discoverTests(request -> request.selectors(selectors)); } protected EngineDiscoveryResults discoverTests(LauncherDiscoveryRequestBuilder builder) { return discoverTests(builder.build()); } protected EngineDiscoveryResults discoverTests(LauncherDiscoveryRequest request) { return EngineTestKit.discover(this.engine, request); } protected EngineTestKit.Builder jupiterTestEngine() { return EngineTestKit.engine(this.engine) // .outputDirectoryCreator(dummyOutputDirectoryCreator()) // .configurationParameter(STACKTRACE_PRUNING_ENABLED_PROPERTY_NAME, String.valueOf(false)) // .configurationParameter(CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME, Severity.INFO.name()) // .enableImplicitConfigurationParameters(false); } protected static LauncherDiscoveryRequestBuilder defaultRequest() { return request() // .outputDirectoryCreator(dummyOutputDirectoryCreator()) // .configurationParameter(STACKTRACE_PRUNING_ENABLED_PROPERTY_NAME, String.valueOf(false)) // .configurationParameter(CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME, Severity.INFO.name()) // .enableImplicitConfigurationParameters(false); } protected UniqueId discoverUniqueId(Class clazz, String methodName) { var results = discoverTests(selectMethod(clazz, methodName)); var engineDescriptor = results.getEngineDescriptor(); var descendants = engineDescriptor.getDescendants(); // @formatter:off var testDescriptor = descendants.stream() .skip(descendants.size() - 1) .findFirst() .orElseGet(() -> fail("no descendants")); // @formatter:on return testDescriptor.getUniqueId(); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/AtypicalJvmMethodNameTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.engine.kotlin.ArbitraryNamingKotlinTestCase.METHOD_NAME; import org.junit.jupiter.api.Test; import org.junit.jupiter.engine.kotlin.ArbitraryNamingKotlinTestCase; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.testkit.engine.EngineExecutionResults; /** * Integration tests for JVM languages that allow special characters * in method names (e.g., Kotlin, Groovy, etc.) which are forbidden in * Java source code. * * @since 5.1 */ class AtypicalJvmMethodNameTests extends AbstractJupiterTestEngineTests { @Test void kotlinTestWithMethodNameContainingSpecialCharacters() { EngineExecutionResults executionResults = executeTestsForClass(ArbitraryNamingKotlinTestCase.class); assertThat(executionResults.testEvents().started().count()).isEqualTo(2); TestDescriptor testDescriptor1 = executionResults.testEvents().succeeded().list().get(0).getTestDescriptor(); assertAll(// () -> assertEquals(METHOD_NAME + "()", testDescriptor1.getDisplayName()), // () -> assertEquals(METHOD_NAME + "()", testDescriptor1.getLegacyReportingName())); TestDescriptor testDescriptor2 = executionResults.testEvents().succeeded().list().get(1).getTestDescriptor(); assertAll(// () -> assertEquals("test name ends with parentheses()()", testDescriptor2.getDisplayName()), // () -> assertEquals("test name ends with parentheses()()", testDescriptor2.getLegacyReportingName())); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/BeforeAllAndAfterAllComposedAnnotationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import static org.assertj.core.api.Assertions.assertThat; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; /** * Integration tests that verify support for {@link BeforeAll} and {@link AfterAll} * when used as meta-annotations in the {@link JupiterTestEngine}. * * @since 5.0 * @see BeforeEachAndAfterEachComposedAnnotationTests */ class BeforeAllAndAfterAllComposedAnnotationTests extends AbstractJupiterTestEngineTests { private static final List methodsInvoked = new ArrayList<>(); @Test void beforeAllAndAfterAllAsMetaAnnotations() { executeTestsForClass(TestCase.class).testEvents().assertStatistics(stats -> stats.started(1).succeeded(1)); assertThat(methodsInvoked).containsExactly("beforeAll", "test", "afterAll"); } static class TestCase { @CustomBeforeAll static void beforeAll() { methodsInvoked.add("beforeAll"); } @Test void test() { methodsInvoked.add("test"); } @CustomAfterAll static void afterAll() { methodsInvoked.add("afterAll"); } } @BeforeAll @Retention(RetentionPolicy.RUNTIME) private @interface CustomBeforeAll { } @AfterAll @Retention(RetentionPolicy.RUNTIME) private @interface CustomAfterAll { } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/BeforeEachAndAfterEachComposedAnnotationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import static org.assertj.core.api.Assertions.assertThat; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; /** * Integration tests that verify support for {@link BeforeEach} and {@link AfterEach} * when used as meta-annotations in the {@link JupiterTestEngine}. * * @since 5.0 * @see BeforeAllAndAfterAllComposedAnnotationTests */ class BeforeEachAndAfterEachComposedAnnotationTests extends AbstractJupiterTestEngineTests { private static final List methodsInvoked = new ArrayList<>(); @Test void beforeEachAndAfterEachAsMetaAnnotations() { executeTestsForClass(TestCase.class).testEvents().assertStatistics(stats -> stats.started(1).succeeded(1)); assertThat(methodsInvoked).containsExactly("beforeEach", "test", "afterEach"); } static class TestCase { @CustomBeforeEach void beforeEach() { methodsInvoked.add("beforeEach"); } @Test void test() { methodsInvoked.add("test"); } @CustomAfterEach void afterEach() { methodsInvoked.add("afterEach"); } } @BeforeEach @Retention(RetentionPolicy.RUNTIME) private @interface CustomBeforeEach { } @AfterEach @Retention(RetentionPolicy.RUNTIME) private @interface CustomAfterEach { } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/ClassTemplateInvocationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import static org.assertj.core.api.Assertions.assertThat; 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.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import static org.junit.platform.commons.util.ExceptionUtils.throwAsUncheckedException; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectIteration; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectNestedClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectNestedMethod; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.launcher.TagFilter.excludeTags; import static org.junit.platform.launcher.TagFilter.includeTags; import static org.junit.platform.testkit.engine.EventConditions.container; import static org.junit.platform.testkit.engine.EventConditions.displayName; import static org.junit.platform.testkit.engine.EventConditions.dynamicTestRegistered; import static org.junit.platform.testkit.engine.EventConditions.engine; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.EventConditions.legacyReportingName; import static org.junit.platform.testkit.engine.EventConditions.started; import static org.junit.platform.testkit.engine.EventConditions.test; import static org.junit.platform.testkit.engine.EventConditions.uniqueId; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.suppressed; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.IntStream; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; 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.ClassTemplate; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestReporter; import org.junit.jupiter.api.extension.AfterClassTemplateInvocationCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.BeforeClassTemplateInvocationCallback; import org.junit.jupiter.api.extension.ClassTemplateInvocationContext; import org.junit.jupiter.api.extension.ClassTemplateInvocationContextProvider; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Namespace; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.parallel.ResourceLock; import org.junit.jupiter.engine.descriptor.ClassTemplateInvocationTestDescriptor; import org.junit.jupiter.engine.descriptor.ClassTemplateTestDescriptor; import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; import org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor; import org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor; import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor; import org.junit.jupiter.engine.descriptor.TestTemplateInvocationTestDescriptor; import org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor; import org.junit.jupiter.params.ParameterizedClass; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.engine.TestTag; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.discovery.DiscoverySelectors; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.engine.support.hierarchical.ExclusiveResource; import org.junit.platform.testkit.engine.EngineExecutionResults; import org.opentest4j.AssertionFailedError; import org.opentest4j.TestAbortedException; /** * @since 5.13 */ public class ClassTemplateInvocationTests extends AbstractJupiterTestEngineTests { @ParameterizedTest @ValueSource(strings = { // "class:%s", // "uid:[engine:junit-jupiter]/[class-template:%s]" // }) void executesClassTemplateClassTwice(String selectorIdentifierTemplate) { var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); var classTemplateId = engineId.append(ClassTemplateTestDescriptor.STANDALONE_CLASS_SEGMENT_TYPE, TwoInvocationsTestCase.class.getName()); var invocationId1 = classTemplateId.append(ClassTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#1"); var invocation1MethodAId = invocationId1.append(TestMethodTestDescriptor.SEGMENT_TYPE, "a()"); var invocation1NestedClassId = invocationId1.append(NestedClassTestDescriptor.SEGMENT_TYPE, "NestedTestCase"); var invocation1NestedMethodBId = invocation1NestedClassId.append(TestMethodTestDescriptor.SEGMENT_TYPE, "b()"); var invocationId2 = classTemplateId.append(ClassTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); var invocation2MethodAId = invocationId2.append(TestMethodTestDescriptor.SEGMENT_TYPE, "a()"); var invocation2NestedClassId = invocationId2.append(NestedClassTestDescriptor.SEGMENT_TYPE, "NestedTestCase"); var invocation2NestedMethodBId = invocation2NestedClassId.append(TestMethodTestDescriptor.SEGMENT_TYPE, "b()"); var results = executeTests(DiscoverySelectors.parse( selectorIdentifierTemplate.formatted(TwoInvocationsTestCase.class.getName())).orElseThrow()); results.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(uniqueId(classTemplateId)), started()), // event(dynamicTestRegistered(uniqueId(invocationId1)), displayName("[1] A of TwoInvocationsTestCase"), legacyReportingName("%s[1]".formatted(TwoInvocationsTestCase.class.getName()))), // event(container(uniqueId(invocationId1)), started()), // event(dynamicTestRegistered(uniqueId(invocation1MethodAId))), // event(dynamicTestRegistered(uniqueId(invocation1NestedClassId))), // event(dynamicTestRegistered(uniqueId(invocation1NestedMethodBId))), // event(test(uniqueId(invocation1MethodAId)), started()), // event(test(uniqueId(invocation1MethodAId)), finishedSuccessfully()), // event(container(uniqueId(invocation1NestedClassId)), started()), // event(test(uniqueId(invocation1NestedMethodBId)), started()), // event(test(uniqueId(invocation1NestedMethodBId)), finishedSuccessfully()), // event(container(uniqueId(invocation1NestedClassId)), finishedSuccessfully()), // event(container(uniqueId(invocationId1)), finishedSuccessfully()), // event(dynamicTestRegistered(uniqueId(invocationId2)), displayName("[2] B of TwoInvocationsTestCase"), legacyReportingName("%s[2]".formatted(TwoInvocationsTestCase.class.getName()))), // event(container(uniqueId(invocationId2)), started()), // event(dynamicTestRegistered(uniqueId(invocation2MethodAId))), // event(dynamicTestRegistered(uniqueId(invocation2NestedClassId))), // event(dynamicTestRegistered(uniqueId(invocation2NestedMethodBId))), // event(test(uniqueId(invocation2MethodAId)), started()), // event(test(uniqueId(invocation2MethodAId)), finishedSuccessfully()), // event(container(uniqueId(invocation2NestedClassId)), started()), // event(test(uniqueId(invocation2NestedMethodBId)), started()), // event(test(uniqueId(invocation2NestedMethodBId)), finishedSuccessfully()), // event(container(uniqueId(invocation2NestedClassId)), finishedSuccessfully()), // event(container(uniqueId(invocationId2)), finishedSuccessfully()), // event(container(uniqueId(classTemplateId)), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void classTemplateAnnotationIsInherited() { var results = executeTestsForClass(InheritedTwoInvocationsTestCase.class); results.allEvents().assertStatistics(stats -> stats.started(12).succeeded(12)); } @Test void executesOnlySelectedMethodsDeclaredInClassTemplate() { var results = executeTests(selectMethod(TwoInvocationsTestCase.class, "a")); results.testEvents() // .assertStatistics(stats -> stats.started(2).succeeded(2)) // .assertEventsMatchLoosely(event(test(displayName("a()")), finishedSuccessfully())); } @Test void executesOnlySelectedMethodsDeclaredInNestedClassOfClassTemplate() { var results = executeTests(selectNestedMethod(List.of(TwoInvocationsTestCase.class), TwoInvocationsTestCase.NestedTestCase.class, "b")); results.testEvents().assertStatistics(stats -> stats.started(2).succeeded(2)) // .assertEventsMatchLoosely(event(test(displayName("b()")), finishedSuccessfully())); } @Test void executesOnlyTestsPassingPostDiscoveryFilter() { var results = executeTests(request -> request // .selectors(selectClass(TwoInvocationsTestCase.class)) // .filters(includeTags("nested"))); results.testEvents().assertStatistics(stats -> stats.started(2).succeeded(2)) // .assertEventsMatchLoosely(event(test(displayName("b()")), finishedSuccessfully())); } @Test void prunesEmptyNestedTestClasses() { var results = executeTests(request -> request // .selectors(selectClass(TwoInvocationsTestCase.class)) // .filters(excludeTags("nested"))); results.containerEvents().assertThatEvents() // .noneMatch(container(TwoInvocationsTestCase.NestedTestCase.class.getSimpleName())::matches); results.testEvents().assertStatistics(stats -> stats.started(2).succeeded(2)) // .assertEventsMatchLoosely(event(test(displayName("a()")), finishedSuccessfully())); } @Test void executesNestedClassTemplateClassTwiceWithClassSelectorForEnclosingClass() { var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); var classId = engineId.append(ClassTestDescriptor.SEGMENT_TYPE, NestedClassTemplateWithTwoInvocationsTestCase.class.getName()); var methodAId = classId.append(TestMethodTestDescriptor.SEGMENT_TYPE, "a()"); var nestedClassTemplateId = classId.append(ClassTemplateTestDescriptor.NESTED_CLASS_SEGMENT_TYPE, "NestedTestCase"); var invocationId1 = nestedClassTemplateId.append(ClassTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#1"); var invocation1NestedMethodBId = invocationId1.append(TestMethodTestDescriptor.SEGMENT_TYPE, "b()"); var invocationId2 = nestedClassTemplateId.append(ClassTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); var invocation2NestedMethodBId = invocationId2.append(TestMethodTestDescriptor.SEGMENT_TYPE, "b()"); var results = executeTestsForClass(NestedClassTemplateWithTwoInvocationsTestCase.class); results.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(uniqueId(classId)), started()), // event(test(uniqueId(methodAId)), started()), // event(test(uniqueId(methodAId)), finishedSuccessfully()), // event(container(uniqueId(nestedClassTemplateId)), started()), // event(dynamicTestRegistered(uniqueId(invocationId1)), displayName("[1] A of NestedTestCase"), legacyReportingName( "%s[1]".formatted(NestedClassTemplateWithTwoInvocationsTestCase.NestedTestCase.class.getName()))), // event(container(uniqueId(invocationId1)), started()), // event(dynamicTestRegistered(uniqueId(invocation1NestedMethodBId))), // event(test(uniqueId(invocation1NestedMethodBId)), started()), // event(test(uniqueId(invocation1NestedMethodBId)), finishedSuccessfully()), // event(container(uniqueId(invocationId1)), finishedSuccessfully()), // event(dynamicTestRegistered(uniqueId(invocationId2)), displayName("[2] B of NestedTestCase"), legacyReportingName( "%s[2]".formatted(NestedClassTemplateWithTwoInvocationsTestCase.NestedTestCase.class.getName()))), // event(container(uniqueId(invocationId2)), started()), // event(dynamicTestRegistered(uniqueId(invocation2NestedMethodBId))), // event(test(uniqueId(invocation2NestedMethodBId)), started()), // event(test(uniqueId(invocation2NestedMethodBId)), finishedSuccessfully()), // event(container(uniqueId(invocationId2)), finishedSuccessfully()), // event(container(uniqueId(nestedClassTemplateId)), finishedSuccessfully()), // event(container(uniqueId(classId)), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void executesNestedClassTemplateClassTwiceWithNestedClassSelector() { var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); var classId = engineId.append(ClassTestDescriptor.SEGMENT_TYPE, NestedClassTemplateWithTwoInvocationsTestCase.class.getName()); var nestedClassTemplateId = classId.append(ClassTemplateTestDescriptor.NESTED_CLASS_SEGMENT_TYPE, "NestedTestCase"); var invocationId1 = nestedClassTemplateId.append(ClassTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#1"); var invocation1NestedMethodBId = invocationId1.append(TestMethodTestDescriptor.SEGMENT_TYPE, "b()"); var invocationId2 = nestedClassTemplateId.append(ClassTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); var invocation2NestedMethodBId = invocationId2.append(TestMethodTestDescriptor.SEGMENT_TYPE, "b()"); var results = executeTestsForClass(NestedClassTemplateWithTwoInvocationsTestCase.NestedTestCase.class); results.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(uniqueId(classId)), started()), // event(container(uniqueId(nestedClassTemplateId)), started()), // event(dynamicTestRegistered(uniqueId(invocationId1)), displayName("[1] A of NestedTestCase")), // event(container(uniqueId(invocationId1)), started()), // event(dynamicTestRegistered(uniqueId(invocation1NestedMethodBId))), // event(test(uniqueId(invocation1NestedMethodBId)), started()), // event(test(uniqueId(invocation1NestedMethodBId)), finishedSuccessfully()), // event(container(uniqueId(invocationId1)), finishedSuccessfully()), // event(dynamicTestRegistered(uniqueId(invocationId2)), displayName("[2] B of NestedTestCase")), // event(container(uniqueId(invocationId2)), started()), // event(dynamicTestRegistered(uniqueId(invocation2NestedMethodBId))), // event(test(uniqueId(invocation2NestedMethodBId)), started()), // event(test(uniqueId(invocation2NestedMethodBId)), finishedSuccessfully()), // event(container(uniqueId(invocationId2)), finishedSuccessfully()), // event(container(uniqueId(nestedClassTemplateId)), finishedSuccessfully()), // event(container(uniqueId(classId)), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void executesNestedClassTemplatesTwiceEach() { var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); var outerClassTemplateId = engineId.append(ClassTemplateTestDescriptor.STANDALONE_CLASS_SEGMENT_TYPE, TwoTimesTwoInvocationsTestCase.class.getName()); var outerInvocation1Id = outerClassTemplateId.append(ClassTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#1"); var outerInvocation1NestedClassTemplateId = outerInvocation1Id.append( ClassTemplateTestDescriptor.NESTED_CLASS_SEGMENT_TYPE, "NestedTestCase"); var outerInvocation1InnerInvocation1Id = outerInvocation1NestedClassTemplateId.append( ClassTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#1"); var outerInvocation1InnerInvocation1NestedMethodId = outerInvocation1InnerInvocation1Id.append( TestMethodTestDescriptor.SEGMENT_TYPE, "test()"); var outerInvocation1InnerInvocation2Id = outerInvocation1NestedClassTemplateId.append( ClassTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); var outerInvocation1InnerInvocation2NestedMethodId = outerInvocation1InnerInvocation2Id.append( TestMethodTestDescriptor.SEGMENT_TYPE, "test()"); var outerInvocation2Id = outerClassTemplateId.append(ClassTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); var outerInvocation2NestedClassTemplateId = outerInvocation2Id.append( ClassTemplateTestDescriptor.NESTED_CLASS_SEGMENT_TYPE, "NestedTestCase"); var outerInvocation2InnerInvocation1Id = outerInvocation2NestedClassTemplateId.append( ClassTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#1"); var outerInvocation2InnerInvocation1NestedMethodId = outerInvocation2InnerInvocation1Id.append( TestMethodTestDescriptor.SEGMENT_TYPE, "test()"); var outerInvocation2InnerInvocation2Id = outerInvocation2NestedClassTemplateId.append( ClassTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); var outerInvocation2InnerInvocation2NestedMethodId = outerInvocation2InnerInvocation2Id.append( TestMethodTestDescriptor.SEGMENT_TYPE, "test()"); var results = executeTestsForClass(TwoTimesTwoInvocationsTestCase.class); results.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(uniqueId(outerClassTemplateId)), started()), // event(dynamicTestRegistered(uniqueId(outerInvocation1Id)), displayName("[1] A of TwoTimesTwoInvocationsTestCase")), // event(container(uniqueId(outerInvocation1Id)), started()), // event(dynamicTestRegistered(uniqueId(outerInvocation1NestedClassTemplateId))), // event(container(uniqueId(outerInvocation1NestedClassTemplateId)), started()), // event(dynamicTestRegistered(uniqueId(outerInvocation1InnerInvocation1Id)), displayName("[1] A of NestedTestCase")), // event(container(uniqueId(outerInvocation1InnerInvocation1Id)), started()), // event(dynamicTestRegistered(uniqueId(outerInvocation1InnerInvocation1NestedMethodId))), // event(test(uniqueId(outerInvocation1InnerInvocation1NestedMethodId)), started()), // event(test(uniqueId(outerInvocation1InnerInvocation1NestedMethodId)), finishedSuccessfully()), // event(container(uniqueId(outerInvocation1InnerInvocation1Id)), finishedSuccessfully()), // event(dynamicTestRegistered(uniqueId(outerInvocation1InnerInvocation2Id)), displayName("[2] B of NestedTestCase")), // event(container(uniqueId(outerInvocation1InnerInvocation2Id)), started()), // event(dynamicTestRegistered(uniqueId(outerInvocation1InnerInvocation2NestedMethodId))), // event(test(uniqueId(outerInvocation1InnerInvocation2NestedMethodId)), started()), // event(test(uniqueId(outerInvocation1InnerInvocation2NestedMethodId)), finishedSuccessfully()), // event(container(uniqueId(outerInvocation1InnerInvocation2Id)), finishedSuccessfully()), // event(container(uniqueId(outerInvocation1NestedClassTemplateId)), finishedSuccessfully()), // event(container(uniqueId(outerInvocation1Id)), finishedSuccessfully()), // event(dynamicTestRegistered(uniqueId(outerInvocation2Id)), displayName("[2] B of TwoTimesTwoInvocationsTestCase")), // event(container(uniqueId(outerInvocation2Id)), started()), // event(dynamicTestRegistered(uniqueId(outerInvocation2NestedClassTemplateId))), // event(container(uniqueId(outerInvocation2NestedClassTemplateId)), started()), // event(dynamicTestRegistered(uniqueId(outerInvocation2InnerInvocation1Id)), displayName("[1] A of NestedTestCase")), // event(container(uniqueId(outerInvocation2InnerInvocation1Id)), started()), // event(dynamicTestRegistered(uniqueId(outerInvocation2InnerInvocation1NestedMethodId))), // event(test(uniqueId(outerInvocation2InnerInvocation1NestedMethodId)), started()), // event(test(uniqueId(outerInvocation2InnerInvocation1NestedMethodId)), finishedSuccessfully()), // event(container(uniqueId(outerInvocation2InnerInvocation1Id)), finishedSuccessfully()), // event(dynamicTestRegistered(uniqueId(outerInvocation2InnerInvocation2Id)), displayName("[2] B of NestedTestCase")), // event(container(uniqueId(outerInvocation2InnerInvocation2Id)), started()), // event(dynamicTestRegistered(uniqueId(outerInvocation2InnerInvocation2NestedMethodId))), // event(test(uniqueId(outerInvocation2InnerInvocation2NestedMethodId)), started()), // event(test(uniqueId(outerInvocation2InnerInvocation2NestedMethodId)), finishedSuccessfully()), // event(container(uniqueId(outerInvocation2InnerInvocation2Id)), finishedSuccessfully()), // event(container(uniqueId(outerInvocation2NestedClassTemplateId)), finishedSuccessfully()), // event(container(uniqueId(outerInvocation2Id)), finishedSuccessfully()), // event(container(uniqueId(outerClassTemplateId)), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void invocationContextProviderCanRegisterAdditionalExtensions() { var results = executeTestsForClass(AdditionalExtensionRegistrationTestCase.class); results.testEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); } @Test void eachInvocationHasSeparateExtensionContext() { var results = executeTestsForClass(SeparateExtensionContextTestCase.class); results.testEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); } @Test void supportsTestTemplateMethodsInsideClassTemplateClasses() { var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); var classTemplateId = engineId.append(ClassTemplateTestDescriptor.STANDALONE_CLASS_SEGMENT_TYPE, CombinationWithTestTemplateTestCase.class.getName()); var invocationId1 = classTemplateId.append(ClassTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#1"); var testTemplateId1 = invocationId1.append(TestTemplateTestDescriptor.SEGMENT_TYPE, "test(int)"); var testTemplate1InvocationId1 = testTemplateId1.append(TestTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#1"); var testTemplate1InvocationId2 = testTemplateId1.append(TestTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); var invocationId2 = classTemplateId.append(ClassTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); var testTemplateId2 = invocationId2.append(TestTemplateTestDescriptor.SEGMENT_TYPE, "test(int)"); var testTemplate2InvocationId1 = testTemplateId2.append(TestTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#1"); var testTemplate2InvocationId2 = testTemplateId2.append(TestTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); var results = executeTestsForClass(CombinationWithTestTemplateTestCase.class); results.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(uniqueId(classTemplateId)), started()), // event(dynamicTestRegistered(uniqueId(invocationId1)), displayName("[1] A of CombinationWithTestTemplateTestCase")), // event(container(uniqueId(invocationId1)), started()), // event(dynamicTestRegistered(uniqueId(testTemplateId1))), // event(container(uniqueId(testTemplateId1)), started()), // event(dynamicTestRegistered(uniqueId(testTemplate1InvocationId1))), // event(test(uniqueId(testTemplate1InvocationId1)), started()), // event(test(uniqueId(testTemplate1InvocationId1)), finishedSuccessfully()), // event(dynamicTestRegistered(uniqueId(testTemplate1InvocationId2))), // event(test(uniqueId(testTemplate1InvocationId2)), started()), // event(test(uniqueId(testTemplate1InvocationId2)), finishedSuccessfully()), // event(container(uniqueId(testTemplateId1)), finishedSuccessfully()), // event(container(uniqueId(invocationId1)), finishedSuccessfully()), // event(dynamicTestRegistered(uniqueId(invocationId2)), displayName("[2] B of CombinationWithTestTemplateTestCase")), // event(container(uniqueId(invocationId2)), started()), // event(dynamicTestRegistered(uniqueId(testTemplateId2))), // event(container(uniqueId(testTemplateId2)), started()), // event(dynamicTestRegistered(uniqueId(testTemplate2InvocationId1))), // event(test(uniqueId(testTemplate2InvocationId1)), started()), // event(test(uniqueId(testTemplate2InvocationId1)), finishedSuccessfully()), // event(dynamicTestRegistered(uniqueId(testTemplate2InvocationId2))), // event(test(uniqueId(testTemplate2InvocationId2)), started()), // event(test(uniqueId(testTemplate2InvocationId2)), finishedSuccessfully()), // event(container(uniqueId(testTemplateId2)), finishedSuccessfully()), // event(container(uniqueId(invocationId2)), finishedSuccessfully()), // event(container(uniqueId(classTemplateId)), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void testTemplateInvocationInsideClassTemplateClassCanBeSelectedByUniqueId() { var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); var classTemplateId = engineId.append(ClassTemplateTestDescriptor.STANDALONE_CLASS_SEGMENT_TYPE, CombinationWithTestTemplateTestCase.class.getName()); var invocationId2 = classTemplateId.append(ClassTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); var testTemplateId2 = invocationId2.append(TestTemplateTestDescriptor.SEGMENT_TYPE, "test(int)"); var testTemplate2InvocationId2 = testTemplateId2.append(TestTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); var results = executeTests(selectUniqueId(testTemplate2InvocationId2)); results.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(uniqueId(classTemplateId)), started()), // event(dynamicTestRegistered(uniqueId(invocationId2)), displayName("[2] B of CombinationWithTestTemplateTestCase")), // event(container(uniqueId(invocationId2)), started()), // event(dynamicTestRegistered(uniqueId(testTemplateId2))), // event(container(uniqueId(testTemplateId2)), started()), // event(dynamicTestRegistered(uniqueId(testTemplate2InvocationId2))), // event(test(uniqueId(testTemplate2InvocationId2)), started()), // event(test(uniqueId(testTemplate2InvocationId2)), finishedSuccessfully()), // event(container(uniqueId(testTemplateId2)), finishedSuccessfully()), // event(container(uniqueId(invocationId2)), finishedSuccessfully()), // event(container(uniqueId(classTemplateId)), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void supportsTestFactoryMethodsInsideClassTemplateClasses() { var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); var classTemplateId = engineId.append(ClassTemplateTestDescriptor.STANDALONE_CLASS_SEGMENT_TYPE, CombinationWithTestFactoryTestCase.class.getName()); var invocationId1 = classTemplateId.append(ClassTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#1"); var testFactoryId1 = invocationId1.append(TestFactoryTestDescriptor.SEGMENT_TYPE, "test()"); var testFactory1DynamicTestId1 = testFactoryId1.append(TestFactoryTestDescriptor.DYNAMIC_TEST_SEGMENT_TYPE, "#1"); var testFactory1DynamicTestId2 = testFactoryId1.append(TestFactoryTestDescriptor.DYNAMIC_TEST_SEGMENT_TYPE, "#2"); var invocationId2 = classTemplateId.append(ClassTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); var testFactoryId2 = invocationId2.append(TestFactoryTestDescriptor.SEGMENT_TYPE, "test()"); var testFactory2DynamicTestId1 = testFactoryId2.append(TestFactoryTestDescriptor.DYNAMIC_TEST_SEGMENT_TYPE, "#1"); var testFactory2DynamicTestId2 = testFactoryId2.append(TestFactoryTestDescriptor.DYNAMIC_TEST_SEGMENT_TYPE, "#2"); var results = executeTestsForClass(CombinationWithTestFactoryTestCase.class); results.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(uniqueId(classTemplateId)), started()), // event(dynamicTestRegistered(uniqueId(invocationId1)), displayName("[1] A of CombinationWithTestFactoryTestCase")), // event(container(uniqueId(invocationId1)), started()), // event(dynamicTestRegistered(uniqueId(testFactoryId1))), // event(container(uniqueId(testFactoryId1)), started()), // event(dynamicTestRegistered(uniqueId(testFactory1DynamicTestId1))), // event(test(uniqueId(testFactory1DynamicTestId1)), started()), // event(test(uniqueId(testFactory1DynamicTestId1)), finishedSuccessfully()), // event(dynamicTestRegistered(uniqueId(testFactory1DynamicTestId2))), // event(test(uniqueId(testFactory1DynamicTestId2)), started()), // event(test(uniqueId(testFactory1DynamicTestId2)), finishedSuccessfully()), // event(container(uniqueId(testFactoryId1)), finishedSuccessfully()), // event(container(uniqueId(invocationId1)), finishedSuccessfully()), // event(dynamicTestRegistered(uniqueId(invocationId2)), displayName("[2] B of CombinationWithTestFactoryTestCase")), // event(container(uniqueId(invocationId2)), started()), // event(dynamicTestRegistered(uniqueId(testFactoryId2))), // event(container(uniqueId(testFactoryId2)), started()), // event(dynamicTestRegistered(uniqueId(testFactory2DynamicTestId1))), // event(test(uniqueId(testFactory2DynamicTestId1)), started()), // event(test(uniqueId(testFactory2DynamicTestId1)), finishedSuccessfully()), // event(dynamicTestRegistered(uniqueId(testFactory2DynamicTestId2))), // event(test(uniqueId(testFactory2DynamicTestId2)), started()), // event(test(uniqueId(testFactory2DynamicTestId2)), finishedSuccessfully()), // event(container(uniqueId(testFactoryId2)), finishedSuccessfully()), // event(container(uniqueId(invocationId2)), finishedSuccessfully()), // event(container(uniqueId(classTemplateId)), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void specificDynamicTestInsideClassTemplateClassCanBeSelectedByUniqueId() { var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); var classTemplateId = engineId.append(ClassTemplateTestDescriptor.STANDALONE_CLASS_SEGMENT_TYPE, CombinationWithTestFactoryTestCase.class.getName()); var invocationId2 = classTemplateId.append(ClassTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); var testFactoryId2 = invocationId2.append(TestFactoryTestDescriptor.SEGMENT_TYPE, "test()"); var testFactory2DynamicTestId2 = testFactoryId2.append(TestFactoryTestDescriptor.DYNAMIC_TEST_SEGMENT_TYPE, "#2"); var results = executeTests(selectUniqueId(testFactory2DynamicTestId2)); results.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(uniqueId(classTemplateId)), started()), // event(dynamicTestRegistered(uniqueId(invocationId2)), displayName("[2] B of CombinationWithTestFactoryTestCase")), // event(container(uniqueId(invocationId2)), started()), // event(dynamicTestRegistered(uniqueId(testFactoryId2))), // event(container(uniqueId(testFactoryId2)), started()), // event(dynamicTestRegistered(uniqueId(testFactory2DynamicTestId2))), // event(test(uniqueId(testFactory2DynamicTestId2)), started()), // event(test(uniqueId(testFactory2DynamicTestId2)), finishedSuccessfully()), // event(container(uniqueId(testFactoryId2)), finishedSuccessfully()), // event(container(uniqueId(invocationId2)), finishedSuccessfully()), // event(container(uniqueId(classTemplateId)), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void failsIfProviderReturnsZeroInvocationContextWithoutOptIn() { var results = executeTestsForClass(InvalidZeroInvocationTestCase.class); results.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(InvalidZeroInvocationTestCase.class), started()), // event(container(InvalidZeroInvocationTestCase.class), finishedWithFailure( message("Provider [Ext] did not provide any invocation contexts, but was expected to do so. " + "You may override mayReturnZeroClassTemplateInvocationContexts() to allow this."))), // event(engine(), finishedSuccessfully())); } @Test void succeedsIfProviderReturnsZeroInvocationContextWithOptIn() { var results = executeTestsForClass(ValidZeroInvocationTestCase.class); results.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(ValidZeroInvocationTestCase.class), started()), // event(container(ValidZeroInvocationTestCase.class), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @ParameterizedTest @ValueSource(classes = { NoProviderRegisteredTestCase.class, NoSupportingProviderRegisteredTestCase.class }) void failsIfNoSupportingProviderIsRegistered(Class testClass) { var results = executeTestsForClass(testClass); results.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(container(testClass), finishedWithFailure( message("You must register at least one ClassTemplateInvocationContextProvider that supports " + "@ClassTemplate class [" + testClass.getName() + "]"))), // event(engine(), finishedSuccessfully())); } @Test void classTemplateInvocationCanBeSelectedByUniqueId() { var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); var classTemplateId = engineId.append(ClassTemplateTestDescriptor.STANDALONE_CLASS_SEGMENT_TYPE, TwoInvocationsTestCase.class.getName()); var invocationId2 = classTemplateId.append(ClassTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); var methodAId = invocationId2.append(TestMethodTestDescriptor.SEGMENT_TYPE, "a()"); var nestedClassId = invocationId2.append(NestedClassTestDescriptor.SEGMENT_TYPE, "NestedTestCase"); var nestedMethodBId = nestedClassId.append(TestMethodTestDescriptor.SEGMENT_TYPE, "b()"); var results = executeTests(selectUniqueId(invocationId2)); results.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(uniqueId(classTemplateId)), started()), // event(dynamicTestRegistered(uniqueId(invocationId2)), displayName("[2] B of TwoInvocationsTestCase")), // event(container(uniqueId(invocationId2)), started()), // event(dynamicTestRegistered(uniqueId(methodAId))), // event(dynamicTestRegistered(uniqueId(nestedClassId))), // event(dynamicTestRegistered(uniqueId(nestedMethodBId))), // event(test(uniqueId(methodAId)), started()), // event(test(uniqueId(methodAId)), finishedSuccessfully()), // event(container(uniqueId(nestedClassId)), started()), // event(test(uniqueId(nestedMethodBId)), started()), // event(test(uniqueId(nestedMethodBId)), finishedSuccessfully()), // event(container(uniqueId(nestedClassId)), finishedSuccessfully()), // event(container(uniqueId(invocationId2)), finishedSuccessfully()), // event(container(uniqueId(classTemplateId)), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void classTemplateInvocationCanBeSelectedByIteration() { var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); var classTemplateId = engineId.append(ClassTemplateTestDescriptor.STANDALONE_CLASS_SEGMENT_TYPE, TwoInvocationsTestCase.class.getName()); var invocationId2 = classTemplateId.append(ClassTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); var methodAId = invocationId2.append(TestMethodTestDescriptor.SEGMENT_TYPE, "a()"); var nestedClassId = invocationId2.append(NestedClassTestDescriptor.SEGMENT_TYPE, "NestedTestCase"); var nestedMethodBId = nestedClassId.append(TestMethodTestDescriptor.SEGMENT_TYPE, "b()"); var results = executeTests(selectIteration(selectClass(TwoInvocationsTestCase.class), 1)); results.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(uniqueId(classTemplateId)), started()), // event(dynamicTestRegistered(uniqueId(invocationId2)), displayName("[2] B of TwoInvocationsTestCase")), // event(container(uniqueId(invocationId2)), started()), // event(dynamicTestRegistered(uniqueId(methodAId))), // event(dynamicTestRegistered(uniqueId(nestedClassId))), // event(dynamicTestRegistered(uniqueId(nestedMethodBId))), // event(test(uniqueId(methodAId)), started()), // event(test(uniqueId(methodAId)), finishedSuccessfully()), // event(container(uniqueId(nestedClassId)), started()), // event(test(uniqueId(nestedMethodBId)), started()), // event(test(uniqueId(nestedMethodBId)), finishedSuccessfully()), // event(container(uniqueId(nestedClassId)), finishedSuccessfully()), // event(container(uniqueId(invocationId2)), finishedSuccessfully()), // event(container(uniqueId(classTemplateId)), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @ParameterizedTest @ValueSource(strings = { // "class:org.junit.jupiter.engine.ClassTemplateInvocationTests$TwoInvocationsTestCase", // "uid:[engine:junit-jupiter]/[class-template:org.junit.jupiter.engine.ClassTemplateInvocationTests$TwoInvocationsTestCase]" // }) void executesAllInvocationsForRedundantSelectors(String classTemplateSelectorIdentifier) { var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); var classTemplateId = engineId.append(ClassTemplateTestDescriptor.STANDALONE_CLASS_SEGMENT_TYPE, TwoInvocationsTestCase.class.getName()); var invocationId2 = classTemplateId.append(ClassTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); var results = executeTests(selectUniqueId(invocationId2), DiscoverySelectors.parse(classTemplateSelectorIdentifier).orElseThrow()); results.testEvents().assertStatistics(stats -> stats.started(4).succeeded(4)); } @Test void methodInClassTemplateInvocationCanBeSelectedByUniqueId() { var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); var classTemplateId = engineId.append(ClassTemplateTestDescriptor.STANDALONE_CLASS_SEGMENT_TYPE, TwoInvocationsTestCase.class.getName()); var invocationId2 = classTemplateId.append(ClassTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); var methodAId = invocationId2.append(TestMethodTestDescriptor.SEGMENT_TYPE, "a()"); var results = executeTests(selectUniqueId(methodAId)); results.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(uniqueId(classTemplateId)), started()), // event(dynamicTestRegistered(uniqueId(invocationId2)), displayName("[2] B of TwoInvocationsTestCase")), // event(container(uniqueId(invocationId2)), started()), // event(dynamicTestRegistered(uniqueId(methodAId))), // event(test(uniqueId(methodAId)), started()), // event(test(uniqueId(methodAId)), finishedSuccessfully()), // event(container(uniqueId(invocationId2)), finishedSuccessfully()), // event(container(uniqueId(classTemplateId)), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void nestedMethodInClassTemplateInvocationCanBeSelectedByUniqueId() { var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); var classTemplateId = engineId.append(ClassTemplateTestDescriptor.STANDALONE_CLASS_SEGMENT_TYPE, TwoInvocationsTestCase.class.getName()); var invocationId2 = classTemplateId.append(ClassTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); var nestedClassId = invocationId2.append(NestedClassTestDescriptor.SEGMENT_TYPE, "NestedTestCase"); var nestedMethodBId = nestedClassId.append(TestMethodTestDescriptor.SEGMENT_TYPE, "b()"); var results = executeTests(selectUniqueId(nestedMethodBId)); results.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(uniqueId(classTemplateId)), started()), // event(dynamicTestRegistered(uniqueId(invocationId2)), displayName("[2] B of TwoInvocationsTestCase")), // event(container(uniqueId(invocationId2)), started()), // event(dynamicTestRegistered(uniqueId(nestedClassId))), // event(dynamicTestRegistered(uniqueId(nestedMethodBId))), // event(container(uniqueId(nestedClassId)), started()), // event(test(uniqueId(nestedMethodBId)), started()), // event(test(uniqueId(nestedMethodBId)), finishedSuccessfully()), // event(container(uniqueId(nestedClassId)), finishedSuccessfully()), // event(container(uniqueId(invocationId2)), finishedSuccessfully()), // event(container(uniqueId(classTemplateId)), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void nestedClassTemplateInvocationCanBeSelectedByUniqueId() { var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); var outerClassTemplateId = engineId.append(ClassTemplateTestDescriptor.STANDALONE_CLASS_SEGMENT_TYPE, TwoTimesTwoInvocationsWithMultipleMethodsTestCase.class.getName()); var outerInvocation2Id = outerClassTemplateId.append(ClassTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); var outerInvocation2NestedClassTemplateId = outerInvocation2Id.append( ClassTemplateTestDescriptor.NESTED_CLASS_SEGMENT_TYPE, "NestedTestCase"); var outerInvocation2InnerInvocation2Id = outerInvocation2NestedClassTemplateId.append( ClassTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); var outerInvocation2InnerInvocation2NestedMethodId = outerInvocation2InnerInvocation2Id.append( TestMethodTestDescriptor.SEGMENT_TYPE, "b()"); var results = executeTests(selectUniqueId(outerInvocation2InnerInvocation2NestedMethodId)); results.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(uniqueId(outerClassTemplateId)), started()), // event(dynamicTestRegistered(uniqueId(outerInvocation2Id)), displayName("[2] B of TwoTimesTwoInvocationsWithMultipleMethodsTestCase")), // event(container(uniqueId(outerInvocation2Id)), started()), // event(dynamicTestRegistered(uniqueId(outerInvocation2NestedClassTemplateId))), // event(container(uniqueId(outerInvocation2NestedClassTemplateId)), started()), // event(dynamicTestRegistered(uniqueId(outerInvocation2InnerInvocation2Id)), displayName("[2] B of NestedTestCase")), // event(container(uniqueId(outerInvocation2InnerInvocation2Id)), started()), // event(dynamicTestRegistered(uniqueId(outerInvocation2InnerInvocation2NestedMethodId))), // event(test(uniqueId(outerInvocation2InnerInvocation2NestedMethodId)), started()), // event(test(uniqueId(outerInvocation2InnerInvocation2NestedMethodId)), finishedSuccessfully()), // event(container(uniqueId(outerInvocation2InnerInvocation2Id)), finishedSuccessfully()), // event(container(uniqueId(outerInvocation2NestedClassTemplateId)), finishedSuccessfully()), // event(container(uniqueId(outerInvocation2Id)), finishedSuccessfully()), // event(container(uniqueId(outerClassTemplateId)), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void nestedClassTemplateInvocationCanBeSelectedByIteration() { var engineId = UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); var outerClassTemplateId = engineId.append(ClassTemplateTestDescriptor.STANDALONE_CLASS_SEGMENT_TYPE, TwoTimesTwoInvocationsTestCase.class.getName()); var outerInvocation1Id = outerClassTemplateId.append(ClassTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#1"); var outerInvocation1NestedClassTemplateId = outerInvocation1Id.append( ClassTemplateTestDescriptor.NESTED_CLASS_SEGMENT_TYPE, "NestedTestCase"); var outerInvocation1InnerInvocation2Id = outerInvocation1NestedClassTemplateId.append( ClassTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); var outerInvocation1InnerInvocation2NestedMethodId = outerInvocation1InnerInvocation2Id.append( TestMethodTestDescriptor.SEGMENT_TYPE, "test()"); var outerInvocation2Id = outerClassTemplateId.append(ClassTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); var outerInvocation2NestedClassTemplateId = outerInvocation2Id.append( ClassTemplateTestDescriptor.NESTED_CLASS_SEGMENT_TYPE, "NestedTestCase"); var outerInvocation2InnerInvocation2Id = outerInvocation2NestedClassTemplateId.append( ClassTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); var outerInvocation2InnerInvocation2NestedMethodId = outerInvocation2InnerInvocation2Id.append( TestMethodTestDescriptor.SEGMENT_TYPE, "test()"); var results = executeTests(selectIteration(selectNestedClass(List.of(TwoTimesTwoInvocationsTestCase.class), TwoTimesTwoInvocationsTestCase.NestedTestCase.class), 1)); results.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(uniqueId(outerClassTemplateId)), started()), // event(dynamicTestRegistered(uniqueId(outerInvocation1Id)), displayName("[1] A of TwoTimesTwoInvocationsTestCase")), // event(container(uniqueId(outerInvocation1Id)), started()), // event(dynamicTestRegistered(uniqueId(outerInvocation1NestedClassTemplateId))), // event(container(uniqueId(outerInvocation1NestedClassTemplateId)), started()), // event(dynamicTestRegistered(uniqueId(outerInvocation1InnerInvocation2Id)), displayName("[2] B of NestedTestCase")), // event(container(uniqueId(outerInvocation1InnerInvocation2Id)), started()), // event(dynamicTestRegistered(uniqueId(outerInvocation1InnerInvocation2NestedMethodId))), // event(test(uniqueId(outerInvocation1InnerInvocation2NestedMethodId)), started()), // event(test(uniqueId(outerInvocation1InnerInvocation2NestedMethodId)), finishedSuccessfully()), // event(container(uniqueId(outerInvocation1InnerInvocation2Id)), finishedSuccessfully()), // event(container(uniqueId(outerInvocation1NestedClassTemplateId)), finishedSuccessfully()), // event(container(uniqueId(outerInvocation1Id)), finishedSuccessfully()), // event(dynamicTestRegistered(uniqueId(outerInvocation2Id)), displayName("[2] B of TwoTimesTwoInvocationsTestCase")), // event(container(uniqueId(outerInvocation2Id)), started()), // event(dynamicTestRegistered(uniqueId(outerInvocation2NestedClassTemplateId))), // event(container(uniqueId(outerInvocation2NestedClassTemplateId)), started()), // event(dynamicTestRegistered(uniqueId(outerInvocation2InnerInvocation2Id)), displayName("[2] B of NestedTestCase")), // event(container(uniqueId(outerInvocation2InnerInvocation2Id)), started()), // event(dynamicTestRegistered(uniqueId(outerInvocation2InnerInvocation2NestedMethodId))), // event(test(uniqueId(outerInvocation2InnerInvocation2NestedMethodId)), started()), // event(test(uniqueId(outerInvocation2InnerInvocation2NestedMethodId)), finishedSuccessfully()), // event(container(uniqueId(outerInvocation2InnerInvocation2Id)), finishedSuccessfully()), // event(container(uniqueId(outerInvocation2NestedClassTemplateId)), finishedSuccessfully()), // event(container(uniqueId(outerInvocation2Id)), finishedSuccessfully()), // event(container(uniqueId(outerClassTemplateId)), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void executesLifecycleCallbacksInNestedClassTemplates() { var results = executeTestsForClass(TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase.class); results.containerEvents().assertStatistics(stats -> stats.started(10).succeeded(10)); results.testEvents().assertStatistics(stats -> stats.started(8).succeeded(8)); // @formatter:off assertThat(allReportEntryValues(results)).containsExactly( "beforeAll: TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase", "beforeClassTemplateInvocation: TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase", "beforeAll: NestedTestCase", "beforeClassTemplateInvocation: NestedTestCase", "beforeEach: test1 [TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase]", "beforeEach: test1 [NestedTestCase]", "test1", "afterEach: test1 [NestedTestCase]", "afterEach: test1 [TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase]", "beforeEach: test2 [TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase]", "beforeEach: test2 [NestedTestCase]", "test2", "afterEach: test2 [NestedTestCase]", "afterEach: test2 [TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase]", "afterClassTemplateInvocation: NestedTestCase", "beforeClassTemplateInvocation: NestedTestCase", "beforeEach: test1 [TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase]", "beforeEach: test1 [NestedTestCase]", "test1", "afterEach: test1 [NestedTestCase]", "afterEach: test1 [TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase]", "beforeEach: test2 [TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase]", "beforeEach: test2 [NestedTestCase]", "test2", "afterEach: test2 [NestedTestCase]", "afterEach: test2 [TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase]", "afterClassTemplateInvocation: NestedTestCase", "afterAll: NestedTestCase", "afterClassTemplateInvocation: TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase", "beforeClassTemplateInvocation: TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase", "beforeAll: NestedTestCase", "beforeClassTemplateInvocation: NestedTestCase", "beforeEach: test1 [TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase]", "beforeEach: test1 [NestedTestCase]", "test1", "afterEach: test1 [NestedTestCase]", "afterEach: test1 [TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase]", "beforeEach: test2 [TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase]", "beforeEach: test2 [NestedTestCase]", "test2", "afterEach: test2 [NestedTestCase]", "afterEach: test2 [TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase]", "afterClassTemplateInvocation: NestedTestCase", "beforeClassTemplateInvocation: NestedTestCase", "beforeEach: test1 [TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase]", "beforeEach: test1 [NestedTestCase]", "test1", "afterEach: test1 [NestedTestCase]", "afterEach: test1 [TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase]", "beforeEach: test2 [TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase]", "beforeEach: test2 [NestedTestCase]", "test2", "afterEach: test2 [NestedTestCase]", "afterEach: test2 [TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase]", "afterClassTemplateInvocation: NestedTestCase", "afterAll: NestedTestCase", "afterClassTemplateInvocation: TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase", "afterAll: TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase" ); // @formatter:on } @Test void guaranteesWrappingBehaviorForCallbacks() { var results = executeTestsForClass(CallbackWrappingBehaviorTestCase.class); results.containerEvents().assertStatistics(stats -> stats.started(4).succeeded(4)); results.testEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); // @formatter:off assertThat(allReportEntryValues(results)).containsExactly( "1st -> beforeClassTemplateInvocation: CallbackWrappingBehaviorTestCase", "2nd -> beforeClassTemplateInvocation: CallbackWrappingBehaviorTestCase", "test", "2nd -> afterClassTemplateInvocation: CallbackWrappingBehaviorTestCase", "1st -> afterClassTemplateInvocation: CallbackWrappingBehaviorTestCase", "1st -> beforeClassTemplateInvocation: CallbackWrappingBehaviorTestCase", "2nd -> beforeClassTemplateInvocation: CallbackWrappingBehaviorTestCase", "test", "2nd -> afterClassTemplateInvocation: CallbackWrappingBehaviorTestCase", "1st -> afterClassTemplateInvocation: CallbackWrappingBehaviorTestCase" ); // @formatter:on } @Test void propagatesExceptionsFromCallbacks() { var results = executeTestsForClass(CallbackExceptionBehaviorTestCase.class); results.allEvents().assertStatistics(stats -> stats.started(4).failed(2).succeeded(2)); results.containerEvents().assertThatEvents() // .haveExactly(2, finishedWithFailure( // message("2nd -> afterClassTemplateInvocation: CallbackExceptionBehaviorTestCase"), // suppressed(0, message("1st -> beforeClassTemplateInvocation: CallbackExceptionBehaviorTestCase")), // suppressed(1, message("1st -> afterClassTemplateInvocation: CallbackExceptionBehaviorTestCase")))); assertThat(allReportEntryValues(results).distinct()) // .containsExactly("1st -> beforeClassTemplateInvocation: CallbackExceptionBehaviorTestCase", // "2nd -> afterClassTemplateInvocation: CallbackExceptionBehaviorTestCase", // "1st -> afterClassTemplateInvocation: CallbackExceptionBehaviorTestCase"); } @Test void templateWithPreparations() { var results = executeTestsForClass(ClassTemplateWithPreparationsTestCase.class); results.allEvents().assertStatistics(stats -> stats.started(6).succeeded(6)); assertTrue(CustomCloseableResource.closed, "resource in store was closed"); } @Test void propagatesTagsFromEnclosingClassesToNestedClassTemplates() { var request = defaultRequest() // .selectors(selectClass(NestedClassTemplateWithTagOnEnclosingClassTestCase.class)) // .build(); var engineDescriptor = discoverTestsWithoutIssues(request); var classDescriptor = getOnlyElement(engineDescriptor.getChildren()); var nestedClassTemplateDescriptor = getOnlyElement(classDescriptor.getChildren()); assertThat(classDescriptor.getTags()).extracting(TestTag::getName) // .containsExactly("top-level"); assertThat(nestedClassTemplateDescriptor.getTags()).extracting(TestTag::getName) // .containsExactlyInAnyOrder("top-level", "nested"); } @Test void ignoresComposedAnnotations() { var request = defaultRequest() // .selectors(selectClass(ParameterizedClass.class)) // .build(); var engineDescriptor = discoverTestsWithoutIssues(request); assertThat(engineDescriptor.getDescendants()).isEmpty(); } @Test void classTemplateWithResourceLockCollectsExclusiveResources() { var results = discoverTestsForClass(ClassTemplateWithResourceLockTestCase.class); var classTemplateDescriptor = (ClassTemplateTestDescriptor) getOnlyElement( results.getEngineDescriptor().getChildren()); assertThat(classTemplateDescriptor.getExclusiveResources()).extracting( ExclusiveResource::getKey).containsExactly("test-resource"); } @Test void classTemplateWithResourceLockExecutesSuccessfully() { var results = executeTestsForClass(ClassTemplateWithResourceLockTestCase.class); results.testEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); results.containerEvents().assertStatistics(stats -> stats.started(4).succeeded(4)); } // ------------------------------------------------------------------- private static Stream allReportEntryValues(EngineExecutionResults results) { return results.allEvents().reportingEntryPublished() // .map(event -> event.getRequiredPayload(ReportEntry.class)) // .map(ReportEntry::getKeyValuePairs) // .map(Map::values) // .flatMap(Collection::stream); } @ClassTemplate @ExtendWith(TwoInvocationsClassTemplateInvocationContextProvider.class) static class TwoInvocationsTestCase { @Test void a() { } @Nested class NestedTestCase { @Test @Tag("nested") void b() { } } } static class NestedClassTemplateWithTwoInvocationsTestCase { @Test void a() { } @Nested @ClassTemplate @ExtendWith(TwoInvocationsClassTemplateInvocationContextProvider.class) class NestedTestCase { @Test void b() { } } } @ExtendWith(TwoInvocationsClassTemplateInvocationContextProvider.class) @ClassTemplate static class TwoTimesTwoInvocationsTestCase { @Nested @ClassTemplate class NestedTestCase { @Test void test() { } } } @ClassTemplate @ExtendWith(TwoInvocationsClassTemplateInvocationContextProvider.class) static class TwoInvocationsWithExtensionTestCase { @Test void a() { } @Nested class NestedTestCase { @Test @Tag("nested") void b() { } } } static class TwoInvocationsClassTemplateInvocationContextProvider implements ClassTemplateInvocationContextProvider { @Override public boolean supportsClassTemplate(ExtensionContext context) { return true; } @Override public Stream provideClassTemplateInvocationContexts(ExtensionContext context) { var suffix = " of %s".formatted(context.getRequiredTestClass().getSimpleName()); return Stream.of(new Ctx("A" + suffix), new Ctx("B" + suffix)); } record Ctx(String displayName) implements ClassTemplateInvocationContext { @Override public String getDisplayName(int invocationIndex) { var defaultDisplayName = ClassTemplateInvocationContext.super.getDisplayName(invocationIndex); return "%s %s".formatted(defaultDisplayName, displayName); } } } @SuppressWarnings("JUnitMalformedDeclaration") @ClassTemplate @ExtendWith(AdditionalExtensionRegistrationTestCase.Ext.class) static class AdditionalExtensionRegistrationTestCase { @Test void test(Data data) { assertNotNull(data); assertNotNull(data.value()); } static class Ext implements ClassTemplateInvocationContextProvider { @Override public boolean supportsClassTemplate(ExtensionContext context) { return true; } @Override public Stream provideClassTemplateInvocationContexts(ExtensionContext context) { return Stream.of(new Data("A"), new Data("B")).map(Ctx::new); } } record Ctx(Data data) implements ClassTemplateInvocationContext { @Override public String getDisplayName(int invocationIndex) { return this.data.value(); } @Override public List getAdditionalExtensions() { return List.of(new ParameterResolver() { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return Data.class.equals(parameterContext.getParameter().getType()); } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return Ctx.this.data; } }); } } record Data(String value) { } } @ClassTemplate @ExtendWith(TwoInvocationsClassTemplateInvocationContextProvider.class) @ExtendWith(SeparateExtensionContextTestCase.SomeResourceExtension.class) static class SeparateExtensionContextTestCase { @Test void test(SomeResource someResource) { assertFalse(someResource.closed); } static class SomeResourceExtension implements BeforeAllCallback, ParameterResolver { @Override public void beforeAll(ExtensionContext context) throws Exception { context.getStore(Namespace.GLOBAL).put("someResource", new SomeResource()); } @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { var parentContext = extensionContext.getParent().orElseThrow(); assertAll( // () -> assertEquals(SeparateExtensionContextTestCase.class, parentContext.getRequiredTestClass()), // () -> assertEquals(SeparateExtensionContextTestCase.class, parentContext.getElement().orElseThrow()), // () -> assertEquals(TestInstance.Lifecycle.PER_METHOD, parentContext.getTestInstanceLifecycle().orElseThrow()) // ); return SomeResource.class.equals(parameterContext.getParameter().getType()); } @Override public @Nullable Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return extensionContext.getStore(Namespace.GLOBAL).get("someResource"); } } static class SomeResource implements AutoCloseable { private boolean closed; @Override public void close() { this.closed = true; } } } @ClassTemplate @ExtendWith(TwoInvocationsClassTemplateInvocationContextProvider.class) static class CombinationWithTestTemplateTestCase { @ParameterizedTest @ValueSource(ints = { 1, 2 }) void test(int i) { assertNotEquals(0, i); } } @ClassTemplate @ExtendWith(TwoInvocationsClassTemplateInvocationContextProvider.class) static class CombinationWithTestFactoryTestCase { @TestFactory Stream test() { return IntStream.of(1, 2) // .mapToObj(i -> dynamicTest("test" + i, () -> assertNotEquals(0, i))); } } @ClassTemplate @ExtendWith(InvalidZeroInvocationTestCase.Ext.class) static class InvalidZeroInvocationTestCase { @Test void test() { fail("should not be called"); } static class Ext implements ClassTemplateInvocationContextProvider { @Override public boolean supportsClassTemplate(ExtensionContext context) { return true; } @Override public Stream provideClassTemplateInvocationContexts( ExtensionContext context) { return Stream.empty(); } } } @ClassTemplate @ExtendWith(ValidZeroInvocationTestCase.Ext.class) static class ValidZeroInvocationTestCase { @Test void test() { fail("should not be called"); } static class Ext implements ClassTemplateInvocationContextProvider { @Override public boolean supportsClassTemplate(ExtensionContext context) { return true; } @Override public Stream provideClassTemplateInvocationContexts( ExtensionContext context) { return Stream.empty(); } @Override public boolean mayReturnZeroClassTemplateInvocationContexts(ExtensionContext context) { return true; } } } @ClassTemplate static class NoProviderRegisteredTestCase { @Test void test() { fail("should not be called"); } } @ClassTemplate @ExtendWith(NoSupportingProviderRegisteredTestCase.Ext.class) static class NoSupportingProviderRegisteredTestCase { @Test void test() { fail("should not be called"); } static class Ext implements ClassTemplateInvocationContextProvider { @Override public boolean supportsClassTemplate(ExtensionContext context) { return false; } @Override public Stream provideClassTemplateInvocationContexts( ExtensionContext context) { throw new RuntimeException("should not be called"); } } } @ExtendWith(TwoInvocationsClassTemplateInvocationContextProvider.class) @ClassTemplate static class TwoTimesTwoInvocationsWithMultipleMethodsTestCase { @Test void test() { } @Nested @ClassTemplate class NestedTestCase { @Test void a() { } @Test void b() { } } @Nested @ClassTemplate class AnotherNestedTestCase { @Test void test() { } } } @ExtendWith(TwoInvocationsClassTemplateInvocationContextProvider.class) @ExtendWith(ClassTemplateInvocationCallbacks.class) @ClassTemplate static class TwoTimesTwoInvocationsWithLifecycleCallbacksTestCase extends LifecycleCallbacks { @Nested @ClassTemplate class NestedTestCase extends LifecycleCallbacks { @Test @DisplayName("test1") void test1(TestReporter testReporter) { testReporter.publishEntry("test1"); } @Test @DisplayName("test2") void test2(TestReporter testReporter) { testReporter.publishEntry("test2"); } } } @SuppressWarnings("JUnitMalformedDeclaration") static class LifecycleCallbacks { @BeforeAll static void beforeAll(TestReporter testReporter, TestInfo testInfo) { testReporter.publishEntry("beforeAll: " + testInfo.getTestClass().orElseThrow().getSimpleName()); } @BeforeEach void beforeEach(TestReporter testReporter, TestInfo testInfo) { testReporter.publishEntry( "beforeEach: " + testInfo.getDisplayName() + " [" + getClass().getSimpleName() + "]"); } @AfterEach void afterEach(TestReporter testReporter, TestInfo testInfo) { testReporter.publishEntry( "afterEach: " + testInfo.getDisplayName() + " [" + getClass().getSimpleName() + "]"); } @AfterAll static void afterAll(TestReporter testReporter, TestInfo testInfo) { testReporter.publishEntry("afterAll: " + testInfo.getTestClass().orElseThrow().getSimpleName()); } } @ClassTemplate @ExtendWith({ PreparingClassTemplateInvocationContextProvider.class, CompanionExtension.class }) static class ClassTemplateWithPreparationsTestCase { @Test void test(CustomCloseableResource resource) { assertNotNull(resource); assertFalse(CustomCloseableResource.closed, "should not be closed yet"); } } private static class PreparingClassTemplateInvocationContextProvider implements ClassTemplateInvocationContextProvider { static final Namespace NAMESPACE = Namespace.create(PreparingClassTemplateInvocationContextProvider.class); @Override public boolean supportsClassTemplate(ExtensionContext context) { return true; } @Override public Stream provideClassTemplateInvocationContexts( ExtensionContext context) { var invocationContext = new PreparingClassTemplateInvocationContext(); return Stream.of(invocationContext, invocationContext); } } private static class PreparingClassTemplateInvocationContext implements ClassTemplateInvocationContext { @Override public void prepareInvocation(ExtensionContext context) { CustomCloseableResource.closed = false; context.getStore(PreparingClassTemplateInvocationContextProvider.NAMESPACE) // .put("resource", new CustomCloseableResource()); } } private static class CompanionExtension implements ParameterResolver { @Override public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { return ExtensionContextScope.TEST_METHOD; } @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return CustomCloseableResource.class.equals(parameterContext.getParameter().getType()); } @Override public @Nullable Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return extensionContext.getStore(PreparingClassTemplateInvocationContextProvider.NAMESPACE).get("resource"); } } @SuppressWarnings("deprecation") private static class CustomCloseableResource implements ExtensionContext.Store.CloseableResource { static boolean closed; @Override public void close() { closed = true; } } @ExtendWith(TwoInvocationsClassTemplateInvocationContextProvider.class) @ClassTemplate static class CallbackWrappingBehaviorTestCase { @RegisterExtension @Order(1) static Extension first = new ClassTemplateInvocationCallbacks("1st -> "); @RegisterExtension @Order(2) static Extension second = new ClassTemplateInvocationCallbacks("2nd -> "); @Test void test(TestReporter testReporter) { testReporter.publishEntry("test"); } } @ExtendWith(TwoInvocationsClassTemplateInvocationContextProvider.class) @ClassTemplate static class CallbackExceptionBehaviorTestCase { @RegisterExtension @Order(1) static Extension first = new ClassTemplateInvocationCallbacks("1st -> ", TestAbortedException::new); @RegisterExtension @Order(2) static Extension second = new ClassTemplateInvocationCallbacks("2nd -> ", AssertionFailedError::new); @Test void test() { fail("should not be called"); } } static class ClassTemplateInvocationCallbacks implements BeforeClassTemplateInvocationCallback, AfterClassTemplateInvocationCallback { private final String prefix; private final Function exceptionFactory; @SuppressWarnings("unused") ClassTemplateInvocationCallbacks() { this(""); } ClassTemplateInvocationCallbacks(String prefix) { this(prefix, __ -> null); } ClassTemplateInvocationCallbacks(String prefix, Function exceptionFactory) { this.prefix = prefix; this.exceptionFactory = exceptionFactory; } @Override public void beforeClassTemplateInvocation(ExtensionContext context) { handle("beforeClassTemplateInvocation", context); } @Override public void afterClassTemplateInvocation(ExtensionContext context) { handle("afterClassTemplateInvocation", context); } private void handle(String methodName, ExtensionContext context) { var message = format(methodName, context); context.publishReportEntry(message); var throwable = exceptionFactory.apply(message); if (throwable != null) { throw throwAsUncheckedException(throwable); } } private String format(String methodName, ExtensionContext context) { return "%s%s: %s".formatted(prefix, methodName, context.getRequiredTestClass().getSimpleName()); } } static class InheritedTwoInvocationsTestCase extends TwoInvocationsTestCase { @Test void c() { } } @Tag("top-level") static class NestedClassTemplateWithTagOnEnclosingClassTestCase { @Nested @ClassTemplate @Tag("nested") @ExtendWith(TwoInvocationsClassTemplateInvocationContextProvider.class) class NestedTestCase { @Test void test() { } } } @ClassTemplate @ExtendWith(TwoInvocationsClassTemplateInvocationContextProvider.class) @ResourceLock("test-resource") static class ClassTemplateWithResourceLockTestCase { @Test void test() { // This test verifies that @ResourceLock works with @ClassTemplate (issue #5155) } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/DefaultExecutionModeTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.support.hierarchical.Node.ExecutionMode.CONCURRENT; import static org.junit.platform.engine.support.hierarchical.Node.ExecutionMode.SAME_THREAD; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Constants; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; import org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor; import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.support.hierarchical.Node; import org.junit.platform.engine.support.hierarchical.Node.ExecutionMode; import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; class DefaultExecutionModeTests extends AbstractJupiterTestEngineTests { @Test void defaultExecutionModeIsReadFromConfigurationParameter() { assertUsesExpectedExecutionMode(null, SAME_THREAD); assertUsesExpectedExecutionMode(SAME_THREAD, SAME_THREAD); assertUsesExpectedExecutionMode(CONCURRENT, CONCURRENT); } private void assertUsesExpectedExecutionMode(@Nullable ExecutionMode defaultExecutionMode, ExecutionMode expectedExecutionMode) { var engineDescriptor = discoverTestsWithDefaultExecutionMode(TestCase.class, defaultExecutionMode); assertExecutionModeRecursively(engineDescriptor, expectedExecutionMode); } @Test void annotationOverridesDefaultExecutionModeToConcurrentForAllDescendants() { assertUsesExpectedExecutionModeForTestClassAndItsDescendants(ConcurrentTestCase.class, null, CONCURRENT); assertUsesExpectedExecutionModeForTestClassAndItsDescendants(ConcurrentTestCase.class, SAME_THREAD, CONCURRENT); assertUsesExpectedExecutionModeForTestClassAndItsDescendants(ConcurrentTestCase.class, CONCURRENT, CONCURRENT); } @Test void annotationOverridesDefaultExecutionModeToSameThreadForAllDescendants() { assertUsesExpectedExecutionModeForTestClassAndItsDescendants(SameThreadTestCase.class, null, SAME_THREAD); assertUsesExpectedExecutionModeForTestClassAndItsDescendants(SameThreadTestCase.class, SAME_THREAD, SAME_THREAD); assertUsesExpectedExecutionModeForTestClassAndItsDescendants(SameThreadTestCase.class, CONCURRENT, SAME_THREAD); } private void assertUsesExpectedExecutionModeForTestClassAndItsDescendants(Class testClass, @Nullable ExecutionMode defaultExecutionMode, ExecutionMode expectedExecutionMode) { var engineDescriptor = discoverTestsWithDefaultExecutionMode(testClass, defaultExecutionMode); engineDescriptor.getChildren().forEach(child -> assertExecutionModeRecursively(child, expectedExecutionMode)); } private void assertExecutionModeRecursively(TestDescriptor testDescriptor, ExecutionMode expectedExecutionMode) { assertExecutionMode(testDescriptor, expectedExecutionMode); testDescriptor.getChildren().forEach(child -> assertExecutionModeRecursively(child, expectedExecutionMode)); } @Test void methodsInTestClassesWithInstancePerClassHaveExecutionModeSameThread() { var engineDescriptor = discoverTestsWithDefaultExecutionMode(SimpleTestInstancePerClassTestCase.class, CONCURRENT); var classDescriptor = getOnlyElement(engineDescriptor.getChildren()); classDescriptor.getChildren().forEach(child -> assertExecutionModeRecursively(child, SAME_THREAD)); } @Test void methodsInNestedTestClassesWithInstancePerClassInHierarchyHaveExecutionModeSameThread() { var engineDescriptor = discoverTestsWithDefaultExecutionMode(OuterTestCase.class, CONCURRENT); var outerTestCaseClassDescriptor = firstChild(engineDescriptor, ClassTestDescriptor.class); var outerTestMethodDescriptor = firstChild(outerTestCaseClassDescriptor, TestMethodTestDescriptor.class); var level1NestedClassDescriptor = firstChild(outerTestCaseClassDescriptor, NestedClassTestDescriptor.class); var level1TestMethodDescriptor = firstChild(level1NestedClassDescriptor, TestMethodTestDescriptor.class); var level2NestedClassDescriptor = firstChild(level1NestedClassDescriptor, NestedClassTestDescriptor.class); var level2TestMethodDescriptor = firstChild(level2NestedClassDescriptor, TestMethodTestDescriptor.class); var level3NestedClassDescriptor = firstChild(level2NestedClassDescriptor, NestedClassTestDescriptor.class); var level3TestMethodDescriptor = firstChild(level3NestedClassDescriptor, TestMethodTestDescriptor.class); assertExecutionMode(outerTestCaseClassDescriptor, CONCURRENT); assertExecutionMode(outerTestMethodDescriptor, CONCURRENT); assertExecutionMode(level1NestedClassDescriptor, CONCURRENT); assertExecutionMode(level1TestMethodDescriptor, CONCURRENT); assertExecutionMode(level2NestedClassDescriptor, CONCURRENT); assertExecutionMode(level2TestMethodDescriptor, SAME_THREAD); assertExecutionMode(level3NestedClassDescriptor, SAME_THREAD); assertExecutionMode(level3TestMethodDescriptor, SAME_THREAD); } private JupiterEngineDescriptor discoverTestsWithDefaultExecutionMode(Class testClass, @Nullable ExecutionMode executionMode) { LauncherDiscoveryRequestBuilder request = request().selectors(selectClass(testClass)); if (executionMode != null) { request.configurationParameter(Constants.DEFAULT_EXECUTION_MODE_PROPERTY_NAME, executionMode.name()); } return (JupiterEngineDescriptor) discoverTests(request.build()).getEngineDescriptor(); } private static void assertExecutionMode(TestDescriptor testDescriptor, ExecutionMode expectedExecutionMode) { assertThat(((Node) testDescriptor).getExecutionMode()) // .describedAs("ExecutionMode for %s", testDescriptor) // .isEqualTo(expectedExecutionMode); } @SuppressWarnings("unchecked") private T firstChild(TestDescriptor engineDescriptor, Class testDescriptorClass) { return (T) engineDescriptor.getChildren().stream() // .filter(testDescriptorClass::isInstance) // .findFirst() // .orElseGet(() -> fail("No child of type " + testDescriptorClass + " found")); } static class TestCase { @Test void test() { } @Nested class NestedTestCase { @Test void test() { } } } @TestInstance(PER_CLASS) static class SimpleTestInstancePerClassTestCase extends TestCase { } @Execution(org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT) static class ConcurrentTestCase extends TestCase { } @Execution(org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD) static class SameThreadTestCase extends TestCase { } static class OuterTestCase { @Nested class LevelOne { @Nested @TestInstance(PER_CLASS) class LevelTwo { @Nested class LevelThree { @Test void test() { } } @Test void test() { } } @Test void test() { } } @Test void test() { } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/DefaultMethodTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import static org.assertj.core.api.Assertions.assertThat; 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.assertTrue; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.engine.execution.injection.sample.DoubleParameterResolver; import org.junit.jupiter.engine.execution.injection.sample.LongParameterResolver; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.testkit.engine.EngineExecutionResults; /** * Integration tests that verify support for selecting and executing default * methods from interfaces in conjunction with the {@link JupiterTestEngine}. * * @since 5.0 */ class DefaultMethodTests extends AbstractJupiterTestEngineTests { private static boolean beforeAllInvoked; private static boolean afterAllInvoked; private static boolean defaultMethodInvoked; private static boolean overriddenDefaultMethodInvoked; private static boolean localMethodInvoked; @BeforeEach void resetFlags() { beforeAllInvoked = false; afterAllInvoked = false; defaultMethodInvoked = false; overriddenDefaultMethodInvoked = false; localMethodInvoked = false; } @Test void executeTestCaseWithDefaultMethodFromInterfaceSelectedByFullyQualifedMethodName() { String fqmn = TestCaseWithDefaultMethod.class.getName() + "#test"; LauncherDiscoveryRequest request = request().selectors(selectMethod(fqmn)).build(); EngineExecutionResults executionResults = executeTests(request); // @formatter:off assertAll( () -> assertTrue(beforeAllInvoked, "@BeforeAll static method invoked from interface"), () -> assertTrue(afterAllInvoked, "@AfterAll static method invoked from interface"), () -> assertTrue(defaultMethodInvoked, "default @Test method invoked from interface"), () -> assertEquals(1, executionResults.testEvents().started().count(), "# tests started"), () -> assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"), () -> assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed") ); // @formatter:on } @Test void executeTestCaseWithDefaultMethodFromGenericInterfaceSelectedByFullyQualifedMethodName() { String fqmn = GenericTestCaseWithDefaultMethod.class.getName() + "#test(" + Long.class.getName() + ")"; LauncherDiscoveryRequest request = request().selectors(selectMethod(fqmn)).build(); EngineExecutionResults executionResults = executeTests(request); // @formatter:off assertAll( () -> assertTrue(beforeAllInvoked, "@BeforeAll default method invoked from interface"), () -> assertTrue(afterAllInvoked, "@AfterAll default method invoked from interface"), () -> assertTrue(defaultMethodInvoked, "default @Test method invoked from interface"), () -> assertFalse(localMethodInvoked, "local @Test method should not have been invoked from class"), () -> assertEquals(1, executionResults.testEvents().started().count(), "# tests started"), () -> assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"), () -> assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed") ); // @formatter:on } @Test void executeTestCaseWithOverloadedMethodNextToGenericDefaultMethodSelectedByFullyQualifedMethodName() { String fqmn = GenericTestCaseWithDefaultMethod.class.getName() + "#test(" + Double.class.getName() + ")"; LauncherDiscoveryRequest request = request().selectors(selectMethod(fqmn)).build(); EngineExecutionResults executionResults = executeTests(request); // @formatter:off assertAll( () -> assertTrue(beforeAllInvoked, "@BeforeAll default method invoked from interface"), () -> assertTrue(afterAllInvoked, "@AfterAll default method invoked from interface"), () -> assertFalse(defaultMethodInvoked, "default @Test method should not have been invoked from interface"), () -> assertTrue(localMethodInvoked, "local @Test method invoked from class"), () -> assertEquals(1, executionResults.testEvents().started().count(), "# tests started"), () -> assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"), () -> assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed") ); // @formatter:on } @Test void executeTestCaseWithOverloadedMethodNextToGenericDefaultMethodSelectedByClass() { Class clazz = GenericTestCaseWithDefaultMethod.class; LauncherDiscoveryRequest request = request().selectors(selectClass(clazz)).build(); EngineExecutionResults executionResults = executeTests(request); // @formatter:off assertAll( () -> assertTrue(beforeAllInvoked, "@BeforeAll default method invoked from interface"), () -> assertTrue(afterAllInvoked, "@AfterAll default method invoked from interface"), () -> assertTrue(defaultMethodInvoked, "default @Test method invoked from interface"), () -> assertTrue(localMethodInvoked, "local @Test method invoked from class"), () -> assertEquals(2, executionResults.testEvents().started().count(), "# tests started"), () -> assertEquals(2, executionResults.testEvents().succeeded().count(), "# tests succeeded"), () -> assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed") ); // @formatter:on } @Test void executeTestCaseWithOverriddenGenericDefaultMethodSelectedByClass() { Class clazz = GenericTestCaseWithOverriddenDefaultMethod.class; LauncherDiscoveryRequest request = request().selectors(selectClass(clazz)).build(); EngineExecutionResults executionResults = executeTests(request); // @formatter:off assertAll( () -> assertTrue(beforeAllInvoked, "@BeforeAll default method invoked from interface"), () -> assertTrue(afterAllInvoked, "@AfterAll default method invoked from interface"), () -> assertFalse(defaultMethodInvoked, "default @Test method should not have been invoked from interface"), () -> assertTrue(overriddenDefaultMethodInvoked, "overridden default @Test method invoked from interface"), () -> assertTrue(localMethodInvoked, "local @Test method invoked from class"), // If defaultMethodInvoked is false and the following ends up being // 3 instead of 2, that means that the overriding method gets invoked // twice: once as itself and a second time "as" the default method which // should not have been "discovered" since it is overridden. () -> assertEquals(2, executionResults.testEvents().started().count(), "# tests started"), () -> assertEquals(2, executionResults.testEvents().succeeded().count(), "# tests succeeded"), () -> assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed") ); // @formatter:on } // ------------------------------------------------------------------------- interface TestInterface { @BeforeAll static void beforeAll() { beforeAllInvoked = true; } @Test default void test() { defaultMethodInvoked = true; } @AfterAll static void afterAll() { afterAllInvoked = true; } } static class TestCaseWithDefaultMethod implements TestInterface { } @ExtendWith({ LongParameterResolver.class, DoubleParameterResolver.class }) @TestInstance(Lifecycle.PER_CLASS) interface GenericTestInterface { @BeforeAll default void beforeAll() { beforeAllInvoked = true; } @Test default void test(N number) { defaultMethodInvoked = true; assertThat(number.intValue()).isEqualTo(42); } @AfterAll default void afterAll() { afterAllInvoked = true; } } @SuppressWarnings("NewClassNamingConvention") static class GenericTestCaseWithDefaultMethod implements GenericTestInterface { @Test void test(Double number) { localMethodInvoked = true; assertThat(number).isEqualTo(42.0); } } @SuppressWarnings("NewClassNamingConvention") static class GenericTestCaseWithOverriddenDefaultMethod implements GenericTestInterface { @Test @Override public void test(Long number) { overriddenDefaultMethodInvoked = true; assertThat(number.intValue()).isEqualTo(42); } @Test void test(Double number) { localMethodInvoked = true; assertThat(number).isEqualTo(42.0); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/DisabledTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.skippedWithReason; import static org.junit.platform.testkit.engine.EventConditions.test; import java.lang.reflect.Method; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.platform.testkit.engine.EngineExecutionResults; /** * Integration tests that verify support for {@link Disabled @Disabled} in the * {@link JupiterTestEngine}. * * @since 5.0 */ class DisabledTests extends AbstractJupiterTestEngineTests { @Test void executeTestsWithDisabledTestClass() { EngineExecutionResults results = executeTestsForClass(DisabledTestClassTestCase.class); results.containerEvents().assertStatistics(stats -> stats.skipped(1)); results.testEvents().assertStatistics(stats -> stats.started(0)); } @Test void executeTestsWithDisabledTestMethods() throws Exception { String methodName = "disabledTest"; Method method = DisabledTestMethodsTestCase.class.getDeclaredMethod(methodName); executeTestsForClass(DisabledTestMethodsTestCase.class).testEvents()// .assertStatistics(stats -> stats.skipped(1).started(1).finished(1).aborted(0).succeeded(1).failed(0))// .skipped().assertEventsMatchExactly( event(test(methodName), skippedWithReason(method + " is @Disabled"))); } // ------------------------------------------------------------------- @Disabled static class DisabledTestClassTestCase { @Test void disabledTest() { fail("this should be @Disabled"); } } static class DisabledTestMethodsTestCase { @Test void enabledTest() { } @Test @Disabled void disabledTest() { fail("this should be @Disabled"); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/DynamicNodeGenerationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import static java.util.Collections.singleton; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Fail.fail; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor.DYNAMIC_CONTAINER_SEGMENT_TYPE; import static org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor.DYNAMIC_TEST_SEGMENT_TYPE; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectIteration; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.junit.platform.testkit.engine.EventConditions.container; import static org.junit.platform.testkit.engine.EventConditions.displayName; import static org.junit.platform.testkit.engine.EventConditions.dynamicTestRegistered; import static org.junit.platform.testkit.engine.EventConditions.engine; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.EventConditions.started; import static org.junit.platform.testkit.engine.EventConditions.test; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.IntStream; import java.util.stream.Stream; import org.junit.jupiter.api.DynamicNode; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.testkit.engine.EngineExecutionResults; import org.junit.platform.testkit.engine.Event; import org.junit.platform.testkit.engine.Events; /** * Integration tests for {@link TestFactory @TestFactory}, {@link DynamicTest}, * and {@link org.junit.jupiter.api.DynamicContainer}. * * @since 5.0 */ class DynamicNodeGenerationTests extends AbstractJupiterTestEngineTests { @Test void testFactoryMethodsAreCorrectlyDiscoveredForClassSelector() { LauncherDiscoveryRequest request = request().selectors(selectClass(MyDynamicTestCase.class)).build(); TestDescriptor engineDescriptor = discoverTests(request).getEngineDescriptor(); assertThat(engineDescriptor.getDescendants()).as("# resolved test descriptors").hasSize(13); } @Test void testFactoryMethodIsCorrectlyDiscoveredForMethodSelector() { LauncherDiscoveryRequest request = request().selectors( selectMethod(MyDynamicTestCase.class, "dynamicStream")).build(); TestDescriptor engineDescriptor = discoverTests(request).getEngineDescriptor(); assertThat(engineDescriptor.getDescendants()).as("# resolved test descriptors").hasSize(2); } @Test void dynamicTestsAreExecutedFromStream() { EngineExecutionResults executionResults = executeTests(selectMethod(MyDynamicTestCase.class, "dynamicStream")); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(MyDynamicTestCase.class), started()), // event(container("dynamicStream"), started()), // event(dynamicTestRegistered("dynamic-test:#1")), // event(test("dynamic-test:#1", "succeedingTest"), started()), // event(test("dynamic-test:#1", "succeedingTest"), finishedSuccessfully()), // event(dynamicTestRegistered("dynamic-test:#2")), // event(test("dynamic-test:#2", "failingTest"), started()), // event(test("dynamic-test:#2", "failingTest"), finishedWithFailure(message("failing"))), // event(container("dynamicStream"), finishedSuccessfully()), // event(container(MyDynamicTestCase.class), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void dynamicTestsAreExecutedFromCollection() { EngineExecutionResults executionResults = executeTests( selectMethod(MyDynamicTestCase.class, "dynamicCollection")); Events containers = executionResults.containerEvents(); Events tests = executionResults.testEvents(); assertAll( // () -> assertEquals(3, containers.started().count(), "# container started"), () -> assertEquals(2, tests.dynamicallyRegistered().count(), "# dynamic registered"), () -> assertEquals(2, tests.started().count(), "# tests started"), () -> assertEquals(1, tests.succeeded().count(), "# tests succeeded"), () -> assertEquals(1, tests.failed().count(), "# tests failed"), () -> assertEquals(3, containers.finished().count(), "# container finished")); } @Test void dynamicTestsAreExecutedFromIterator() { EngineExecutionResults executionResults = executeTests( selectMethod(MyDynamicTestCase.class, "dynamicIterator")); Events containers = executionResults.containerEvents(); Events tests = executionResults.testEvents(); assertAll( // () -> assertEquals(3, containers.started().count(), "# container started"), () -> assertEquals(2, tests.dynamicallyRegistered().count(), "# dynamic registered"), () -> assertEquals(2, tests.started().count(), "# tests started"), () -> assertEquals(1, tests.succeeded().count(), "# tests succeeded"), () -> assertEquals(1, tests.failed().count(), "# tests failed"), () -> assertEquals(3, containers.finished().count(), "# container finished")); } @Test void dynamicTestsAreExecutedFromIterable() { EngineExecutionResults executionResults = executeTests( selectMethod(MyDynamicTestCase.class, "dynamicIterable")); Events containers = executionResults.containerEvents(); Events tests = executionResults.testEvents(); // @TestFactory methods are counted as both container and test assertAll( // () -> assertEquals(3, containers.started().count(), "# container started"), () -> assertEquals(2, tests.dynamicallyRegistered().count(), "# dynamic registered"), () -> assertEquals(2, tests.started().count(), "# tests started"), () -> assertEquals(1, tests.succeeded().count(), "# tests succeeded"), () -> assertEquals(1, tests.failed().count(), "# tests failed"), () -> assertEquals(3, containers.finished().count(), "# container finished")); } @Test void singleDynamicTestIsExecutedWhenDiscoveredByUniqueId() { UniqueId uniqueId = discoverUniqueId(MyDynamicTestCase.class, "dynamicStream") // .append(DYNAMIC_TEST_SEGMENT_TYPE, "#2"); EngineExecutionResults executionResults = executeTests(selectUniqueId(uniqueId)); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(MyDynamicTestCase.class), started()), // event(container("dynamicStream"), started()), // event(dynamicTestRegistered("dynamic-test:#2")), // event(test("dynamic-test:#2", "failingTest"), started()), // event(test("dynamic-test:#2", "failingTest"), finishedWithFailure(message("failing"))), // event(container("dynamicStream"), finishedSuccessfully()), // event(container(MyDynamicTestCase.class), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void singleDynamicTestIsExecutedWhenDiscoveredByIterationIndex() { var methodSelector = selectMethod(MyDynamicTestCase.class, "dynamicStream"); EngineExecutionResults executionResults = executeTests(selectIteration(methodSelector, 1)); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(MyDynamicTestCase.class), started()), // event(container("dynamicStream"), started()), // event(dynamicTestRegistered("dynamic-test:#2")), // event(test("dynamic-test:#2", "failingTest"), started()), // event(test("dynamic-test:#2", "failingTest"), finishedWithFailure(message("failing"))), // event(container("dynamicStream"), finishedSuccessfully()), // event(container(MyDynamicTestCase.class), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void multipleDynamicTestsAreExecutedWhenDiscoveredByIterationIndexAndUniqueId() { UniqueId uniqueId = discoverUniqueId(MyDynamicTestCase.class, "threeTests") // .append(DYNAMIC_TEST_SEGMENT_TYPE, "#3"); var methodSelector = selectMethod(MyDynamicTestCase.class, "threeTests"); EngineExecutionResults executionResults = executeTests(selectIteration(methodSelector, 1), selectUniqueId(uniqueId)); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(MyDynamicTestCase.class), started()), // event(container("threeTests"), started()), // event(dynamicTestRegistered("dynamic-test:#2")), // event(test("dynamic-test:#2", "two"), started()), // event(test("dynamic-test:#2", "two"), finishedSuccessfully()), // event(dynamicTestRegistered("dynamic-test:#3")), // event(test("dynamic-test:#3", "three"), started()), // event(test("dynamic-test:#3", "three"), finishedSuccessfully()), // event(container("threeTests"), finishedSuccessfully()), // event(container(MyDynamicTestCase.class), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void dynamicContainersAreExecutedFromIterable() { EngineExecutionResults executionResults = executeTests( selectMethod(MyDynamicTestCase.class, "dynamicContainerWithIterable")); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(MyDynamicTestCase.class), started()), // event(container("dynamicContainerWithIterable"), started()), // event(dynamicTestRegistered("dynamic-container:#1")), // event(container("dynamic-container:#1"), started()), // event(dynamicTestRegistered("dynamic-test:#1")), // event(test("dynamic-test:#1", "succeedingTest"), started()), // event(test("dynamic-test:#1", "succeedingTest"), finishedSuccessfully()), // event(dynamicTestRegistered("dynamic-test:#2")), // event(test("dynamic-test:#2", "failingTest"), started()), // event(test("dynamic-test:#2", "failingTest"), finishedWithFailure(message("failing"))), // event(container("dynamic-container:#1"), finishedSuccessfully()), // event(container("dynamicContainerWithIterable"), finishedSuccessfully()), // event(container(MyDynamicTestCase.class), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); Events containers = executionResults.containerEvents(); Events tests = executionResults.testEvents(); assertAll( // () -> assertEquals(4, containers.started().count(), "# container started"), () -> assertEquals(1, containers.dynamicallyRegistered().count(), "# dynamic containers registered"), () -> assertEquals(2, tests.dynamicallyRegistered().count(), "# dynamic tests registered"), () -> assertEquals(2, tests.started().count(), "# tests started"), () -> assertEquals(1, tests.succeeded().count(), "# tests succeeded"), () -> assertEquals(1, tests.failed().count(), "# tests failed"), () -> assertEquals(4, containers.finished().count(), "# container finished")); } @Test void singleDynamicTestInNestedDynamicContainerIsExecutedWhenDiscoveredByUniqueId() { UniqueId uniqueId = discoverUniqueId(MyDynamicTestCase.class, "twoNestedContainersWithTwoTestsEach") // .append(DYNAMIC_CONTAINER_SEGMENT_TYPE, "#1") // .append(DYNAMIC_CONTAINER_SEGMENT_TYPE, "#1") // .append(DYNAMIC_TEST_SEGMENT_TYPE, "#2"); EngineExecutionResults executionResults = executeTests(selectUniqueId(uniqueId)); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(MyDynamicTestCase.class), started()), // event(container("twoNestedContainersWithTwoTestsEach"), started()), // event(dynamicTestRegistered(displayName("a"))), // event(container(displayName("a")), started()), // event(dynamicTestRegistered(displayName("a1"))), // event(container(displayName("a1")), started()), // event(dynamicTestRegistered("dynamic-test:#2")), // event(test("dynamic-test:#2", "failingTest"), started()), // event(test("dynamic-test:#2", "failingTest"), finishedWithFailure(message("failing"))), // event(container(displayName("a1")), finishedSuccessfully()), // event(container(displayName("a")), finishedSuccessfully()), // event(container("twoNestedContainersWithTwoTestsEach"), finishedSuccessfully()), // event(container(MyDynamicTestCase.class), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void allDynamicTestInNestedDynamicContainerAreExecutedWhenContainerIsDiscoveredByUniqueId() { UniqueId uniqueId = discoverUniqueId(MyDynamicTestCase.class, "twoNestedContainersWithTwoTestsEach") // .append(DYNAMIC_CONTAINER_SEGMENT_TYPE, "#2") // .append(DYNAMIC_CONTAINER_SEGMENT_TYPE, "#1"); EngineExecutionResults executionResults = executeTests(selectUniqueId(uniqueId)); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(MyDynamicTestCase.class), started()), // event(container("twoNestedContainersWithTwoTestsEach"), started()), // event(dynamicTestRegistered(displayName("b"))), // event(container(displayName("b")), started()), // event(dynamicTestRegistered(displayName("b1"))), // event(container(displayName("b1")), started()), // event(dynamicTestRegistered("dynamic-test:#1")), // event(test("dynamic-test:#1", "succeedingTest"), started()), // event(test("dynamic-test:#1", "succeedingTest"), finishedSuccessfully()), // event(dynamicTestRegistered("dynamic-test:#2")), // event(test("dynamic-test:#2", "failingTest"), started()), // event(test("dynamic-test:#2", "failingTest"), finishedWithFailure(message("failing"))), // event(container(displayName("b1")), finishedSuccessfully()), // event(container(displayName("b")), finishedSuccessfully()), // event(container("twoNestedContainersWithTwoTestsEach"), finishedSuccessfully()), // event(container(MyDynamicTestCase.class), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void nestedDynamicContainersAreExecuted() { EngineExecutionResults executionResults = executeTests( selectMethod(MyDynamicTestCase.class, "nestedDynamicContainers")); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(MyDynamicTestCase.class), started()), // event(container("nestedDynamicContainers"), started()), // event(dynamicTestRegistered(displayName("gift wrap"))), // event(container(displayName("gift wrap")), started()), // event(dynamicTestRegistered(displayName("box"))), // event(container(displayName("box")), started()), // event(dynamicTestRegistered("dynamic-test:#1")), // event(test("dynamic-test:#1", "succeedingTest"), started()), // event(test("dynamic-test:#1", "succeedingTest"), finishedSuccessfully()), // event(dynamicTestRegistered("dynamic-test:#2")), // event(test("dynamic-test:#2", "failingTest"), started()), // event(test("dynamic-test:#2", "failingTest"), finishedWithFailure(message("failing"))), // event(container(displayName("box")), finishedSuccessfully()), // event(container(displayName("gift wrap")), finishedSuccessfully()), // event(container("nestedDynamicContainers"), finishedSuccessfully()), // event(container(MyDynamicTestCase.class), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); Events containers = executionResults.containerEvents(); Events tests = executionResults.testEvents(); assertAll( // () -> assertEquals(5, containers.started().count(), "# container started"), () -> assertEquals(2, containers.dynamicallyRegistered().count(), "# dynamic containers registered"), () -> assertEquals(2, tests.dynamicallyRegistered().count(), "# dynamic tests registered"), () -> assertEquals(2, tests.started().count(), "# tests started"), () -> assertEquals(1, tests.succeeded().count(), "# tests succeeded"), () -> assertEquals(1, tests.failed().count(), "# tests failed"), () -> assertEquals(5, containers.finished().count(), "# container finished")); } @Test void legacyReportingNames() { Events dynamicRegistrations = executeTests(selectMethod(MyDynamicTestCase.class, "nestedDynamicContainers"))// .allEvents().dynamicallyRegistered(); // @formatter:off Stream legacyReportingNames = dynamicRegistrations .map(Event::getTestDescriptor) .map(TestDescriptor::getLegacyReportingName); assertThat(legacyReportingNames) .containsExactly("nestedDynamicContainers()[1]", "nestedDynamicContainers()[1][1]", "nestedDynamicContainers()[1][1][1]", "nestedDynamicContainers()[1][1][2]"); // @formatter:on } @Test void dynamicContainersAreExecutedFromExceptionThrowingStream() { EngineExecutionResults executionResults = executeTests( selectMethod(MyDynamicTestCase.class, "dynamicContainerWithExceptionThrowingStream")); assertTrue(MyDynamicTestCase.exceptionThrowingStreamClosed.get(), "stream should be closed"); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(MyDynamicTestCase.class), started()), // event(container("dynamicContainerWithExceptionThrowingStream"), started()), // event(dynamicTestRegistered("dynamic-container:#1")), // event(container("dynamic-container:#1"), started()), // event(dynamicTestRegistered("dynamic-test:#1")), // event(test("dynamic-test:#1", "succeedingTest"), started()), // event(test("dynamic-test:#1", "succeedingTest"), finishedSuccessfully()), // event(dynamicTestRegistered("dynamic-test:#2")), // event(test("dynamic-test:#2", "failingTest"), started()), // event(test("dynamic-test:#2", "failingTest"), finishedWithFailure(message("failing"))), // event(container("dynamic-container:#1"), finishedWithFailure(instanceOf(ArrayIndexOutOfBoundsException.class))), // event(container("dynamicContainerWithExceptionThrowingStream"), finishedSuccessfully()), // event(container(MyDynamicTestCase.class), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); Events containers = executionResults.containerEvents(); Events tests = executionResults.testEvents(); assertAll( // () -> assertEquals(4, containers.started().count(), "# container started"), () -> assertEquals(1, containers.dynamicallyRegistered().count(), "# dynamic containers registered"), () -> assertEquals(2, tests.dynamicallyRegistered().count(), "# dynamic tests registered"), () -> assertEquals(2, tests.started().count(), "# tests started"), () -> assertEquals(1, tests.succeeded().count(), "# tests succeeded"), () -> assertEquals(1, tests.failed().count(), "# tests failed"), () -> assertEquals(4, containers.finished().count(), "# container finished")); } @Test void dynamicContainersChildrenMustNotBeNull() { EngineExecutionResults executionResults = executeTests( selectMethod(MyDynamicTestCase.class, "dynamicContainerWithNullChildren")); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(MyDynamicTestCase.class), started()), // event(container("dynamicContainerWithNullChildren"), started()), // event(dynamicTestRegistered("dynamic-container:#1")), // event(container("dynamic-container:#1"), started()), // event(container("dynamic-container:#1"), // finishedWithFailure(message("individual dynamic node must not be null"))), // event(container("dynamicContainerWithNullChildren"), finishedSuccessfully()), // event(container(MyDynamicTestCase.class), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void testFactoryMethodsMayReturnSingleDynamicContainer() { EngineExecutionResults executionResults = executeTests( selectMethod(MyDynamicTestCase.class, "singleContainer")); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(MyDynamicTestCase.class), started()), // event(container("singleContainer"), started()), // event(dynamicTestRegistered("dynamic-container:#1")), // event(container("dynamic-container:#1"), started()), // event(dynamicTestRegistered("dynamic-test:#1")), // event(test("dynamic-test:#1", "succeedingTest"), started()), // event(test("dynamic-test:#1", "succeedingTest"), finishedSuccessfully()), // event(dynamicTestRegistered("dynamic-test:#2")), // event(test("dynamic-test:#2", "failingTest"), started()), // event(test("dynamic-test:#2", "failingTest"), finishedWithFailure(message("failing"))), // event(container("dynamic-container:#1"), finishedSuccessfully()), // event(container("singleContainer"), finishedSuccessfully()), // event(container(MyDynamicTestCase.class), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void testFactoryMethodsMayReturnSingleDynamicTest() { EngineExecutionResults executionResults = executeTests(selectMethod(MyDynamicTestCase.class, "singleTest")); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(MyDynamicTestCase.class), started()), // event(container("singleTest"), started()), // event(dynamicTestRegistered("dynamic-test:#1")), // event(test("dynamic-test:#1", "succeedingTest"), started()), // event(test("dynamic-test:#1", "succeedingTest"), finishedSuccessfully()), // event(container("singleTest"), finishedSuccessfully()), // event(container(MyDynamicTestCase.class), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } static class MyDynamicTestCase { private static final List list = Arrays.asList( dynamicTest("succeedingTest", () -> assertTrue(true, "succeeding")), dynamicTest("failingTest", () -> fail("failing"))); private static final AtomicBoolean exceptionThrowingStreamClosed = new AtomicBoolean(false); @TestFactory Collection dynamicCollection() { return list; } @TestFactory Stream dynamicStream() { return list.stream(); } @TestFactory Iterator dynamicIterator() { return list.iterator(); } @TestFactory Iterable dynamicIterable() { return this::dynamicIterator; } @TestFactory Iterable dynamicContainerWithIterable() { return Set.of(dynamicContainer("box", list)); } @TestFactory Iterable nestedDynamicContainers() { return Set.of(dynamicContainer("gift wrap", Set.of(dynamicContainer("box", list)))); } @TestFactory Stream twoNestedContainersWithTwoTestsEach() { return Stream.of( // dynamicContainer("a", Set.of(dynamicContainer("a1", list))), // dynamicContainer("b", Set.of(dynamicContainer("b1", list))) // ); } @TestFactory Iterable dynamicContainerWithExceptionThrowingStream() { // @formatter:off return Set.of(dynamicContainer("box", IntStream.rangeClosed(0, 100) .mapToObj(list::get) .onClose(() -> exceptionThrowingStreamClosed.set(true)))); // @formatter:on } @TestFactory Iterable dynamicContainerWithNullChildren() { return Set.of(dynamicContainer("box", singleton(null))); } @TestFactory DynamicNode singleContainer() { return dynamicContainer("box", list); } @TestFactory DynamicNode singleTest() { return dynamicTest("succeedingTest", () -> assertTrue(true)); } @TestFactory Stream threeTests() { return Stream.of( // dynamicTest("one", () -> assertTrue(true)), // dynamicTest("two", () -> assertTrue(true)), // dynamicTest("three", () -> assertTrue(true)) // ); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/ExceptionHandlingTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import static org.junit.jupiter.api.Assumptions.abort; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.testkit.engine.EventConditions.container; import static org.junit.platform.testkit.engine.EventConditions.engine; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.EventConditions.started; import static org.junit.platform.testkit.engine.EventConditions.test; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.suppressed; import java.io.IOException; import java.util.Optional; import org.jspecify.annotations.NullMarked; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.platform.testkit.engine.EngineExecutionResults; import org.junit.platform.testkit.engine.Events; import org.opentest4j.AssertionFailedError; import org.opentest4j.TestAbortedException; /** * Integration tests that verify correct exception handling in the {@link JupiterTestEngine}. * * @since 5.0 */ class ExceptionHandlingTests extends AbstractJupiterTestEngineTests { @Test void failureInTestMethodIsRegistered() { EngineExecutionResults executionResults = executeTests(selectMethod(FailureTestCase.class, "failingTest")); Events tests = executionResults.testEvents(); tests.assertStatistics(stats -> stats.started(1).failed(1)); tests.failed().assertEventsMatchExactly( // event(test("failingTest"), finishedWithFailure(instanceOf(AssertionFailedError.class), message("always fails")))); } @Test void uncheckedExceptionInTestMethodIsRegistered() { EngineExecutionResults executionResults = executeTests( selectMethod(FailureTestCase.class, "testWithUncheckedException")); Events tests = executionResults.testEvents(); tests.assertStatistics(stats -> stats.started(1).failed(1)); tests.failed().assertEventsMatchExactly( // event(test("testWithUncheckedException"), finishedWithFailure(instanceOf(RuntimeException.class), message("unchecked")))); } @Test void checkedExceptionInTestMethodIsRegistered() { EngineExecutionResults executionResults = executeTests( selectMethod(FailureTestCase.class, "testWithCheckedException")); Events tests = executionResults.testEvents(); tests.assertStatistics(stats -> stats.started(1).failed(1)); tests.failed().assertEventsMatchExactly( // event(test("testWithCheckedException"), finishedWithFailure(instanceOf(IOException.class), message("checked")))); } @Test void checkedExceptionInBeforeEachIsRegistered() { FailureTestCase.exceptionToThrowInBeforeEach = Optional.of(new IOException("checked")); EngineExecutionResults executionResults = executeTests(selectMethod(FailureTestCase.class, "succeedingTest")); Events tests = executionResults.testEvents(); tests.assertStatistics(stats -> stats.started(1).failed(1)); tests.failed().assertEventsMatchExactly( event(test("succeedingTest"), finishedWithFailure(instanceOf(IOException.class), message("checked")))); } @Test void checkedExceptionInAfterEachIsRegistered() { FailureTestCase.exceptionToThrowInAfterEach = Optional.of(new IOException("checked")); EngineExecutionResults executionResults = executeTests(selectMethod(FailureTestCase.class, "succeedingTest")); Events tests = executionResults.testEvents(); tests.assertStatistics(stats -> stats.started(1).failed(1)); tests.failed().assertEventsMatchExactly( event(test("succeedingTest"), finishedWithFailure(instanceOf(IOException.class), message("checked")))); } @Test void checkedExceptionInAfterEachIsSuppressedByExceptionInTest() { Class testClass = FailureTestCase.class; FailureTestCase.exceptionToThrowInAfterEach = Optional.of(new IOException("checked")); EngineExecutionResults executionResults = executeTests(selectMethod(testClass, "testWithUncheckedException")); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(test("testWithUncheckedException"), started()), // event(test("testWithUncheckedException"), // finishedWithFailure( // instanceOf(RuntimeException.class), // message("unchecked"), // suppressed(0, instanceOf(IOException.class), message("checked")))), // event(container(testClass), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void exceptionInAfterEachTakesPrecedenceOverFailedAssumptionInTest() { FailureTestCase.exceptionToThrowInAfterEach = Optional.of(new IOException("checked")); EngineExecutionResults executionResults = executeTests(selectMethod(FailureTestCase.class, "abortedTest")); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(FailureTestCase.class), started()), // event(test("abortedTest"), started()), // event(test("abortedTest"), // finishedWithFailure(instanceOf(IOException.class), message("checked"), // suppressed(0, instanceOf(TestAbortedException.class)))), // event(container(FailureTestCase.class), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void checkedExceptionInBeforeAllIsRegistered() { Class testClass = FailureTestCase.class; FailureTestCase.exceptionToThrowInBeforeAll = Optional.of(new IOException("checked")); EngineExecutionResults executionResults = executeTests(selectMethod(testClass, "succeedingTest")); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(container(testClass), finishedWithFailure(instanceOf(IOException.class), message("checked"))), // event(engine(), finishedSuccessfully())); } @Test void checkedExceptionInAfterAllIsRegistered() { Class testClass = FailureTestCase.class; FailureTestCase.exceptionToThrowInAfterAll = Optional.of(new IOException("checked")); EngineExecutionResults executionResults = executeTests(selectMethod(testClass, "succeedingTest")); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(test("succeedingTest"), started()), // event(test("succeedingTest"), finishedSuccessfully()), // event(container(testClass), finishedWithFailure(instanceOf(IOException.class), message("checked"))), // event(engine(), finishedSuccessfully())); } @Test void exceptionInAfterAllCallbackDoesNotHideExceptionInBeforeAllCallback() { Class testClass = TestCaseWithThrowingBeforeAllAndAfterAllCallbacks.class; EngineExecutionResults executionResults = executeTestsForClass(testClass); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(container(testClass), finishedWithFailure( // message("beforeAll callback"), // suppressed(0, message("afterAll callback")))), // event(engine(), finishedSuccessfully())); } @Test void exceptionsInConstructorAndAfterAllCallbackAreReportedWhenTestInstancePerMethodIsUsed() { Class testClass = TestCaseWithInvalidConstructorAndThrowingAfterAllCallbackAndPerMethodLifecycle.class; EngineExecutionResults executionResults = executeTestsForClass(testClass); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(test("test"), started()), // event(test("test"), finishedWithFailure(message("constructor"))), // event(container(testClass), finishedWithFailure(message("afterAll callback"))), // event(engine(), finishedSuccessfully())); } @Test void exceptionInConstructorPreventsExecutionOfAfterAllCallbacksWhenTestInstancePerClassIsUsed() { Class testClass = TestCaseWithInvalidConstructorAndThrowingAfterAllCallbackAndPerClassLifecycle.class; EngineExecutionResults executionResults = executeTestsForClass(testClass); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(container(testClass), finishedWithFailure(message("constructor"))), event(engine(), finishedSuccessfully())); } @Test void failureInAfterAllTakesPrecedenceOverTestAbortedExceptionInBeforeAll() { FailureTestCase.exceptionToThrowInBeforeAll = Optional.of(new TestAbortedException("aborted")); FailureTestCase.exceptionToThrowInAfterAll = Optional.of(new IOException("checked")); EngineExecutionResults executionResults = executeTests(selectMethod(FailureTestCase.class, "succeedingTest")); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(FailureTestCase.class), started()), // event(container(FailureTestCase.class), finishedWithFailure(instanceOf(IOException.class), message("checked"), suppressed(0, instanceOf(TestAbortedException.class), message("aborted")))), // event(engine(), finishedSuccessfully())); } @AfterEach void cleanUpExceptions() { FailureTestCase.exceptionToThrowInBeforeAll = Optional.empty(); FailureTestCase.exceptionToThrowInAfterAll = Optional.empty(); FailureTestCase.exceptionToThrowInBeforeEach = Optional.empty(); FailureTestCase.exceptionToThrowInAfterEach = Optional.empty(); } // ------------------------------------------------------------------------- @SuppressWarnings("OptionalUsedAsFieldOrParameterType") static class FailureTestCase { static Optional exceptionToThrowInBeforeAll = Optional.empty(); static Optional exceptionToThrowInAfterAll = Optional.empty(); static Optional exceptionToThrowInBeforeEach = Optional.empty(); static Optional exceptionToThrowInAfterEach = Optional.empty(); @BeforeAll static void beforeAll() throws Throwable { if (exceptionToThrowInBeforeAll.isPresent()) { throw exceptionToThrowInBeforeAll.get(); } } @AfterAll static void afterAll() throws Throwable { if (exceptionToThrowInAfterAll.isPresent()) { throw exceptionToThrowInAfterAll.get(); } } @BeforeEach void beforeEach() throws Throwable { if (exceptionToThrowInBeforeEach.isPresent()) { throw exceptionToThrowInBeforeEach.get(); } } @AfterEach void afterEach() throws Throwable { if (exceptionToThrowInAfterEach.isPresent()) { throw exceptionToThrowInAfterEach.get(); } } @Test void succeedingTest() { } @Test void failingTest() { Assertions.fail("always fails"); } @Test void testWithUncheckedException() { throw new RuntimeException("unchecked"); } @Test void testWithCheckedException() throws IOException { throw new IOException("checked"); } @Test void abortedTest() { abort("abortedTest"); } } @TestInstance(PER_METHOD) @ExtendWith(ThrowingAfterAllCallback.class) static class TestCaseWithInvalidConstructorAndThrowingAfterAllCallbackAndPerMethodLifecycle { TestCaseWithInvalidConstructorAndThrowingAfterAllCallbackAndPerMethodLifecycle() { throw new IllegalStateException("constructor"); } @Test void test() { } } @TestInstance(PER_CLASS) @ExtendWith(ThrowingAfterAllCallback.class) static class TestCaseWithInvalidConstructorAndThrowingAfterAllCallbackAndPerClassLifecycle { TestCaseWithInvalidConstructorAndThrowingAfterAllCallbackAndPerClassLifecycle() { throw new IllegalStateException("constructor"); } @Test void test() { } } @ExtendWith(ThrowingBeforeAllCallback.class) @ExtendWith(ThrowingAfterAllCallback.class) static class TestCaseWithThrowingBeforeAllAndAfterAllCallbacks { @Test void test() { } } @NullMarked static class ThrowingBeforeAllCallback implements BeforeAllCallback { @Override public void beforeAll(ExtensionContext context) { throw new IllegalStateException("beforeAll callback"); } } @NullMarked static class ThrowingAfterAllCallback implements AfterAllCallback { @Override public void afterAll(ExtensionContext context) { throw new IllegalStateException("afterAll callback"); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/ExecutionCancellationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import static java.util.Objects.requireNonNull; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.testkit.engine.EventConditions.container; import static org.junit.platform.testkit.engine.EventConditions.displayName; import static org.junit.platform.testkit.engine.EventConditions.dynamicTestRegistered; import static org.junit.platform.testkit.engine.EventConditions.engine; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; import static org.junit.platform.testkit.engine.EventConditions.reportEntry; import static org.junit.platform.testkit.engine.EventConditions.skippedWithReason; import static org.junit.platform.testkit.engine.EventConditions.started; import static org.junit.platform.testkit.engine.EventConditions.test; import java.util.Map; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DynamicNode; import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.TestReporter; import org.junit.platform.engine.CancellationToken; class ExecutionCancellationTests extends AbstractJupiterTestEngineTests { @BeforeEach void initializeCancellationToken() { TestCase.cancellationToken = CancellationToken.create(); } @AfterEach void resetCancellationToken() { TestCase.cancellationToken = null; } @Test void canCancelExecutionWhileTestClassIsRunning() { var testClass = RegularTestCase.class; var results = jupiterTestEngine() // .selectors(selectClass(testClass)) // .cancellationToken(TestCase.requiredCancellationToken()) // .execute(); results.testEvents().assertStatistics(stats -> stats.started(1).finished(1).skipped(1)); results.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(test("first"), started()), // event(test("first"), reportEntry(Map.of("cancelled", "true"))), // event(test("first"), finishedSuccessfully()), // event(test("second"), skippedWithReason("Execution cancelled")), // event(container(testClass), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void canCancelExecutionWhileDynamicTestsAreRunning() { var testClass = DynamicTestCase.class; var results = jupiterTestEngine() // .selectors(selectClass(testClass)) // .cancellationToken(TestCase.requiredCancellationToken()) // .execute(); results.containerEvents().assertStatistics(stats -> stats.skipped(1)); results.testEvents().assertStatistics(stats -> stats.started(1).finished(1).skipped(0)); results.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(container("testFactory"), started()), // event(dynamicTestRegistered("#1"), displayName("first")), // event(test("#1"), started()), // event(test("#1"), finishedSuccessfully()), // event(dynamicTestRegistered("#2"), displayName("container")), // event(container("#2"), skippedWithReason("Execution cancelled")), // event(container("testFactory"), finishedSuccessfully()), // event(container(testClass), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } static class TestCase { static @Nullable CancellationToken cancellationToken; static CancellationToken requiredCancellationToken() { return requireNonNull(cancellationToken); } } @TestMethodOrder(OrderAnnotation.class) static class RegularTestCase extends TestCase { @Test @Order(1) void first() { requiredCancellationToken().cancel(); } @AfterEach void afterEach(TestReporter reporter) { reporter.publishEntry("cancelled", String.valueOf(requiredCancellationToken().isCancellationRequested())); } @Test @Order(2) void second() { fail("should not be called"); } } static class DynamicTestCase extends TestCase { @TestFactory Stream testFactory() { return Stream.of( // dynamicTest("first", () -> requiredCancellationToken().cancel()), // dynamicContainer("container", Stream.of( // dynamicTest("second", () -> fail("should not be called")) // )) // ); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/FailedAssumptionsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import org.junit.Assume; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.platform.testkit.engine.EngineExecutionResults; /** * Integration tests that verify support for failed assumptions in the * {@link JupiterTestEngine}. * * @since 5.4 */ class FailedAssumptionsTests extends AbstractJupiterTestEngineTests { @Test void testAbortedExceptionInBeforeAll() { EngineExecutionResults results = executeTestsForClass(TestAbortedExceptionInBeforeAllTestCase.class); results.containerEvents().assertStatistics(stats -> stats.aborted(1)); results.testEvents().assertStatistics(stats -> stats.started(0)); } @Test void assumptionViolatedExceptionInBeforeAll() { EngineExecutionResults results = executeTestsForClass(AssumptionViolatedExceptionInBeforeAllTestCase.class); results.containerEvents().assertStatistics(stats -> stats.aborted(1)); results.testEvents().assertStatistics(stats -> stats.started(0)); } // ------------------------------------------------------------------- static class TestAbortedExceptionInBeforeAllTestCase { @BeforeAll static void beforeAll() { Assumptions.abort(); } @Test void test() { } } static class AssumptionViolatedExceptionInBeforeAllTestCase { @SuppressWarnings("DataFlowIssue") @BeforeAll static void beforeAll() { Assume.assumeTrue(false); } @Test void test() { } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/InvalidLifecycleMethodConfigurationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import static java.util.function.Predicate.isEqual; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.platform.commons.util.FunctionUtils.where; import java.lang.annotation.Annotation; 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.Test; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; /** * Integration tests that verify proper handling of invalid configuration for * lifecycle methods in conjunction with the {@link JupiterTestEngine}. * * @since 5.0 */ class InvalidLifecycleMethodConfigurationTests extends AbstractJupiterTestEngineTests { @Test void executeValidTestCaseAlongsideTestCaseWithInvalidNonStaticBeforeAllDeclaration() { assertReportsError(TestCaseWithInvalidNonStaticBeforeAllMethod.class, BeforeAll.class); } @Test void executeValidTestCaseAlongsideTestCaseWithInvalidNonStaticAfterAllDeclaration() { assertReportsError(TestCaseWithInvalidNonStaticAfterAllMethod.class, AfterAll.class); } @Test void executeValidTestCaseAlongsideTestCaseWithInvalidStaticBeforeEachDeclaration() { assertReportsError(TestCaseWithInvalidStaticBeforeEachMethod.class, BeforeEach.class); } @Test void executeValidTestCaseAlongsideTestCaseWithInvalidStaticAfterEachDeclaration() { assertReportsError(TestCaseWithInvalidStaticAfterEachMethod.class, AfterEach.class); } private void assertReportsError(Class invalidTestClass, Class annotationType) { var results = discoverTestsForClass(invalidTestClass); assertThat(results.getDiscoveryIssues()) // .filteredOn(where(DiscoveryIssue::severity, isEqual(Severity.ERROR))) // .extracting(DiscoveryIssue::message) // .asString().contains("@%s method".formatted(annotationType.getSimpleName())); } // ------------------------------------------------------------------------- @SuppressWarnings("JUnitMalformedDeclaration") static class TestCaseWithInvalidNonStaticBeforeAllMethod { // must be static @SuppressWarnings("unused") @BeforeAll void beforeAll() { } @Test void test() { } } @SuppressWarnings("JUnitMalformedDeclaration") static class TestCaseWithInvalidNonStaticAfterAllMethod { // must be static @SuppressWarnings("unused") @AfterAll void afterAll() { } @Test void test() { } } @SuppressWarnings("JUnitMalformedDeclaration") static class TestCaseWithInvalidStaticBeforeEachMethod { // must NOT be static @SuppressWarnings("unused") @BeforeEach static void beforeEach() { } @Test void test() { } } @SuppressWarnings("JUnitMalformedDeclaration") static class TestCaseWithInvalidStaticAfterEachMethod { // must NOT be static @SuppressWarnings("unused") @AfterEach static void afterEach() { } @Test void test() { } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/JupiterTestEngineTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; import org.junit.platform.commons.JUnitException; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.launcher.core.NamespacedHierarchicalStoreProviders; /** * @since 5.13 */ public class JupiterTestEngineTests { private final JupiterEngineDescriptor jupiterEngineDescriptor = mock(); private final ConfigurationParameters configurationParameters = mock(); private final EngineExecutionListener engineExecutionListener = mock(); private final ExecutionRequest executionRequest = mock(); private final JupiterTestEngine engine = new JupiterTestEngine(); private final JupiterTestEngine jupiter = new JupiterTestEngine(); @BeforeEach void setUp() { when(executionRequest.getEngineExecutionListener()).thenReturn(engineExecutionListener); when(executionRequest.getConfigurationParameters()).thenReturn(configurationParameters); when(executionRequest.getRootTestDescriptor()).thenReturn(jupiterEngineDescriptor); } @Test void createExecutionContextWithValidRequest() { when(executionRequest.getStore()).thenReturn( NamespacedHierarchicalStoreProviders.dummyNamespacedHierarchicalStore()); JupiterEngineExecutionContext context = engine.createExecutionContext(executionRequest); assertThat(context).isNotNull(); } @Test void createExecutionContextWithNoParentsRequestLevelStore() { when(executionRequest.getStore()).thenReturn( NamespacedHierarchicalStoreProviders.dummyNamespacedHierarchicalStoreWithNoParent()); assertThatThrownBy(() -> engine // .createExecutionContext(executionRequest)) // .isInstanceOf(JUnitException.class) // .hasMessageContaining("Request-level store must have a parent"); } @Test void id() { assertEquals("junit-jupiter", jupiter.getId()); } @Test void groupId() { assertEquals("org.junit.jupiter", jupiter.getGroupId().orElseThrow()); } @Test void artifactId() { assertEquals("junit-jupiter-engine", jupiter.getArtifactId().orElseThrow()); } @Test void version() { assertThat(jupiter.getVersion().orElseThrow()).isIn( // System.getProperty("developmentVersion"), // with Test Distribution "DEVELOPMENT" // without Test Distribution ); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/LifecycleMethodOverridingTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.fail; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.engine.subpackage.SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase; /** * Integration tests that explicitly demonstrate the overriding rules for * lifecycle methods in the {@link JupiterTestEngine}. * * @since 5.9 */ class LifecycleMethodOverridingTests { @Nested @DisplayName("A package-private lifecycle method can be overridden by") class PackagePrivateSuperClassTests { @Nested @DisplayName("a protected lifecycle method in a subclass") class ProtectedExtendsPackagePrivateLifecycleMethod extends SuperClassWithPackagePrivateLifecycleMethodTestCase { @Override @BeforeEach protected void beforeEach() { } } @Nested @DisplayName("a package-private lifecycle method in a subclass") class PackagePrivateExtendsPackagePrivateLifecycleMethod extends SuperClassWithPackagePrivateLifecycleMethodTestCase { @Override @BeforeEach void beforeEach() { } } @Nested @DisplayName("a public lifecycle method in a subclass") class PublicExtendsPackagePrivateLifecycleMethod extends SuperClassWithPackagePrivateLifecycleMethodTestCase { @Override @BeforeEach public void beforeEach() { } } } @Nested @DisplayName("A package-private lifecycle method from a different package cannot be overridden by") class PackagePrivateSuperClassInDifferentPackageTests { @Nested @DisplayName("a protected lifecycle method in a subclass") class ProtectedExtendsPackagePrivateLifecycleMethod extends SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase { // @Override @BeforeEach protected void beforeEach() { assertThat(super.beforeEachInvoked).isTrue(); } } @Nested @DisplayName("a package-private lifecycle method in a subclass") class PackagePrivateExtendsPackagePrivateLifecycleMethod extends SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase { // @Override @BeforeEach void beforeEach() { assertThat(super.beforeEachInvoked).isTrue(); } } @Nested @DisplayName("a public lifecycle method in a subclass") class PublicExtendsPackagePrivateLifecycleMethod extends SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase { // @Override @BeforeEach public void beforeEach() { assertThat(super.beforeEachInvoked).isTrue(); } } } @Nested @DisplayName("A protected lifecycle method can be overridden by") class ProtectedSuperClassTests { @Nested @DisplayName("a protected lifecycle method in a subclass") class ProtectedExtendsPackagePrivate extends SuperClassWithProtectedLifecycleMethodTestCase { @Override @BeforeEach protected void beforeEach() { } } @Nested @DisplayName("a public lifecycle method in a subclass") class PublicExtendsPackagePrivate extends SuperClassWithProtectedLifecycleMethodTestCase { @Override @BeforeEach public void beforeEach() { } } } @Nested @DisplayName("A public lifecycle method can be overridden by") class PublicSuperClassTests { @Nested @DisplayName("a public lifecycle method in a subclass") class PublicExtendsPackagePrivate extends SuperClassWithPublicLifecycleMethodTestCase { @Override @BeforeEach public void beforeEach() { } } } } // ------------------------------------------------------------------------- class SuperClassWithPackagePrivateLifecycleMethodTestCase { @BeforeEach void beforeEach() { fail(); } @Test void test() { } } class SuperClassWithProtectedLifecycleMethodTestCase { @BeforeEach protected void beforeEach() { fail(); } @Test void test() { } } class SuperClassWithPublicLifecycleMethodTestCase { @BeforeEach public void beforeEach() { fail(); } @Test void test() { } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/MultipleTestableAnnotationsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.RepetitionInfo; import org.junit.jupiter.api.Test; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.support.descriptor.MethodSource; /** * Integration tests that verify the correct behavior for methods annotated * with multiple testable annotations simultaneously. * * @since 5.0 */ class MultipleTestableAnnotationsTests extends AbstractJupiterTestEngineTests { @Test void testAndRepeatedTest() throws Exception { var results = discoverTestsForClass(TestCase.class); var discoveryIssue = getOnlyElement(results.getDiscoveryIssues()); assertThat(discoveryIssue.severity()) // .isEqualTo(Severity.WARNING); assertThat(discoveryIssue.message()) // .matches("Possible configuration error: method .+ resulted in multiple TestDescriptors .+"); assertThat(discoveryIssue.source()) // .contains( MethodSource.from(TestCase.class.getDeclaredMethod("testAndRepeatedTest", RepetitionInfo.class))); } @SuppressWarnings("JUnitMalformedDeclaration") static class TestCase { @Test @RepeatedTest(1) void testAndRepeatedTest(RepetitionInfo repetitionInfo) { assertNotNull(repetitionInfo); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/NestedTestClassesTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.platform.engine.discovery.ClassNameFilter.includeClassNamePatterns; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.launcher.LauncherConstants.CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.List; import java.util.function.Consumer; import java.util.regex.Pattern; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Named; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.engine.NestedTestClassesTests.OuterClass.NestedClass; import org.junit.jupiter.engine.NestedTestClassesTests.OuterClass.NestedClass.RecursiveNestedClass; import org.junit.jupiter.engine.NestedTestClassesTests.OuterClass.NestedClass.RecursiveNestedSiblingClass; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; import org.junit.platform.testkit.engine.EngineExecutionResults; import org.junit.platform.testkit.engine.Events; /** * Integration tests that verify support for {@linkplain Nested nested contexts} * in the {@link JupiterTestEngine}. * * @since 5.0 */ class NestedTestClassesTests extends AbstractJupiterTestEngineTests { @Test void nestedTestsAreCorrectlyDiscovered() { LauncherDiscoveryRequest request = defaultRequest().selectors(selectClass(TestCaseWithNesting.class)).build(); TestDescriptor engineDescriptor = discoverTestsWithoutIssues(request); assertEquals(5, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @ParameterizedTest(name = "{0}") @MethodSource void nestedTestsAreExecutedInTheRightOrder(Consumer configurer) { EngineExecutionResults executionResults = executeTests(configurer); Events tests = executionResults.testEvents(); assertEquals(3, tests.started().count(), "# tests started"); assertEquals(2, tests.succeeded().count(), "# tests succeeded"); assertEquals(1, tests.failed().count(), "# tests failed"); assertThat(tests.started().map(it -> it.getTestDescriptor().getDisplayName())) // .containsExactlyInAnyOrder("someTest()", "successful()", "failing()") // .containsSubsequence("someTest()", "successful()") // .containsSubsequence("someTest()", "failing()"); Events containers = executionResults.containerEvents(); assertEquals(3, containers.started().count(), "# containers started"); assertEquals(3, containers.finished().count(), "# containers finished"); } static List>> nestedTestsAreExecutedInTheRightOrder() { return List.of( // Named.of("class selector", request -> request // .selectors(selectClass(TestCaseWithNesting.class))), Named.of("package selector", request -> request // .selectors(selectPackage(TestCaseWithNesting.class.getPackageName())) // .filters(includeClassNamePatterns(Pattern.quote(TestCaseWithNesting.class.getName()) + ".*"))) // ); } @Test void doublyNestedTestsAreCorrectlyDiscovered() { LauncherDiscoveryRequest request = defaultRequest().selectors( selectClass(TestCaseWithDoubleNesting.class)).build(); TestDescriptor engineDescriptor = discoverTestsWithoutIssues(request); assertEquals(8, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @Test void doublyNestedTestsAreExecuted() { EngineExecutionResults executionResults = executeTestsForClass(TestCaseWithDoubleNesting.class); Events containers = executionResults.containerEvents(); Events tests = executionResults.testEvents(); assertEquals(5, tests.started().count(), "# tests started"); assertEquals(3, tests.succeeded().count(), "# tests succeeded"); assertEquals(2, tests.failed().count(), "# tests failed"); assertEquals(4, containers.started().count(), "# containers started"); assertEquals(4, containers.finished().count(), "# containers finished"); assertAll("before each counts", // () -> assertEquals(5, TestCaseWithDoubleNesting.beforeTopCount), () -> assertEquals(4, TestCaseWithDoubleNesting.beforeNestedCount), () -> assertEquals(2, TestCaseWithDoubleNesting.beforeDoublyNestedCount)); assertAll("after each counts", // () -> assertEquals(5, TestCaseWithDoubleNesting.afterTopCount), () -> assertEquals(4, TestCaseWithDoubleNesting.afterNestedCount), () -> assertEquals(2, TestCaseWithDoubleNesting.afterDoublyNestedCount)); } @Test void inheritedNestedTestsAreExecuted() { var discoveryIssues = discoverTestsForClass(TestCaseWithInheritedNested.class).getDiscoveryIssues(); assertThat(discoveryIssues).hasSize(1); assertThat(discoveryIssues.getFirst().source()) // .contains(ClassSource.from(InterfaceWithNestedClass.NestedInInterface.class)); var executionResults = executeTests(request -> request // .selectors(selectClass(TestCaseWithInheritedNested.class)) // .configurationParameter(CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME, Severity.ERROR.name())); Events containers = executionResults.containerEvents(); Events tests = executionResults.testEvents(); assertEquals(3, tests.started().count(), "# tests started"); assertEquals(2, tests.succeeded().count(), "# tests succeeded"); assertEquals(1, tests.failed().count(), "# tests failed"); assertEquals(4, containers.started().count(), "# containers started"); assertEquals(4, containers.finished().count(), "# containers finished"); } @Test void extendedNestedTestsAreExecuted() { var discoveryIssues = discoverTestsForClass(TestCaseWithExtendedNested.class).getDiscoveryIssues(); assertThat(discoveryIssues).hasSize(1); assertThat(discoveryIssues.getFirst().source()) // .contains(ClassSource.from(InterfaceWithNestedClass.NestedInInterface.class)); var executionResults = executeTests(request -> request // .selectors(selectClass(TestCaseWithExtendedNested.class)) // .configurationParameter(CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME, Severity.ERROR.name())); Events containers = executionResults.containerEvents(); Events tests = executionResults.testEvents(); assertEquals(6, tests.started().count(), "# tests started"); assertEquals(4, tests.succeeded().count(), "# tests succeeded"); assertEquals(2, tests.failed().count(), "# tests failed"); assertEquals(8, containers.started().count(), "# containers started"); assertEquals(8, containers.finished().count(), "# containers finished"); } @Test void deeplyNestedInheritedMethodsAreExecutedWhenSelectedViaUniqueId() { var selectors = List.of( // selectUniqueId( "[engine:junit-jupiter]/[class:org.junit.jupiter.engine.NestedTestClassesTests$TestCaseWithExtendedNested]/[nested-class:ConcreteInner1]/[nested-class:NestedInAbstractClass]/[nested-class:SecondLevelInherited]/[method:test()]"), selectUniqueId( "[engine:junit-jupiter]/[class:org.junit.jupiter.engine.NestedTestClassesTests$TestCaseWithExtendedNested]/[nested-class:ConcreteInner2]/[nested-class:NestedInAbstractClass]/[nested-class:SecondLevelInherited]/[method:test()]")); var discoveryIssues = discoverTests(request -> request.selectors(selectors)).getDiscoveryIssues(); assertThat(discoveryIssues).hasSize(1); assertThat(discoveryIssues.getFirst().source()) // .contains(ClassSource.from(InterfaceWithNestedClass.NestedInInterface.class)); var executionResults = executeTests(request -> request // .selectors(selectors) // .configurationParameter(CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME, Severity.ERROR.name())); Events containers = executionResults.containerEvents(); Events tests = executionResults.testEvents(); assertEquals(2, tests.started().count(), "# tests started"); assertEquals(2, tests.succeeded().count(), "# tests succeeded"); assertEquals(0, tests.failed().count(), "# tests failed"); assertEquals(8, containers.started().count(), "# containers started"); assertEquals(8, containers.finished().count(), "# containers finished"); } /** * @since 1.6 */ @Test void recursiveNestedTestClassHierarchiesAreNotExecuted() { assertNestedCycle(OuterClass.class, RecursiveNestedClass.class, OuterClass.class); assertNestedCycle(NestedClass.class, RecursiveNestedClass.class, OuterClass.class); assertNestedCycle(RecursiveNestedClass.class, RecursiveNestedClass.class, OuterClass.class); } /** * NOTE: We do not actually support this as a feature, but we currently only * check for cycles if a class is selected. Thus, the tests in this method * pass, since the selection of a particular method does not result in a * lookup for nested test classes. * * @since 1.6 */ @Test void individualMethodsWithinRecursiveNestedTestClassHierarchiesAreExecuted() { EngineExecutionResults executionResults = executeTests(selectMethod(OuterClass.class, "outer")); executionResults.containerEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); executionResults.testEvents().assertStatistics(stats -> stats.started(1).succeeded(1)); executionResults = executeTests(selectMethod(NestedClass.class, "nested")); executionResults.containerEvents().assertStatistics(stats -> stats.started(3).succeeded(3)); executionResults.testEvents().assertStatistics(stats -> stats.started(1).succeeded(1)); executionResults = executeTests(selectMethod(RecursiveNestedClass.class, "nested")); executionResults.containerEvents().assertStatistics(stats -> stats.started(4).succeeded(4)); executionResults.testEvents().assertStatistics(stats -> stats.started(1).succeeded(1)); executionResults = executeTests(selectMethod(RecursiveNestedSiblingClass.class, "nested")); executionResults.containerEvents().assertStatistics(stats -> stats.started(4).succeeded(4)); executionResults.testEvents().assertStatistics(stats -> stats.started(1).succeeded(1)); } @Test void doesNotReportDiscoveryIssueForClassWithAbstractInnerClass() { var discoveryIssues = discoverTestsForClass(ConcreteWithExtendedInnerClassTestCase.class).getDiscoveryIssues(); assertThat(discoveryIssues).isEmpty(); } @Test void doesNotReportDiscoveryIssueForAbstractInnerClass() { var discoveryIssues = discoverTestsForClass( AbstractBaseWithInnerClassTestCase.AbstractInnerClass.class).getDiscoveryIssues(); assertThat(discoveryIssues).isEmpty(); } @Test void nestedTestsWithCustomAnnotationAreCorrectlyDiscovered() { LauncherDiscoveryRequest request = defaultRequest().selectors( selectClass(CustomAnnotationTestCase.class)).build(); TestDescriptor engineDescriptor = discoverTestsWithoutIssues(request); assertEquals(3, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @ParameterizedTest @ValueSource(classes = { TopLevelComposedNested.class, CustomAnnotationTestCase.MyNested.class }) void ignoresComposedAnnotations(Class annotationType) { LauncherDiscoveryRequest request = defaultRequest().selectors(selectClass(annotationType)).build(); TestDescriptor engineDescriptor = discoverTestsWithoutIssues(request); assertEquals(0, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } private void assertNestedCycle(Class start, Class from, Class to) { var results = executeTestsForClass(start); var expectedMessage = "Cause: org.junit.platform.commons.JUnitException: Detected cycle in inner class hierarchy between %s and %s".formatted( from.getName(), to.getName()); results.containerEvents().assertThatEvents() // .haveExactly(1, finishedWithFailure(message(it -> it.contains(expectedMessage)))); } @Test void discoversButWarnsAboutTopLevelNestedTestClasses() { var results = discoverTestsForClass(TopLevelNestedTestCase.class); var engineDescriptor = results.getEngineDescriptor(); assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); var discoveryIssues = results.getDiscoveryIssues(); assertThat(discoveryIssues).hasSize(1); assertThat(discoveryIssues.getFirst().message()) // .isEqualTo( "Top-level class '%s' must not be annotated with @Nested. " + "It will be executed anyway for backward compatibility. " + "You should remove the @Nested annotation to resolve this warning.", TopLevelNestedTestCase.class.getName()); } @Test void discoversButWarnsAboutStaticNestedTestClasses() { var results = discoverTestsForClass(StaticNestedTestCase.TestCase.class); var engineDescriptor = results.getEngineDescriptor(); assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); var discoveryIssues = results.getDiscoveryIssues(); assertThat(discoveryIssues).hasSize(1); assertThat(discoveryIssues.getFirst().message()) // .isEqualTo( "@Nested class '%s' must not be static. " + "It will only be executed if discovered as a standalone test class. " + "You should remove the annotation or make it non-static to resolve this warning.", StaticNestedTestCase.TestCase.class.getName()); } // ------------------------------------------------------------------- static class TestCaseWithNesting { @Test void someTest() { } @Nested class NestedTestCase { @Test void successful() { } @Test void failing() { Assertions.fail("Something went horribly wrong"); } } } static class TestCaseWithDoubleNesting { static int beforeTopCount = 0; static int beforeNestedCount = 0; static int beforeDoublyNestedCount = 0; static int afterTopCount = 0; static int afterNestedCount = 0; static int afterDoublyNestedCount = 0; @BeforeEach void beforeTop() { beforeTopCount++; } @AfterEach void afterTop() { afterTopCount++; } @Test void someTest() { } @Nested class NestedTestCase { @BeforeEach void beforeNested() { beforeNestedCount++; } @AfterEach void afterNested() { afterNestedCount++; } @Test void successful() { } @Test void failing() { Assertions.fail("Something went horribly wrong"); } @Nested class DoublyNestedTestCase { @BeforeEach void beforeDoublyNested() { beforeDoublyNestedCount++; } @BeforeEach void afterDoublyNested() { afterDoublyNestedCount++; } @Test void successful() { } @Test void failing() { Assertions.fail("Something went horribly wrong"); } } } } interface InterfaceWithNestedClass { @SuppressWarnings({ "JUnitMalformedDeclaration", "NewClassNamingConvention" }) @Nested class NestedInInterface { @Test void notExecutedByImplementingClass() { Assertions.fail("class in interface is static and should have been filtered out"); } } } static abstract class AbstractSuperClass implements InterfaceWithNestedClass { @Nested class NestedInAbstractClass { @Test void successful() { } @Test void failing() { Assertions.fail("something went wrong"); } @Nested class SecondLevelInherited { @Test void test() { } } } } static class TestCaseWithInheritedNested extends AbstractSuperClass { // empty on purpose } static class TestCaseWithExtendedNested { @Nested class ConcreteInner1 extends AbstractSuperClass { } @Nested class ConcreteInner2 extends AbstractSuperClass { } } static class AbstractOuterClass { } @SuppressWarnings("NewClassNamingConvention") static class OuterClass extends AbstractOuterClass { @Test void outer() { } @Nested class NestedClass { @Test void nested() { } @Nested class RecursiveNestedClass extends OuterClass { @Test void nested() { } } @Nested // sibling of OuterClass due to common super type class RecursiveNestedSiblingClass extends AbstractOuterClass { @Test void nested() { } } } } static class AbstractBaseWithInnerClassTestCase { @SuppressWarnings("InnerClassMayBeStatic") abstract class AbstractInnerClass { @Test void test() { } } } static class ConcreteWithExtendedInnerClassTestCase extends AbstractBaseWithInnerClassTestCase { @Nested class NestedTests extends AbstractInnerClass { } } static class CustomAnnotationTestCase { @Nested @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @interface MyNested { } @SuppressWarnings({ "JUnitMalformedDeclaration", "InnerClassMayBeStatic" }) @MyNested class Inner { @Test void test() { } } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/NestedWithInheritanceTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import static org.assertj.core.api.Assertions.assertThat; import java.util.ArrayList; import java.util.List; import org.jspecify.annotations.NullUnmarked; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @NullUnmarked class NestedWithInheritanceTests extends SuperClass { static List lifecycleInvokingClassNames; static String OUTER = NestedWithInheritanceTests.class.getSimpleName(); static String NESTED = NestedClass.class.getSimpleName(); static String NESTEDNESTED = NestedClass.NestedNestedClass.class.getSimpleName(); @Nested class NestedClass extends SuperClass { @Test public void test() { assertThat(lifecycleInvokingClassNames).containsExactly(OUTER, NESTED); } @Nested class NestedNestedClass extends SuperClass { @Test public void test() { assertThat(lifecycleInvokingClassNames).containsExactly(OUTER, NESTED, NESTEDNESTED); } } } } class SuperClass { @BeforeAll static void setup() { NestedWithInheritanceTests.lifecycleInvokingClassNames = new ArrayList<>(); } @BeforeEach public void beforeEach() { String invokingClass = this.getClass().getSimpleName(); NestedWithInheritanceTests.lifecycleInvokingClassNames.add(invokingClass); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/NestedWithSeparateInheritanceTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import java.util.ArrayList; import java.util.List; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; class NestedWithSeparateInheritanceTests extends SuperClass1 { static @Nullable List lifecycleInvokingClassNames; static String OUTER = NestedWithSeparateInheritanceTests.class.getSimpleName(); static String NESTED = NestedClass.class.getSimpleName(); static String NESTEDNESTED = NestedClass.NestedNestedClass.class.getSimpleName(); @Nested class NestedClass extends SuperClass2 { @Test public void test() { assertThat(lifecycleInvokingClassNames).containsExactly(OUTER, NESTED); } @Nested class NestedNestedClass extends SuperClass3 { @Test public void test() { assertThat(lifecycleInvokingClassNames).containsExactly(OUTER, NESTED, NESTEDNESTED); } } } } class SuperClass1 { @BeforeAll static void setup() { NestedWithSeparateInheritanceTests.lifecycleInvokingClassNames = new ArrayList<>(); } @BeforeEach public void beforeEach() { String invokingClass = this.getClass().getSimpleName(); requireNonNull(NestedWithSeparateInheritanceTests.lifecycleInvokingClassNames).add(invokingClass); } } class SuperClass2 { @BeforeAll static void setup() { NestedWithSeparateInheritanceTests.lifecycleInvokingClassNames = new ArrayList<>(); } @BeforeEach public void beforeEach() { String invokingClass = this.getClass().getSimpleName(); requireNonNull(NestedWithSeparateInheritanceTests.lifecycleInvokingClassNames).add(invokingClass); } } class SuperClass3 { @BeforeAll static void setup() { NestedWithSeparateInheritanceTests.lifecycleInvokingClassNames = new ArrayList<>(); } @BeforeEach public void beforeEach() { String invokingClass = this.getClass().getSimpleName(); requireNonNull(NestedWithSeparateInheritanceTests.lifecycleInvokingClassNames).add(invokingClass); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/OverloadedTestMethodTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import java.util.Optional; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; import org.junit.platform.engine.UniqueId; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.testkit.engine.Event; import org.junit.platform.testkit.engine.Events; /** * Integration tests for support of overloaded test methods in conjunction with * the {@link JupiterTestEngine}. * * @since 5.0 */ class OverloadedTestMethodTests extends AbstractJupiterTestEngineTests { @Test void executeTestCaseWithOverloadedMethodsAndThenRerunOnlyOneOfTheMethodsSelectedByUniqueId() { Events tests = executeTestsForClass(TestCase.class).testEvents(); tests.assertStatistics(stats -> stats.started(2).succeeded(2).failed(0)); Optional first = tests.succeeded().filter( event -> event.getTestDescriptor().getUniqueId().toString().contains(TestInfo.class.getName())).findFirst(); assertTrue(first.isPresent()); TestIdentifier testIdentifier = TestIdentifier.from(first.get().getTestDescriptor()); UniqueId uniqueId = testIdentifier.getUniqueIdObject(); tests = executeTests(selectUniqueId(uniqueId)).testEvents(); tests.assertStatistics(stats -> stats.started(1).succeeded(1).failed(0)); first = tests.succeeded().filter( event -> event.getTestDescriptor().getUniqueId().toString().contains(TestInfo.class.getName())).findFirst(); assertTrue(first.isPresent()); } @Test void executeTestCaseWithOverloadedMethodsWithSingleMethodThatAcceptsArgumentsSelectedByFullyQualifiedMethodName() { String fqmn = TestCase.class.getName() + "#test(" + TestInfo.class.getName() + ")"; Events tests = executeTests(selectMethod(fqmn)).testEvents(); tests.assertStatistics(stats -> stats.started(1).succeeded(1).failed(0)); Optional first = tests.succeeded().stream().filter( event -> event.getTestDescriptor().getUniqueId().toString().contains(TestInfo.class.getName())).findFirst(); assertTrue(first.isPresent()); } static class TestCase { @Test void test() { } @Test void test(TestInfo testInfo) { } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/RecordTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import org.junit.jupiter.api.Test; class RecordTests extends AbstractJupiterTestEngineTests { @Test void recordsAreTestClasses() { executeTestsForClass(TestCase.class).testEvents() // .assertStatistics(stats -> stats.finished(2).succeeded(1).failed(1)); } record TestCase() { @Test void succeedingTest() { assertTrue(true); } @Test void failingTest() { fail("always fails"); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/ReportingTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import static org.junit.jupiter.api.Constants.DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.junit.platform.launcher.core.OutputDirectoryCreators.hierarchicalOutputDirectoryCreator; import static org.junit.platform.testkit.engine.EventConditions.fileEntry; import static org.junit.platform.testkit.engine.EventConditions.reportEntry; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; import java.util.Map; import java.util.function.Predicate; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.MediaType; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.TestReporter; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.junit.platform.engine.reporting.FileEntry; /** * @since 5.0 */ class ReportingTests extends AbstractJupiterTestEngineTests { @ParameterizedTest @CsvSource(textBlock = """ PER_CLASS, 1, 7, 5 PER_METHOD, 0, 9, 7 """) void reportAndFileEntriesArePublished(Lifecycle lifecycle, int containerEntries, int testReportEntries, int testFileEntries, @TempDir Path tempDir) { var request = request() // .selectors(selectClass(MyReportingTestCase.class)) // .configurationParameter(DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME, lifecycle.name()) // .outputDirectoryCreator(hierarchicalOutputDirectoryCreator(tempDir)); var results = executeTests(request); results // .containerEvents() // .assertStatistics(stats -> stats // .started(2) // .succeeded(2) // .reportingEntryPublished(containerEntries) // .fileEntryPublished(containerEntries)); results // .testEvents() // .assertStatistics(stats -> stats // .started(2) // .succeeded(2) // .reportingEntryPublished(testReportEntries) // .fileEntryPublished(testFileEntries)) // .assertThatEvents() // .haveExactly(2, reportEntry(Map.of("value", "@BeforeEach"))) // .haveExactly(2, fileEntry(nameAndContent("beforeEach", MediaType.TEXT_PLAIN_UTF_8))) // .haveExactly(1, reportEntry(Map.of())) // .haveExactly(1, reportEntry(Map.of("user name", "dk38"))) // .haveExactly(1, reportEntry(Map.of("value", "message"))) // .haveExactly(1, fileEntry(nameAndContent("succeedingTest", MediaType.APPLICATION_OCTET_STREAM))) // .haveExactly(2, reportEntry(Map.of("value", "@AfterEach"))) // .haveExactly(2, fileEntry(nameAndContent("afterEach", MediaType.TEXT_PLAIN_UTF_8))); } static class MyReportingTestCase { MyReportingTestCase(TestReporter reporter) { // Reported on class-level for PER_CLASS lifecycle and on method-level for PER_METHOD lifecycle reporter.publishEntry("Constructor"); reporter.publishFile("constructor", MediaType.TEXT_PLAIN_UTF_8, file -> Files.writeString(file, "constructor")); } @BeforeEach void beforeEach(TestReporter reporter) { reporter.publishEntry("@BeforeEach"); reporter.publishFile("beforeEach", MediaType.TEXT_PLAIN_UTF_8, file -> Files.writeString(file, "beforeEach")); } @AfterEach void afterEach(TestReporter reporter) { reporter.publishEntry("@AfterEach"); reporter.publishFile("afterEach", MediaType.TEXT_PLAIN_UTF_8, file -> Files.writeString(file, "afterEach")); } @Test void succeedingTest(TestReporter reporter) { reporter.publishEntry(Map.of()); reporter.publishEntry("user name", "dk38"); reporter.publishEntry("message"); reporter.publishFile("succeedingTest", MediaType.APPLICATION_OCTET_STREAM, file -> Files.writeString(file, "succeedingTest")); } @SuppressWarnings("DataFlowIssue") @Test void invalidReportData(TestReporter reporter) { // Maps Map map = new HashMap<>(); map.put("key", null); assertPreconditionViolationFor(() -> reporter.publishEntry(map)); map.clear(); map.put(null, "value"); assertPreconditionViolationFor(() -> reporter.publishEntry(map)); assertPreconditionViolationFor(() -> reporter.publishEntry((Map) null)); // Key-Value pair assertPreconditionViolationFor(() -> reporter.publishEntry(null, "bar")); assertPreconditionViolationFor(() -> reporter.publishEntry("foo", null)); // Value assertPreconditionViolationFor(() -> reporter.publishEntry((String) null)); } } private static Predicate nameAndContent(String expectedName, MediaType mediaType) { Predicate filePredicate = file -> { try { return Path.of(expectedName).equals(file.getFileName()) && expectedName.equals(Files.readString(file)); } catch (IOException e) { throw new UncheckedIOException(e); } }; return fileEntry -> filePredicate.test(fileEntry.getPath()) // && mediaType.toString().equals(fileEntry.getMediaType().orElse(null)); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/SealedClassTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import org.junit.jupiter.api.Test; class SealedClassTests extends AbstractJupiterTestEngineTests { @Test void sealedTestClassesAreTestClasses() { executeTestsForClass(TestCase.class).testEvents() // .assertStatistics(stats -> stats.finished(2).succeeded(1).failed(1)); } sealed abstract static class AbstractTestCase permits TestCase { @Test void succeedingTest() { assertTrue(true); } @Test void failingTest() { fail("always fails"); } } static final class TestCase extends AbstractTestCase { } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/StandardTestClassTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasses; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.testkit.engine.EngineExecutionResults; import org.junit.platform.testkit.engine.Events; import org.opentest4j.TestAbortedException; /** * Tests for discovery and execution of standard test cases for the * {@link JupiterTestEngine}. * * @since 5.0 */ class StandardTestClassTests extends AbstractJupiterTestEngineTests { @Test void standardTestClassIsCorrectlyDiscovered() { LauncherDiscoveryRequest request = request().selectors(selectClass(MyStandardTestCase.class)).build(); TestDescriptor engineDescriptor = discoverTests(request).getEngineDescriptor(); assertEquals(1 /*class*/ + 6 /*methods*/, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @Test void moreThanOneTestClassIsCorrectlyDiscovered() { LauncherDiscoveryRequest request = // request().selectors(selectClasses(FirstOfTwoTestCases.class, SecondOfTwoTestCases.class)).build(); TestDescriptor engineDescriptor = discoverTests(request).getEngineDescriptor(); assertEquals(2 /*class*/ + 6 /*methods*/, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @Test void moreThanOneTestClassIsExecuted() { LauncherDiscoveryRequest request = // request().selectors(selectClasses(FirstOfTwoTestCases.class, SecondOfTwoTestCases.class)).build(); EngineExecutionResults executionResults = executeTests(request); Events containers = executionResults.containerEvents(); Events tests = executionResults.testEvents(); assertEquals(6, tests.started().count(), "# tests started"); assertEquals(5, tests.succeeded().count(), "# tests succeeded"); assertEquals(1, tests.failed().count(), "# tests failed"); assertEquals(3, containers.started().count(), "# containers started"); assertEquals(3, containers.finished().count(), "# containers finished"); } @Test void allTestsInClassAreRunWithBeforeEachAndAfterEachMethods() { EngineExecutionResults executionResults = executeTestsForClass(MyStandardTestCase.class); Events containers = executionResults.containerEvents(); Events tests = executionResults.testEvents(); assertEquals(2, containers.started().count(), "# containers started"); assertEquals(2, containers.finished().count(), "# containers finished"); assertEquals(6, tests.started().count(), "# tests started"); assertEquals(2, tests.succeeded().count(), "# tests succeeded"); assertEquals(3, tests.aborted().count(), "# tests aborted"); assertEquals(1, tests.failed().count(), "# tests failed"); assertEquals(6, MyStandardTestCase.countBefore1, "# before1 calls"); assertEquals(6, MyStandardTestCase.countBefore2, "# before2 calls"); assertEquals(6, MyStandardTestCase.countAfter, "# after each calls"); } @Test void testsFailWhenBeforeEachFails() { EngineExecutionResults executionResults = executeTestsForClass(TestCaseWithFailingBefore.class); Events containers = executionResults.containerEvents(); Events tests = executionResults.testEvents(); assertEquals(2, tests.started().count(), "# tests started"); assertEquals(0, tests.succeeded().count(), "# tests succeeded"); assertEquals(2, tests.failed().count(), "# tests failed"); assertEquals(2, containers.started().count(), "# containers started"); assertEquals(2, containers.finished().count(), "# containers finished"); assertEquals(2, TestCaseWithFailingBefore.countBefore, "# before each calls"); } @Test void testsFailWhenAfterEachFails() { EngineExecutionResults executionResults = executeTestsForClass(TestCaseWithFailingAfter.class); Events containers = executionResults.containerEvents(); Events tests = executionResults.testEvents(); assertEquals(1, tests.started().count(), "# tests started"); assertEquals(0, tests.succeeded().count(), "# tests succeeded"); assertEquals(1, tests.failed().count(), "# tests failed"); assertEquals(2, containers.started().count(), "# containers started"); assertEquals(2, containers.finished().count(), "# containers finished"); assertTrue(TestCaseWithFailingAfter.testExecuted, "test executed?"); } static class MyStandardTestCase { static int countBefore1 = 0; static int countBefore2 = 0; static int countAfter = 0; @BeforeEach void before1() { countBefore1++; } @BeforeEach void before2() { countBefore2++; } @AfterEach void after() { countAfter++; } @Test void succeedingTest1() { assertTrue(true); } @Test void succeedingTest2() { assertTrue(true); } @Test void failingTest() { fail("always fails"); } @Test @DisplayName("Test aborted via the OTA's TestAbortedException") void testAbortedOpenTest4J() { throw new TestAbortedException("aborted!"); } @Test @DisplayName("Test aborted via JUnit 4's AssumptionViolatedException") void testAbortedJUnit4() { throw new org.junit.AssumptionViolatedException("aborted!"); } @Test @DisplayName("Test aborted via JUnit 4's legacy, deprecated AssumptionViolatedException") @SuppressWarnings("deprecation") void testAbortedJUnit4Legacy() { throw new org.junit.internal.AssumptionViolatedException("aborted!"); } } @SuppressWarnings("NewClassNamingConvention") static class FirstOfTwoTestCases { @Test void succeedingTest1() { assertTrue(true); } @Test void succeedingTest2() { assertTrue(true); } @Test void failingTest() { fail("always fails"); } } @SuppressWarnings("NewClassNamingConvention") static class SecondOfTwoTestCases { @Test void succeedingTest1() { assertTrue(true); } @Test void succeedingTest2() { assertTrue(true); } @Test void succeedingTest3() { assertTrue(true); } } static class TestCaseWithFailingBefore { static int countBefore = 0; @BeforeEach void before() { countBefore++; throw new RuntimeException("Problem during setup"); } @Test void test1() { } @Test void test2() { } } static class TestCaseWithFailingAfter { static boolean testExecuted = false; @AfterEach void after() { throw new RuntimeException("Problem during 'after'"); } @Test void test1() { testExecuted = true; } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/StaticNestedBeforeAllAndAfterAllMethodsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import static org.assertj.core.api.Assertions.assertThat; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; /** * Integration tests that verify support for {@code static} {@link BeforeAll} and * {@link AfterAll} methods in {@link Nested} tests. * * @since 5.9 * @see BeforeAllAndAfterAllComposedAnnotationTests */ class StaticNestedBeforeAllAndAfterAllMethodsTests extends AbstractJupiterTestEngineTests { private static final List methodsInvoked = new ArrayList<>(); @DisplayName("static @BeforeAll and @AfterAll methods in @Nested test class") @Test void staticBeforeAllAndAfterAllMethodsInNestedTestClass() { executeTestsForClass(TestCase.class).testEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); assertThat(methodsInvoked).containsExactly(// "@BeforeAll: top-level", // "@Test: top-level", // "@BeforeAll: nested", // "@Test: nested", // "@AfterAll: nested", // "@AfterAll: top-level"// ); } static class TestCase { @BeforeAll static void beforeAll() { methodsInvoked.add("@BeforeAll: top-level"); } @Test void test() { methodsInvoked.add("@Test: top-level"); } @AfterAll static void afterAll() { methodsInvoked.add("@AfterAll: top-level"); } @Nested // Lifecycle.PER_METHOD is the default, but we declare it here in order // to be very explicit about what we are testing, namely static lifecycle // methods in an inner class WITHOUT Lifecycle.PER_CLASS semantics. @TestInstance(Lifecycle.PER_METHOD) class NestedTestCase { @BeforeAll static void beforeAllInner() { methodsInvoked.add("@BeforeAll: nested"); } @Test void test() { methodsInvoked.add("@Test: nested"); } @AfterAll static void afterAllInner() { methodsInvoked.add("@AfterAll: nested"); } } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/StaticNestedTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; class StaticNestedTestCase { @SuppressWarnings("JUnitMalformedDeclaration") @Nested static class TestCase { @Test void test() { } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/TestClassInheritanceTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import static java.util.Arrays.asList; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assumptions.abort; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import java.util.ArrayList; import java.util.List; 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.Test; import org.junit.platform.testkit.engine.EngineExecutionResults; /** * Integration tests for test class hierarchy support in the {@link JupiterTestEngine}. * * @since 5.0 */ class TestClassInheritanceTests extends AbstractJupiterTestEngineTests { private static final List callSequence = new ArrayList<>(); @BeforeEach void initStatics() { callSequence.clear(); LocalTestCase.countBeforeInvoked = 0; LocalTestCase.countAfterInvoked = 0; AbstractTestCase.countSuperBeforeInvoked = 0; AbstractTestCase.countSuperAfterInvoked = 0; } @Test void executeAllTestsInClass() { EngineExecutionResults executionResults = executeTestsForClass(LocalTestCase.class); assertEquals(6, executionResults.testEvents().started().count(), "# tests started"); assertEquals(3, executionResults.testEvents().succeeded().count(), "# tests succeeded"); assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); assertEquals(1, executionResults.testEvents().aborted().count(), "# tests aborted"); assertEquals(2, executionResults.testEvents().failed().count(), "# tests failed"); assertEquals(6, LocalTestCase.countBeforeInvoked, "# before calls"); assertEquals(6, LocalTestCase.countAfterInvoked, "# after calls"); assertEquals(6, AbstractTestCase.countSuperBeforeInvoked, "# super before calls"); assertEquals(6, AbstractTestCase.countSuperAfterInvoked, "# super after calls"); } @Test void executeSingleTest() { EngineExecutionResults executionResults = executeTests(selectMethod(LocalTestCase.class, "alwaysPasses")); assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); } @Test void executeTestDeclaredInSuperClass() { EngineExecutionResults executionResults = executeTests(selectMethod(LocalTestCase.class, "superTest")); assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); assertEquals(1, LocalTestCase.countBeforeInvoked, "# after calls"); assertEquals(1, LocalTestCase.countAfterInvoked, "# after calls"); assertEquals(1, AbstractTestCase.countSuperBeforeInvoked, "# super before calls"); assertEquals(1, AbstractTestCase.countSuperAfterInvoked, "# super after calls"); } @Test void executeTestWithExceptionThrownInAfterMethod() { EngineExecutionResults executionResults = executeTests( selectMethod(LocalTestCase.class, "throwExceptionInAfterMethod")); assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); assertEquals(0, executionResults.testEvents().succeeded().count(), "# tests succeeded"); assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); assertEquals(1, executionResults.testEvents().failed().count(), "# tests failed"); } @Test void beforeAndAfterMethodsInTestClassHierarchy() { EngineExecutionResults executionResults = executeTestsForClass(TestCase3.class); // @formatter:off assertAll( () -> assertEquals(1, executionResults.testEvents().started().count(), "# tests started"), () -> assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"), () -> assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"), () -> assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"), () -> assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed") ); // @formatter:on // @formatter:off assertEquals(asList( "beforeAll1", "beforeAll2", "beforeAll3", "beforeEach1", "beforeEach2", "beforeEach3", "test3", "afterEach3", "afterEach2", "afterEach1", "afterAll3", "afterAll2", "afterAll1" ), callSequence, "wrong call sequence"); // @formatter:on } // ------------------------------------------------------------------- private static abstract class AbstractTestCase { static int countSuperBeforeInvoked = 0; static int countSuperAfterInvoked = 0; @BeforeEach void superBefore() { countSuperBeforeInvoked++; } @AfterEach void superAfter() { countSuperAfterInvoked++; } @Test void superTest() { /* no-op */ } } static class LocalTestCase extends AbstractTestCase { boolean throwExceptionInAfterMethod = false; static int countBeforeInvoked = 0; static int countAfterInvoked = 0; @BeforeEach void before() { countBeforeInvoked++; // Reset state, since the test instance is retained across all test methods; // otherwise, after() always throws an exception. this.throwExceptionInAfterMethod = false; } @AfterEach void after() { countAfterInvoked++; if (this.throwExceptionInAfterMethod) { throw new RuntimeException("Exception thrown from @AfterEach method"); } } @Test void otherTest() { /* no-op */ } @Test void throwExceptionInAfterMethod() { this.throwExceptionInAfterMethod = true; } @Test void alwaysPasses() { /* no-op */ } @Test void aborted() { abort(); } @Test void alwaysFails() { fail("#fail"); } } static class TestCase1 { @BeforeAll static void beforeAll1() { callSequence.add("beforeAll1"); } @BeforeEach void beforeEach1() { callSequence.add("beforeEach1"); } @AfterEach void afterEach1() { callSequence.add("afterEach1"); } @AfterAll static void afterAll1() { callSequence.add("afterAll1"); } } static class TestCase2 extends TestCase1 { @BeforeAll static void beforeAll2() { callSequence.add("beforeAll2"); } @BeforeEach void beforeEach2() { callSequence.add("beforeEach2"); } @AfterEach void afterEach2() { callSequence.add("afterEach2"); } @AfterAll static void afterAll2() { callSequence.add("afterAll2"); } } static class TestCase3 extends TestCase2 { @BeforeAll static void beforeAll3() { callSequence.add("beforeAll3"); } @BeforeEach void beforeEach3() { callSequence.add("beforeEach3"); } @Test void test3() { callSequence.add("test3"); } @AfterEach void afterEach3() { callSequence.add("afterEach3"); } @AfterAll static void afterAll3() { callSequence.add("afterAll3"); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleConfigurationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.launcher.LauncherConstants.DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; 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.Constants; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.util.ClearSystemProperty; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.launcher.Launcher; import org.junit.platform.testkit.engine.EngineExecutionResults; /** * Integration tests for {@link TestInstance @TestInstance} lifecycle * configuration support, not to be confused with {@link TestInstanceLifecycleTests}. * *

Specifically, this class tests custom lifecycle configuration specified * via {@code @TestInstance} as well as via {@link ConfigurationParameters} * supplied to the {@link Launcher} or via a JVM system property. * * @since 5.0 * @see TestInstanceLifecycleTests */ @ClearSystemProperty(key = TestInstanceLifecycleConfigurationTests.KEY) class TestInstanceLifecycleConfigurationTests extends AbstractJupiterTestEngineTests { static final String KEY = Constants.DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME; private static final List methodsInvoked = new ArrayList<>(); @BeforeEach @AfterEach void reset() { methodsInvoked.clear(); } @Test void instancePerMethodUsingStandardDefaultConfiguration() { performAssertions(AssumedInstancePerTestMethodTestCase.class, 2, 0, 1, "beforeAll", "test", "afterAll"); } @Test void instancePerClassConfiguredViaExplicitAnnotationDeclaration() { performAssertions(ExplicitInstancePerClassTestCase.class, 2, 0, 1, "beforeAll", "test", "afterAll"); } @Test void instancePerClassConfiguredViaSystemProperty() { Class testClass = AssumedInstancePerClassTestCase.class; // Should fail by default... performAssertions(testClass, 1, 1, 0); // Should pass with the system property set System.setProperty(KEY, PER_CLASS.name()); performAssertions(testClass, 2, 0, 1, "beforeAll", "test", "afterAll"); } @Test void instancePerClassConfiguredViaConfigParam() { Class testClass = AssumedInstancePerClassTestCase.class; // Should fail by default... performAssertions(testClass, 1, 1, 0); // Should pass with the config param performAssertions(testClass, Map.of(KEY, PER_CLASS.name()), 2, 0, 1, "beforeAll", "test", "afterAll"); } @Test void instancePerClassConfiguredViaConfigParamThatOverridesSystemProperty() { Class testClass = AssumedInstancePerClassTestCase.class; // Should fail with system property System.setProperty(KEY, PER_METHOD.name()); performAssertions(testClass, 1, 1, 0); // Should pass with the config param performAssertions(testClass, Map.of(KEY, PER_CLASS.name()), 2, 0, 1, "beforeAll", "test", "afterAll"); } @Test void instancePerMethodConfiguredViaExplicitAnnotationDeclarationThatOverridesSystemProperty() { System.setProperty(KEY, PER_CLASS.name()); performAssertions(ExplicitInstancePerTestMethodTestCase.class, 2, 0, 1, "beforeAll", "test", "afterAll"); } @Test void instancePerMethodConfiguredViaExplicitAnnotationDeclarationThatOverridesConfigParam() { Class testClass = ExplicitInstancePerTestMethodTestCase.class; performAssertions(testClass, Map.of(KEY, PER_CLASS.name()), 2, 0, 1, "beforeAll", "test", "afterAll"); } private void performAssertions(Class testClass, int containers, int containersFailed, int tests, String... methods) { performAssertions(testClass, Map.of(), containers, containersFailed, tests, methods); } private void performAssertions(Class testClass, Map configParams, int numContainers, int numFailedContainers, int numTests, String... methods) { // @formatter:off EngineExecutionResults executionResults = executeTests( request() .selectors(selectClass(testClass)) .configurationParameters(configParams) .configurationParameter(DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME, "execution") .build() ); // @formatter:on executionResults.containerEvents().assertStatistics(// stats -> stats.started(numContainers).finished(numContainers).failed(numFailedContainers)); executionResults.testEvents().assertStatistics(// stats -> stats.started(numTests).finished(numTests)); assertEquals(Arrays.asList(methods), methodsInvoked); } // ------------------------------------------------------------------------- @TestInstance(PER_METHOD) static class ExplicitInstancePerTestMethodTestCase { @BeforeAll static void beforeAll() { methodsInvoked.add("beforeAll"); } @Test void test() { methodsInvoked.add("test"); } @AfterAll static void afterAllStatic() { methodsInvoked.add("afterAll"); } } /** * "per-method" lifecycle mode is assumed since the {@code @BeforeAll} and * {@code @AfterAll} methods are static, even though there is no explicit * {@code @TestInstance} declaration. */ static class AssumedInstancePerTestMethodTestCase { @BeforeAll static void beforeAll() { methodsInvoked.add("beforeAll"); } @Test void test() { methodsInvoked.add("test"); } @AfterAll static void afterAllStatic() { methodsInvoked.add("afterAll"); } } @TestInstance(PER_CLASS) static class ExplicitInstancePerClassTestCase { @BeforeAll void beforeAll(TestInfo testInfo) { methodsInvoked.add("beforeAll"); } @Test void test() { methodsInvoked.add("test"); } @AfterAll void afterAll(TestInfo testInfo) { methodsInvoked.add("afterAll"); } } /** * "per-class" lifecycle mode is assumed since the {@code @BeforeAll} and * {@code @AfterAll} methods are non-static, even though there is no * explicit {@code @TestInstance} declaration. */ static class AssumedInstancePerClassTestCase { @SuppressWarnings("JUnitMalformedDeclaration") @BeforeAll void beforeAll(TestInfo testInfo) { methodsInvoked.add("beforeAll"); } @Test void test() { methodsInvoked.add("test"); } @SuppressWarnings("JUnitMalformedDeclaration") @AfterAll void afterAll(TestInfo testInfo) { methodsInvoked.add("afterAll"); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleKotlinTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.engine.kotlin.InstancePerClassKotlinTestCase; import org.junit.jupiter.engine.kotlin.InstancePerMethodKotlinTestCase; import org.junit.platform.testkit.engine.EngineExecutionResults; /** * Kotlin-specific integration tests for {@link TestInstance @TestInstance} * lifecycle support. * * @since 5.1 * @see TestInstanceLifecycleConfigurationTests * @see TestInstanceLifecycleTests */ class TestInstanceLifecycleKotlinTests extends AbstractJupiterTestEngineTests { @Test void instancePerClassCanBeUsedForKotlinTestClasses() { Class testClass = InstancePerClassKotlinTestCase.class; InstancePerClassKotlinTestCase.TEST_INSTANCES.clear(); EngineExecutionResults executionResults = executeTestsForClass(testClass); assertThat(executionResults.testEvents().finished().count()).isEqualTo(2); assertThat(InstancePerClassKotlinTestCase.TEST_INSTANCES.keySet()).hasSize(1); assertThat(getOnlyElement(InstancePerClassKotlinTestCase.TEST_INSTANCES.values())) // .containsEntry("beforeAll", 1) // .containsEntry("beforeEach", 2) // .containsEntry("test", 2) // .containsEntry("afterEach", 2) // .containsEntry("afterAll", 1); } @Test void instancePerMethodIsDefaultForKotlinTestClasses() { Class testClass = InstancePerMethodKotlinTestCase.class; InstancePerMethodKotlinTestCase.TEST_INSTANCES.clear(); EngineExecutionResults executionResults = executeTestsForClass(testClass); assertThat(executionResults.testEvents().finished().count()).isEqualTo(2); List instances = new ArrayList<>(InstancePerMethodKotlinTestCase.TEST_INSTANCES.keySet()); assertThat(instances) // .hasSize(3) // .extracting(o -> (Object) o.getClass()) // .containsExactly(InstancePerMethodKotlinTestCase.Companion.getClass(), // InstancePerMethodKotlinTestCase.class, // InstancePerMethodKotlinTestCase.class); assertThat(InstancePerMethodKotlinTestCase.TEST_INSTANCES.get(instances.get(0))) // .containsEntry("beforeAll", 1) // .containsEntry("afterAll", 1); assertThat(InstancePerMethodKotlinTestCase.TEST_INSTANCES.get(instances.get(1))) // .containsEntry("beforeEach", 1) // .containsEntry("test", 1) // .containsEntry("afterEach", 1); assertThat(InstancePerMethodKotlinTestCase.TEST_INSTANCES.get(instances.get(2))) // .containsEntry("beforeEach", 1) // .containsEntry("test", 1) // .containsEntry("afterEach", 1); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import static java.lang.String.join; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; 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.ClassTemplate; import org.junit.jupiter.api.Constants; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ClassTemplateInvocationContext; import org.junit.jupiter.api.extension.ClassTemplateInvocationContextProvider; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestInstancePostProcessor; import org.junit.jupiter.api.extension.TestInstancePreDestroyCallback; import org.junit.jupiter.api.extension.TestInstances; import org.junit.jupiter.api.extension.TestTemplateInvocationContext; import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; import org.junit.jupiter.engine.execution.DefaultTestInstances; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.junit.platform.testkit.engine.EngineExecutionResults; /** * Integration tests for {@link TestInstance @TestInstance} lifecycle support. * * @since 5.0 * @see TestInstanceLifecycleConfigurationTests * @see TestInstanceLifecycleKotlinTests */ class TestInstanceLifecycleTests extends AbstractJupiterTestEngineTests { private static final Map, List> lifecyclesMap = new LinkedHashMap<>(); private static final Map instanceMap = new LinkedHashMap<>(); private static final List testsInvoked = new ArrayList<>(); private static final Map, Integer> instanceCount = new LinkedHashMap<>(); private static int beforeAllCount; private static int afterAllCount; private static int beforeEachCount; private static int afterEachCount; @BeforeEach void init() { lifecyclesMap.clear(); instanceMap.clear(); testsInvoked.clear(); instanceCount.clear(); beforeAllCount = 0; afterAllCount = 0; beforeEachCount = 0; afterEachCount = 0; } @SuppressWarnings("NullAway") @Test void instancePerMethod() { Class testClass = InstancePerMethodTestCase.class; int containers = 3; int tests = 3; Map.Entry, Integer>[] instances = instanceCounts(entry(InstancePerMethodTestCase.class, 3)); int allMethods = 1; int eachMethods = 3; performAssertions(testClass, containers, tests, instances, allMethods, eachMethods); String containerExecutionConditionKey = executionConditionKey(testClass, null); String postProcessTestInstanceKey = postProcessTestInstanceKey(testClass); String preDestroyCallbackTestInstanceKey = preDestroyCallbackTestInstanceKey(testClass); String beforeAllCallbackKey = beforeAllCallbackKey(testClass); String afterAllCallbackKey = afterAllCallbackKey(testClass); String testTemplateKey = testTemplateKey(testClass, "singletonTest"); String testExecutionConditionKey1 = executionConditionKey(testClass, testsInvoked.getFirst()); String beforeEachCallbackKey1 = beforeEachCallbackKey(testClass, testsInvoked.get(0)); String afterEachCallbackKey1 = afterEachCallbackKey(testClass, testsInvoked.get(0)); String testExecutionConditionKey2 = executionConditionKey(testClass, testsInvoked.get(1)); String beforeEachCallbackKey2 = beforeEachCallbackKey(testClass, testsInvoked.get(1)); String afterEachCallbackKey2 = afterEachCallbackKey(testClass, testsInvoked.get(1)); String testExecutionConditionKey3 = executionConditionKey(testClass, testsInvoked.get(2)); String beforeEachCallbackKey3 = beforeEachCallbackKey(testClass, testsInvoked.get(2)); String afterEachCallbackKey3 = afterEachCallbackKey(testClass, testsInvoked.get(2)); // @formatter:off assertThat(instanceMap.keySet()).containsExactlyInAnyOrder( containerExecutionConditionKey, beforeAllCallbackKey, postProcessTestInstanceKey, preDestroyCallbackTestInstanceKey, testTemplateKey, testExecutionConditionKey1, beforeEachCallbackKey1, afterEachCallbackKey1, testExecutionConditionKey2, beforeEachCallbackKey2, afterEachCallbackKey2, testExecutionConditionKey3, beforeEachCallbackKey3, afterEachCallbackKey3, afterAllCallbackKey ); // @formatter:on assertNull(instanceMap.get(containerExecutionConditionKey)); assertNull(instanceMap.get(beforeAllCallbackKey)); assertNull(instanceMap.get(afterAllCallbackKey)); TestInstances testInstances = instanceMap.get(beforeEachCallbackKey1); assertNotNull(testInstances.getInnermostInstance()); assertSame(testInstances, instanceMap.get(afterEachCallbackKey1)); assertSame(testInstances, instanceMap.get(testExecutionConditionKey1)); testInstances = instanceMap.get(beforeEachCallbackKey2); assertNotNull(testInstances.getInnermostInstance()); assertSame(testInstances, instanceMap.get(afterEachCallbackKey2)); assertSame(testInstances, instanceMap.get(testExecutionConditionKey2)); testInstances = instanceMap.get(beforeEachCallbackKey3); assertNotNull(testInstances.getInnermostInstance()); assertSame(testInstances, instanceMap.get(afterEachCallbackKey3)); assertSame(testInstances, instanceMap.get(testExecutionConditionKey3)); assertSame(testInstances.getInnermostInstance(), instanceMap.get(postProcessTestInstanceKey).getInnermostInstance()); assertSame(testInstances.getInnermostInstance(), instanceMap.get(preDestroyCallbackTestInstanceKey).getInnermostInstance()); assertThat(lifecyclesMap.keySet()).containsExactly(testClass); assertThat(lifecyclesMap.get(testClass).stream()).allMatch(Lifecycle.PER_METHOD::equals); } @Test void instancePerClass() { instancePerClass(InstancePerClassTestCase.class, instanceCounts(entry(InstancePerClassTestCase.class, 1))); } @Test void instancePerClassWithInheritedLifecycleMode() { instancePerClass(SubInstancePerClassTestCase.class, instanceCounts(entry(SubInstancePerClassTestCase.class, 1))); } @SuppressWarnings("NullAway") private void instancePerClass(Class testClass, Map.Entry, Integer>[] instances) { int containers = 3; int tests = 3; int allMethods = 2; int eachMethods = 3; performAssertions(testClass, containers, tests, instances, allMethods, eachMethods); String containerExecutionConditionKey = executionConditionKey(testClass, null); String testTemplateKey = testTemplateKey(testClass, "singletonTest"); String postProcessTestInstanceKey = postProcessTestInstanceKey(testClass); String preDestroyCallbackTestInstanceKey = preDestroyCallbackTestInstanceKey(testClass); String beforeAllCallbackKey = beforeAllCallbackKey(testClass); String afterAllCallbackKey = afterAllCallbackKey(testClass); String testExecutionConditionKey1 = executionConditionKey(testClass, testsInvoked.getFirst()); String beforeEachCallbackKey1 = beforeEachCallbackKey(testClass, testsInvoked.get(0)); String afterEachCallbackKey1 = afterEachCallbackKey(testClass, testsInvoked.get(0)); String testExecutionConditionKey2 = executionConditionKey(testClass, testsInvoked.get(1)); String beforeEachCallbackKey2 = beforeEachCallbackKey(testClass, testsInvoked.get(1)); String afterEachCallbackKey2 = afterEachCallbackKey(testClass, testsInvoked.get(1)); String testExecutionConditionKey3 = executionConditionKey(testClass, testsInvoked.get(2)); String beforeEachCallbackKey3 = beforeEachCallbackKey(testClass, testsInvoked.get(2)); String afterEachCallbackKey3 = afterEachCallbackKey(testClass, testsInvoked.get(2)); // @formatter:off assertThat(instanceMap.keySet()).containsExactlyInAnyOrder( postProcessTestInstanceKey, preDestroyCallbackTestInstanceKey, containerExecutionConditionKey, beforeAllCallbackKey, testTemplateKey, testExecutionConditionKey1, beforeEachCallbackKey1, afterEachCallbackKey1, testExecutionConditionKey2, beforeEachCallbackKey2, afterEachCallbackKey2, testExecutionConditionKey3, beforeEachCallbackKey3, afterEachCallbackKey3, afterAllCallbackKey ); // @formatter:on TestInstances testInstances = instanceMap.get(beforeAllCallbackKey); assertNotNull(testInstances.getInnermostInstance()); assertSame(testInstances, instanceMap.get(afterAllCallbackKey)); assertSame(testInstances, instanceMap.get(testExecutionConditionKey1)); assertSame(testInstances, instanceMap.get(beforeEachCallbackKey1)); assertSame(testInstances, instanceMap.get(afterEachCallbackKey1)); assertSame(testInstances, instanceMap.get(testExecutionConditionKey2)); assertSame(testInstances, instanceMap.get(beforeEachCallbackKey2)); assertSame(testInstances, instanceMap.get(afterEachCallbackKey2)); assertSame(testInstances, instanceMap.get(testExecutionConditionKey3)); assertSame(testInstances, instanceMap.get(beforeEachCallbackKey3)); assertSame(testInstances, instanceMap.get(afterEachCallbackKey3)); assertSame(testInstances.getInnermostInstance(), instanceMap.get(postProcessTestInstanceKey).getInnermostInstance()); assertSame(testInstances.getInnermostInstance(), instanceMap.get(preDestroyCallbackTestInstanceKey).getInnermostInstance()); assertNull(instanceMap.get(containerExecutionConditionKey)); assertThat(lifecyclesMap.keySet()).containsExactly(testClass); assertThat(lifecyclesMap.get(testClass).stream()).allMatch(Lifecycle.PER_CLASS::equals); } @SuppressWarnings("NullAway") @Test void instancePerMethodWithNestedTestClass() { Class testClass = InstancePerMethodOuterTestCase.class; Class nestedTestClass = InstancePerMethodOuterTestCase.NestedInstancePerMethodTestCase.class; int containers = 4; int tests = 4; Map.Entry, Integer>[] instances = instanceCounts(entry(testClass, 4), entry(nestedTestClass, 3)); int allMethods = 1; int eachMethods = 3; performAssertions(testClass, containers, tests, instances, allMethods, eachMethods); String containerExecutionConditionKey = executionConditionKey(testClass, null); String nestedContainerExecutionConditionKey = executionConditionKey(nestedTestClass, null); String nestedTestTemplateKey = testTemplateKey(nestedTestClass, "singletonTest"); String postProcessTestInstanceKey = postProcessTestInstanceKey(testClass); String nestedPostProcessTestInstanceKey = postProcessTestInstanceKey(nestedTestClass); String preDestroyCallbackTestInstanceKey = preDestroyCallbackTestInstanceKey(testClass); String nestedPreDestroyCallbackTestInstanceKey = preDestroyCallbackTestInstanceKey(nestedTestClass); String beforeAllCallbackKey = beforeAllCallbackKey(testClass); String afterAllCallbackKey = afterAllCallbackKey(testClass); String outerTestExecutionConditionKey = executionConditionKey(testClass, "outerTest"); String beforeEachCallbackKey = beforeEachCallbackKey(testClass, "outerTest"); String afterEachCallbackKey = afterEachCallbackKey(testClass, "outerTest"); String nestedBeforeAllCallbackKey = beforeAllCallbackKey(nestedTestClass); String nestedAfterAllCallbackKey = afterAllCallbackKey(nestedTestClass); String nestedExecutionConditionKey1 = executionConditionKey(nestedTestClass, testsInvoked.getFirst()); String nestedBeforeEachCallbackKey1 = beforeEachCallbackKey(nestedTestClass, testsInvoked.get(0)); String nestedAfterEachCallbackKey1 = afterEachCallbackKey(nestedTestClass, testsInvoked.get(0)); String nestedExecutionConditionKey2 = executionConditionKey(nestedTestClass, testsInvoked.get(1)); String nestedBeforeEachCallbackKey2 = beforeEachCallbackKey(nestedTestClass, testsInvoked.get(1)); String nestedAfterEachCallbackKey2 = afterEachCallbackKey(nestedTestClass, testsInvoked.get(1)); String nestedExecutionConditionKey3 = executionConditionKey(nestedTestClass, testsInvoked.get(2)); String nestedBeforeEachCallbackKey3 = beforeEachCallbackKey(nestedTestClass, testsInvoked.get(2)); String nestedAfterEachCallbackKey3 = afterEachCallbackKey(nestedTestClass, testsInvoked.get(2)); // @formatter:off assertThat(instanceMap.keySet()).containsExactlyInAnyOrder( containerExecutionConditionKey, nestedTestTemplateKey, nestedContainerExecutionConditionKey, postProcessTestInstanceKey, nestedPostProcessTestInstanceKey, preDestroyCallbackTestInstanceKey, nestedPreDestroyCallbackTestInstanceKey, beforeAllCallbackKey, afterAllCallbackKey, outerTestExecutionConditionKey, beforeEachCallbackKey, afterEachCallbackKey, nestedBeforeAllCallbackKey, nestedAfterAllCallbackKey, nestedExecutionConditionKey1, nestedBeforeEachCallbackKey1, nestedAfterEachCallbackKey1, nestedExecutionConditionKey2, nestedBeforeEachCallbackKey2, nestedAfterEachCallbackKey2, nestedExecutionConditionKey3, nestedBeforeEachCallbackKey3, nestedAfterEachCallbackKey3 ); // @formatter:on assertNull(instanceMap.get(containerExecutionConditionKey)); assertNull(instanceMap.get(beforeAllCallbackKey)); assertNull(instanceMap.get(afterAllCallbackKey)); assertNull(instanceMap.get(nestedContainerExecutionConditionKey)); assertNull(instanceMap.get(nestedBeforeAllCallbackKey)); assertNull(instanceMap.get(nestedAfterAllCallbackKey)); TestInstances outerInstances = instanceMap.get(beforeEachCallbackKey); assertNotNull(outerInstances.getInnermostInstance()); assertSame(outerInstances, instanceMap.get(afterEachCallbackKey)); assertSame(outerInstances, instanceMap.get(outerTestExecutionConditionKey)); TestInstances nestedInstances1 = instanceMap.get(nestedBeforeEachCallbackKey1); assertNotNull(nestedInstances1.getInnermostInstance()); assertNotSame(outerInstances.getInnermostInstance(), nestedInstances1.getInnermostInstance()); assertSame(nestedInstances1, instanceMap.get(nestedAfterEachCallbackKey1)); assertSame(nestedInstances1, instanceMap.get(nestedExecutionConditionKey1)); TestInstances nestedInstances2 = instanceMap.get(nestedBeforeEachCallbackKey2); assertNotNull(nestedInstances2.getInnermostInstance()); assertNotSame(outerInstances.getInnermostInstance(), nestedInstances2.getInnermostInstance()); assertNotSame(nestedInstances1.getInnermostInstance(), nestedInstances2.getInnermostInstance()); assertSame(nestedInstances2, instanceMap.get(nestedAfterEachCallbackKey2)); assertSame(nestedInstances2, instanceMap.get(nestedExecutionConditionKey2)); TestInstances nestedInstances3 = instanceMap.get(nestedPostProcessTestInstanceKey); assertNotNull(nestedInstances3.getInnermostInstance()); assertNotSame(outerInstances.getInnermostInstance(), nestedInstances3.getInnermostInstance()); assertNotSame(nestedInstances1.getInnermostInstance(), nestedInstances3.getInnermostInstance()); assertSame(nestedInstances3.getInnermostInstance(), instanceMap.get(nestedAfterEachCallbackKey3).getInnermostInstance()); assertSame(nestedInstances3.getInnermostInstance(), instanceMap.get(nestedExecutionConditionKey3).getInnermostInstance()); assertSame(nestedInstances3.getInnermostInstance(), instanceMap.get(nestedPreDestroyCallbackTestInstanceKey).getInnermostInstance()); Object outerInstance1 = instanceMap.get(nestedExecutionConditionKey1).findInstance(testClass).get(); Object outerInstance2 = instanceMap.get(nestedExecutionConditionKey2).findInstance(testClass).get(); Object outerInstance3 = instanceMap.get(nestedExecutionConditionKey3).findInstance(testClass).get(); assertNotSame(outerInstance1, outerInstance2); assertNotSame(outerInstance1, outerInstance3); assertThat(instanceMap.get(nestedExecutionConditionKey1).getAllInstances()).containsExactly(outerInstance1, nestedInstances1.getInnermostInstance()); assertThat(instanceMap.get(nestedExecutionConditionKey2).getAllInstances()).containsExactly(outerInstance2, nestedInstances2.getInnermostInstance()); assertThat(instanceMap.get(nestedExecutionConditionKey3).getAllInstances()).containsExactly(outerInstance3, nestedInstances3.getInnermostInstance()); // The last tracked instance stored under postProcessTestInstanceKey // is only created in order to instantiate the nested test class for // test2(). assertSame(outerInstance3, instanceMap.get(postProcessTestInstanceKey).getInnermostInstance()); assertThat(lifecyclesMap.keySet()).containsExactly(testClass, nestedTestClass); assertThat(lifecyclesMap.get(testClass).stream()).allMatch(Lifecycle.PER_METHOD::equals); assertThat(lifecyclesMap.get(nestedTestClass).stream()).allMatch(Lifecycle.PER_METHOD::equals); } @SuppressWarnings("NullAway") @Test void instancePerClassWithNestedTestClass() { Class testClass = InstancePerClassOuterTestCase.class; Class nestedTestClass = InstancePerClassOuterTestCase.NestedInstancePerClassTestCase.class; int containers = 4; int tests = 4; Map.Entry, Integer>[] instances = instanceCounts(entry(testClass, 1), entry(nestedTestClass, 1)); int allMethods = 2; int eachMethods = 3; performAssertions(testClass, containers, tests, instances, allMethods, eachMethods); String containerExecutionConditionKey = executionConditionKey(testClass, null); String nestedContainerExecutionConditionKey = executionConditionKey(nestedTestClass, null); String nestedTestTemplateKey = testTemplateKey(nestedTestClass, "singletonTest"); String postProcessTestInstanceKey = postProcessTestInstanceKey(testClass); String nestedPostProcessTestInstanceKey = postProcessTestInstanceKey(nestedTestClass); String preDestroyCallbackTestInstanceKey = preDestroyCallbackTestInstanceKey(testClass); String nestedPreDestroyCallbackTestInstanceKey = preDestroyCallbackTestInstanceKey(nestedTestClass); String beforeAllCallbackKey = beforeAllCallbackKey(testClass); String afterAllCallbackKey = afterAllCallbackKey(testClass); String outerTestExecutionConditionKey = executionConditionKey(testClass, "outerTest"); String beforeEachCallbackKey = beforeEachCallbackKey(testClass, "outerTest"); String afterEachCallbackKey = afterEachCallbackKey(testClass, "outerTest"); String nestedBeforeAllCallbackKey = beforeAllCallbackKey(nestedTestClass); String nestedAfterAllCallbackKey = afterAllCallbackKey(nestedTestClass); String nestedExecutionConditionKey1 = executionConditionKey(nestedTestClass, testsInvoked.getFirst()); String nestedBeforeEachCallbackKey1 = beforeEachCallbackKey(nestedTestClass, testsInvoked.get(0)); String nestedAfterEachCallbackKey1 = afterEachCallbackKey(nestedTestClass, testsInvoked.get(0)); String nestedExecutionConditionKey2 = executionConditionKey(nestedTestClass, testsInvoked.get(1)); String nestedBeforeEachCallbackKey2 = beforeEachCallbackKey(nestedTestClass, testsInvoked.get(1)); String nestedAfterEachCallbackKey2 = afterEachCallbackKey(nestedTestClass, testsInvoked.get(1)); String nestedExecutionConditionKey3 = executionConditionKey(nestedTestClass, testsInvoked.get(2)); String nestedBeforeEachCallbackKey3 = beforeEachCallbackKey(nestedTestClass, testsInvoked.get(2)); String nestedAfterEachCallbackKey3 = afterEachCallbackKey(nestedTestClass, testsInvoked.get(2)); // @formatter:off assertThat(instanceMap.keySet()).containsExactlyInAnyOrder( containerExecutionConditionKey, nestedTestTemplateKey, nestedContainerExecutionConditionKey, postProcessTestInstanceKey, nestedPostProcessTestInstanceKey, preDestroyCallbackTestInstanceKey, nestedPreDestroyCallbackTestInstanceKey, beforeAllCallbackKey, afterAllCallbackKey, outerTestExecutionConditionKey, beforeEachCallbackKey, afterEachCallbackKey, nestedBeforeAllCallbackKey, nestedAfterAllCallbackKey, nestedExecutionConditionKey1, nestedBeforeEachCallbackKey1, nestedAfterEachCallbackKey1, nestedExecutionConditionKey2, nestedBeforeEachCallbackKey2, nestedAfterEachCallbackKey2, nestedExecutionConditionKey3, nestedBeforeEachCallbackKey3, nestedAfterEachCallbackKey3 ); // @formatter:on Object instance = instanceMap.get(postProcessTestInstanceKey).getInnermostInstance(); assertNotNull(instance); assertNull(instanceMap.get(containerExecutionConditionKey)); assertSame(instance, instanceMap.get(beforeAllCallbackKey).getInnermostInstance()); assertSame(instance, instanceMap.get(afterAllCallbackKey).getInnermostInstance()); assertSame(instance, instanceMap.get(outerTestExecutionConditionKey).getInnermostInstance()); assertSame(instance, instanceMap.get(beforeEachCallbackKey).getInnermostInstance()); assertSame(instance, instanceMap.get(afterEachCallbackKey).getInnermostInstance()); assertSame(instance, instanceMap.get(preDestroyCallbackTestInstanceKey).getInnermostInstance()); Object nestedInstance = instanceMap.get(nestedPostProcessTestInstanceKey).getInnermostInstance(); assertNotNull(nestedInstance); assertNotSame(instance, nestedInstance); assertNull(instanceMap.get(nestedContainerExecutionConditionKey)); assertSame(nestedInstance, instanceMap.get(nestedBeforeAllCallbackKey).getInnermostInstance()); assertSame(nestedInstance, instanceMap.get(nestedAfterAllCallbackKey).getInnermostInstance()); assertSame(nestedInstance, instanceMap.get(nestedExecutionConditionKey1).getInnermostInstance()); assertSame(nestedInstance, instanceMap.get(nestedBeforeEachCallbackKey1).getInnermostInstance()); assertSame(nestedInstance, instanceMap.get(nestedAfterEachCallbackKey1).getInnermostInstance()); assertSame(nestedInstance, instanceMap.get(nestedExecutionConditionKey2).getInnermostInstance()); assertSame(nestedInstance, instanceMap.get(nestedBeforeEachCallbackKey2).getInnermostInstance()); assertSame(nestedInstance, instanceMap.get(nestedAfterEachCallbackKey2).getInnermostInstance()); assertSame(nestedInstance, instanceMap.get(nestedExecutionConditionKey3).getInnermostInstance()); assertSame(nestedInstance, instanceMap.get(nestedBeforeEachCallbackKey3).getInnermostInstance()); assertSame(nestedInstance, instanceMap.get(nestedAfterEachCallbackKey3).getInnermostInstance()); assertSame(nestedInstance, instanceMap.get(nestedPreDestroyCallbackTestInstanceKey).getInnermostInstance()); Object outerInstance = instanceMap.get(nestedExecutionConditionKey1).findInstance(testClass).get(); assertSame(outerInstance, instance); assertSame(outerInstance, instanceMap.get(postProcessTestInstanceKey).getInnermostInstance()); assertSame(outerInstance, instanceMap.get(preDestroyCallbackTestInstanceKey).getInnermostInstance()); assertThat(instanceMap.get(nestedExecutionConditionKey1).getAllInstances()).containsExactly(outerInstance, nestedInstance); assertThat(instanceMap.get(nestedBeforeEachCallbackKey1).getAllInstances()).containsExactly(outerInstance, nestedInstance); assertThat(instanceMap.get(nestedAfterEachCallbackKey1).getAllInstances()).containsExactly(outerInstance, nestedInstance); assertThat(instanceMap.get(nestedExecutionConditionKey2).getAllInstances()).containsExactly(outerInstance, nestedInstance); assertThat(instanceMap.get(nestedBeforeEachCallbackKey2).getAllInstances()).containsExactly(outerInstance, nestedInstance); assertThat(instanceMap.get(nestedAfterEachCallbackKey2).getAllInstances()).containsExactly(outerInstance, nestedInstance); assertThat(instanceMap.get(nestedExecutionConditionKey3).getAllInstances()).containsExactly(outerInstance, nestedInstance); assertThat(instanceMap.get(nestedBeforeEachCallbackKey3).getAllInstances()).containsExactly(outerInstance, nestedInstance); assertThat(instanceMap.get(nestedAfterEachCallbackKey3).getAllInstances()).containsExactly(outerInstance, nestedInstance); assertThat(lifecyclesMap.keySet()).containsExactly(testClass, nestedTestClass); assertThat(lifecyclesMap.get(testClass).stream()).allMatch(Lifecycle.PER_CLASS::equals); assertThat(lifecyclesMap.get(nestedTestClass).stream()).allMatch(Lifecycle.PER_CLASS::equals); } @SuppressWarnings("NullAway") @Test void instancePerMethodOnOuterTestClassWithInstancePerClassOnNestedTestClass() { Class testClass = MixedLifecyclesOuterTestCase.class; Class nestedTestClass = MixedLifecyclesOuterTestCase.NestedInstancePerClassTestCase.class; int containers = 4; int tests = 4; Map.Entry, Integer>[] instances = instanceCounts(entry(testClass, 2), entry(nestedTestClass, 1)); int allMethods = 1; int eachMethods = 7; performAssertions(testClass, containers, tests, instances, allMethods, eachMethods); String containerExecutionConditionKey = executionConditionKey(testClass, null); String nestedContainerExecutionConditionKey = executionConditionKey(nestedTestClass, null); String nestedTestTemplateKey = testTemplateKey(nestedTestClass, "singletonTest"); String postProcessTestInstanceKey = postProcessTestInstanceKey(testClass); String nestedPostProcessTestInstanceKey = postProcessTestInstanceKey(nestedTestClass); String preDestroyCallbackTestInstanceKey = preDestroyCallbackTestInstanceKey(testClass); String nestedPreDestroyCallbackTestInstanceKey = preDestroyCallbackTestInstanceKey(nestedTestClass); String beforeAllCallbackKey = beforeAllCallbackKey(testClass); String afterAllCallbackKey = afterAllCallbackKey(testClass); String outerTestExecutionConditionKey = executionConditionKey(testClass, "outerTest"); String beforeEachCallbackKey = beforeEachCallbackKey(testClass, "outerTest"); String afterEachCallbackKey = afterEachCallbackKey(testClass, "outerTest"); String nestedBeforeAllCallbackKey = beforeAllCallbackKey(nestedTestClass); String nestedAfterAllCallbackKey = afterAllCallbackKey(nestedTestClass); String nestedExecutionConditionKey1 = executionConditionKey(nestedTestClass, testsInvoked.getFirst()); String nestedBeforeEachCallbackKey1 = beforeEachCallbackKey(nestedTestClass, testsInvoked.get(0)); String nestedAfterEachCallbackKey1 = afterEachCallbackKey(nestedTestClass, testsInvoked.get(0)); String nestedExecutionConditionKey2 = executionConditionKey(nestedTestClass, testsInvoked.get(1)); String nestedBeforeEachCallbackKey2 = beforeEachCallbackKey(nestedTestClass, testsInvoked.get(1)); String nestedAfterEachCallbackKey2 = afterEachCallbackKey(nestedTestClass, testsInvoked.get(1)); String nestedExecutionConditionKey3 = executionConditionKey(nestedTestClass, testsInvoked.get(2)); String nestedBeforeEachCallbackKey3 = beforeEachCallbackKey(nestedTestClass, testsInvoked.get(2)); String nestedAfterEachCallbackKey3 = afterEachCallbackKey(nestedTestClass, testsInvoked.get(2)); // @formatter:off assertThat(instanceMap.keySet()).containsExactlyInAnyOrder( containerExecutionConditionKey, nestedTestTemplateKey, nestedContainerExecutionConditionKey, postProcessTestInstanceKey, nestedPostProcessTestInstanceKey, preDestroyCallbackTestInstanceKey, nestedPreDestroyCallbackTestInstanceKey, beforeAllCallbackKey, afterAllCallbackKey, outerTestExecutionConditionKey, beforeEachCallbackKey, afterEachCallbackKey, nestedBeforeAllCallbackKey, nestedAfterAllCallbackKey, nestedExecutionConditionKey1, nestedBeforeEachCallbackKey1, nestedAfterEachCallbackKey1, nestedExecutionConditionKey2, nestedBeforeEachCallbackKey2, nestedAfterEachCallbackKey2, nestedExecutionConditionKey3, nestedBeforeEachCallbackKey3, nestedAfterEachCallbackKey3 ); // @formatter:on assertNull(instanceMap.get(containerExecutionConditionKey)); assertNull(instanceMap.get(beforeAllCallbackKey)); assertNull(instanceMap.get(afterAllCallbackKey)); TestInstances outerInstances = instanceMap.get(beforeEachCallbackKey); assertSame(outerInstances, instanceMap.get(afterEachCallbackKey)); assertSame(outerInstances, instanceMap.get(outerTestExecutionConditionKey)); Object nestedInstance = instanceMap.get(nestedPostProcessTestInstanceKey).getInnermostInstance(); assertNotNull(nestedInstance); assertNotSame(outerInstances.getInnermostInstance(), nestedInstance); assertNull(instanceMap.get(nestedContainerExecutionConditionKey)); assertSame(nestedInstance, instanceMap.get(nestedBeforeAllCallbackKey).getInnermostInstance()); assertSame(nestedInstance, instanceMap.get(nestedAfterAllCallbackKey).getInnermostInstance()); assertSame(nestedInstance, instanceMap.get(nestedExecutionConditionKey1).getInnermostInstance()); assertSame(nestedInstance, instanceMap.get(nestedBeforeEachCallbackKey1).getInnermostInstance()); assertSame(nestedInstance, instanceMap.get(nestedAfterEachCallbackKey1).getInnermostInstance()); assertSame(nestedInstance, instanceMap.get(nestedExecutionConditionKey2).getInnermostInstance()); assertSame(nestedInstance, instanceMap.get(nestedBeforeEachCallbackKey2).getInnermostInstance()); assertSame(nestedInstance, instanceMap.get(nestedAfterEachCallbackKey2).getInnermostInstance()); assertSame(nestedInstance, instanceMap.get(nestedExecutionConditionKey3).getInnermostInstance()); assertSame(nestedInstance, instanceMap.get(nestedBeforeEachCallbackKey3).getInnermostInstance()); assertSame(nestedInstance, instanceMap.get(nestedAfterEachCallbackKey3).getInnermostInstance()); assertSame(nestedInstance, instanceMap.get(nestedPreDestroyCallbackTestInstanceKey).getInnermostInstance()); // The last tracked instance stored under postProcessTestInstanceKey // is only created in order to instantiate the nested test class. Object outerInstance = instanceMap.get(nestedExecutionConditionKey1).findInstance(testClass).get(); assertEquals(outerInstances.getInnermostInstance().getClass(), outerInstance.getClass()); assertNotSame(outerInstances.getInnermostInstance(), outerInstance); assertThat(instanceMap.get(nestedExecutionConditionKey1).getAllInstances()).containsExactly(outerInstance, nestedInstance); assertThat(instanceMap.get(nestedBeforeEachCallbackKey1).getAllInstances()).containsExactly(outerInstance, nestedInstance); assertThat(instanceMap.get(nestedAfterEachCallbackKey1).getAllInstances()).containsExactly(outerInstance, nestedInstance); assertThat(instanceMap.get(nestedExecutionConditionKey2).getAllInstances()).containsExactly(outerInstance, nestedInstance); assertThat(instanceMap.get(nestedBeforeEachCallbackKey2).getAllInstances()).containsExactly(outerInstance, nestedInstance); assertThat(instanceMap.get(nestedAfterEachCallbackKey2).getAllInstances()).containsExactly(outerInstance, nestedInstance); assertThat(instanceMap.get(nestedExecutionConditionKey3).getAllInstances()).containsExactly(outerInstance, nestedInstance); assertThat(instanceMap.get(nestedBeforeEachCallbackKey3).getAllInstances()).containsExactly(outerInstance, nestedInstance); assertThat(instanceMap.get(nestedAfterEachCallbackKey3).getAllInstances()).containsExactly(outerInstance, nestedInstance); assertThat(lifecyclesMap.keySet()).containsExactly(testClass, nestedTestClass); assertThat(lifecyclesMap.get(testClass).stream()).allMatch(Lifecycle.PER_METHOD::equals); assertThat(lifecyclesMap.get(nestedTestClass).stream()).allMatch(Lifecycle.PER_CLASS::equals); } @ParameterizedTest @EnumSource(Lifecycle.class) void classTemplate(Lifecycle lifecycle) { var classTemplate = ClassTemplateWithDefaultLifecycleTestCase.class; var results = executeTests(r -> r // .selectors(selectClass(classTemplate)) // .configurationParameter(Constants.DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME, lifecycle.name())); results.allEvents().assertStatistics(stats -> stats.failed(0)); results.testEvents().assertStatistics(stats -> stats.succeeded(4)); assertThat(instanceCount).containsExactly(entry(classTemplate, lifecycle == Lifecycle.PER_CLASS ? 1 : 4)); assertThat(lifecyclesMap.keySet()).containsExactly(classTemplate); assertThat(lifecyclesMap.get(classTemplate)).filteredOn(Objects::nonNull).containsOnly(lifecycle); } @ParameterizedTest @EnumSource(Lifecycle.class) void classTemplateWithNestedClass(Lifecycle lifecycle) { var classTemplate = ClassTemplateWithDefaultLifecycleAndNestedClassTestCase.class; var nestedClass = ClassTemplateWithDefaultLifecycleAndNestedClassTestCase.InnerTestCase.class; var results = executeTests(r -> r // .selectors(selectClass(classTemplate)) // .configurationParameter(Constants.DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME, lifecycle.name())); results.allEvents().assertStatistics(stats -> stats.failed(0)); results.testEvents().assertStatistics(stats -> stats.succeeded(4)); assertThat(instanceCount).containsExactly( // entry(classTemplate, lifecycle == Lifecycle.PER_CLASS ? 1 : 4), // entry(nestedClass, lifecycle == Lifecycle.PER_CLASS ? 2 : 4)); assertThat(lifecyclesMap.keySet()).containsExactlyInAnyOrder(classTemplate, nestedClass); assertThat(lifecyclesMap.get(classTemplate)).filteredOn(Objects::nonNull).containsOnly(lifecycle); assertThat(lifecyclesMap.get(nestedClass)).filteredOn(Objects::nonNull).containsOnly(lifecycle); } private void performAssertions(Class testClass, int numContainers, int numTests, Map.Entry, Integer>[] instanceCountEntries, int allMethods, int eachMethods) { EngineExecutionResults executionResults = executeTestsForClass(testClass); executionResults.containerEvents().assertStatistics( stats -> stats.started(numContainers).finished(numContainers)); executionResults.testEvents().assertStatistics(stats -> stats.started(numTests).finished(numTests)); // @formatter:off assertAll( () -> assertThat(instanceCount).describedAs("instance count").contains(instanceCountEntries), () -> assertEquals(allMethods, beforeAllCount, "@BeforeAll count"), () -> assertEquals(allMethods, afterAllCount, "@AfterAll count"), () -> assertEquals(eachMethods, beforeEachCount, "@BeforeEach count"), () -> assertEquals(eachMethods, afterEachCount, "@AfterEach count") ); // @formatter:on } @SafeVarargs @SuppressWarnings("varargs") private Map.Entry, Integer>[] instanceCounts(Map.Entry, Integer>... entries) { return entries; } private static void incrementInstanceCount(Class testClass) { instanceCount.compute(testClass, (key, value) -> value == null ? 1 : value + 1); } private static String executionConditionKey(Class testClass, @Nullable String testMethod) { return concat(ExecutionCondition.class, testClass, testMethod); } private static String postProcessTestInstanceKey(Class testClass) { return concat(TestInstancePostProcessor.class, testClass); } private static String preDestroyCallbackTestInstanceKey(Class testClass) { return concat(TestInstancePreDestroyCallback.class, testClass); } private static String beforeAllCallbackKey(Class testClass) { return concat(BeforeAllCallback.class, testClass); } private static String afterAllCallbackKey(Class testClass) { return concat(AfterAllCallback.class, testClass); } private static String beforeEachCallbackKey(Class testClass, String testMethod) { return concat(BeforeEachCallback.class, testClass, testMethod); } private static String afterEachCallbackKey(Class testClass, String testMethod) { return concat(AfterEachCallback.class, testClass, testMethod); } private static String testTemplateKey(Class testClass, String testMethod) { return concat(TestTemplateInvocationContextProvider.class, testClass, testMethod); } private static String concat(Class c1, Class c2, @Nullable String str) { return concat(c1.getSimpleName(), c2.getSimpleName(), str); } private static String concat(Class c1, Class c2) { return concat(c1.getSimpleName(), c2.getSimpleName()); } private static String concat(@Nullable String... args) { return join(".", args); } // ------------------------------------------------------------------------- @ExtendWith(InstanceTrackingExtension.class) // The following is commented out b/c it's the default. // @TestInstance(Lifecycle.PER_METHOD) static class InstancePerMethodTestCase { InstancePerMethodTestCase() { incrementInstanceCount(InstancePerMethodTestCase.class); } @BeforeAll static void beforeAllStatic(TestInfo testInfo) { assertNotNull(testInfo); beforeAllCount++; } @BeforeEach void beforeEach() { beforeEachCount++; } @Test void test1(TestInfo testInfo) { testsInvoked.add(testInfo.getTestMethod().get().getName()); } @Test void test2(TestInfo testInfo) { testsInvoked.add(testInfo.getTestMethod().get().getName()); } @SingletonTest void singletonTest(TestInfo testInfo) { testsInvoked.add(testInfo.getTestMethod().get().getName()); } @AfterEach void afterEach() { afterEachCount++; } @AfterAll static void afterAllStatic(TestInfo testInfo) { assertNotNull(testInfo); afterAllCount++; } } @TestInstance(Lifecycle.PER_CLASS) static class InstancePerClassTestCase extends InstancePerMethodTestCase { InstancePerClassTestCase() { incrementInstanceCount(InstancePerClassTestCase.class); } @BeforeAll void beforeAll(TestInfo testInfo) { assertNotNull(testInfo); beforeAllCount++; } @AfterAll void afterAll(TestInfo testInfo) { assertNotNull(testInfo); afterAllCount++; } } static class SubInstancePerClassTestCase extends InstancePerClassTestCase { SubInstancePerClassTestCase() { incrementInstanceCount(SubInstancePerClassTestCase.class); } } @ExtendWith(InstanceTrackingExtension.class) // The following is commented out b/c it's the default. // @TestInstance(Lifecycle.PER_METHOD) static class InstancePerMethodOuterTestCase { InstancePerMethodOuterTestCase() { incrementInstanceCount(InstancePerMethodOuterTestCase.class); } @BeforeAll static void beforeAll(TestInfo testInfo) { assertNotNull(testInfo); beforeAllCount++; } @SuppressWarnings("NullAway") @Test void outerTest() { assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); } @AfterAll static void afterAll(TestInfo testInfo) { assertNotNull(testInfo); afterAllCount++; } @Nested // The following is commented out b/c it's the default. // @TestInstance(Lifecycle.PER_METHOD) class NestedInstancePerMethodTestCase { NestedInstancePerMethodTestCase() { incrementInstanceCount(NestedInstancePerMethodTestCase.class); } @BeforeEach void beforeEach() { beforeEachCount++; } @SuppressWarnings("NullAway") @Test void test1(TestInfo testInfo) { assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); testsInvoked.add(testInfo.getTestMethod().get().getName()); } @SuppressWarnings("NullAway") @Test void test2(TestInfo testInfo) { assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); testsInvoked.add(testInfo.getTestMethod().get().getName()); } @SuppressWarnings("NullAway") @SingletonTest void singletonTest(TestInfo testInfo) { assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); testsInvoked.add(testInfo.getTestMethod().get().getName()); } @AfterEach void afterEach() { afterEachCount++; } } } @ExtendWith(InstanceTrackingExtension.class) @TestInstance(Lifecycle.PER_CLASS) static class InstancePerClassOuterTestCase { InstancePerClassOuterTestCase() { incrementInstanceCount(InstancePerClassOuterTestCase.class); } @BeforeAll static void beforeAll(TestInfo testInfo) { assertNotNull(testInfo); beforeAllCount++; } @SuppressWarnings("NullAway") @Test void outerTest() { assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); } @AfterAll static void afterAll(TestInfo testInfo) { assertNotNull(testInfo); afterAllCount++; } @Nested @TestInstance(Lifecycle.PER_CLASS) class NestedInstancePerClassTestCase { NestedInstancePerClassTestCase() { incrementInstanceCount(NestedInstancePerClassTestCase.class); } @BeforeAll void beforeAll(TestInfo testInfo) { assertNotNull(testInfo); beforeAllCount++; } @BeforeEach void beforeEach() { beforeEachCount++; } @SuppressWarnings("NullAway") @Test void test1(TestInfo testInfo) { assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); testsInvoked.add(testInfo.getTestMethod().get().getName()); } @SuppressWarnings("NullAway") @Test void test2(TestInfo testInfo) { assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); testsInvoked.add(testInfo.getTestMethod().get().getName()); } @SuppressWarnings("NullAway") @SingletonTest void singletonTest(TestInfo testInfo) { assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); testsInvoked.add(testInfo.getTestMethod().get().getName()); } @AfterEach void afterEach() { afterEachCount++; } @AfterAll void afterAll(TestInfo testInfo) { assertNotNull(testInfo); afterAllCount++; } } } @ExtendWith(InstanceTrackingExtension.class) // The following is commented out b/c it's the default. // @TestInstance(Lifecycle.PER_METHOD) static class MixedLifecyclesOuterTestCase { MixedLifecyclesOuterTestCase() { incrementInstanceCount(MixedLifecyclesOuterTestCase.class); } @BeforeEach void beforeEach() { beforeEachCount++; } @SuppressWarnings("NullAway") @Test void outerTest() { assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); } @AfterEach void afterEach() { afterEachCount++; } @Nested @TestInstance(Lifecycle.PER_CLASS) class NestedInstancePerClassTestCase { NestedInstancePerClassTestCase() { incrementInstanceCount(NestedInstancePerClassTestCase.class); } @BeforeAll void beforeAll(TestInfo testInfo) { assertNotNull(testInfo); beforeAllCount++; } @BeforeEach void beforeEach() { beforeEachCount++; } @SuppressWarnings("NullAway") @Test void test1(TestInfo testInfo) { assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); testsInvoked.add(testInfo.getTestMethod().get().getName()); } @SuppressWarnings("NullAway") @Test void test2(TestInfo testInfo) { assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); testsInvoked.add(testInfo.getTestMethod().get().getName()); } @SuppressWarnings("NullAway") @SingletonTest void singletonTest(TestInfo testInfo) { assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); testsInvoked.add(testInfo.getTestMethod().get().getName()); } @AfterEach void afterEach() { afterEachCount++; } @AfterAll void afterAll(TestInfo testInfo) { assertNotNull(testInfo); afterAllCount++; } } } // Intentionally not implementing BeforeTestExecutionCallback, AfterTestExecutionCallback, // and TestExecutionExceptionHandler, since they are analogous to BeforeEachCallback and // AfterEachCallback with regard to instance scope and Lifecycle. static class InstanceTrackingExtension implements ExecutionCondition, TestInstancePostProcessor, TestInstancePreDestroyCallback, BeforeAllCallback, AfterAllCallback, BeforeEachCallback, AfterEachCallback, TestTemplateInvocationContextProvider { @Override public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { trackLifecycle(context); String testMethod = context.getTestMethod().map(Method::getName).orElse(null); if (testMethod == null) { assertThat(context.getTestInstance()).isNotPresent(); if (!isAnnotated(context.getRequiredTestClass().getEnclosingClass(), ClassTemplate.class)) { assertThat(instanceCount.getOrDefault(context.getRequiredTestClass(), 0)).isEqualTo(0); } } instanceMap.put(executionConditionKey(context.getRequiredTestClass(), testMethod), context.getTestInstances().orElse(null)); return ConditionEvaluationResult.enabled("enigma"); } @Override public void postProcessTestInstance(Object testInstance, ExtensionContext context) { trackLifecycle(context); assertThat(context.getTestInstance()).isNotPresent(); assertNotNull(testInstance); instanceMap.put(postProcessTestInstanceKey(testInstance.getClass()), DefaultTestInstances.of(testInstance)); } @Override public void preDestroyTestInstance(ExtensionContext context) { trackLifecycle(context); assertThat(context.getTestInstance()).isPresent(); instanceMap.put(preDestroyCallbackTestInstanceKey(context.getRequiredTestClass()), DefaultTestInstances.of(context.getTestInstance().get())); } @Override public void beforeAll(ExtensionContext context) { trackLifecycle(context); instanceMap.put(beforeAllCallbackKey(context.getRequiredTestClass()), context.getTestInstances().orElse(null)); } @Override public void afterAll(ExtensionContext context) { trackLifecycle(context); instanceMap.put(afterAllCallbackKey(context.getRequiredTestClass()), context.getTestInstances().orElse(null)); } @Override public void beforeEach(ExtensionContext context) { trackLifecycle(context); instanceMap.put( beforeEachCallbackKey(context.getRequiredTestClass(), context.getRequiredTestMethod().getName()), context.getRequiredTestInstances()); } @Override public void afterEach(ExtensionContext context) { trackLifecycle(context); instanceMap.put( afterEachCallbackKey(context.getRequiredTestClass(), context.getRequiredTestMethod().getName()), context.getRequiredTestInstances()); } @Override public boolean supportsTestTemplate(ExtensionContext context) { trackLifecycle(context); return isAnnotated(context.getTestMethod(), SingletonTest.class); } @Override public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { trackLifecycle(context); instanceMap.put(testTemplateKey(context.getRequiredTestClass(), context.getRequiredTestMethod().getName()), context.getTestInstances().orElse(null)); return Stream.of(new TestTemplateInvocationContext() { }); } private static void trackLifecycle(ExtensionContext context) { assertThat(context.getRoot().getTestInstanceLifecycle()).isEmpty(); lifecyclesMap.computeIfAbsent(context.getRequiredTestClass(), clazz -> new ArrayList<>()).add( context.getTestInstanceLifecycle().orElse(null)); } } @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @TestTemplate @interface SingletonTest { } @ClassTemplate @ExtendWith(Twice.class) @ExtendWith(InstanceTrackingExtension.class) static class ClassTemplateWithDefaultLifecycleTestCase { ClassTemplateWithDefaultLifecycleTestCase() { incrementInstanceCount(ClassTemplateWithDefaultLifecycleTestCase.class); } @Test void test1() { } @Test void test2() { } } @ClassTemplate @ExtendWith(Twice.class) @ExtendWith(InstanceTrackingExtension.class) static class ClassTemplateWithDefaultLifecycleAndNestedClassTestCase { ClassTemplateWithDefaultLifecycleAndNestedClassTestCase() { incrementInstanceCount(ClassTemplateWithDefaultLifecycleAndNestedClassTestCase.class); } @Nested class InnerTestCase { InnerTestCase() { incrementInstanceCount(InnerTestCase.class); } @Test void test1() { } @Test void test2() { } } } private static class Twice implements ClassTemplateInvocationContextProvider { @Override public boolean supportsClassTemplate(ExtensionContext context) { return true; } @Override public Stream provideClassTemplateInvocationContexts(ExtensionContext context) { return Stream.of(new Ctx(), new Ctx()); } private record Ctx() implements ClassTemplateInvocationContext { } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/TestMethodOverridingTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import java.lang.reflect.Method; import java.util.Map; import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestReporter; import org.junit.jupiter.engine.subpackage.SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.engine.support.descriptor.MethodSource; import org.junit.platform.testkit.engine.EngineExecutionResults; /** * @since 5.14.1 */ class TestMethodOverridingTests extends AbstractJupiterTestEngineTests { @Test void bothPackagePrivateTestMethodsAreDiscovered() throws Exception { var results = discoverTestsForClass(DuplicateTestMethodDueToPackagePrivateVisibilityTestCase.class); var classDescriptor = getOnlyElement(results.getEngineDescriptor().getChildren()); var parentUniqueId = classDescriptor.getUniqueId(); var inheritedMethodUniqueId = parentUniqueId.append("method", "org.junit.jupiter.engine.subpackage.SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase#" + "test(org.junit.jupiter.api.TestInfo, org.junit.jupiter.api.TestReporter)"); var declaredMethodUniqueId = parentUniqueId.append("method", "test(org.junit.jupiter.api.TestInfo, org.junit.jupiter.api.TestReporter)"); var inheritedMethod = SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase.class.getDeclaredMethod( "test", TestInfo.class, TestReporter.class); var declaredMethod = DuplicateTestMethodDueToPackagePrivateVisibilityTestCase.class.getDeclaredMethod("test", TestInfo.class, TestReporter.class); assertThat(classDescriptor.getChildren()) // .hasSize(2) // .extracting(TestDescriptor::getUniqueId, TestMethodOverridingTests::getSourceMethod) // .containsExactly(tuple(inheritedMethodUniqueId, inheritedMethod), tuple(declaredMethodUniqueId, declaredMethod)); results = discoverTests(selectUniqueId(inheritedMethodUniqueId)); classDescriptor = getOnlyElement(results.getEngineDescriptor().getChildren()); assertThat(classDescriptor.getChildren()) // .extracting(TestDescriptor::getUniqueId, TestMethodOverridingTests::getSourceMethod) // .containsExactly(tuple(inheritedMethodUniqueId, inheritedMethod)); results = discoverTests(selectUniqueId(declaredMethodUniqueId)); classDescriptor = getOnlyElement(results.getEngineDescriptor().getChildren()); assertThat(classDescriptor.getChildren()) // .extracting(TestDescriptor::getUniqueId, TestMethodOverridingTests::getSourceMethod) // .containsExactly(tuple(declaredMethodUniqueId, declaredMethod)); } @Test void bothPackagePrivateTestMethodsAreExecuted() throws Exception { var results = executeTestsForClass(DuplicateTestMethodDueToPackagePrivateVisibilityTestCase.class); results.testEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); assertThat(allReportEntries(results)) // .containsExactly( Map.of("invokedSuper", SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase.class.getDeclaredMethod( "test", TestInfo.class, TestReporter.class).toGenericString()), Map.of("invokedChild", DuplicateTestMethodDueToPackagePrivateVisibilityTestCase.class.getDeclaredMethod("test", TestInfo.class, TestReporter.class).toGenericString())); } private static Method getSourceMethod(TestDescriptor it) { return ((MethodSource) it.getSource().orElseThrow()).getJavaMethod(); } private static Stream> allReportEntries(EngineExecutionResults results) { return results.allEvents().reportingEntryPublished() // .map(event -> event.getRequiredPayload(ReportEntry.class)) // .map(ReportEntry::getKeyValuePairs); } static class DuplicateTestMethodDueToPackagePrivateVisibilityTestCase extends SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase { // @Override @Test void test(TestInfo testInfo, TestReporter reporter) { reporter.publishEntry("invokedChild", testInfo.getTestMethod().orElseThrow().toGenericString()); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/TestTemplateInvocationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import static org.assertj.core.api.Assertions.assertThat; 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.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.junit.platform.testkit.engine.EventConditions.container; import static org.junit.platform.testkit.engine.EventConditions.displayName; import static org.junit.platform.testkit.engine.EventConditions.dynamicTestRegistered; import static org.junit.platform.testkit.engine.EventConditions.engine; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.EventConditions.skippedWithReason; import static org.junit.platform.testkit.engine.EventConditions.started; import static org.junit.platform.testkit.engine.EventConditions.test; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Predicate; import java.util.stream.Stream; import org.assertj.core.api.Condition; import org.jspecify.annotations.Nullable; 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.Test; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.AfterTestExecutionCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Namespace; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; import org.junit.jupiter.api.extension.TestInstancePostProcessor; import org.junit.jupiter.api.extension.TestTemplateInvocationContext; import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; import org.junit.jupiter.engine.descriptor.TestTemplateInvocationTestDescriptor; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.testkit.engine.EngineExecutionResults; import org.junit.platform.testkit.engine.Event; import org.junit.platform.testkit.engine.Events; import org.opentest4j.AssertionFailedError; /** * @since 5.0 */ class TestTemplateInvocationTests extends AbstractJupiterTestEngineTests { @Test void templateWithSingleRegisteredExtensionIsInvoked() { LauncherDiscoveryRequest request = request().selectors( selectMethod(MyTestTemplateTestCase.class, "templateWithSingleRegisteredExtension")).build(); EngineExecutionResults executionResults = executeTests(request); executionResults.allEvents().assertEventsMatchExactly( // wrappedInContainerEvents(MyTestTemplateTestCase.class, // event(container("templateWithSingleRegisteredExtension"), started()), // event(dynamicTestRegistered("test-template-invocation:#1")), // event(test("test-template-invocation:#1"), started()), // event(test("test-template-invocation:#1"), finishedWithFailure(message("invocation is expected to fail"))), // event(container("templateWithSingleRegisteredExtension"), finishedSuccessfully()))); } @Test void parentRelationshipIsEstablished() { LauncherDiscoveryRequest request = request().selectors( selectMethod(MyTestTemplateTestCase.class, "templateWithSingleRegisteredExtension")).build(); EngineExecutionResults executionResults = executeTests(request); TestDescriptor templateMethodDescriptor = findTestDescriptor(executionResults, container("templateWithSingleRegisteredExtension")); TestDescriptor invocationDescriptor = findTestDescriptor(executionResults, test("test-template-invocation:#1")); assertThat(invocationDescriptor.getParent()).hasValue(templateMethodDescriptor); } @Test void beforeAndAfterEachMethodsAreExecutedAroundInvocation() { LauncherDiscoveryRequest request = request().selectors( selectMethod(TestTemplateTestClassWithBeforeAndAfterEach.class, "testTemplateWithTwoInvocations")).build(); executeTests(request); assertThat(TestTemplateTestClassWithBeforeAndAfterEach.lifecycleEvents).containsExactly( "beforeAll:TestTemplateInvocationTests$TestTemplateTestClassWithBeforeAndAfterEach", "beforeEach:[1]", "afterEach:[1]", "beforeEach:[2]", "afterEach:[2]", "afterAll:TestTemplateInvocationTests$TestTemplateTestClassWithBeforeAndAfterEach"); } @Test void templateWithTwoRegisteredExtensionsIsInvoked() { LauncherDiscoveryRequest request = request().selectors( selectMethod(MyTestTemplateTestCase.class, "templateWithTwoRegisteredExtensions")).build(); EngineExecutionResults executionResults = executeTests(request); executionResults.allEvents().assertEventsMatchExactly( // wrappedInContainerEvents(MyTestTemplateTestCase.class, // event(container("templateWithTwoRegisteredExtensions"), started()), // event(dynamicTestRegistered("test-template-invocation:#1"), displayName("[1]")), // event(test("test-template-invocation:#1"), started()), // event(test("test-template-invocation:#1"), finishedWithFailure(message("invocation is expected to fail"))), // event(dynamicTestRegistered("test-template-invocation:#2"), displayName("[2]")), // event(test("test-template-invocation:#2"), started()), // event(test("test-template-invocation:#2"), finishedWithFailure(message("invocation is expected to fail"))), // event(container("templateWithTwoRegisteredExtensions"), finishedSuccessfully()))); } @Test void legacyReportingNames() { LauncherDiscoveryRequest request = request().selectors( selectMethod(MyTestTemplateTestCase.class, "templateWithTwoRegisteredExtensions")).build(); EngineExecutionResults results = executeTests(request); Events events = results.allEvents(); events.assertStatistics(stats -> stats.dynamicallyRegistered(2)); // events.dynamicallyRegistered().debug(); // results.testEvents().dynamicallyRegistered().debug(); // results.containerEvents().dynamicallyRegistered().debug(); // @formatter:off Stream legacyReportingNames = events.dynamicallyRegistered() .map(Event::getTestDescriptor) .map(TestDescriptor::getLegacyReportingName); // @formatter:off assertThat(legacyReportingNames).containsExactly("templateWithTwoRegisteredExtensions()[1]", "templateWithTwoRegisteredExtensions()[2]"); } @Test void templateWithTwoInvocationsFromSingleExtensionIsInvoked() { LauncherDiscoveryRequest request = request().selectors( selectMethod(MyTestTemplateTestCase.class, "templateWithTwoInvocationsFromSingleExtension")).build(); EngineExecutionResults executionResults = executeTests(request); executionResults.allEvents().assertEventsMatchExactly( // wrappedInContainerEvents(MyTestTemplateTestCase.class, // event(container("templateWithTwoInvocationsFromSingleExtension"), started()), // event(dynamicTestRegistered("test-template-invocation:#1"), displayName("[1]")), // event(test("test-template-invocation:#1"), started()), // event(test("test-template-invocation:#1"), finishedWithFailure(message("invocation is expected to fail"))), // event(dynamicTestRegistered("test-template-invocation:#2"), displayName("[2]")), // event(test("test-template-invocation:#2"), started()), // event(test("test-template-invocation:#2"), finishedWithFailure(message("invocation is expected to fail"))), // event(container("templateWithTwoInvocationsFromSingleExtension"), finishedSuccessfully()))); } @Test void singleInvocationIsExecutedWhenDiscoveredByUniqueId() { UniqueId uniqueId = discoverUniqueId(MyTestTemplateTestCase.class, "templateWithTwoInvocationsFromSingleExtension") // .append(TestTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#2"); EngineExecutionResults executionResults = executeTests(selectUniqueId(uniqueId)); executionResults.allEvents().assertEventsMatchExactly( // wrappedInContainerEvents(MyTestTemplateTestCase.class, // event(container("templateWithTwoInvocationsFromSingleExtension"), started()), // event(dynamicTestRegistered("test-template-invocation:#2"), displayName("[2]")), // event(test("test-template-invocation:#2"), started()), // event(test("test-template-invocation:#2"), finishedWithFailure(message("invocation is expected to fail"))), // event(container("templateWithTwoInvocationsFromSingleExtension"), finishedSuccessfully()))); } @Test void templateWithDisabledInvocationsIsSkipped() { LauncherDiscoveryRequest request = request().selectors( selectMethod(MyTestTemplateTestCase.class, "templateWithDisabledInvocations")).build(); EngineExecutionResults executionResults = executeTests(request); executionResults.allEvents().assertEventsMatchExactly( // wrappedInContainerEvents(MyTestTemplateTestCase.class, // event(container("templateWithDisabledInvocations"), started()), // event(dynamicTestRegistered("test-template-invocation:#1")), // event(test("test-template-invocation:#1"), skippedWithReason("always disabled")), // event(container("templateWithDisabledInvocations"), finishedSuccessfully()))); } @Test void disabledTemplateIsSkipped() { LauncherDiscoveryRequest request = request().selectors( selectMethod(MyTestTemplateTestCase.class, "disabledTemplate")).build(); EngineExecutionResults executionResults = executeTests(request); executionResults.allEvents().assertEventsMatchExactly( // wrappedInContainerEvents(MyTestTemplateTestCase.class, // event(container("disabledTemplate"), skippedWithReason("always disabled")))); } @Test void templateWithCustomizedDisplayNamesIsInvoked() { LauncherDiscoveryRequest request = request().selectors( selectMethod(MyTestTemplateTestCase.class, "templateWithCustomizedDisplayNames")).build(); EngineExecutionResults executionResults = executeTests(request); executionResults.allEvents().assertEventsMatchExactly( // wrappedInContainerEvents(MyTestTemplateTestCase.class, // event(container("templateWithCustomizedDisplayNames"), started()), // event(dynamicTestRegistered("test-template-invocation:#1"), displayName("1 --> templateWithCustomizedDisplayNames()")), // event(test("test-template-invocation:#1"), started()), // event(test("test-template-invocation:#1"), finishedWithFailure(message("invocation is expected to fail"))), // event(container("templateWithCustomizedDisplayNames"), finishedSuccessfully()))); } @Test void templateWithDynamicParameterResolverIsInvoked() { LauncherDiscoveryRequest request = request().selectors(selectMethod(MyTestTemplateTestCase.class, "templateWithDynamicParameterResolver", "java.lang.String")).build(); EngineExecutionResults executionResults = executeTests(request); executionResults.allEvents().assertEventsMatchExactly( // wrappedInContainerEvents(MyTestTemplateTestCase.class, // event(container("templateWithDynamicParameterResolver"), started()), // event(dynamicTestRegistered("test-template-invocation:#1"), displayName("[1] foo")), // event(test("test-template-invocation:#1"), started()), // event(test("test-template-invocation:#1"), finishedWithFailure(message("foo"))), // event(dynamicTestRegistered("test-template-invocation:#2"), displayName("[2] bar")), // event(test("test-template-invocation:#2"), started()), // event(test("test-template-invocation:#2"), finishedWithFailure(message("bar"))), // event(container("templateWithDynamicParameterResolver"), finishedSuccessfully()))); } @Test void contextParameterResolverCanResolveConstructorArguments() { LauncherDiscoveryRequest request = request().selectors( selectMethod(MyTestTemplateTestCaseWithConstructor.class, "template", "java.lang.String")).build(); EngineExecutionResults executionResults = executeTests(request); executionResults.allEvents().assertEventsMatchExactly( // wrappedInContainerEvents(MyTestTemplateTestCase.class, // event(container("template"), started()), // event(dynamicTestRegistered("test-template-invocation:#1"), displayName("[1] foo")), // event(test("test-template-invocation:#1"), started()), // event(test("test-template-invocation:#1"), finishedSuccessfully()), // event(dynamicTestRegistered("test-template-invocation:#2"), displayName("[2] bar")), // event(test("test-template-invocation:#2"), started()), // event(test("test-template-invocation:#2"), finishedSuccessfully()), // event(container("template"), finishedSuccessfully()))); } @Test void templateWithDynamicTestInstancePostProcessorIsInvoked() { LauncherDiscoveryRequest request = request().selectors( selectMethod(MyTestTemplateTestCase.class, "templateWithDynamicTestInstancePostProcessor")).build(); EngineExecutionResults executionResults = executeTests(request); executionResults.allEvents().assertEventsMatchExactly( // wrappedInContainerEvents(MyTestTemplateTestCase.class, // event(container("templateWithDynamicTestInstancePostProcessor"), started()), // event(dynamicTestRegistered("test-template-invocation:#1")), // event(test("test-template-invocation:#1"), started()), // event(test("test-template-invocation:#1"), finishedWithFailure(message("foo"))), // event(dynamicTestRegistered("test-template-invocation:#2")), // event(test("test-template-invocation:#2"), started()), // event(test("test-template-invocation:#2"), finishedWithFailure(message("bar"))), // event(container("templateWithDynamicTestInstancePostProcessor"), finishedSuccessfully()))); } @Test void lifecycleCallbacksAreExecutedForInvocation() { LauncherDiscoveryRequest request = request().selectors( selectClass(TestTemplateTestClassWithDynamicLifecycleCallbacks.class)).build(); executeTests(request); // @formatter:off assertThat(TestTemplateTestClassWithDynamicLifecycleCallbacks.lifecycleEvents).containsExactly( "beforeEach", "beforeTestExecution", "testTemplate:foo", "handleTestExecutionException", "afterTestExecution", "afterEach", "beforeEach", "beforeTestExecution", "testTemplate:bar", "afterTestExecution", "afterEach"); // @formatter:on } @Test void extensionIsAskedForSupportBeforeItMustProvide() { LauncherDiscoveryRequest request = request().selectors( selectMethod(MyTestTemplateTestCase.class, "templateWithWrongParameterType", int.class.getName())).build(); EngineExecutionResults executionResults = executeTests(request); executionResults.allEvents().assertEventsMatchExactly( // wrappedInContainerEvents(MyTestTemplateTestCase.class, // event(container("templateWithWrongParameterType"), started()), // event(container("templateWithWrongParameterType"), finishedWithFailure(message(s -> s.startsWith( "You must register at least one TestTemplateInvocationContextProvider that supports @TestTemplate method [")))))); } @Test void templateWithSupportingProviderButNoInvocationsReportsFailure() { LauncherDiscoveryRequest request = request().selectors( selectMethod(MyTestTemplateTestCase.class, "templateWithSupportingProviderButNoInvocations")).build(); EngineExecutionResults executionResults = executeTests(request); executionResults.allEvents().assertEventsMatchExactly( // wrappedInContainerEvents(MyTestTemplateTestCase.class, // event(container("templateWithSupportingProviderButNoInvocations"), started()), // event(container("templateWithSupportingProviderButNoInvocations"), finishedWithFailure(message( "Provider [%s] did not provide any invocation contexts, but was expected to do so. ".formatted( InvocationContextProviderThatSupportsEverythingButProvidesNothing.class.getSimpleName()) + "You may override mayReturnZeroTestTemplateInvocationContexts() to allow this."))))); } @Test void templateWithSupportingProviderAllowingNoInvocationsDoesNotFail() { LauncherDiscoveryRequest request = request().selectors( selectMethod(MyTestTemplateTestCase.class, "templateWithSupportingProviderAllowingNoInvocations")).build(); EngineExecutionResults executionResults = executeTests(request); executionResults.allEvents().assertEventsMatchExactly( // wrappedInContainerEvents(MyTestTemplateTestCase.class, event(container("templateWithSupportingProviderAllowingNoInvocations"), started()), event(container("templateWithSupportingProviderAllowingNoInvocations"), finishedSuccessfully()))); } @Test void templateWithMixedProvidersNoInvocationReportsFailure() { LauncherDiscoveryRequest request = request().selectors(selectMethod(MyTestTemplateTestCase.class, "templateWithMultipleProvidersAllowingAndRestrictingToProvideNothing")).build(); EngineExecutionResults executionResults = executeTests(request); executionResults.allEvents().assertEventsMatchExactly( // wrappedInContainerEvents(MyTestTemplateTestCase.class, // event(container("templateWithMultipleProvidersAllowingAndRestrictingToProvideNothing"), started()), // event(container("templateWithMultipleProvidersAllowingAndRestrictingToProvideNothing"), finishedWithFailure(message( "Provider [%s] did not provide any invocation contexts, but was expected to do so. ".formatted( InvocationContextProviderThatSupportsEverythingButProvidesNothing.class.getSimpleName()) + "You may override mayReturnZeroTestTemplateInvocationContexts() to allow this."))))); } @Test void templateWithCloseableStream() { LauncherDiscoveryRequest request = request().selectors( selectMethod(MyTestTemplateTestCase.class, "templateWithCloseableStream")).build(); EngineExecutionResults executionResults = executeTests(request); assertThat(InvocationContextProviderWithCloseableStream.streamClosed.get()).describedAs( "streamClosed").isTrue(); executionResults.allEvents().assertEventsMatchExactly( // wrappedInContainerEvents(MyTestTemplateTestCase.class, // event(container("templateWithCloseableStream"), started()), // event(dynamicTestRegistered("test-template-invocation:#1")), // event(test("test-template-invocation:#1"), started()), // event(test("test-template-invocation:#1"), finishedSuccessfully()), // event(container("templateWithCloseableStream"), finishedSuccessfully()))); } @Test void templateWithPreparations() { var results = executeTestsForClass(TestTemplateWithPreparationsTestCase.class); assertTrue(CustomCloseableResource.closed, "resource in store was closed"); results.allEvents().assertStatistics(stats -> stats.started(4).succeeded(4)); } private TestDescriptor findTestDescriptor(EngineExecutionResults executionResults, Condition condition) { // @formatter:off return executionResults.allEvents() .filter(condition::matches) .findAny() .map(Event::getTestDescriptor) .orElseThrow(() -> new AssertionFailedError("Could not find event for condition: " + condition)); // @formatter:on } @SafeVarargs @SuppressWarnings({ "unchecked", "varargs", "rawtypes" }) private final Condition[] wrappedInContainerEvents(Class clazz, Condition... wrappedConditions) { List> conditions = new ArrayList<>(); conditions.add(event(engine(), started())); conditions.add(event(container(clazz), started())); Collections.addAll(conditions, wrappedConditions); conditions.add(event(container(clazz), finishedSuccessfully())); conditions.add(event(engine(), finishedSuccessfully())); return conditions.toArray(new Condition[0]); } static class MyTestTemplateTestCase { @TestTemplate void templateWithoutRegisteredExtension() { } @ExtendWith(SingleInvocationContextProvider.class) @TestTemplate void templateWithSingleRegisteredExtension() { fail("invocation is expected to fail"); } @ExtendWith({ SingleInvocationContextProvider.class, AnotherInvocationContextProviderWithASingleInvocation.class }) @TestTemplate void templateWithTwoRegisteredExtensions() { fail("invocation is expected to fail"); } @ExtendWith(TwoInvocationsContextProvider.class) @TestTemplate void templateWithTwoInvocationsFromSingleExtension() { fail("invocation is expected to fail"); } @ExtendWith({ SingleInvocationContextProviderWithDisabledInvocations.class }) @TestTemplate void templateWithDisabledInvocations() { fail("this is never called"); } @ExtendWith(AlwaysDisabledExecutionCondition.class) @TestTemplate void disabledTemplate() { fail("this is never called"); } @ExtendWith(InvocationContextProviderWithCustomizedDisplayNames.class) @TestTemplate void templateWithCustomizedDisplayNames() { fail("invocation is expected to fail"); } @ExtendWith(StringParameterResolvingInvocationContextProvider.class) @TestTemplate void templateWithDynamicParameterResolver(String parameter) { fail(parameter); } @ExtendWith(StringParameterResolvingInvocationContextProvider.class) @TestTemplate void templateWithWrongParameterType(int parameter) { fail("never called: " + parameter); } @Nullable String parameterInstanceVariable; @ExtendWith(StringParameterInjectingInvocationContextProvider.class) @TestTemplate void templateWithDynamicTestInstancePostProcessor() { fail(parameterInstanceVariable); } @ExtendWith(InvocationContextProviderThatSupportsEverythingButProvidesNothing.class) @TestTemplate void templateWithSupportingProviderButNoInvocations() { fail("never called"); } @ExtendWith(InvocationContextProviderThatSupportsEverythingAllowsProvideNothing.class) @TestTemplate void templateWithSupportingProviderAllowingNoInvocations() { fail("never called"); } @ExtendWith(InvocationContextProviderThatSupportsEverythingButProvidesNothing.class) @ExtendWith(InvocationContextProviderThatSupportsEverythingAllowsProvideNothing.class) @TestTemplate void templateWithMultipleProvidersAllowingAndRestrictingToProvideNothing() { fail("never called"); } @ExtendWith(InvocationContextProviderWithCloseableStream.class) @TestTemplate void templateWithCloseableStream() { } } @ExtendWith(StringParameterResolvingInvocationContextProvider.class) static class MyTestTemplateTestCaseWithConstructor { private final String constructorParameter; MyTestTemplateTestCaseWithConstructor(String constructorParameter) { this.constructorParameter = constructorParameter; } @TestTemplate void template(String parameter) { assertEquals(constructorParameter, parameter); } } static class TestTemplateTestClassWithBeforeAndAfterEach { private static final List lifecycleEvents = new ArrayList<>(); @BeforeAll static void beforeAll(TestInfo testInfo) { lifecycleEvents.add("beforeAll:" + testInfo.getDisplayName()); } @AfterAll static void afterAll(TestInfo testInfo) { lifecycleEvents.add("afterAll:" + testInfo.getDisplayName()); } @BeforeEach void beforeEach(TestInfo testInfo) { lifecycleEvents.add("beforeEach:" + testInfo.getDisplayName()); } @AfterEach void afterEach(TestInfo testInfo) { lifecycleEvents.add("afterEach:" + testInfo.getDisplayName()); } @ExtendWith(TwoInvocationsContextProvider.class) @TestTemplate void testTemplateWithTwoInvocations() { fail("invocation is expected to fail"); } } static class TestTemplateTestClassWithDynamicLifecycleCallbacks { private static List lifecycleEvents = new ArrayList<>(); @ExtendWith(InvocationContextProviderWithDynamicLifecycleCallbacks.class) @TestTemplate void testTemplate(TestInfo testInfo) { lifecycleEvents.add("testTemplate:" + testInfo.getDisplayName()); assertEquals("bar", testInfo.getDisplayName()); } } private static class SingleInvocationContextProvider implements TestTemplateInvocationContextProvider { @Override public boolean supportsTestTemplate(ExtensionContext context) { return true; } @Override public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { return Stream.of(emptyTestTemplateInvocationContext()); } } private static class SingleInvocationContextProviderWithDisabledInvocations extends SingleInvocationContextProvider { @Override public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { return Stream.of(new TestTemplateInvocationContext() { @Override public List getAdditionalExtensions() { return List.of(new AlwaysDisabledExecutionCondition()); } }); } } private static class AnotherInvocationContextProviderWithASingleInvocation implements TestTemplateInvocationContextProvider { @Override public boolean supportsTestTemplate(ExtensionContext context) { return true; } @Override public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { return Stream.of(emptyTestTemplateInvocationContext()); } } private static class TwoInvocationsContextProvider implements TestTemplateInvocationContextProvider { @Override public boolean supportsTestTemplate(ExtensionContext context) { return true; } @Override public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { return Stream.of(emptyTestTemplateInvocationContext(), emptyTestTemplateInvocationContext()); } } private static class AlwaysDisabledExecutionCondition implements ExecutionCondition { @Override public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { return ConditionEvaluationResult.disabled("always disabled"); } } private static class InvocationContextProviderWithCustomizedDisplayNames implements TestTemplateInvocationContextProvider { @Override public boolean supportsTestTemplate(ExtensionContext context) { return true; } @Override public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { return Stream. generate(() -> new TestTemplateInvocationContext() { @Override public String getDisplayName(int invocationIndex) { return invocationIndex + " --> " + context.getDisplayName(); } }).limit(1); } } private static class StringParameterResolvingInvocationContextProvider implements TestTemplateInvocationContextProvider { @Override public boolean supportsTestTemplate(ExtensionContext context) { // @formatter:off return context.getTestMethod() .map(Method::getParameterTypes) .map(Arrays::stream) .map(parameters -> parameters.anyMatch(Predicate.isEqual(String.class))) .orElse(false); // @formatter:on } @Override public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { return Stream.of(createContext("foo"), createContext("bar")); } private TestTemplateInvocationContext createContext(String argument) { return new TestTemplateInvocationContext() { @Override public String getDisplayName(int invocationIndex) { return TestTemplateInvocationContext.super.getDisplayName(invocationIndex) + " " + argument; } @Override public List getAdditionalExtensions() { return List.of(new ParameterResolver() { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return true; } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return argument; } }); } }; } } private static class StringParameterInjectingInvocationContextProvider implements TestTemplateInvocationContextProvider { @Override public boolean supportsTestTemplate(ExtensionContext context) { return true; } @Override public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { return Stream.of(createContext("foo"), createContext("bar")); } private TestTemplateInvocationContext createContext(String argument) { return new TestTemplateInvocationContext() { @Override public String getDisplayName(int invocationIndex) { return TestTemplateInvocationContext.super.getDisplayName(invocationIndex) + " " + argument; } @Override public List getAdditionalExtensions() { return List.of((TestInstancePostProcessor) (testInstance, context) -> { Field field = testInstance.getClass().getDeclaredField("parameterInstanceVariable"); field.setAccessible(true); field.set(testInstance, argument); }); } }; } } private static class InvocationContextProviderWithDynamicLifecycleCallbacks implements TestTemplateInvocationContextProvider { @Override public boolean supportsTestTemplate(ExtensionContext context) { return true; } @Override public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { return Stream.of(createContext("foo"), createContext("bar")); } private TestTemplateInvocationContext createContext(String argument) { return new TestTemplateInvocationContext() { @Override public String getDisplayName(int invocationIndex) { return argument; } @Override public List getAdditionalExtensions() { return List.of(new LifecycleCallbackExtension()); } }; } private static class LifecycleCallbackExtension implements BeforeEachCallback, BeforeTestExecutionCallback, TestExecutionExceptionHandler, AfterTestExecutionCallback, AfterEachCallback { @Override public void beforeEach(ExtensionContext context) { TestTemplateTestClassWithDynamicLifecycleCallbacks.lifecycleEvents.add("beforeEach"); } @Override public void beforeTestExecution(ExtensionContext context) { TestTemplateTestClassWithDynamicLifecycleCallbacks.lifecycleEvents.add("beforeTestExecution"); } @Override public void handleTestExecutionException(ExtensionContext context, Throwable throwable) { TestTemplateTestClassWithDynamicLifecycleCallbacks.lifecycleEvents.add("handleTestExecutionException"); throw new AssertionError(throwable); } @Override public void afterTestExecution(ExtensionContext context) { TestTemplateTestClassWithDynamicLifecycleCallbacks.lifecycleEvents.add("afterTestExecution"); } @Override public void afterEach(ExtensionContext context) { TestTemplateTestClassWithDynamicLifecycleCallbacks.lifecycleEvents.add("afterEach"); } } } private static class InvocationContextProviderThatSupportsEverythingButProvidesNothing implements TestTemplateInvocationContextProvider { @Override public boolean supportsTestTemplate(ExtensionContext context) { return true; } @Override public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { return Stream.empty(); } } private static class InvocationContextProviderThatSupportsEverythingAllowsProvideNothing implements TestTemplateInvocationContextProvider { @Override public boolean supportsTestTemplate(ExtensionContext context) { return true; } @Override public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { return Stream.empty(); } @Override public boolean mayReturnZeroTestTemplateInvocationContexts(ExtensionContext extensionContext) { return true; } } private static class InvocationContextProviderWithCloseableStream implements TestTemplateInvocationContextProvider { private static AtomicBoolean streamClosed = new AtomicBoolean(false); @Override public boolean supportsTestTemplate(ExtensionContext context) { return true; } @Override public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { return Stream.of(emptyTestTemplateInvocationContext()).onClose(() -> streamClosed.set(true)); } } private static TestTemplateInvocationContext emptyTestTemplateInvocationContext() { return new TestTemplateInvocationContext() { }; } static class TestTemplateWithPreparationsTestCase { @TestTemplate @ExtendWith({ PreparingTestTemplateInvocationContextProvider.class, CompanionExtension.class }) void test(CustomCloseableResource resource) { assertNotNull(resource); assertFalse(CustomCloseableResource.closed, "should not be closed yet"); } } private static class PreparingTestTemplateInvocationContextProvider implements TestTemplateInvocationContextProvider { static final Namespace NAMESPACE = Namespace.create(PreparingTestTemplateInvocationContextProvider.class); @Override public boolean supportsTestTemplate(ExtensionContext context) { return true; } @Override public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { return Stream.of(new PreparingTestTemplateInvocationContext()); } } private static class PreparingTestTemplateInvocationContext implements TestTemplateInvocationContext { @Override public void prepareInvocation(ExtensionContext context) { context.getStore(PreparingTestTemplateInvocationContextProvider.NAMESPACE) // .put("resource", new CustomCloseableResource()); } } private static class CompanionExtension implements ParameterResolver { @Override public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { return ExtensionContextScope.TEST_METHOD; } @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return CustomCloseableResource.class.equals(parameterContext.getParameter().getType()); } @Override public @Nullable Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return extensionContext.getStore(PreparingTestTemplateInvocationContextProvider.NAMESPACE).get("resource"); } } private static class CustomCloseableResource implements AutoCloseable { static boolean closed; @Override public void close() { closed = true; } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/TopLevelComposedNested.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.junit.jupiter.api.Nested; @Nested @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface TopLevelComposedNested { } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/TopLevelNestedTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @Nested public class TopLevelNestedTestCase { @Test void test() { } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/bridge/AbstractNonGenericTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.bridge; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; /** * @since 5.0 */ @ExtendWith(NumberResolver.class) abstract class AbstractNonGenericTests { @Test void mA() { BridgeMethodTests.sequence.add("mA()"); } @Test void test(Number value) { BridgeMethodTests.sequence.add("A.test(Number)"); Assertions.assertEquals(42, value); } static class B extends AbstractNonGenericTests { @Test void mB() { BridgeMethodTests.sequence.add("mB()"); } @Test void test(Byte value) { BridgeMethodTests.sequence.add("B.test(Byte)"); Assertions.assertEquals(123, value.intValue()); } } static class C extends B { @Test void mC() { BridgeMethodTests.sequence.add("mC()"); } @Override @Test void test(Byte value) { BridgeMethodTests.sequence.add("C.test(Byte)"); Assertions.assertEquals(123, value.intValue()); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/bridge/AbstractNumberTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.bridge; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; /** * @since 5.0 */ @ExtendWith(NumberResolver.class) abstract class AbstractNumberTests { @Test void test(N number) { BridgeMethodTests.sequence.add("test(N)"); assertNotNull(number); assertEquals(123, number.intValue()); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/bridge/BridgeMethodTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.bridge; 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.assertTrue; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.jupiter.engine.bridge.NumberTestGroup.ByteTestCase; import org.junit.jupiter.engine.bridge.NumberTestGroup.ShortTestCase; /** * @since 5.0 */ class BridgeMethodTests extends AbstractJupiterTestEngineTests { static List sequence = new ArrayList<>(); @Test void childrenHaveBridgeMethods() throws Exception { assertFalse(ChildWithBridgeMethods.class.getMethod("anotherBeforeEach").isBridge()); assertFalse(ChildWithBridgeMethods.class.getMethod("anotherAfterEach").isBridge()); assertTrue(ChildWithBridgeMethods.class.getMethod("beforeEach").isBridge()); assertTrue(ChildWithBridgeMethods.class.getMethod("afterEach").isBridge()); assertTrue(ByteTestCase.class.getDeclaredMethod("test", Number.class).isBridge()); assertFalse(ByteTestCase.class.getDeclaredMethod("test", Byte.class).isBridge()); assertTrue(ShortTestCase.class.getDeclaredMethod("test", Number.class).isBridge()); assertFalse(ShortTestCase.class.getDeclaredMethod("test", Short.class).isBridge()); } @Test void childHasNoBridgeMethods() throws Exception { assertFalse(ChildWithoutBridgeMethods.class.getMethod("anotherBeforeEach").isBridge()); assertFalse(ChildWithoutBridgeMethods.class.getMethod("anotherAfterEach").isBridge()); assertFalse(ChildWithoutBridgeMethods.class.getMethod("beforeEach").isBridge()); assertFalse(ChildWithoutBridgeMethods.class.getMethod("afterEach").isBridge()); } @Test void compareMethodExecutionSequenceOrder() { String withoutBridgeMethods = execute(1, ChildWithoutBridgeMethods.class); String withBridgeMethods = execute(1, ChildWithBridgeMethods.class); assertEquals(withoutBridgeMethods, withBridgeMethods); } @TestFactory List ensureSingleTestMethodsExecute() { return Arrays.asList( // dynamicTest("Byte", // () -> assertEquals("[test(Byte) BEGIN, test(N), test(Byte) END, test(Long) BEGIN, test(Long) END]", // execute(2, ByteTestCase.class))), dynamicTest("Short", // () -> assertEquals("[test(Long) BEGIN, test(Long) END, test(Short) BEGIN, test(N), test(Short) END]", // execute(2, ShortTestCase.class)))); } @Test void inheritedNonGenericMethodsAreExecuted() { String b = execute(4, AbstractNonGenericTests.B.class); assertAll("Missing expected test(s) in sequence: " + b, // () -> assertTrue(b.contains("A.test(Number)")), // () -> assertTrue(b.contains("mA()")), // () -> assertTrue(b.contains("mB()")), // () -> assertTrue(b.contains("B.test(Byte)")) // ); String c = execute(5, AbstractNonGenericTests.C.class); assertAll("Missing expected test(s) in sequence: " + c, // () -> assertTrue(c.contains("A.test(Number)")), // () -> assertTrue(c.contains("mA()")), // () -> assertTrue(c.contains("mB()")), // () -> assertTrue(c.contains("mC()")), // () -> assertTrue(c.contains("C.test(Byte)")) // ); } private String execute(int expectedTestFinishedCount, Class testClass) { sequence.clear(); executeTestsForClass(testClass).testEvents()// .assertStatistics(stats -> stats.started(expectedTestFinishedCount)); return sequence.toString(); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/bridge/ChildWithBridgeMethods.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.bridge; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; /** * @since 5.0 */ // modifier "public" is necessary for creating bridge methods by the compiler public class ChildWithBridgeMethods extends PackagePrivateParent { @BeforeEach public void anotherBeforeEach() { BridgeMethodTests.sequence.add("child.anotherBeforeEach()"); } @Test void test() { BridgeMethodTests.sequence.add("child.test()"); } @AfterEach public void anotherAfterEach() { BridgeMethodTests.sequence.add("child.anotherAfterEach()"); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/bridge/ChildWithoutBridgeMethods.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.bridge; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; /** * @since 5.0 */ // modifier "public" is *not* present for not creating bridge methods by the compiler class ChildWithoutBridgeMethods extends PackagePrivateParent { @BeforeEach public void anotherBeforeEach() { BridgeMethodTests.sequence.add("child.anotherBeforeEach()"); } @Test void test() { BridgeMethodTests.sequence.add("child.test()"); } @AfterEach public void anotherAfterEach() { BridgeMethodTests.sequence.add("child.anotherAfterEach()"); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/bridge/NumberResolver.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.bridge; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.extension.ParameterResolver; /** * @since 5.0 */ class NumberResolver implements ParameterResolver { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return Number.class.isAssignableFrom(parameterContext.getParameter().getType()); } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { Class type = parameterContext.getParameter().getType(); if (type == Number.class) { return 42; } try { return type.getMethod("valueOf", String.class).invoke(null, "123"); } catch (Exception e) { throw new AssertionError("Could not resolve number type: " + type, e); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/bridge/NumberTestGroup.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.bridge; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import org.junit.jupiter.api.Test; /** * @since 5.0 */ interface NumberTestGroup { class ByteTestCase extends AbstractNumberTests { @Test @Override void test(Byte number) { BridgeMethodTests.sequence.add("test(Byte) BEGIN"); super.test(number); BridgeMethodTests.sequence.add("test(Byte) END"); } @Test void test(Long number) { BridgeMethodTests.sequence.add("test(Long) BEGIN"); assertNotNull(number); assertEquals(123, number.intValue()); BridgeMethodTests.sequence.add("test(Long) END"); } } class ShortTestCase extends AbstractNumberTests { @Test @Override void test(Short number) { BridgeMethodTests.sequence.add("test(Short) BEGIN"); super.test(number); BridgeMethodTests.sequence.add("test(Short) END"); } @Test void test(Long number) { BridgeMethodTests.sequence.add("test(Long) BEGIN"); assertNotNull(number); assertEquals(123, number.intValue()); BridgeMethodTests.sequence.add("test(Long) END"); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/bridge/PackagePrivateParent.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.bridge; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; /** * @since 5.0 */ class PackagePrivateParent { @BeforeAll static void beforeAll() { BridgeMethodTests.sequence.add("static parent.beforeAll()"); } @AfterAll static void afterAll() { BridgeMethodTests.sequence.add("static parent.afterAll()"); } @BeforeEach public void beforeEach() { BridgeMethodTests.sequence.add("parent.beforeEach()"); } @AfterEach public void afterEach() { BridgeMethodTests.sequence.add("parent.afterEach()"); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/config/CachingJupiterConfigurationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.config; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.io.CleanupMode.NEVER; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.only; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import java.util.Optional; import java.util.function.Predicate; import java.util.function.Supplier; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.io.TempDirFactory; import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.jupiter.engine.descriptor.CustomDisplayNameGenerator; /** * Unit tests for {@link CachingJupiterConfiguration}. */ class CachingJupiterConfigurationTests { private final JupiterConfiguration delegate = mock(); private final JupiterConfiguration cache = new CachingJupiterConfiguration(delegate); @Test void cachesDefaultExecutionMode() { when(delegate.getDefaultExecutionMode()).thenReturn(ExecutionMode.CONCURRENT); assertThat(cache.getDefaultExecutionMode()).isEqualTo(ExecutionMode.CONCURRENT); assertThat(cache.getDefaultExecutionMode()).isEqualTo(ExecutionMode.CONCURRENT); verify(delegate, only()).getDefaultExecutionMode(); } @Test void cachesDefaultTestInstanceLifecycle() { when(delegate.getDefaultTestInstanceLifecycle()).thenReturn(Lifecycle.PER_CLASS); assertThat(cache.getDefaultTestInstanceLifecycle()).isEqualTo(Lifecycle.PER_CLASS); assertThat(cache.getDefaultTestInstanceLifecycle()).isEqualTo(Lifecycle.PER_CLASS); verify(delegate, only()).getDefaultTestInstanceLifecycle(); } @Test void cachesExecutionConditionFilter() { Predicate predicate = executionCondition -> true; when(delegate.getExecutionConditionFilter()).thenReturn(predicate); assertThat(cache.getExecutionConditionFilter()).isSameAs(predicate); assertThat(cache.getExecutionConditionFilter()).isSameAs(predicate); verify(delegate, only()).getExecutionConditionFilter(); } @Test void cachesExtensionAutoDetectionEnabled() { when(delegate.isExtensionAutoDetectionEnabled()).thenReturn(true); assertThat(cache.isExtensionAutoDetectionEnabled()).isTrue(); assertThat(cache.isExtensionAutoDetectionEnabled()).isTrue(); verify(delegate, only()).isExtensionAutoDetectionEnabled(); } @Test void cachesParallelExecutionEnabled() { when(delegate.isParallelExecutionEnabled()).thenReturn(true); assertThat(cache.isParallelExecutionEnabled()).isTrue(); assertThat(cache.isParallelExecutionEnabled()).isTrue(); verify(delegate, only()).isParallelExecutionEnabled(); } @Test void cachesDefaultDisplayNameGenerator() { CustomDisplayNameGenerator customDisplayNameGenerator = new CustomDisplayNameGenerator(); when(delegate.getDefaultDisplayNameGenerator()).thenReturn(customDisplayNameGenerator); // call `cache.getDefaultDisplayNameGenerator()` twice to verify the delegate method is called only once. assertThat(cache.getDefaultDisplayNameGenerator()).isSameAs(customDisplayNameGenerator); assertThat(cache.getDefaultDisplayNameGenerator()).isSameAs(customDisplayNameGenerator); verify(delegate, only()).getDefaultDisplayNameGenerator(); } @Test void cachesDefaultTestMethodOrderer() { final Optional methodOrderer = Optional.of(new MethodOrderer.MethodName()); when(delegate.getDefaultTestMethodOrderer()).thenReturn(methodOrderer); // call `cache.getDefaultTestMethodOrderer()` twice to verify the delegate method is called only once. assertThat(cache.getDefaultTestMethodOrderer()).isSameAs(methodOrderer); assertThat(cache.getDefaultTestMethodOrderer()).isSameAs(methodOrderer); verify(delegate, only()).getDefaultTestMethodOrderer(); } @Test void cachesDefaultTempDirCleanupMode() { when(delegate.getDefaultTempDirCleanupMode()).thenReturn(NEVER); // call `cache.getDefaultTempStrategyDirCleanupMode()` twice to verify the delegate method is called only once. assertThat(cache.getDefaultTempDirCleanupMode()).isSameAs(NEVER); assertThat(cache.getDefaultTempDirCleanupMode()).isSameAs(NEVER); verify(delegate, only()).getDefaultTempDirCleanupMode(); } @Test void cachesDefaultTempDirFactorySupplier() { Supplier supplier = mock(); when(delegate.getDefaultTempDirFactorySupplier()).thenReturn(supplier); // call `cache.getDefaultTempDirFactorySupplier()` twice to verify the delegate method is called only once. assertThat(cache.getDefaultTempDirFactorySupplier()).isSameAs(supplier); assertThat(cache.getDefaultTempDirFactorySupplier()).isSameAs(supplier); verify(delegate, only()).getDefaultTempDirFactorySupplier(); } @Test void doesNotCacheRawParameters() { when(delegate.getRawConfigurationParameter("foo")).thenReturn(Optional.of("bar")).thenReturn( Optional.of("baz")); assertThat(cache.getRawConfigurationParameter("foo")).contains("bar"); assertThat(cache.getRawConfigurationParameter("foo")).contains("baz"); verify(delegate, times(2)).getRawConfigurationParameter("foo"); verifyNoMoreInteractions(delegate); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.config; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Constants.DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.jupiter.api.io.CleanupMode.ALWAYS; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullFor; import static org.junit.platform.launcher.core.OutputDirectoryCreators.dummyOutputDirectoryCreator; import static org.mockito.Mockito.mock; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.Supplier; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Constants; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.extension.AnnotatedElementContext; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDirFactory; import org.junit.jupiter.engine.descriptor.CustomDisplayNameGenerator; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter; import org.junit.platform.engine.support.hierarchical.ParallelHierarchicalTestExecutorServiceFactory.ParallelExecutorServiceType; import org.junit.platform.launcher.core.ConfigurationParametersFactoryForTests; class DefaultJupiterConfigurationTests { private static final String KEY = DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME; @SuppressWarnings("DataFlowIssue") @Test void getDefaultTestInstanceLifecyclePreconditions() { assertPreconditionViolationNotNullFor("ConfigurationParameters", () -> new DefaultJupiterConfiguration(null, dummyOutputDirectoryCreator(), mock())); } @Test void getDefaultTestInstanceLifecycleWithNoConfigParamSet() { JupiterConfiguration configuration = new DefaultJupiterConfiguration(configurationParameters(Map.of()), dummyOutputDirectoryCreator(), mock()); Lifecycle lifecycle = configuration.getDefaultTestInstanceLifecycle(); assertThat(lifecycle).isEqualTo(PER_METHOD); } @Test void getDefaultTempDirCleanupModeWithNoConfigParamSet() { JupiterConfiguration configuration = new DefaultJupiterConfiguration(configurationParameters(Map.of()), dummyOutputDirectoryCreator(), mock()); CleanupMode cleanupMode = configuration.getDefaultTempDirCleanupMode(); assertThat(cleanupMode).isEqualTo(ALWAYS); } @Test void getDefaultTestInstanceLifecycleWithConfigParamSet() { assertAll(// () -> assertDefaultConfigParam(null, PER_METHOD), // () -> assertThatThrownBy(() -> getDefaultTestInstanceLifecycleConfigParam("")) // .hasMessage("Invalid test instance lifecycle mode '' set via the '%s' configuration parameter.", DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME), // () -> assertThatThrownBy(() -> getDefaultTestInstanceLifecycleConfigParam("bogus")) // .hasMessage( "Invalid test instance lifecycle mode 'BOGUS' set via the '%s' configuration parameter.", DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME), // () -> assertDefaultConfigParam(PER_METHOD.name(), PER_METHOD), // () -> assertDefaultConfigParam(PER_METHOD.name().toLowerCase(), PER_METHOD), // () -> assertDefaultConfigParam(" " + PER_METHOD.name() + " ", PER_METHOD), // () -> assertDefaultConfigParam(PER_CLASS.name(), PER_CLASS), // () -> assertDefaultConfigParam(PER_CLASS.name().toLowerCase(), PER_CLASS), // () -> assertDefaultConfigParam(" " + PER_CLASS.name() + " ", Lifecycle.PER_CLASS) // ); } @Test void shouldGetDefaultDisplayNameGeneratorWithConfigParamSet() { var parameters = configurationParameters( Map.of(Constants.DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME, CustomDisplayNameGenerator.class.getName())); JupiterConfiguration configuration = new DefaultJupiterConfiguration(parameters, dummyOutputDirectoryCreator(), mock()); DisplayNameGenerator defaultDisplayNameGenerator = configuration.getDefaultDisplayNameGenerator(); assertThat(defaultDisplayNameGenerator).isInstanceOf(CustomDisplayNameGenerator.class); } @Test void shouldGetStandardAsDefaultDisplayNameGeneratorWithoutConfigParamSet() { JupiterConfiguration configuration = new DefaultJupiterConfiguration(configurationParameters(Map.of()), dummyOutputDirectoryCreator(), mock()); DisplayNameGenerator defaultDisplayNameGenerator = configuration.getDefaultDisplayNameGenerator(); assertThat(defaultDisplayNameGenerator).isInstanceOf(DisplayNameGenerator.Standard.class); } @Test void shouldGetNothingAsDefaultTestMethodOrderWithoutConfigParamSet() { JupiterConfiguration configuration = new DefaultJupiterConfiguration(configurationParameters(Map.of()), dummyOutputDirectoryCreator(), mock()); final Optional defaultTestMethodOrder = configuration.getDefaultTestMethodOrderer(); assertThat(defaultTestMethodOrder).isEmpty(); } @Test void shouldGetDefaultTempDirFactorySupplierWithConfigParamSet() { var parameters = configurationParameters( Map.of(Constants.DEFAULT_TEMP_DIR_FACTORY_PROPERTY_NAME, CustomFactory.class.getName())); JupiterConfiguration configuration = new DefaultJupiterConfiguration(parameters, dummyOutputDirectoryCreator(), mock()); Supplier supplier = configuration.getDefaultTempDirFactorySupplier(); assertThat(supplier.get()).isInstanceOf(CustomFactory.class); } @Test void shouldGetStandardAsDefaultTempDirFactorySupplierWithoutConfigParamSet() { JupiterConfiguration configuration = new DefaultJupiterConfiguration(configurationParameters(Map.of()), dummyOutputDirectoryCreator(), mock()); Supplier supplier = configuration.getDefaultTempDirFactorySupplier(); assertThat(supplier.get()).isSameAs(TempDirFactory.Standard.INSTANCE); } @Test void doesNotReportAnyIssuesIfConfigurationParametersAreEmpty() { List issues = new ArrayList<>(); new DefaultJupiterConfiguration(configurationParameters(Map.of()), dummyOutputDirectoryCreator(), DiscoveryIssueReporter.collecting(issues)).getDefaultTestInstanceLifecycle(); assertThat(issues).isEmpty(); } @ParameterizedTest @EnumSource(ParallelExecutorServiceType.class) void doesNotReportAnyIssuesIfParallelExecutionIsEnabledAndConfigurationParameterIsSet( ParallelExecutorServiceType executorServiceType) { var parameters = Map.of(Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, true, // Constants.PARALLEL_CONFIG_EXECUTOR_SERVICE_PROPERTY_NAME, executorServiceType); List issues = new ArrayList<>(); new DefaultJupiterConfiguration(ConfigurationParametersFactoryForTests.create(parameters), dummyOutputDirectoryCreator(), DiscoveryIssueReporter.collecting(issues)).getDefaultTestInstanceLifecycle(); assertThat(issues).isEmpty(); } @Test void asksUsersToTryWorkerThreadPoolHierarchicalExecutorServiceIfParallelExecutionIsEnabled() { var parameters = Map.of(Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, true); List issues = new ArrayList<>(); new DefaultJupiterConfiguration(configurationParameters(parameters), dummyOutputDirectoryCreator(), DiscoveryIssueReporter.collecting(issues)).getDefaultTestInstanceLifecycle(); assertThat(issues).containsExactly(DiscoveryIssue.create(Severity.INFO, """ Parallel test execution is enabled but the default ForkJoinPool-based executor service will be used. \ Please give the new implementation based on a regular thread pool a try by setting the \ 'junit.jupiter.execution.parallel.config.executor-service' configuration parameter to \ 'WORKER_THREAD_POOL' and report any issues to the JUnit team. Alternatively, set the configuration \ parameter to 'FORK_JOIN_POOL' to hide this message and keep using the original implementation.""")); } private void assertDefaultConfigParam(@Nullable String configValue, Lifecycle expected) { var lifecycle = getDefaultTestInstanceLifecycleConfigParam(configValue); assertThat(lifecycle).isEqualTo(expected); } private static Lifecycle getDefaultTestInstanceLifecycleConfigParam(@Nullable String configValue) { var configParams = configurationParameters(configValue == null ? Map.of() : Map.of(KEY, configValue)); return new DefaultJupiterConfiguration(configParams, dummyOutputDirectoryCreator(), mock()).getDefaultTestInstanceLifecycle(); } private static ConfigurationParameters configurationParameters(Map<@NonNull String, ?> parameters) { return ConfigurationParametersFactoryForTests.create(parameters); } @NullMarked private static class CustomFactory implements TempDirFactory { @Override public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) { throw new UnsupportedOperationException(); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverterTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.config; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Constants.DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.Optional; import java.util.logging.Level; import java.util.logging.LogRecord; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.fixtures.TrackLogRecords; import org.junit.jupiter.engine.descriptor.CustomDisplayNameGenerator; import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.engine.ConfigurationParameters; /** * @since 5.5 */ @TrackLogRecords class InstantiatingConfigurationParameterConverterTests { private static final String KEY = DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME; @Test void shouldInstantiateConfiguredClass(LogRecordListener listener) { ConfigurationParameters configurationParameters = mock(); when(configurationParameters.get(KEY)).thenReturn(Optional.of(CustomDisplayNameGenerator.class.getName())); InstantiatingConfigurationParameterConverter converter = new InstantiatingConfigurationParameterConverter<>( DisplayNameGenerator.class, "display name generator"); DisplayNameGenerator displayNameGenerator = converter.get(configurationParameters, KEY).orElseThrow(); assertThat(displayNameGenerator).isInstanceOf(CustomDisplayNameGenerator.class); assertExpectedLogMessage(listener, Level.CONFIG, "Using default display name generator " + "'org.junit.jupiter.engine.descriptor.CustomDisplayNameGenerator' set via the " + "'junit.jupiter.displayname.generator.default' configuration parameter."); } @Test void shouldReturnEmptyOptionalIfNoConfigurationFound() { ConfigurationParameters configurationParameters = mock(); when(configurationParameters.get(KEY)).thenReturn(Optional.empty()); InstantiatingConfigurationParameterConverter converter = new InstantiatingConfigurationParameterConverter<>( DisplayNameGenerator.class, "display name generator"); Optional displayNameGenerator = converter.get(configurationParameters, KEY); assertThat(displayNameGenerator).isEmpty(); } @Test void shouldReturnEmptyOptionalIfConfigurationIsBlank() { ConfigurationParameters configurationParameters = mock(); when(configurationParameters.get(KEY)).thenReturn(Optional.of("")); InstantiatingConfigurationParameterConverter converter = new InstantiatingConfigurationParameterConverter<>( DisplayNameGenerator.class, "display name generator"); Optional displayNameGenerator = converter.get(configurationParameters, KEY); assertThat(displayNameGenerator).isEmpty(); } @Test void shouldTrimAndInstantiateConfiguredClass(LogRecordListener listener) { ConfigurationParameters configurationParameters = mock(); String classNameWithSpaces = " " + CustomDisplayNameGenerator.class.getName() + " "; when(configurationParameters.get(KEY)).thenReturn(Optional.of(classNameWithSpaces)); InstantiatingConfigurationParameterConverter converter = new InstantiatingConfigurationParameterConverter<>( DisplayNameGenerator.class, "display name generator"); DisplayNameGenerator displayNameGenerator = converter.get(configurationParameters, KEY).orElseThrow(); assertThat(displayNameGenerator).isInstanceOf(CustomDisplayNameGenerator.class); assertExpectedLogMessage(listener, Level.CONFIG, "Using default display name generator " + "'org.junit.jupiter.engine.descriptor.CustomDisplayNameGenerator' set via the " + "'junit.jupiter.displayname.generator.default' configuration parameter."); } @Test void shouldReturnEmptyOptionalIfNoClassFound(LogRecordListener listener) { ConfigurationParameters configurationParameters = mock(); when(configurationParameters.get(KEY)).thenReturn(Optional.of("random-string")); InstantiatingConfigurationParameterConverter converter = new InstantiatingConfigurationParameterConverter<>( DisplayNameGenerator.class, "display name generator"); Optional displayNameGenerator = converter.get(configurationParameters, KEY); assertThat(displayNameGenerator).isEmpty(); assertExpectedLogMessage(listener, Level.WARNING, "Failed to load default display name generator " + "class 'random-string' set via the 'junit.jupiter.displayname.generator.default' " + "configuration parameter. Falling back to default behavior."); } @Test void shouldReturnEmptyOptionalIfClassFoundIsNotATypeOfExpectedType(LogRecordListener listener) { ConfigurationParameters configurationParameters = mock(); when(configurationParameters.get(KEY)).thenReturn(Optional.of(Object.class.getName())); InstantiatingConfigurationParameterConverter converter = new InstantiatingConfigurationParameterConverter<>( DisplayNameGenerator.class, "display name generator"); Optional displayNameGenerator = converter.get(configurationParameters, KEY); assertThat(displayNameGenerator).isEmpty(); assertExpectedLogMessage(listener, Level.WARNING, "Failed to load default display name generator class 'java.lang.Object' " + "set via the 'junit.jupiter.displayname.generator.default' configuration parameter. " + "Falling back to default behavior."); } @Test void shouldReturnEmptyOptionalIfClassNameIsNotFullyQualified(LogRecordListener listener) { ConfigurationParameters configurationParameters = mock(); when(configurationParameters.get(KEY)).thenReturn( Optional.of(CustomDisplayNameGenerator.class.getSimpleName())); InstantiatingConfigurationParameterConverter converter = new InstantiatingConfigurationParameterConverter<>( DisplayNameGenerator.class, "display name generator"); Optional displayNameGenerator = converter.get(configurationParameters, KEY); assertThat(displayNameGenerator).isEmpty(); assertExpectedLogMessage(listener, Level.WARNING, "Failed to load default display name generator class 'CustomDisplayNameGenerator' " + "set via the 'junit.jupiter.displayname.generator.default' configuration parameter. " + "Falling back to default behavior."); } private void assertExpectedLogMessage(LogRecordListener listener, Level level, String expectedMessage) { // @formatter:off assertTrue(listener.stream(level) .map(LogRecord::getMessage) .anyMatch(expectedMessage::equals)); // @formatter:on } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/CustomDisplayNameGenerator.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import java.lang.reflect.Method; import java.util.List; import org.junit.jupiter.api.DisplayNameGenerator; public class CustomDisplayNameGenerator implements DisplayNameGenerator { @Override public String generateDisplayNameForClass(Class testClass) { return "class-display-name"; } @Override public String generateDisplayNameForNestedClass(List> enclosingInstanceTypes, Class nestedClass) { return "nested-class-display-name"; } @Override public String generateDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, Method testMethod) { return "method-display-name"; } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.lang.reflect.Method; import java.util.List; import java.util.function.Supplier; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.engine.config.JupiterConfiguration; /** * Unit tests for {@link DisplayNameUtils}. * * @since 5.5 */ class DisplayNameUtilsTests { @Nested class ClassDisplayNameTests { @Test void shouldGetDisplayNameFromDisplayNameAnnotation() { String displayName = DisplayNameUtils.determineDisplayName(MyTestCase.class, () -> "default-name"); assertThat(displayName).isEqualTo("my-test-case"); } @Test void shouldGetDisplayNameFromSupplierIfDisplayNameAnnotationProvidesBlankString() { String displayName = DisplayNameUtils.determineDisplayName(BlankDisplayNameTestCase.class, () -> "default-name"); assertThat(displayName).isEqualTo("default-name"); } @Test void shouldGetDisplayNameFromSupplierIfNoDisplayNameAnnotationPresent() { String displayName = DisplayNameUtils.determineDisplayName(NotDisplayNameTestCase.class, () -> "default-name"); assertThat(displayName).isEqualTo("default-name"); } @Nested class ClassDisplayNameSupplierTests { private final JupiterConfiguration configuration = mock(); @Test void shouldGetDisplayNameFromDisplayNameGenerationAnnotation() { when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForClass( StandardDisplayNameTestCase.class, configuration); String name = StandardDisplayNameTestCase.class.getName(); String expectedClassName = name.substring(name.lastIndexOf(".") + 1); assertThat(displayName.get()).isEqualTo(expectedClassName); } @Test void shouldGetUnderscoreDisplayNameFromDisplayNameGenerationAnnotation() { when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForClass( Underscore_DisplayName_TestCase.class, configuration); assertThat(displayName.get()).isEqualTo("DisplayNameUtilsTests$Underscore DisplayName TestCase"); } @Test void shouldGetDisplayNameFromDefaultDisplayNameGenerator() { when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForClass(MyTestCase.class, configuration); assertThat(displayName.get()).isEqualTo("class-display-name"); } @Test void shouldFallbackOnDefaultDisplayNameGeneratorWhenNullIsGenerated() { when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForClass( NullDisplayNameTestCase.class, configuration); assertThat(displayName.get()).isEqualTo("class-display-name"); } } } @Nested class NestedClassDisplayNameTests { private final JupiterConfiguration configuration = mock(); @Test void shouldGetDisplayNameFromDisplayNameGenerationAnnotation() { when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForNestedClass(List::of, StandardDisplayNameTestCase.class, configuration); assertThat(displayName.get()).isEqualTo(StandardDisplayNameTestCase.class.getSimpleName()); } @Test void shouldGetDisplayNameFromDefaultDisplayNameGenerator() { when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForNestedClass(List::of, NestedTestCase.class, configuration); assertThat(displayName.get()).isEqualTo("nested-class-display-name"); } @Test void shouldFallbackOnDefaultDisplayNameGeneratorWhenNullIsGenerated() { when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForNestedClass(List::of, NullDisplayNameTestCase.NestedTestCase.class, configuration); assertThat(displayName.get()).isEqualTo("nested-class-display-name"); } } @Nested class MethodDisplayNameTests { private final JupiterConfiguration configuration = mock(); @Test void shouldGetDisplayNameFromDisplayNameGenerationAnnotation() throws Exception { when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); Method method = MyTestCase.class.getDeclaredMethod("test1"); String displayName = DisplayNameUtils.determineDisplayNameForMethod(List::of, StandardDisplayNameTestCase.class, method, configuration); assertThat(displayName).isEqualTo("test1()"); } @Test void shouldGetDisplayNameFromDefaultNameGenerator() throws Exception { Method method = MyTestCase.class.getDeclaredMethod("test1"); when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); String displayName = DisplayNameUtils.determineDisplayNameForMethod(List::of, NotDisplayNameTestCase.class, method, configuration); assertThat(displayName).isEqualTo("method-display-name"); } @Test void shouldFallbackOnDefaultDisplayNameGeneratorWhenNullIsGenerated() throws Exception { Method method = NullDisplayNameTestCase.class.getDeclaredMethod("test"); when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); String displayName = DisplayNameUtils.determineDisplayNameForMethod(List::of, NullDisplayNameTestCase.class, method, configuration); assertThat(displayName).isEqualTo("method-display-name"); } } @DisplayName("my-test-case\t") @DisplayNameGeneration(value = CustomDisplayNameGenerator.class) static class MyTestCase { void test1() { } } @DisplayName("") static class BlankDisplayNameTestCase { } @DisplayNameGeneration(value = DisplayNameGenerator.Standard.class) static class StandardDisplayNameTestCase { } @DisplayNameGeneration(value = DisplayNameGenerator.ReplaceUnderscores.class) static class Underscore_DisplayName_TestCase { } static class NotDisplayNameTestCase { } @Nested class NestedTestCase { } @DisplayNameGeneration(value = NullDisplayNameGenerator.class) static class NullDisplayNameTestCase { @Test void test() { } @Nested class NestedTestCase { } } @SuppressWarnings("DataFlowIssue") static class NullDisplayNameGenerator implements DisplayNameGenerator { @Override public String generateDisplayNameForClass(Class testClass) { return null; } @Override public String generateDisplayNameForNestedClass(List> enclosingInstanceTypes, Class nestedClass) { return null; } @Override public String generateDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, Method testMethod) { return null; } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.MediaType.TEXT_PLAIN; import static org.junit.jupiter.api.MediaType.TEXT_PLAIN_UTF_8; import static org.junit.jupiter.api.Named.named; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullOrBlankFor; import static org.junit.platform.launcher.core.OutputDirectoryCreators.dummyOutputDirectoryCreator; import static org.junit.platform.launcher.core.OutputDirectoryCreators.hierarchicalOutputDirectoryCreator; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.io.File; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.Function; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Named; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Namespace; import org.junit.jupiter.api.extension.PreInterruptCallback; import org.junit.jupiter.api.function.ThrowingConsumer; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.jupiter.engine.config.DefaultJupiterConfiguration; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.DefaultTestInstances; import org.junit.jupiter.engine.execution.LauncherStoreFacade; import org.junit.jupiter.engine.extension.ExtensionRegistry; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.NullAndEmptySource; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.engine.support.hierarchical.OpenTest4JAwareThrowableCollector; import org.junit.platform.launcher.core.NamespacedHierarchicalStoreProviders; import org.mockito.ArgumentCaptor; /** * Unit tests for concrete implementations of {@link ExtensionContext}: * {@link JupiterEngineExtensionContext}, {@link ClassExtensionContext}, and * {@link MethodExtensionContext}. * * @since 5.0 */ public class ExtensionContextTests { private static final ThrowingConsumer failingAction = __ -> fail("should not be called"); private final JupiterConfiguration configuration = mock(); private final ExtensionRegistry extensionRegistry = mock(); private final LauncherStoreFacade launcherStoreFacade = new LauncherStoreFacade( NamespacedHierarchicalStoreProviders.dummyNamespacedHierarchicalStore()); @BeforeEach void setUp() { when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new DisplayNameGenerator.Standard()); when(configuration.getDefaultExecutionMode()).thenReturn(ExecutionMode.SAME_THREAD); when(configuration.getDefaultClassesExecutionMode()).thenReturn(ExecutionMode.SAME_THREAD); when(configuration.getOutputDirectoryCreator()).thenReturn(dummyOutputDirectoryCreator()); } @Test void fromJupiterEngineDescriptor() { var engineTestDescriptor = new JupiterEngineDescriptor(UniqueId.root("engine", "junit-jupiter"), configuration); try (var engineContext = new JupiterEngineExtensionContext(mock(), engineTestDescriptor, configuration, extensionRegistry, launcherStoreFacade)) { // @formatter:off assertAll("engineContext", () -> assertThat(engineContext.getElement()).isEmpty(), () -> assertThat(engineContext.getTestClass()).isEmpty(), () -> assertThat(engineContext.getTestInstance()).isEmpty(), () -> assertThat(engineContext.getTestMethod()).isEmpty(), () -> assertPreconditionViolationFor(engineContext::getRequiredTestClass), () -> assertPreconditionViolationFor(engineContext::getRequiredTestInstance), () -> assertPreconditionViolationFor(engineContext::getRequiredTestMethod), () -> assertThat(engineContext.getDisplayName()).isEqualTo(engineTestDescriptor.getDisplayName()), () -> assertThat(engineContext.getParent()).isEmpty(), () -> assertThat(engineContext.getRoot()).isSameAs(engineContext), () -> assertThat(engineContext.getExecutionMode()).isEqualTo(ExecutionMode.SAME_THREAD), () -> assertThat(engineContext.getExtensions(PreInterruptCallback.class)).isEmpty() ); // @formatter:on } } @Test @SuppressWarnings("resource") void fromClassTestDescriptor() { var nestedClassDescriptor = nestedClassDescriptor(); var outerClassDescriptor = outerClassDescriptor(nestedClassDescriptor); var doublyNestedClassDescriptor = doublyNestedClassDescriptor(); var methodTestDescriptor = nestedMethodDescriptor(); nestedClassDescriptor.addChild(doublyNestedClassDescriptor); nestedClassDescriptor.addChild(methodTestDescriptor); var parentExtensionContext = mock(AbstractExtensionContext.class); var outerExtensionContext = new ClassExtensionContext(parentExtensionContext, mock(), outerClassDescriptor, PER_METHOD, configuration, extensionRegistry, launcherStoreFacade, mock()); // @formatter:off assertAll("outerContext", () -> assertThat(outerExtensionContext.getElement()).contains(OuterClassTestCase.class), () -> assertThat(outerExtensionContext.getTestClass()).contains(OuterClassTestCase.class), () -> assertThat(outerExtensionContext.getTestInstance()).isEmpty(), () -> assertThat(outerExtensionContext.getTestMethod()).isEmpty(), () -> assertThat(outerExtensionContext.getRequiredTestClass()).isEqualTo(OuterClassTestCase.class), () -> assertPreconditionViolationFor(outerExtensionContext::getRequiredTestInstance), () -> assertPreconditionViolationFor(outerExtensionContext::getRequiredTestMethod), () -> assertThat(outerExtensionContext.getDisplayName()).isEqualTo(outerClassDescriptor.getDisplayName()), () -> assertThat(outerExtensionContext.getParent()).containsSame(parentExtensionContext), () -> assertThat(outerExtensionContext.getExecutionMode()).isEqualTo(ExecutionMode.SAME_THREAD), () -> assertThat(outerExtensionContext.getExtensions(PreInterruptCallback.class)).isEmpty(), () -> assertThat(outerExtensionContext.getEnclosingTestClasses()).isEmpty() ); // @formatter:on var nestedExtensionContext = new ClassExtensionContext(outerExtensionContext, mock(), nestedClassDescriptor, PER_METHOD, configuration, extensionRegistry, launcherStoreFacade, mock()); // @formatter:off assertAll("nestedContext", () -> assertThat(nestedExtensionContext.getParent()).containsSame(outerExtensionContext), () -> assertThat(nestedExtensionContext.getTestClass()).contains(OuterClassTestCase.NestedClass.class), () -> assertThat(nestedExtensionContext.getEnclosingTestClasses()).containsExactly(OuterClassTestCase.class) ); // @formatter:on var doublyNestedExtensionContext = new ClassExtensionContext(nestedExtensionContext, mock(), doublyNestedClassDescriptor, PER_METHOD, configuration, extensionRegistry, launcherStoreFacade, mock()); // @formatter:off assertAll("doublyNestedContext", () -> assertThat(doublyNestedExtensionContext.getParent()).containsSame(nestedExtensionContext), () -> assertThat(doublyNestedExtensionContext.getTestClass()).contains(OuterClassTestCase.NestedClass.DoublyNestedClass.class), () -> assertThat(doublyNestedExtensionContext.getEnclosingTestClasses()).containsExactly(OuterClassTestCase.class, OuterClassTestCase.NestedClass.class) ); // @formatter:on var methodExtensionContext = new MethodExtensionContext(nestedExtensionContext, mock(), methodTestDescriptor, configuration, extensionRegistry, launcherStoreFacade, new OpenTest4JAwareThrowableCollector()); // @formatter:off assertAll("methodContext", () -> assertThat(methodExtensionContext.getParent()).containsSame(nestedExtensionContext), () -> assertThat(methodExtensionContext.getTestClass()).contains(OuterClassTestCase.NestedClass.class), () -> assertThat(methodExtensionContext.getEnclosingTestClasses()).containsExactly(OuterClassTestCase.class) ); // @formatter:on } @Test void ExtensionContext_With_ExtensionRegistry_getExtensions() { var classTestDescriptor = nestedClassDescriptor(); try (var ctx = new ClassExtensionContext(mock(AbstractExtensionContext.class), mock(), classTestDescriptor, PER_METHOD, configuration, extensionRegistry, launcherStoreFacade, mock())) { Extension ext = mock(); when(extensionRegistry.getExtensions(Extension.class)).thenReturn(List.of(ext)); assertThat(ctx.getExtensions(Extension.class)).isEqualTo(List.of(ext)); } } @Test @SuppressWarnings("resource") void tagsCanBeRetrievedInExtensionContext() { var nestedClassDescriptor = nestedClassDescriptor(); var outerClassDescriptor = outerClassDescriptor(nestedClassDescriptor); var methodTestDescriptor = methodDescriptor(); outerClassDescriptor.addChild(methodTestDescriptor); var rootExtensionContext = new JupiterEngineExtensionContext(mock(), mock(), configuration, extensionRegistry, launcherStoreFacade); assertThat(rootExtensionContext.getRoot()).isSameAs(rootExtensionContext); var outerExtensionContext = new ClassExtensionContext(rootExtensionContext, mock(), outerClassDescriptor, PER_METHOD, configuration, extensionRegistry, launcherStoreFacade, mock()); assertThat(outerExtensionContext.getTags()).containsExactly("outer-tag"); assertThat(outerExtensionContext.getRoot()).isSameAs(rootExtensionContext); var nestedExtensionContext = new ClassExtensionContext(outerExtensionContext, mock(), nestedClassDescriptor, PER_METHOD, configuration, extensionRegistry, launcherStoreFacade, mock()); assertThat(nestedExtensionContext.getTags()).containsExactlyInAnyOrder("outer-tag", "nested-tag"); assertThat(nestedExtensionContext.getRoot()).isSameAs(rootExtensionContext); var methodExtensionContext = new MethodExtensionContext(outerExtensionContext, mock(), methodTestDescriptor, configuration, extensionRegistry, launcherStoreFacade, new OpenTest4JAwareThrowableCollector()); methodExtensionContext.setTestInstances(DefaultTestInstances.of(new OuterClassTestCase())); assertThat(methodExtensionContext.getTags()).containsExactlyInAnyOrder("outer-tag", "method-tag"); assertThat(methodExtensionContext.getRoot()).isSameAs(rootExtensionContext); } @Test @SuppressWarnings("resource") void fromMethodTestDescriptor() { var methodTestDescriptor = methodDescriptor(); var classTestDescriptor = outerClassDescriptor(methodTestDescriptor); var engineDescriptor = new JupiterEngineDescriptor(UniqueId.forEngine("junit-jupiter"), configuration); engineDescriptor.addChild(classTestDescriptor); Object testInstance = new OuterClassTestCase(); var testMethod = methodTestDescriptor.getTestMethod(); var engineExtensionContext = new JupiterEngineExtensionContext(mock(), engineDescriptor, configuration, extensionRegistry, launcherStoreFacade); var classExtensionContext = new ClassExtensionContext(engineExtensionContext, mock(), classTestDescriptor, PER_METHOD, configuration, extensionRegistry, launcherStoreFacade, mock()); var methodExtensionContext = new MethodExtensionContext(classExtensionContext, mock(), methodTestDescriptor, configuration, extensionRegistry, launcherStoreFacade, new OpenTest4JAwareThrowableCollector()); methodExtensionContext.setTestInstances(DefaultTestInstances.of(testInstance)); // @formatter:off assertAll("methodContext", () -> assertThat(methodExtensionContext.getElement()).contains(testMethod), () -> assertThat(methodExtensionContext.getTestClass()).contains(OuterClassTestCase.class), () -> assertThat(methodExtensionContext.getEnclosingTestClasses()).isEmpty(), () -> assertThat(methodExtensionContext.getTestInstance()).contains(testInstance), () -> assertThat(methodExtensionContext.getTestMethod()).contains(testMethod), () -> assertThat(methodExtensionContext.getRequiredTestClass()).isEqualTo(OuterClassTestCase.class), () -> assertThat(methodExtensionContext.getRequiredTestInstance()).isEqualTo(testInstance), () -> assertThat(methodExtensionContext.getRequiredTestMethod()).isEqualTo(testMethod), () -> assertThat(methodExtensionContext.getDisplayName()).isEqualTo(methodTestDescriptor.getDisplayName()), () -> assertThat(methodExtensionContext.getParent()).contains(classExtensionContext), () -> assertThat(methodExtensionContext.getRoot()).isSameAs(engineExtensionContext), () -> assertThat(methodExtensionContext.getExecutionMode()).isEqualTo(ExecutionMode.SAME_THREAD) ); // @formatter:on } @Test @SuppressWarnings("resource") void reportEntriesArePublishedToExecutionListener() { var classTestDescriptor = outerClassDescriptor(mock()); var engineExecutionListener = spy(EngineExecutionListener.class); ExtensionContext extensionContext = new ClassExtensionContext(mock(AbstractExtensionContext.class), engineExecutionListener, classTestDescriptor, PER_METHOD, configuration, extensionRegistry, launcherStoreFacade, mock()); var map1 = Map.of("key", "value"); var map2 = Map.of("other key", "other value"); extensionContext.publishReportEntry(map1); extensionContext.publishReportEntry(map2); extensionContext.publishReportEntry("3rd key", "third value"); extensionContext.publishReportEntry("status message"); var entryCaptor = ArgumentCaptor.forClass(ReportEntry.class); verify(engineExecutionListener, times(4)) // .reportingEntryPublished(eq(classTestDescriptor), entryCaptor.capture()); var reportEntry1 = entryCaptor.getAllValues().get(0); var reportEntry2 = entryCaptor.getAllValues().get(1); var reportEntry3 = entryCaptor.getAllValues().get(2); var reportEntry4 = entryCaptor.getAllValues().get(3); assertEquals(map1, reportEntry1.getKeyValuePairs()); assertEquals(map2, reportEntry2.getKeyValuePairs()); assertEquals("third value", reportEntry3.getKeyValuePairs().get("3rd key")); assertEquals("status message", reportEntry4.getKeyValuePairs().get("value")); } @Test void fileEntriesArePublishedToExecutionListener(@TempDir Path tempDir) { var engineExecutionListener = mock(EngineExecutionListener.class); var classTestDescriptor = outerClassDescriptor(null); var extensionContext = createExtensionContextForFilePublishing(tempDir, engineExecutionListener, classTestDescriptor); extensionContext.publishFile("test1.txt", TEXT_PLAIN_UTF_8, file -> Files.writeString(file, "Test 1")); extensionContext.publishDirectory("test2", dir -> { Files.writeString(dir.resolve("nested1.txt"), "Nested content 1"); Files.writeString(dir.resolve("nested2.txt"), "Nested content 2"); }); var entryCaptor = ArgumentCaptor.forClass(FileEntry.class); verify(engineExecutionListener, times(2)) // .fileEntryPublished(eq(classTestDescriptor), entryCaptor.capture()); var fileEntries = entryCaptor.getAllValues(); var fileEntry1 = fileEntries.getFirst(); assertThat(fileEntry1.getPath()).isEqualTo(tempDir.resolve("OuterClass/test1.txt")); assertThat(fileEntry1.getMediaType()).contains(TEXT_PLAIN_UTF_8.toString()); var fileEntry2 = fileEntries.get(1); assertThat(fileEntry2.getPath()).isEqualTo(tempDir.resolve("OuterClass/test2")); assertThat(fileEntry2.getMediaType()).isEmpty(); assertThat(fileEntry2.getPath().resolve("nested1.txt")).usingCharset(UTF_8).hasContent("Nested content 1"); assertThat(fileEntry2.getPath().resolve("nested2.txt")).usingCharset(UTF_8).hasContent("Nested content 2"); } @ParameterizedTest @NullAndEmptySource @ValueSource(strings = { " ", " \t " }) @SuppressWarnings("DataFlowIssue") // publishFile() parameters are not @Nullable void failsWhenAttemptingToPublishFileWithNullOrBlankName(@Nullable String name, @TempDir Path tempDir) { var extensionContext = createExtensionContextForFilePublishing(tempDir); assertPreconditionViolationNotNullOrBlankFor("name", () -> extensionContext.publishFile(name, TEXT_PLAIN, failingAction)); } @ParameterizedTest @NullAndEmptySource @ValueSource(strings = { " ", " \t " }) @SuppressWarnings("DataFlowIssue") // publishDirectory() parameters are not @Nullable void failsWhenAttemptingToPublishDirectoryWithNullOrBlankName(@Nullable String name, @TempDir Path tempDir) { var extensionContext = createExtensionContextForFilePublishing(tempDir); assertPreconditionViolationNotNullOrBlankFor("name", () -> extensionContext.publishDirectory(name, failingAction)); } @Test void failsWhenAttemptingToPublishFileWithPathSeparators(@TempDir Path tempDir) { var extensionContext = createExtensionContextForFilePublishing(tempDir); var name = "test" + File.separator + "subDir"; assertPreconditionViolationFor(() -> extensionContext.publishFile(name, TEXT_PLAIN, failingAction))// .withMessage("name must not contain path separators: " + name); } @Test void failsWhenAttemptingToPublishDirectoryWithPathSeparators(@TempDir Path tempDir) { var extensionContext = createExtensionContextForFilePublishing(tempDir); var name = "test" + File.separator + "subDir"; assertPreconditionViolationFor(() -> extensionContext.publishDirectory(name, failingAction))// .withMessage("name must not contain path separators: " + name); } @Test void failsWhenAttemptingToPublishMissingFiles(@TempDir Path tempDir) { var extensionContext = createExtensionContextForFilePublishing(tempDir); assertPreconditionViolationFor(() -> extensionContext.publishFile("test", TEXT_PLAIN, Files::deleteIfExists)) // .withMessage("Published path must exist: " + tempDir.resolve("OuterClass").resolve("test")); } @Test void failsWhenAttemptingToPublishMissingDirectory(@TempDir Path tempDir) { var extensionContext = createExtensionContextForFilePublishing(tempDir); assertPreconditionViolationFor(() -> extensionContext.publishDirectory("test", Files::delete)) // .withMessage("Published path must exist: " + tempDir.resolve("OuterClass").resolve("test")); } @Test void failsWhenAttemptingToPublishDirectoriesAsRegularFiles(@TempDir Path tempDir) { var extensionContext = createExtensionContextForFilePublishing(tempDir); assertPreconditionViolationFor(() -> extensionContext.publishFile("test", TEXT_PLAIN, Files::createDirectory))// .withMessage("Published path must be a regular file: " + tempDir.resolve("OuterClass").resolve("test")); } @Test void failsWhenAttemptingToPublishRegularFilesAsDirectories(@TempDir Path tempDir) { var extensionContext = createExtensionContextForFilePublishing(tempDir); assertPreconditionViolationFor(() -> extensionContext.publishDirectory("test", dir -> { Files.delete(dir); Files.createFile(dir); })).withMessage("Published path must be a directory: " + tempDir.resolve("OuterClass").resolve("test")); } @Test void allowsPublishingToTheSameDirectoryTwice(@TempDir Path tempDir) { var extensionContext = createExtensionContextForFilePublishing(tempDir); extensionContext.publishDirectory("test", dir -> Files.writeString(dir.resolve("nested1.txt"), "Nested content 1")); extensionContext.publishDirectory("test", dir -> Files.writeString(dir.resolve("nested2.txt"), "Nested content 2")); assertThat(tempDir.resolve("OuterClass/test/nested1.txt")).hasContent("Nested content 1"); assertThat(tempDir.resolve("OuterClass/test/nested2.txt")).hasContent("Nested content 2"); } private ExtensionContext createExtensionContextForFilePublishing(Path tempDir) { return createExtensionContextForFilePublishing(tempDir, mock(EngineExecutionListener.class), outerClassDescriptor(null)); } private ExtensionContext createExtensionContextForFilePublishing(Path tempDir, EngineExecutionListener engineExecutionListener, ClassTestDescriptor classTestDescriptor) { when(configuration.getOutputDirectoryCreator()) // .thenReturn(hierarchicalOutputDirectoryCreator(tempDir)); return new ClassExtensionContext(mock(AbstractExtensionContext.class), engineExecutionListener, classTestDescriptor, PER_METHOD, configuration, extensionRegistry, launcherStoreFacade, mock()); } @Test @SuppressWarnings({ "resource", "deprecation" }) void usingStore() { var methodTestDescriptor = methodDescriptor(); var classTestDescriptor = outerClassDescriptor(methodTestDescriptor); ExtensionContext parentContext = new ClassExtensionContext(mock(AbstractExtensionContext.class), mock(), classTestDescriptor, PER_METHOD, configuration, extensionRegistry, launcherStoreFacade, mock()); var childContext = new MethodExtensionContext(parentContext, mock(), methodTestDescriptor, configuration, extensionRegistry, launcherStoreFacade, new OpenTest4JAwareThrowableCollector()); childContext.setTestInstances(DefaultTestInstances.of(new OuterClassTestCase())); var childStore = childContext.getStore(Namespace.GLOBAL); var parentStore = parentContext.getStore(Namespace.GLOBAL); final Object key1 = "key 1"; final var value1 = "a value"; childStore.put(key1, value1); assertEquals(value1, childStore.get(key1)); assertEquals(value1, childStore.remove(key1)); assertNull(childStore.get(key1)); childStore.put(key1, value1); assertEquals(value1, childStore.get(key1)); assertEquals(value1, childStore.remove(key1, String.class)); assertNull(childStore.get(key1)); final Object key2 = "key 2"; final var value2 = "other value"; assertEquals(value2, childStore.computeIfAbsent(key2, key -> value2)); assertEquals(value2, childStore.computeIfAbsent(key2, key -> "a different value", String.class)); assertEquals(value2, childStore.getOrComputeIfAbsent(key2, key -> "a different value")); assertEquals(value2, childStore.getOrComputeIfAbsent(key2, key -> "a different value", String.class)); assertEquals(value2, childStore.get(key2)); assertEquals(value2, childStore.get(key2, String.class)); final Object parentKey = "parent key"; final var parentValue = "parent value"; parentStore.put(parentKey, parentValue); assertEquals(parentValue, childStore.computeIfAbsent(parentKey, k -> "a different value")); assertEquals(parentValue, childStore.getOrComputeIfAbsent(parentKey, k -> "a different value")); assertEquals(parentValue, childStore.get(parentKey)); } @ParameterizedTest @MethodSource("extensionContextFactories") void configurationParameter(Function extensionContextFactory) { var key = "123"; var expected = Optional.of(key); ConfigurationParameters configurationParameters = mock(); when(configurationParameters.get("123")).thenReturn(expected); JupiterConfiguration echo = new DefaultJupiterConfiguration(configurationParameters, dummyOutputDirectoryCreator(), mock()); var context = extensionContextFactory.apply(echo); assertEquals(expected, context.getConfigurationParameter(key)); } static List>> extensionContextFactories() { ExtensionRegistry extensionRegistry = mock(); LauncherStoreFacade launcherStoreFacade = mock(); var testClass = ExtensionContextTests.class; return List.of( // named("engine", (JupiterConfiguration configuration) -> { var engineUniqueId = UniqueId.parse("[engine:junit-jupiter]"); var engineDescriptor = new JupiterEngineDescriptor(engineUniqueId, configuration); return new JupiterEngineExtensionContext(mock(), engineDescriptor, configuration, extensionRegistry, launcherStoreFacade); }), // named("class", (JupiterConfiguration configuration) -> { var classUniqueId = UniqueId.parse("[engine:junit-jupiter]/[class:MyClass]"); var classTestDescriptor = new ClassTestDescriptor(classUniqueId, testClass, configuration); return new ClassExtensionContext(mock(AbstractExtensionContext.class), mock(), classTestDescriptor, PER_METHOD, configuration, extensionRegistry, launcherStoreFacade, mock()); }), // named("method", (JupiterConfiguration configuration) -> { var method = ReflectionSupport.findMethod(testClass, "extensionContextFactories").orElseThrow(); var methodUniqueId = UniqueId.parse("[engine:junit-jupiter]/[class:MyClass]/[method:myMethod]"); var methodTestDescriptor = new TestMethodTestDescriptor(methodUniqueId, testClass, method, List::of, configuration); return new MethodExtensionContext(mock(AbstractExtensionContext.class), mock(), methodTestDescriptor, configuration, extensionRegistry, launcherStoreFacade, mock()); }) // ); } private NestedClassTestDescriptor nestedClassDescriptor() { return new NestedClassTestDescriptor(UniqueId.root("nested-class", "NestedClass"), OuterClassTestCase.NestedClass.class, List::of, configuration); } private NestedClassTestDescriptor doublyNestedClassDescriptor() { return new NestedClassTestDescriptor(UniqueId.root("nested-class", "DoublyNestedClass"), OuterClassTestCase.NestedClass.DoublyNestedClass.class, List::of, configuration); } private ClassTestDescriptor outerClassDescriptor(@Nullable TestDescriptor child) { var classTestDescriptor = new ClassTestDescriptor(UniqueId.root("class", "OuterClass"), OuterClassTestCase.class, configuration); if (child != null) { classTestDescriptor.addChild(child); } return classTestDescriptor; } private TestMethodTestDescriptor methodDescriptor() { try { return new TestMethodTestDescriptor(UniqueId.root("method", "aMethod"), OuterClassTestCase.class, OuterClassTestCase.class.getDeclaredMethod("aMethod"), List::of, configuration); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } } private TestMethodTestDescriptor nestedMethodDescriptor() { try { return new TestMethodTestDescriptor(UniqueId.root("method", "nestedMethod"), OuterClassTestCase.NestedClass.class, BaseNestedTestCase.class.getDeclaredMethod("nestedMethod"), List::of, configuration); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } } static abstract class BaseNestedTestCase { @Test void nestedMethod() { } @Nested class DoublyNestedClass { } } @Tag("outer-tag") static class OuterClassTestCase { @Tag("nested-tag") @Nested class NestedClass extends BaseNestedTestCase { } @Tag("method-tag") void aMethod() { } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionsUtilsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Field; import java.util.function.Function; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.engine.extension.ExtensionRegistrar; /** * Tests for {@link ExtensionUtils}. * * @since 5.11.3 */ class ExtensionsUtilsTests { @Test void registerExtensionsViaStaticFields() throws Exception { Field field = TestCase.class.getDeclaredField("staticField"); ExtensionRegistrar registrar = mock(); ExtensionUtils.registerExtensionsFromStaticFields(registrar, TestCase.class); verify(registrar).registerExtension(Extension1.class); verify(registrar).registerExtension(Extension2.class); verify(registrar).registerExtension(TestCase.staticField, field); } @Test @SuppressWarnings("unchecked") void registerExtensionsViaInstanceFields() throws Exception { Class testClass = TestCase.class; Field field = testClass.getDeclaredField("instanceField"); ExtensionRegistrar registrar = mock(); ExtensionUtils.registerExtensionsFromInstanceFields(registrar, testClass); verify(registrar).registerExtension(Extension1.class); verify(registrar).registerExtension(Extension2.class); verify(registrar).registerUninitializedExtension(eq(testClass), eq(field), any(Function.class)); } static class Extension1 implements Extension { } static class Extension2 implements Extension { } static class Extension3 implements Extension { } static class Extension4 implements Extension { } @Retention(RetentionPolicy.RUNTIME) @ExtendWith(Extension1.class) @ExtendWith(Extension2.class) @interface UseCustomExtensions { } static class TestCase { @UseCustomExtensions @RegisterExtension static Extension3 staticField = new Extension3(); @UseCustomExtensions @RegisterExtension Extension4 instanceField = new Extension4(); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Method; import java.math.BigDecimal; import java.util.List; import java.util.Optional; 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.DisplayName; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.descriptor.JupiterTestDescriptorTests.StaticTestCase.StaticTestCaseLevel2; import org.junit.platform.engine.TestSource; import org.junit.platform.engine.TestTag; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.MethodSource; /** * Unit tests for {@link ClassTestDescriptor}, {@link NestedClassTestDescriptor}, * and {@link TestMethodTestDescriptor}. * * @since 5.0 * @see org.junit.jupiter.engine.descriptor.LifecycleMethodUtilsTests */ class JupiterTestDescriptorTests { private static final UniqueId uniqueId = UniqueId.root("enigma", "foo"); private final JupiterConfiguration configuration = mock(); @BeforeEach void setUp() { when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new DisplayNameGenerator.Standard()); when(configuration.getDefaultExecutionMode()).thenReturn(ExecutionMode.SAME_THREAD); } @Test void constructFromClass() { ClassTestDescriptor descriptor = new ClassTestDescriptor(uniqueId, TestCase.class, configuration); assertEquals(TestCase.class, descriptor.getTestClass()); assertThat(descriptor.getTags()).containsExactly(TestTag.create("inherited-class-level-tag"), TestTag.create("classTag1"), TestTag.create("classTag2")); } @Test void constructFromClassWithInvalidBeforeAllDeclaration() { // Note: if we can instantiate the descriptor, then the invalid configuration // will not be reported during the test engine discovery phase. ClassTestDescriptor descriptor = new ClassTestDescriptor(uniqueId, TestCaseWithInvalidBeforeAllMethod.class, configuration); assertEquals(TestCaseWithInvalidBeforeAllMethod.class, descriptor.getTestClass()); } @Test void constructFromClassWithInvalidAfterAllDeclaration() { // Note: if we can instantiate the descriptor, then the invalid configuration // will not be reported during the test engine discovery phase. ClassTestDescriptor descriptor = new ClassTestDescriptor(uniqueId, TestCaseWithInvalidAfterAllMethod.class, configuration); assertEquals(TestCaseWithInvalidAfterAllMethod.class, descriptor.getTestClass()); } @Test void constructFromClassWithInvalidBeforeEachDeclaration() { // Note: if we can instantiate the descriptor, then the invalid configuration // will not be reported during the test engine discovery phase. ClassTestDescriptor descriptor = new ClassTestDescriptor(uniqueId, TestCaseWithInvalidBeforeEachMethod.class, configuration); assertEquals(TestCaseWithInvalidBeforeEachMethod.class, descriptor.getTestClass()); } @Test void constructFromClassWithInvalidAfterEachDeclaration() { // Note: if we can instantiate the descriptor, then the invalid configuration // will not be reported during the test engine discovery phase. ClassTestDescriptor descriptor = new ClassTestDescriptor(uniqueId, TestCaseWithInvalidAfterEachMethod.class, configuration); assertEquals(TestCaseWithInvalidAfterEachMethod.class, descriptor.getTestClass()); } @Test void constructFromMethod() throws Exception { Class testClass = TestCase.class; Method testMethod = testClass.getDeclaredMethod("test"); TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, testClass, testMethod, List::of, configuration); assertEquals(uniqueId, descriptor.getUniqueId()); assertEquals(testMethod, descriptor.getTestMethod()); assertEquals("test()", descriptor.getDisplayName(), "display name:"); assertEquals("test()", descriptor.getLegacyReportingName(), "legacy name:"); } @Test void constructFromMethodWithAnnotations() throws Exception { JupiterTestDescriptor classDescriptor = new ClassTestDescriptor(uniqueId, TestCase.class, configuration); Method testMethod = TestCase.class.getDeclaredMethod("foo"); TestMethodTestDescriptor methodDescriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, List::of, configuration); classDescriptor.addChild(methodDescriptor); assertEquals(testMethod, methodDescriptor.getTestMethod()); assertEquals("custom test name", methodDescriptor.getDisplayName(), "display name:"); assertEquals("foo()", methodDescriptor.getLegacyReportingName(), "legacy name:"); List tags = methodDescriptor.getTags().stream().map(TestTag::getName).toList(); assertThat(tags).containsExactlyInAnyOrder("inherited-class-level-tag", "classTag1", "classTag2", "methodTag1", "methodTag2"); } @Test void constructFromMethodWithCustomTestAnnotation() throws Exception { Method testMethod = TestCase.class.getDeclaredMethod("customTestAnnotation"); TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, List::of, configuration); assertEquals(testMethod, descriptor.getTestMethod()); assertEquals("custom name", descriptor.getDisplayName(), "display name:"); assertEquals("customTestAnnotation()", descriptor.getLegacyReportingName(), "legacy name:"); assertThat(descriptor.getTags()).containsExactly(TestTag.create("custom-tag")); } @Test void constructFromMethodWithParameters() throws Exception { Method testMethod = TestCase.class.getDeclaredMethod("test", String.class, BigDecimal.class); TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, List::of, configuration); assertEquals(testMethod, descriptor.getTestMethod()); assertEquals("test(String, BigDecimal)", descriptor.getDisplayName(), "display name"); assertEquals("test(String, BigDecimal)", descriptor.getLegacyReportingName(), "legacy name"); } @Test void constructFromMethodWithPrimitiveArrayParameter() throws Exception { Method testMethod = TestCase.class.getDeclaredMethod("test", int[].class); TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, List::of, configuration); assertEquals(testMethod, descriptor.getTestMethod()); assertEquals("test(int[])", descriptor.getDisplayName(), "display name"); assertEquals("test(int[])", descriptor.getLegacyReportingName(), "legacy name"); } @Test void constructFromMethodWithObjectArrayParameter() throws Exception { Method testMethod = TestCase.class.getDeclaredMethod("test", String[].class); TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, List::of, configuration); assertEquals(testMethod, descriptor.getTestMethod()); assertEquals("test(String[])", descriptor.getDisplayName(), "display name"); assertEquals("test(String[])", descriptor.getLegacyReportingName(), "legacy name"); } @Test void constructFromMethodWithMultidimensionalPrimitiveArrayParameter() throws Exception { Method testMethod = TestCase.class.getDeclaredMethod("test", int[][][][][].class); TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, List::of, configuration); assertEquals(testMethod, descriptor.getTestMethod()); assertEquals("test(int[][][][][])", descriptor.getDisplayName(), "display name"); assertEquals("test(int[][][][][])", descriptor.getLegacyReportingName(), "legacy name"); } @Test void constructFromMethodWithMultidimensionalObjectArrayParameter() throws Exception { Method testMethod = TestCase.class.getDeclaredMethod("test", String[][][][][].class); TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, List::of, configuration); assertEquals(testMethod, descriptor.getTestMethod()); assertEquals("test(String[][][][][])", descriptor.getDisplayName(), "display name"); assertEquals("test(String[][][][][])", descriptor.getLegacyReportingName(), "legacy name"); } @Test void constructFromInheritedMethod() throws Exception { Method testMethod = ConcreteTestCase.class.getMethod("theTest"); TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, ConcreteTestCase.class, testMethod, List::of, configuration); assertEquals(testMethod, descriptor.getTestMethod()); Optional sourceOptional = descriptor.getSource(); assertThat(sourceOptional).containsInstanceOf(MethodSource.class); MethodSource methodSource = (MethodSource) sourceOptional.orElseThrow(); assertEquals(ConcreteTestCase.class.getName(), methodSource.getClassName()); assertEquals("theTest", methodSource.getMethodName()); } @Test void shouldTakeCustomMethodNameDescriptorFromConfigurationIfPresent() { when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); ClassBasedTestDescriptor descriptor = new ClassTestDescriptor(uniqueId, getClass(), configuration); assertEquals("class-display-name", descriptor.getDisplayName()); assertEquals(getClass().getName(), descriptor.getLegacyReportingName()); descriptor = new NestedClassTestDescriptor(uniqueId, NestedTestCase.class, List::of, configuration); assertEquals("nested-class-display-name", descriptor.getDisplayName()); assertEquals(NestedTestCase.class.getName(), descriptor.getLegacyReportingName()); descriptor = new ClassTestDescriptor(uniqueId, StaticTestCase.class, configuration); assertEquals("class-display-name", descriptor.getDisplayName()); assertEquals(StaticTestCase.class.getName(), descriptor.getLegacyReportingName()); descriptor = new ClassTestDescriptor(uniqueId, StaticTestCaseLevel2.class, configuration); assertEquals("class-display-name", descriptor.getDisplayName()); assertEquals(StaticTestCaseLevel2.class.getName(), descriptor.getLegacyReportingName()); } @Test void defaultDisplayNamesForTestClasses() { ClassBasedTestDescriptor descriptor = new ClassTestDescriptor(uniqueId, getClass(), configuration); assertEquals(getClass().getSimpleName(), descriptor.getDisplayName()); assertEquals(getClass().getName(), descriptor.getLegacyReportingName()); descriptor = new NestedClassTestDescriptor(uniqueId, NestedTestCase.class, List::of, configuration); assertEquals(NestedTestCase.class.getSimpleName(), descriptor.getDisplayName()); assertEquals(NestedTestCase.class.getName(), descriptor.getLegacyReportingName()); descriptor = new ClassTestDescriptor(uniqueId, StaticTestCase.class, configuration); String staticDisplayName = getClass().getSimpleName() + "$" + StaticTestCase.class.getSimpleName(); assertEquals(staticDisplayName, descriptor.getDisplayName()); assertEquals(StaticTestCase.class.getName(), descriptor.getLegacyReportingName()); descriptor = new ClassTestDescriptor(uniqueId, StaticTestCaseLevel2.class, configuration); staticDisplayName += "$" + StaticTestCaseLevel2.class.getSimpleName(); assertEquals(staticDisplayName, descriptor.getDisplayName()); assertEquals(StaticTestCaseLevel2.class.getName(), descriptor.getLegacyReportingName()); } @Test void enclosingClassesAreDerivedFromParent() { ClassBasedTestDescriptor parentDescriptor = new ClassTestDescriptor(uniqueId, StaticTestCase.class, configuration); ClassBasedTestDescriptor nestedDescriptor = new NestedClassTestDescriptor(uniqueId, NestedTestCase.class, List::of, configuration); assertThat(parentDescriptor.getEnclosingTestClasses()).isEmpty(); assertThat(nestedDescriptor.getEnclosingTestClasses()).isEmpty(); parentDescriptor.addChild(nestedDescriptor); assertThat(parentDescriptor.getEnclosingTestClasses()).isEmpty(); assertThat(nestedDescriptor.getEnclosingTestClasses()).containsExactly(StaticTestCase.class); } @Test void ancestorsAreConsistent() throws Exception { ClassBasedTestDescriptor parentDescriptor = new ClassTestDescriptor(uniqueId, StaticTestCase.class, configuration); ClassBasedTestDescriptor nestedDescriptor = new NestedClassTestDescriptor(uniqueId, NestedTestCase.class, List::of, configuration); parentDescriptor.addChild(nestedDescriptor); TestMethodTestDescriptor methodDescriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, NestedTestCase.class.getDeclaredMethod("test"), List::of, configuration); nestedDescriptor.addChild(methodDescriptor); assertThat(methodDescriptor.ancestors()) // .containsExactlyElementsOf(methodDescriptor.getAncestors()) // .containsExactly(nestedDescriptor, parentDescriptor); assertThat(nestedDescriptor.ancestors()) // .containsExactlyElementsOf(nestedDescriptor.getAncestors()) // .containsExactly(parentDescriptor); assertThat(parentDescriptor.ancestors()) // .containsExactlyElementsOf(parentDescriptor.getAncestors()) // .isEmpty(); } // ------------------------------------------------------------------------- @Test @DisplayName("custom name") @Tag(" custom-tag ") @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @interface CustomTestAnnotation { } @Tag("inherited-class-level-tag") private static abstract class AbstractTestCase { } @Tag("classTag1") @Tag("classTag2") @DisplayName("custom class name") @SuppressWarnings("unused") private static class TestCase extends AbstractTestCase { void test() { } void test(String txt, BigDecimal sum) { } void test(int[] nums) { } void test(int[][][][][] nums) { } void test(String[] info) { } void test(String[][][][][] info) { } @Test @DisplayName("custom test name") @Tag("methodTag1") @Tag("methodTag2") @Tag("tag containing whitespace") void foo() { } @CustomTestAnnotation void customTestAnnotation() { } } @SuppressWarnings("JUnitMalformedDeclaration") private static class TestCaseWithInvalidBeforeAllMethod { // must be static @SuppressWarnings("JUnitMalformedDeclaration") @BeforeAll void beforeAll() { } @Test void test() { } } @SuppressWarnings("JUnitMalformedDeclaration") private static class TestCaseWithInvalidAfterAllMethod { // must be static @SuppressWarnings("JUnitMalformedDeclaration") @AfterAll void afterAll() { } @Test void test() { } } @SuppressWarnings("JUnitMalformedDeclaration") private static class TestCaseWithInvalidBeforeEachMethod { // must NOT be static @SuppressWarnings("JUnitMalformedDeclaration") @BeforeEach static void beforeEach() { } @Test void test() { } } @SuppressWarnings("JUnitMalformedDeclaration") private static class TestCaseWithInvalidAfterEachMethod { // must NOT be static @SuppressWarnings("JUnitMalformedDeclaration") @AfterEach static void afterEach() { } @Test void test() { } } @Nested class NestedTestCase { @Test void test() { } } static class StaticTestCase { static class StaticTestCaseLevel2 { } } private abstract static class AbstractTestBase { @Test public void theTest() { } } private static class ConcreteTestCase extends AbstractTestBase { } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/LauncherStoreFacadeTest.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrowsExactly; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.engine.execution.LauncherStoreFacade; import org.junit.jupiter.engine.execution.NamespaceAwareStore; import org.junit.platform.commons.JUnitException; import org.junit.platform.engine.support.store.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; /** * Tests for {@link LauncherStoreFacade}. * * @since 5.13 */ class LauncherStoreFacadeTest { private NamespacedHierarchicalStore requestLevelStore; private NamespacedHierarchicalStore sessionLevelStore; private ExtensionContext.Namespace extensionNamespace; @BeforeEach void setUp() { sessionLevelStore = new NamespacedHierarchicalStore<>(null); requestLevelStore = new NamespacedHierarchicalStore<>(sessionLevelStore); extensionNamespace = ExtensionContext.Namespace.create("foo", "bar"); } @Test void createsInstanceSuccessfullyWithValidStore() { assertDoesNotThrow(() -> new LauncherStoreFacade(requestLevelStore)); } @Test void throwsExceptionWhenRequestLevelStoreHasNoParent() { assertThrowsExactly(JUnitException.class, () -> new LauncherStoreFacade(sessionLevelStore), () -> { throw new JUnitException("Request-level store must have a parent"); }); } @Test void returnsRequestLevelStore() { LauncherStoreFacade facade = new LauncherStoreFacade(requestLevelStore); assertEquals(requestLevelStore, facade.getRequestLevelStore()); } @Test void returnsNamespaceAwareStoreWithRequestLevelStore() { LauncherStoreFacade facade = new LauncherStoreFacade(requestLevelStore); ExtensionContext.Store store = facade.getRequestLevelStore(extensionNamespace); assertNotNull(store); assertInstanceOf(NamespaceAwareStore.class, store); } @Test void returnsNamespaceAwareStore() { LauncherStoreFacade facade = new LauncherStoreFacade(requestLevelStore); NamespaceAwareStore adapter = facade.getStoreAdapter(requestLevelStore, extensionNamespace); assertNotNull(adapter); } @SuppressWarnings("DataFlowIssue") @Test void throwsExceptionWhenNamespaceIsNull() { LauncherStoreFacade facade = new LauncherStoreFacade(requestLevelStore); assertPreconditionViolationFor(() -> facade.getStoreAdapter(requestLevelStore, null)); } @Test void returnsNamespaceAwareStoreWithGlobalNamespace() { requestLevelStore.put(Namespace.GLOBAL, "foo", "bar"); LauncherStoreFacade facade = new LauncherStoreFacade(requestLevelStore); ExtensionContext.Store store = facade.getRequestLevelStore(ExtensionContext.Namespace.GLOBAL); assertEquals("bar", store.get("foo")); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtilsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static java.util.function.Predicate.isEqual; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findAfterAllMethods; import static org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findAfterEachMethods; import static org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findBeforeAllMethods; import static org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findBeforeEachMethods; import static org.junit.platform.commons.util.FunctionUtils.where; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import org.jspecify.annotations.NullUnmarked; 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.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.support.descriptor.MethodSource; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter; /** * Unit tests for {@link LifecycleMethodUtils}. * * @since 5.0 */ class LifecycleMethodUtilsTests { List discoveryIssues = new ArrayList<>(); DiscoveryIssueReporter issueReporter = DiscoveryIssueReporter.collecting(discoveryIssues); @Test void findNonVoidBeforeAllMethodsWithStandardLifecycle() throws Exception { var methods = findBeforeAllMethods(TestCaseWithInvalidLifecycleMethods.class, true, issueReporter); assertThat(methods).isEmpty(); var methodSource = MethodSource.from(TestCaseWithInvalidLifecycleMethods.class.getDeclaredMethod("cc")); var notVoidIssue = DiscoveryIssue.builder(Severity.ERROR, "@BeforeAll method 'private java.lang.Double org.junit.jupiter.engine.descriptor.TestCaseWithInvalidLifecycleMethods.cc()' must not return a value.") // .source(methodSource) // .build(); var notStaticIssue = DiscoveryIssue.builder(Severity.ERROR, "@BeforeAll method 'private java.lang.Double org.junit.jupiter.engine.descriptor.TestCaseWithInvalidLifecycleMethods.cc()' must be static unless the test class is annotated with @TestInstance(Lifecycle.PER_CLASS).") // .source(methodSource) // .build(); var privateIssue = DiscoveryIssue.builder(Severity.WARNING, "@BeforeAll method 'private java.lang.Double org.junit.jupiter.engine.descriptor.TestCaseWithInvalidLifecycleMethods.cc()' should not be private. This will be disallowed in a future release.") // .source(methodSource) // .build(); assertThat(discoveryIssues).containsExactlyInAnyOrder(notVoidIssue, notStaticIssue, privateIssue); } @Test void findNonVoidAfterAllMethodsWithStandardLifecycle() throws Exception { var methods = findAfterAllMethods(TestCaseWithInvalidLifecycleMethods.class, true, issueReporter); assertThat(methods).isEmpty(); var methodSource = MethodSource.from(TestCaseWithInvalidLifecycleMethods.class.getDeclaredMethod("dd")); var notVoidIssue = DiscoveryIssue.builder(Severity.ERROR, "@AfterAll method 'private java.lang.String org.junit.jupiter.engine.descriptor.TestCaseWithInvalidLifecycleMethods.dd()' must not return a value.") // .source(methodSource) // .build(); var notStaticIssue = DiscoveryIssue.builder(Severity.ERROR, "@AfterAll method 'private java.lang.String org.junit.jupiter.engine.descriptor.TestCaseWithInvalidLifecycleMethods.dd()' must be static unless the test class is annotated with @TestInstance(Lifecycle.PER_CLASS).") // .source(methodSource) // .build(); var privateIssue = DiscoveryIssue.builder(Severity.WARNING, "@AfterAll method 'private java.lang.String org.junit.jupiter.engine.descriptor.TestCaseWithInvalidLifecycleMethods.dd()' should not be private. This will be disallowed in a future release.") // .source(methodSource) // .build(); assertThat(discoveryIssues).containsExactlyInAnyOrder(notVoidIssue, notStaticIssue, privateIssue); } @Test void findNonVoidBeforeEachMethodsWithStandardLifecycle() throws Exception { var methods = findBeforeEachMethods(TestCaseWithInvalidLifecycleMethods.class, issueReporter); assertThat(methods).isEmpty(); var methodSource = MethodSource.from(TestCaseWithInvalidLifecycleMethods.class.getDeclaredMethod("aa")); var notVoidIssue = DiscoveryIssue.builder(Severity.ERROR, "@BeforeEach method 'private java.lang.String org.junit.jupiter.engine.descriptor.TestCaseWithInvalidLifecycleMethods.aa()' must not return a value.") // .source(methodSource) // .build(); var privateIssue = DiscoveryIssue.builder(Severity.WARNING, "@BeforeEach method 'private java.lang.String org.junit.jupiter.engine.descriptor.TestCaseWithInvalidLifecycleMethods.aa()' should not be private. This will be disallowed in a future release.") // .source(methodSource) // .build(); assertThat(discoveryIssues).containsExactlyInAnyOrder(notVoidIssue, privateIssue); } @Test void findNonVoidAfterEachMethodsWithStandardLifecycle() throws Exception { var methods = findAfterEachMethods(TestCaseWithInvalidLifecycleMethods.class, issueReporter); assertThat(methods).isEmpty(); var methodSource = MethodSource.from(TestCaseWithInvalidLifecycleMethods.class.getDeclaredMethod("bb")); var notVoidIssue = DiscoveryIssue.builder(Severity.ERROR, "@AfterEach method 'private int org.junit.jupiter.engine.descriptor.TestCaseWithInvalidLifecycleMethods.bb()' must not return a value.") // .source(methodSource) // .build(); var privateIssue = DiscoveryIssue.builder(Severity.WARNING, "@AfterEach method 'private int org.junit.jupiter.engine.descriptor.TestCaseWithInvalidLifecycleMethods.bb()' should not be private. This will be disallowed in a future release.") // .source(methodSource) // .build(); assertThat(discoveryIssues).containsExactlyInAnyOrder(notVoidIssue, privateIssue); } @Test void findBeforeEachMethodsWithStandardLifecycle() { List methods = findBeforeEachMethods(TestCaseWithStandardLifecycle.class, issueReporter); assertThat(namesOf(methods)).containsExactlyInAnyOrder("nine", "ten"); assertThat(discoveryIssues).isEmpty(); } @Test void findAfterEachMethodsWithStandardLifecycle() { List methods = findAfterEachMethods(TestCaseWithStandardLifecycle.class, issueReporter); assertThat(namesOf(methods)).containsExactlyInAnyOrder("eleven", "twelve"); } @Test void findBeforeAllMethodsWithStandardLifecycleAndWithoutRequiringStatic() { List methods = findBeforeAllMethods(TestCaseWithStandardLifecycle.class, false, issueReporter); assertThat(namesOf(methods)).containsExactly("one"); assertThat(discoveryIssues).isEmpty(); } @Test void findBeforeAllMethodsWithStandardLifecycleAndRequiringStatic() throws Exception { var methods = findBeforeAllMethods(TestCaseWithStandardLifecycle.class, true, issueReporter); assertThat(methods).isEmpty(); var expectedIssue = DiscoveryIssue.builder(Severity.ERROR, "@BeforeAll method 'void org.junit.jupiter.engine.descriptor.TestCaseWithStandardLifecycle.one()' must be static unless the test class is annotated with @TestInstance(Lifecycle.PER_CLASS).") // .source(MethodSource.from(TestCaseWithStandardLifecycle.class.getDeclaredMethod("one"))) // .build(); assertThat(discoveryIssues).containsExactly(expectedIssue); } @Test void findBeforeAllMethodsWithLifeCyclePerClassAndRequiringStatic() { List methods = findBeforeAllMethods(TestCaseWithLifecyclePerClass.class, false, issueReporter); assertThat(namesOf(methods)).containsExactlyInAnyOrder("three", "four"); assertThat(discoveryIssues).isEmpty(); } @Test void findAfterAllMethodsWithStandardLifecycleAndWithoutRequiringStatic() { List methods = findAfterAllMethods(TestCaseWithStandardLifecycle.class, false, issueReporter); assertThat(namesOf(methods)).containsExactlyInAnyOrder("five", "six"); assertThat(discoveryIssues).isEmpty(); } @Test void findAfterAllMethodsWithStandardLifecycleAndRequiringStatic() { var methods = findAfterAllMethods(TestCaseWithStandardLifecycle.class, true, issueReporter); assertThat(methods).isEmpty(); assertThat(discoveryIssues) // .filteredOn(where(DiscoveryIssue::severity, isEqual(Severity.ERROR))) // .isNotEmpty(); } @Test void findAfterAllMethodsWithLifeCyclePerClassAndRequiringStatic() { List methods = findAfterAllMethods(TestCaseWithLifecyclePerClass.class, false, issueReporter); assertThat(namesOf(methods)).containsExactlyInAnyOrder("seven", "eight"); } private static List namesOf(List methods) { return methods.stream().map(Method::getName).toList(); } } class TestCaseWithStandardLifecycle { @SuppressWarnings("JUnitMalformedDeclaration") @BeforeAll void one() { } @BeforeEach void nine() { } @BeforeEach void ten() { } @AfterEach void eleven() { } @AfterEach void twelve() { } @SuppressWarnings("JUnitMalformedDeclaration") @AfterAll void five() { } @SuppressWarnings("JUnitMalformedDeclaration") @AfterAll void six() { } } @TestInstance(Lifecycle.PER_CLASS) class TestCaseWithLifecyclePerClass { @BeforeAll void three() { } @BeforeAll void four() { } @AfterAll void seven() { } @AfterAll void eight() { } } @SuppressWarnings("JUnitMalformedDeclaration") @NullUnmarked class TestCaseWithInvalidLifecycleMethods { @BeforeEach private String aa() { return null; } @AfterEach private int bb() { return 1; } @BeforeAll private Double cc() { return null; } @AfterAll private String dd() { return ""; } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/NamespaceTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext.Namespace; class NamespaceTests { @Test void namespacesEqualForSamePartsSequence() { Namespace ns1 = Namespace.create("part1", "part2"); Namespace ns2 = Namespace.create("part1", "part2"); assertEquals(ns1, ns2); } @Test void orderOfNamespacePartsDoesMatter() { Namespace ns1 = Namespace.create("part1", "part2"); Namespace ns2 = Namespace.create("part2", "part1"); assertNotEquals(ns1, ns2); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ResourceAutoClosingTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.logging.Level; import java.util.logging.LogRecord; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.fixtures.TrackLogRecords; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.LauncherStoreFacade; import org.junit.jupiter.engine.extension.ExtensionRegistry; import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.launcher.core.NamespacedHierarchicalStoreProviders; import org.junit.platform.testkit.engine.ExecutionRecorder; class ResourceAutoClosingTests { private final JupiterConfiguration configuration = mock(); private final ExtensionRegistry extensionRegistry = mock(); private final JupiterEngineDescriptor testDescriptor = mock(); private final LauncherStoreFacade launcherStoreFacade = new LauncherStoreFacade( NamespacedHierarchicalStoreProviders.dummyNamespacedHierarchicalStore()); @Test @SuppressWarnings("resource") void shouldCloseAutoCloseableWhenIsClosingStoredAutoCloseablesEnabledIsTrue() throws Exception { AutoCloseableResource resource = new AutoCloseableResource(); when(configuration.isClosingStoredAutoCloseablesEnabled()).thenReturn(true); ExtensionContext extensionContext = new JupiterEngineExtensionContext(mock(), testDescriptor, configuration, extensionRegistry, launcherStoreFacade); ExtensionContext.Store store = extensionContext.getStore(ExtensionContext.Namespace.GLOBAL); store.put("resource", resource); ((AutoCloseable) extensionContext).close(); assertThat(resource.closed).isTrue(); } @Test @SuppressWarnings("resource") void shouldNotCloseAutoCloseableWhenIsClosingStoredAutoCloseablesEnabledIsFalse() throws Exception { AutoCloseableResource resource = new AutoCloseableResource(); when(configuration.isClosingStoredAutoCloseablesEnabled()).thenReturn(false); ExtensionContext extensionContext = new JupiterEngineExtensionContext(mock(), testDescriptor, configuration, extensionRegistry, launcherStoreFacade); ExtensionContext.Store store = extensionContext.getStore(ExtensionContext.Namespace.GLOBAL); store.put("resource", resource); ((AutoCloseable) extensionContext).close(); assertThat(resource.closed).isFalse(); } @Test @SuppressWarnings("resource") void shouldLogWarningWhenResourceImplementsCloseableResourceButNotAutoCloseableAndConfigIsTrue( @TrackLogRecords LogRecordListener listener) throws Exception { ExecutionRecorder executionRecorder = new ExecutionRecorder(); 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("resource1", resource1); store.put("resource2", resource2); store.put("resource3", resource3); ((AutoCloseable) extensionContext).close(); 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 @SuppressWarnings("resource") void shouldNotLogWarningWhenResourceImplementsCloseableResourceAndAutoCloseableAndConfigIsFalse( @TrackLogRecords LogRecordListener listener) throws Exception { ExecutionRecorder executionRecorder = new ExecutionRecorder(); CloseableResource resource = new CloseableResource(); when(configuration.isClosingStoredAutoCloseablesEnabled()).thenReturn(false); ExtensionContext extensionContext = new JupiterEngineExtensionContext(executionRecorder, testDescriptor, configuration, extensionRegistry, launcherStoreFacade); ExtensionContext.Store store = extensionContext.getStore(ExtensionContext.Namespace.GLOBAL); store.put("resource", resource); ((AutoCloseable) extensionContext).close(); assertThat(listener.stream(Level.WARNING)).isEmpty(); assertThat(resource.closed).isTrue(); } static class AutoCloseableResource implements AutoCloseable { private boolean closed = false; @Override public void close() { closed = true; } } @SuppressWarnings("deprecation") static class CloseableResource implements ExtensionContext.Store.CloseableResource { private boolean closed = false; @Override public void close() { closed = true; } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.io.File; import java.lang.reflect.Method; import java.net.URI; import java.util.List; import java.util.Optional; import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; import org.junit.platform.engine.TestSource; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.ClasspathResourceSource; import org.junit.platform.engine.support.descriptor.DirectorySource; import org.junit.platform.engine.support.descriptor.FilePosition; import org.junit.platform.engine.support.descriptor.FileSource; import org.junit.platform.engine.support.descriptor.MethodSource; import org.junit.platform.engine.support.descriptor.UriSource; import org.junit.platform.engine.support.hierarchical.OpenTest4JAwareThrowableCollector; /** * Unit tests for {@link TestFactoryTestDescriptor}. * * @since 5.0 */ class TestFactoryTestDescriptorTests { @Test void copyIncludesTransformedDynamicDescendantFilter() throws Exception { var rootUniqueId = UniqueId.forEngine("engine"); var parentUniqueId = rootUniqueId.append("class", "myClass"); var originalUniqueId = parentUniqueId.append("old", "testFactory()"); var configuration = mock(JupiterConfiguration.class); when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); Method testMethod = CustomStreamTestCase.class.getDeclaredMethod("customStream"); var original = new TestFactoryTestDescriptor(originalUniqueId, CustomStreamTestCase.class, testMethod, List::of, configuration); original.getDynamicDescendantFilter().allowUniqueIdPrefix(originalUniqueId.append("foo", "bar")); original.getDynamicDescendantFilter().allowIndex(42); var newUniqueId = parentUniqueId.append("new", "testFactory()"); var copy = original.withUniqueId(new UniqueIdPrefixTransformer(originalUniqueId, newUniqueId)); assertThat(copy.getUniqueId()).isEqualTo(newUniqueId); assertThat(copy.getDynamicDescendantFilter().test(newUniqueId, 0)).isTrue(); assertThat(copy.getDynamicDescendantFilter().test(newUniqueId, 42)).isTrue(); assertThat(copy.getDynamicDescendantFilter().test(originalUniqueId, 1)).isFalse(); } /** * @since 5.3 */ @Nested class TestSources { @Test void classpathResourceSourceFromUriWithFilePosition() { FilePosition position = FilePosition.from(42, 21); URI uri = URI.create("classpath:/test.js?line=42&column=21"); TestSource testSource = TestFactoryTestDescriptor.fromUri(uri); assertThat(testSource).isInstanceOf(ClasspathResourceSource.class); ClasspathResourceSource source = (ClasspathResourceSource) testSource; assertThat(source.getClasspathResourceName()).isEqualTo("test.js"); assertThat(source.getPosition()).hasValue(position); } @Test void fileSourceFromUriWithFilePosition() { File file = new File("src/test/resources/log4j2-test.xml"); assertThat(file).isFile(); FilePosition position = FilePosition.from(42, 21); URI uri = URI.create(file.toURI() + "?line=42&column=21"); TestSource testSource = TestFactoryTestDescriptor.fromUri(uri); assertThat(testSource).isInstanceOf(FileSource.class); FileSource source = (FileSource) testSource; assertThat(source.getFile().getAbsolutePath()).isEqualTo(file.getAbsolutePath()); assertThat(source.getPosition()).hasValue(position); } @Test void directorySourceFromUri() { File file = new File("src/test/resources"); assertThat(file).isDirectory(); URI uri = file.toURI(); TestSource testSource = TestFactoryTestDescriptor.fromUri(uri); assertThat(testSource).isInstanceOf(DirectorySource.class); DirectorySource source = (DirectorySource) testSource; assertThat(source.getFile().getAbsolutePath()).isEqualTo(file.getAbsolutePath()); } @Test void defaultUriSourceFromUri() { File file = new File("src/test/resources"); assertThat(file).isDirectory(); URI uri = URI.create("https://example.com?foo=bar&line=42"); TestSource testSource = TestFactoryTestDescriptor.fromUri(uri); assertThat(testSource).isInstanceOf(UriSource.class); assertThat(testSource.getClass().getSimpleName()).isEqualTo("DefaultUriSource"); UriSource source = (UriSource) testSource; assertThat(source.getUri()).isEqualTo(uri); } @Test void methodSourceFromUri() { URI uri = URI.create("method:org.junit.Foo#bar(java.lang.String,%20java.lang.String[])"); TestSource testSource = TestFactoryTestDescriptor.fromUri(uri); assertThat(testSource).isInstanceOf(MethodSource.class); assertThat(testSource.getClass().getSimpleName()).isEqualTo("MethodSource"); MethodSource source = (MethodSource) testSource; assertThat(source.getClassName()).isEqualTo("org.junit.Foo"); assertThat(source.getMethodName()).isEqualTo("bar"); assertThat(source.getMethodParameterTypes()).isEqualTo("java.lang.String, java.lang.String[]"); } } @Nested class Streams { private JupiterEngineExecutionContext context; private ExtensionContext extensionContext; private TestFactoryTestDescriptor descriptor; private boolean isClosed; @BeforeEach void before() throws Exception { JupiterConfiguration jupiterConfiguration = mock(); when(jupiterConfiguration.getDefaultDisplayNameGenerator()).thenReturn(new DisplayNameGenerator.Standard()); extensionContext = mock(); isClosed = false; context = new JupiterEngineExecutionContext(mock(), mock(), mock()) // .extend() // .withThrowableCollector(new OpenTest4JAwareThrowableCollector()) // .withExtensionContext(extensionContext) // .withExtensionRegistry(mock()) // .build(); Method testMethod = CustomStreamTestCase.class.getDeclaredMethod("customStream"); descriptor = new TestFactoryTestDescriptor(UniqueId.forEngine("engine"), CustomStreamTestCase.class, testMethod, List::of, jupiterConfiguration); when(extensionContext.getTestMethod()).thenReturn(Optional.of(testMethod)); } @Test void streamsFromTestFactoriesShouldBeClosed() { Stream dynamicTestStream = Stream.empty(); prepareMockForTestInstanceWithCustomStream(dynamicTestStream); descriptor.invokeTestMethod(context, mock()); assertTrue(isClosed); } @Test void streamsFromTestFactoriesShouldBeClosedWhenTheyThrow() { Stream integerStream = Stream.of(1, 2); prepareMockForTestInstanceWithCustomStream(integerStream); descriptor.invokeTestMethod(context, mock()); assertTrue(isClosed); } private void prepareMockForTestInstanceWithCustomStream(Stream stream) { Stream mockStream = stream.onClose(() -> isClosed = true); when(extensionContext.getRequiredTestInstance()).thenReturn(new CustomStreamTestCase(mockStream)); } } private record CustomStreamTestCase(Stream mockStream) { @TestFactory Stream customStream() { return mockStream; } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtilsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Constants.DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.jupiter.engine.descriptor.TestInstanceLifecycleUtils.getTestInstanceLifecycle; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullFor; import static org.junit.platform.launcher.core.OutputDirectoryCreators.dummyOutputDirectoryCreator; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.Optional; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.engine.config.DefaultJupiterConfiguration; import org.junit.platform.engine.ConfigurationParameters; /** * Unit tests for {@link TestInstanceLifecycleUtils}. * *

NOTE: it doesn't make sense to unit test the JVM system property fallback * support in this test class since that feature is a concrete implementation * detail of {@code LauncherConfigurationParameters} which necessitates an * integration test via the {@code Launcher} API. * * @since 5.0 */ class TestInstanceLifecycleUtilsTests { private static final String KEY = DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME; @SuppressWarnings("DataFlowIssue") @Test void getTestInstanceLifecyclePreconditions() { assertPreconditionViolationNotNullFor("testClass", () -> getTestInstanceLifecycle(null, new DefaultJupiterConfiguration(mock(), dummyOutputDirectoryCreator(), mock()))); assertPreconditionViolationNotNullFor("configuration", () -> getTestInstanceLifecycle(getClass(), null)); } @Test void getTestInstanceLifecycleWithNoConfigParamSet() { Lifecycle lifecycle = getTestInstanceLifecycle(getClass(), new DefaultJupiterConfiguration(mock(), dummyOutputDirectoryCreator(), mock())); assertThat(lifecycle).isEqualTo(PER_METHOD); } @Test void getTestInstanceLifecycleWithConfigParamSet() { ConfigurationParameters configParams = mock(); when(configParams.get(KEY)).thenReturn(Optional.of(PER_CLASS.name().toLowerCase())); Lifecycle lifecycle = getTestInstanceLifecycle(getClass(), new DefaultJupiterConfiguration(configParams, dummyOutputDirectoryCreator(), mock())); assertThat(lifecycle).isEqualTo(PER_CLASS); } @Test void getTestInstanceLifecycleWithLocalConfigThatOverridesCustomDefaultSetViaConfigParam() { ConfigurationParameters configParams = mock(); when(configParams.get(KEY)).thenReturn(Optional.of(PER_CLASS.name().toLowerCase())); Lifecycle lifecycle = getTestInstanceLifecycle(TestCase.class, new DefaultJupiterConfiguration(configParams, dummyOutputDirectoryCreator(), mock())); assertThat(lifecycle).isEqualTo(PER_METHOD); } @Test void getTestInstanceLifecycleFromMetaAnnotationWithNoConfigParamSet() { Class testClass = BaseMetaAnnotatedTestCase.class; Lifecycle lifecycle = getTestInstanceLifecycle(testClass, new DefaultJupiterConfiguration(mock(), dummyOutputDirectoryCreator(), mock())); assertThat(lifecycle).isEqualTo(PER_CLASS); } @Test void getTestInstanceLifecycleFromSpecializedClassWithNoConfigParamSet() { Class testClass = SpecializedTestCase.class; Lifecycle lifecycle = getTestInstanceLifecycle(testClass, new DefaultJupiterConfiguration(mock(), dummyOutputDirectoryCreator(), mock())); assertThat(lifecycle).isEqualTo(PER_CLASS); } @TestInstance(Lifecycle.PER_METHOD) private static class TestCase { } @Inherited @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @TestInstance(Lifecycle.PER_CLASS) private @interface PerClassLifeCycle { } @PerClassLifeCycle private static class BaseMetaAnnotatedTestCase { } private static class SpecializedTestCase extends BaseMetaAnnotatedTestCase { } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.lang.reflect.Method; import java.util.List; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.TestTemplateInvocationContext; import org.junit.jupiter.api.parallel.ResourceLock; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.platform.engine.UniqueId; class TestTemplateInvocationTestDescriptorTests { @Test void invocationsDoNotDeclareExclusiveResources() throws Exception { Class testClass = MyTestCase.class; Method testTemplateMethod = testClass.getDeclaredMethod("testTemplate"); JupiterConfiguration configuration = mock(); when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new DisplayNameGenerator.Standard()); TestTemplateTestDescriptor parent = new TestTemplateTestDescriptor(UniqueId.root("segment", "template"), testClass, testTemplateMethod, List::of, configuration); TestTemplateInvocationContext invocationContext = mock(); when(invocationContext.getDisplayName(anyInt())).thenReturn("invocation"); TestTemplateInvocationTestDescriptor testDescriptor = new TestTemplateInvocationTestDescriptor( parent.getUniqueId().append(TestTemplateInvocationTestDescriptor.SEGMENT_TYPE, "1"), testClass, testTemplateMethod, invocationContext, 1, configuration); assertThat(parent.getExclusiveResources()).hasSize(1); assertThat(testDescriptor.getExclusiveResources()).isEmpty(); } static class MyTestCase { @TestTemplate @ResourceLock("a") void testTemplate() { } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.List; import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.platform.engine.TestTag; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; /** * Unit tests for {@link TestTemplateTestDescriptor}. * * @since 5.0 */ class TestTemplateTestDescriptorTests { private JupiterConfiguration jupiterConfiguration = mock(); @BeforeEach void prepareJupiterConfiguration() { when(jupiterConfiguration.getDefaultDisplayNameGenerator()).thenReturn(new DisplayNameGenerator.Standard()); } @Test void inheritsTagsFromParent() throws Exception { var rootUniqueId = UniqueId.root("segment", "template"); var parentUniqueId = rootUniqueId.append("class", "myClass"); var parent = containerTestDescriptorWithTags(parentUniqueId, Set.of(TestTag.create("foo"))); var testDescriptor = new TestTemplateTestDescriptor(parentUniqueId.append("tmp", "testTemplate()"), MyTestCase.class, MyTestCase.class.getDeclaredMethod("testTemplate"), List::of, jupiterConfiguration); parent.addChild(testDescriptor); assertThat(testDescriptor.getTags()).containsExactlyInAnyOrder(TestTag.create("foo"), TestTag.create("bar"), TestTag.create("baz")); } @Test void shouldUseCustomDisplayNameGeneratorIfPresentFromConfiguration() throws Exception { var rootUniqueId = UniqueId.root("segment", "template"); var parentUniqueId = rootUniqueId.append("class", "myClass"); var parent = containerTestDescriptorWithTags(parentUniqueId, Set.of(TestTag.create("foo"))); when(jupiterConfiguration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); var testDescriptor = new TestTemplateTestDescriptor(parentUniqueId.append("tmp", "testTemplate()"), MyTestCase.class, MyTestCase.class.getDeclaredMethod("testTemplate"), List::of, jupiterConfiguration); parent.addChild(testDescriptor); assertThat(testDescriptor.getDisplayName()).isEqualTo("method-display-name"); } @Test void shouldUseStandardDisplayNameGeneratorIfConfigurationNotPresent() throws Exception { var rootUniqueId = UniqueId.root("segment", "template"); var parentUniqueId = rootUniqueId.append("class", "myClass"); var parent = containerTestDescriptorWithTags(parentUniqueId, Set.of(TestTag.create("foo"))); var testDescriptor = new TestTemplateTestDescriptor(parentUniqueId.append("tmp", "testTemplate()"), MyTestCase.class, MyTestCase.class.getDeclaredMethod("testTemplate"), List::of, jupiterConfiguration); parent.addChild(testDescriptor); assertThat(testDescriptor.getDisplayName()).isEqualTo("testTemplate()"); } @Test void copyIncludesTransformedDynamicDescendantFilter() throws Exception { var rootUniqueId = UniqueId.root("segment", "template"); var parentUniqueId = rootUniqueId.append("class", "myClass"); var originalUniqueId = parentUniqueId.append("old", "testTemplate()"); var original = new TestTemplateTestDescriptor(originalUniqueId, MyTestCase.class, MyTestCase.class.getDeclaredMethod("testTemplate"), List::of, jupiterConfiguration); original.getDynamicDescendantFilter().allowUniqueIdPrefix(originalUniqueId.append("foo", "bar")); original.getDynamicDescendantFilter().allowIndex(42); var newUniqueId = parentUniqueId.append("new", "testTemplate()"); var copy = original.withUniqueId(new UniqueIdPrefixTransformer(originalUniqueId, newUniqueId)); assertThat(copy.getUniqueId()).isEqualTo(newUniqueId); assertThat(copy.getDynamicDescendantFilter().test(newUniqueId, 0)).isTrue(); assertThat(copy.getDynamicDescendantFilter().test(newUniqueId, 42)).isTrue(); assertThat(copy.getDynamicDescendantFilter().test(originalUniqueId, 1)).isFalse(); } private AbstractTestDescriptor containerTestDescriptorWithTags(UniqueId uniqueId, Set tags) { return new AbstractTestDescriptor(uniqueId, "testDescriptor with tags") { @Override public Type getType() { return Type.CONTAINER; } @Override public Set getTags() { return tags; } }; } static class MyTestCase { @Tag("bar") @Tag("baz") @TestTemplate void testTemplate() { } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/Class1WithTestCases.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor.subpackage; import org.junit.jupiter.api.Test; /** * @since 5.0 */ public class Class1WithTestCases { @Test void test1() { } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/Class2WithTestCases.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor.subpackage; import org.junit.jupiter.api.Test; /** * @since 5.0 */ public class Class2WithTestCases { @Test void test2() { } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/ClassWithStaticInnerTestCases.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor.subpackage; import org.junit.jupiter.api.Test; /** * @since 5.0 */ public class ClassWithStaticInnerTestCases { @SuppressWarnings("NewClassNamingConvention") public static class ShouldBeDiscovered { @Test void test1() { } } @SuppressWarnings("unused") private static class ShouldNotBeDiscovered { @Test void test2() { } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/ClassWithoutTestCases.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.descriptor.subpackage; /** * @since 5.0 */ public class ClassWithoutTestCases { } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.discovery; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.DisplayNameGenerator.getDisplayNameGenerator; import static org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor.DYNAMIC_CONTAINER_SEGMENT_TYPE; import static org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor.DYNAMIC_TEST_SEGMENT_TYPE; import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.appendClassTemplateInvocationSegment; import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.engineId; import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.uniqueIdForClass; import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.uniqueIdForMethod; import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.uniqueIdForStaticClass; import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.uniqueIdForTestFactoryMethod; import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.uniqueIdForTestTemplateMethod; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import static org.junit.platform.engine.SelectorResolutionResult.Status.FAILED; import static org.junit.platform.engine.SelectorResolutionResult.Status.RESOLVED; import static org.junit.platform.engine.SelectorResolutionResult.Status.UNRESOLVED; import static org.junit.platform.engine.discovery.ClassNameFilter.includeClassNamePatterns; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathRoots; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.engine.discovery.PackageNameFilter.excludePackageNames; import static org.junit.platform.engine.discovery.PackageNameFilter.includePackageNames; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Path; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Predicate; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.ClassTemplate; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.descriptor.ClassTemplateTestDescriptor; import org.junit.jupiter.engine.descriptor.DynamicDescendantFilter; import org.junit.jupiter.engine.descriptor.Filterable; import org.junit.jupiter.engine.descriptor.JupiterTestDescriptor; import org.junit.jupiter.engine.descriptor.TestTemplateInvocationTestDescriptor; import org.junit.jupiter.engine.descriptor.subpackage.Class1WithTestCases; import org.junit.jupiter.engine.descriptor.subpackage.Class2WithTestCases; import org.junit.jupiter.engine.descriptor.subpackage.ClassWithStaticInnerTestCases; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.FilterResult; import org.junit.platform.engine.SelectorResolutionResult; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.discovery.ClassSelector; import org.junit.platform.engine.discovery.ClasspathRootSelector; import org.junit.platform.engine.discovery.MethodSelector; import org.junit.platform.engine.discovery.PackageSelector; import org.junit.platform.engine.discovery.UniqueIdSelector; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.PostDiscoveryFilter; import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; import org.mockito.ArgumentCaptor; /** * @since 5.0 */ class DiscoverySelectorResolverTests extends AbstractJupiterTestEngineTests { private final JupiterConfiguration configuration = mock(); private final LauncherDiscoveryListener discoveryListener = mock(); private @Nullable TestDescriptor engineDescriptor; @BeforeEach void setUp() { when(configuration.getDefaultDisplayNameGenerator()) // .thenReturn(getDisplayNameGenerator(DisplayNameGenerator.Standard.class)); when(configuration.getDefaultExecutionMode()).thenReturn(ExecutionMode.SAME_THREAD); } @Test void nonTestClassResolution() { resolve(request().selectors(selectClass(NonTestClass.class))); assertTrue(requireNonNull(engineDescriptor).getDescendants().isEmpty()); } @Test void doesNotAttemptToResolveMethodsForNonTestClasses() { var methodSelector = selectMethod(NonTestClass.class, "doesNotExist"); resolve(request().selectors(methodSelector)); assertTrue(requireNonNull(engineDescriptor).getDescendants().isEmpty()); assertUnresolved(methodSelector); } @Test void abstractClassResolution() { resolve(request().selectors(selectClass(AbstractTestClass.class))); assertTrue(requireNonNull(engineDescriptor).getDescendants().isEmpty()); assertUnresolved(selectClass(AbstractTestClass.class)); } @Test void singleClassResolution() { ClassSelector selector = selectClass(MyTestClass.class); resolve(request().selectors(selector)); assertEquals(4, requireNonNull(engineDescriptor).getDescendants().size()); assertUniqueIdsForMyTestClass(uniqueIds()); } @Test void classResolutionForNonexistentClass() { ClassSelector selector = selectClass("org.example.DoesNotExist"); resolve(request().selectors(selector)); assertTrue(requireNonNull(engineDescriptor).getDescendants().isEmpty()); var result = verifySelectorProcessed(selector); assertThat(result.getStatus()).isEqualTo(FAILED); assertThat(result.getThrowable().orElseThrow()).hasMessageContaining("Could not load class with name"); } @Test void duplicateClassSelectorOnlyResolvesOnce() { resolve(request().selectors( // selectClass(MyTestClass.class), // selectClass(MyTestClass.class) // )); assertEquals(4, requireNonNull(engineDescriptor).getDescendants().size()); assertUniqueIdsForMyTestClass(uniqueIds()); } @Test void twoClassesResolution() { ClassSelector selector1 = selectClass(MyTestClass.class); ClassSelector selector2 = selectClass(YourTestClass.class); resolve(request().selectors(selector1, selector2)); assertEquals(7, requireNonNull(engineDescriptor).getDescendants().size()); List uniqueIds = uniqueIds(); assertUniqueIdsForMyTestClass(uniqueIds); assertThat(uniqueIds).contains(uniqueIdForClass(YourTestClass.class)); assertThat(uniqueIds).contains(uniqueIdForMethod(YourTestClass.class, "test3()")); assertThat(uniqueIds).contains(uniqueIdForMethod(YourTestClass.class, "test4()")); } private void assertUniqueIdsForMyTestClass(List uniqueIds) { assertThat(uniqueIds).contains(uniqueIdForClass(MyTestClass.class)); assertThat(uniqueIds).contains(uniqueIdForMethod(MyTestClass.class, "test1()")); assertThat(uniqueIds).contains(uniqueIdForMethod(MyTestClass.class, "test2()")); assertThat(uniqueIds).contains(uniqueIdForTestFactoryMethod(MyTestClass.class, "dynamicTest()")); } @Test void classResolutionOfStaticNestedClass() { ClassSelector selector = selectClass(OtherTestClass.NestedTestClass.class); resolve(request().selectors(selector)); assertEquals(3, requireNonNull(engineDescriptor).getDescendants().size()); List uniqueIds = uniqueIds(); assertThat(uniqueIds).contains(uniqueIdForClass(OtherTestClass.NestedTestClass.class)); assertThat(uniqueIds).contains(uniqueIdForMethod(OtherTestClass.NestedTestClass.class, "test5()")); assertThat(uniqueIds).contains(uniqueIdForMethod(OtherTestClass.NestedTestClass.class, "test6()")); } @Test void classResolutionOfClassTemplate() { var selector = selectClass(ClassTemplateTestCase.class); AtomicBoolean verified = new AtomicBoolean(); PostDiscoveryFilter filter = descriptor -> { if (descriptor instanceof ClassTemplateTestDescriptor) { assertThat(descriptor.mayRegisterTests()).isFalse(); assertThat(descriptor.getDescendants()).hasSize(1); verified.set(true); } return FilterResult.included("included"); }; resolve(request().selectors(selector).filters(filter)); assertThat(verified.get()).describedAs("filter can see descendants").isTrue(); TestDescriptor classTemplateDescriptor = getOnlyElement(requireNonNull(engineDescriptor).getChildren()); assertThat(classTemplateDescriptor.mayRegisterTests()).isTrue(); assertThat(classTemplateDescriptor.getDescendants()).isEmpty(); var classTemplateSegment = classTemplateDescriptor.getUniqueId().getLastSegment(); assertThat(classTemplateSegment.getType()).isEqualTo("class-template"); assertThat(classTemplateSegment.getValue()).isEqualTo(ClassTemplateTestCase.class.getName()); } @Test void uniqueIdResolutionOfClassTemplateInvocation() { var selector = selectUniqueId( appendClassTemplateInvocationSegment(uniqueIdForClass(ClassTemplateTestCase.class), 1)); resolve(request().selectors(selector)); assertThat(requireNonNull(engineDescriptor).getChildren()).hasSize(1); TestDescriptor classTemplateDescriptor = getOnlyElement(requireNonNull(engineDescriptor).getChildren()); classTemplateDescriptor.prune(); assertThat(requireNonNull(engineDescriptor).getChildren()).hasSize(1); assertThat(classTemplateDescriptor.mayRegisterTests()).isTrue(); assertThat(classTemplateDescriptor.getDescendants()).isEmpty(); classTemplateDescriptor.prune(); assertThat(requireNonNull(engineDescriptor).getChildren()).hasSize(1); assertThat(classTemplateDescriptor.mayRegisterTests()).isTrue(); assertThat(classTemplateDescriptor.getDescendants()).isEmpty(); } @Test void methodResolution() throws NoSuchMethodException { Method test1 = MyTestClass.class.getDeclaredMethod("test1"); MethodSelector selector = selectMethod(test1.getDeclaringClass(), test1); resolve(request().selectors(selector)); assertEquals(2, requireNonNull(engineDescriptor).getDescendants().size()); List uniqueIds = uniqueIds(); assertThat(uniqueIds).contains(uniqueIdForClass(MyTestClass.class)); assertThat(uniqueIds).contains(uniqueIdForMethod(MyTestClass.class, "test1()")); } @Test void methodResolutionFromInheritedMethod() throws NoSuchMethodException { MethodSelector selector = selectMethod(HerTestClass.class, MyTestClass.class.getDeclaredMethod("test1")); resolve(request().selectors(selector)); assertEquals(2, requireNonNull(engineDescriptor).getDescendants().size()); List uniqueIds = uniqueIds(); assertThat(uniqueIds).contains(uniqueIdForClass(HerTestClass.class)); assertThat(uniqueIds).contains(uniqueIdForMethod(HerTestClass.class, "test1()")); } @Test void resolvingSelectorOfNonTestMethodResolvesNothing() throws NoSuchMethodException { Method notATest = MyTestClass.class.getDeclaredMethod("notATest"); MethodSelector selector = selectMethod(notATest.getDeclaringClass(), notATest); resolve(request().selectors(selector)); assertTrue(requireNonNull(engineDescriptor).getDescendants().isEmpty()); } @Test void methodResolutionForNonexistentClass() { String className = "org.example.DoesNotExist"; String methodName = "bogus"; MethodSelector selector = selectMethod(className, methodName, ""); resolve(request().selectors(selector)); assertTrue(requireNonNull(engineDescriptor).getDescendants().isEmpty()); var result = verifySelectorProcessed(selector); assertThat(result.getStatus()).isEqualTo(FAILED); assertThat(result.getThrowable().orElseThrow())// .isInstanceOf(PreconditionViolationException.class)// .hasMessageStartingWith("Could not load class with name: " + className); } @Test void methodResolutionForNonexistentMethod() { MethodSelector selector = selectMethod(MyTestClass.class, "bogus", ""); resolve(request().selectors(selector)); assertTrue(requireNonNull(engineDescriptor).getDescendants().isEmpty()); var result = verifySelectorProcessed(selector); assertThat(result.getStatus()).isEqualTo(FAILED); assertThat(result.getThrowable().orElseThrow()).hasMessageContaining("Could not find method"); } @Test void classResolutionByUniqueId() { UniqueIdSelector selector = selectUniqueId(uniqueIdForClass(MyTestClass.class).toString()); resolve(request().selectors(selector)); assertEquals(4, requireNonNull(engineDescriptor).getDescendants().size()); List uniqueIds = uniqueIds(); assertUniqueIdsForMyTestClass(uniqueIds); } @Test void staticNestedClassResolutionByUniqueId() { UniqueIdSelector selector = selectUniqueId(uniqueIdForClass(OtherTestClass.NestedTestClass.class).toString()); resolve(request().selectors(selector)); assertEquals(3, requireNonNull(engineDescriptor).getDescendants().size()); List uniqueIds = uniqueIds(); assertThat(uniqueIds).contains(uniqueIdForClass(OtherTestClass.NestedTestClass.class)); assertThat(uniqueIds).contains(uniqueIdForMethod(OtherTestClass.NestedTestClass.class, "test5()")); assertThat(uniqueIds).contains(uniqueIdForMethod(OtherTestClass.NestedTestClass.class, "test6()")); } @Test void methodOfInnerClassByUniqueId() { UniqueIdSelector selector = selectUniqueId( uniqueIdForMethod(OtherTestClass.NestedTestClass.class, "test5()").toString()); resolve(request().selectors(selector)); assertEquals(2, requireNonNull(engineDescriptor).getDescendants().size()); List uniqueIds = uniqueIds(); assertThat(uniqueIds).contains(uniqueIdForClass(OtherTestClass.NestedTestClass.class)); assertThat(uniqueIds).contains(uniqueIdForMethod(OtherTestClass.NestedTestClass.class, "test5()")); } @Test void resolvingUniqueIdWithUnknownSegmentTypeResolvesNothing() { UniqueId uniqueId = engineId().append("bogus", "enigma"); UniqueIdSelector selector = selectUniqueId(uniqueId); resolve(request().selectors(selector)); assertTrue(requireNonNull(engineDescriptor).getDescendants().isEmpty()); assertUnresolved(selector); } @Test void resolvingUniqueIdOfNonTestMethodResolvesNothing() { UniqueIdSelector selector = selectUniqueId(uniqueIdForMethod(MyTestClass.class, "notATest()")); resolve(request().selectors(selector)); assertThat(requireNonNull(engineDescriptor).getDescendants()).isEmpty(); assertUnresolved(selector); } @Test void methodResolutionByUniqueIdWithMissingMethodName() { UniqueId uniqueId = uniqueIdForMethod(getClass(), "()"); resolve(request().selectors(selectUniqueId(uniqueId))); assertTrue(requireNonNull(engineDescriptor).getDescendants().isEmpty()); var result = verifySelectorProcessed(selectUniqueId(uniqueId)); assertThat(result.getStatus()).isEqualTo(FAILED); assertThat(result.getThrowable().orElseThrow())// .isInstanceOf(PreconditionViolationException.class)// .hasMessageStartingWith("Method [()] does not match pattern"); } @Test void methodResolutionByUniqueIdWithMissingParameters() { UniqueId uniqueId = uniqueIdForMethod(getClass(), "methodName"); resolve(request().selectors(selectUniqueId(uniqueId))); assertThat(requireNonNull(engineDescriptor).getDescendants()).isEmpty(); var result = verifySelectorProcessed(selectUniqueId(uniqueId)); assertThat(result.getStatus()).isEqualTo(FAILED); assertThat(result.getThrowable().orElseThrow())// .isInstanceOf(PreconditionViolationException.class)// .hasMessageStartingWith("Method [methodName] does not match pattern"); } @Test void methodResolutionByUniqueIdWithBogusParameters() { UniqueId uniqueId = uniqueIdForMethod(getClass(), "methodName(java.lang.String, junit.foo.Enigma)"); resolve(request().selectors(selectUniqueId(uniqueId))); assertTrue(requireNonNull(engineDescriptor).getDescendants().isEmpty()); var result = verifySelectorProcessed(selectUniqueId(uniqueId)); assertThat(result.getStatus()).isEqualTo(FAILED); assertThat(result.getThrowable().orElseThrow())// .isInstanceOf(JUnitException.class)// .hasMessage("Failed to load parameter type [%s] for method [%s] in class [%s].", "junit.foo.Enigma", "methodName", getClass().getName()); } @Test void methodResolutionByUniqueId() { UniqueIdSelector selector = selectUniqueId(uniqueIdForMethod(MyTestClass.class, "test1()").toString()); resolve(request().selectors(selector)); assertEquals(2, requireNonNull(engineDescriptor).getDescendants().size()); List uniqueIds = uniqueIds(); assertThat(uniqueIds).contains(uniqueIdForClass(MyTestClass.class)); assertThat(uniqueIds).contains(uniqueIdForMethod(MyTestClass.class, "test1()")); } @Test void methodResolutionByUniqueIdFromInheritedClass() { UniqueIdSelector selector = selectUniqueId(uniqueIdForMethod(HerTestClass.class, "test1()").toString()); resolve(request().selectors(selector)); assertEquals(2, requireNonNull(engineDescriptor).getDescendants().size()); List uniqueIds = uniqueIds(); assertThat(uniqueIds).contains(uniqueIdForClass(HerTestClass.class)); assertThat(uniqueIds).contains(uniqueIdForMethod(HerTestClass.class, "test1()")); } @Test void methodResolutionByUniqueIdWithParams() { UniqueIdSelector selector = selectUniqueId( uniqueIdForMethod(HerTestClass.class, "test7(java.lang.String)").toString()); resolve(request().selectors(selector)); assertEquals(2, requireNonNull(engineDescriptor).getDescendants().size()); List uniqueIds = uniqueIds(); assertThat(uniqueIds).contains(uniqueIdForClass(HerTestClass.class)); assertThat(uniqueIds).contains(uniqueIdForMethod(HerTestClass.class, "test7(java.lang.String)")); } @Test void resolvingUniqueIdWithWrongParamsResolvesNothing() { UniqueId uniqueId = uniqueIdForMethod(HerTestClass.class, "test7(java.math.BigDecimal)"); resolve(request().selectors(selectUniqueId(uniqueId))); assertTrue(requireNonNull(engineDescriptor).getDescendants().isEmpty()); assertUnresolved(selectUniqueId(uniqueId)); } @Test void twoMethodResolutionsByUniqueId() { UniqueIdSelector selector1 = selectUniqueId(uniqueIdForMethod(MyTestClass.class, "test1()").toString()); UniqueIdSelector selector2 = selectUniqueId(uniqueIdForMethod(MyTestClass.class, "test2()").toString()); // adding same selector twice should have no effect resolve(request().selectors(selector1, selector2, selector2)); assertEquals(3, requireNonNull(engineDescriptor).getDescendants().size()); List uniqueIds = uniqueIds(); assertThat(uniqueIds).contains(uniqueIdForClass(MyTestClass.class)); assertThat(uniqueIds).contains(uniqueIdForMethod(MyTestClass.class, "test1()")); assertThat(uniqueIds).contains(uniqueIdForMethod(MyTestClass.class, "test2()")); TestDescriptor classFromMethod1 = descriptorByUniqueId( uniqueIdForMethod(MyTestClass.class, "test1()")).getParent().orElseThrow(); TestDescriptor classFromMethod2 = descriptorByUniqueId( uniqueIdForMethod(MyTestClass.class, "test2()")).getParent().orElseThrow(); assertEquals(classFromMethod1, classFromMethod2); assertSame(classFromMethod1, classFromMethod2); } @Test void packageResolutionUsingExplicitBasePackage() { PackageSelector selector = selectPackage("org.junit.jupiter.engine.descriptor.subpackage"); resolve(request().selectors(selector)); assertEquals(6, requireNonNull(engineDescriptor).getDescendants().size()); List uniqueIds = uniqueIds(); assertThat(uniqueIds).contains(uniqueIdForClass(Class1WithTestCases.class)); assertThat(uniqueIds).contains(uniqueIdForMethod(Class1WithTestCases.class, "test1()")); assertThat(uniqueIds).contains(uniqueIdForClass(Class2WithTestCases.class)); assertThat(uniqueIds).contains(uniqueIdForMethod(Class2WithTestCases.class, "test2()")); assertThat(uniqueIds).contains( uniqueIdForMethod(ClassWithStaticInnerTestCases.ShouldBeDiscovered.class, "test1()")); } @Test void packageResolutionUsingDefaultPackage() throws Exception { resolve(request().selectors(selectPackage(""))); // 150 is completely arbitrary. The actual number is likely much higher. assertThat(requireNonNull(engineDescriptor).getDescendants())// .describedAs("Too few test descriptors in classpath")// .hasSizeGreaterThan(150); List uniqueIds = uniqueIds(); assertThat(uniqueIds)// .describedAs("Failed to pick up DefaultPackageTestCase via classpath scanning")// .contains(uniqueIdForClass(ReflectionSupport.tryToLoadClass("DefaultPackageTestCase").get())); assertThat(uniqueIds).contains(uniqueIdForClass(Class1WithTestCases.class)); assertThat(uniqueIds).contains(uniqueIdForMethod(Class1WithTestCases.class, "test1()")); assertThat(uniqueIds).contains(uniqueIdForClass(Class2WithTestCases.class)); assertThat(uniqueIds).contains(uniqueIdForMethod(Class2WithTestCases.class, "test2()")); } @Test void classpathResolution() throws Exception { Path classpath = Path.of( DiscoverySelectorResolverTests.class.getProtectionDomain().getCodeSource().getLocation().toURI()); List selectors = selectClasspathRoots(Set.of(classpath)); resolve(request().selectors(selectors)); // 150 is completely arbitrary. The actual number is likely much higher. assertThat(requireNonNull(engineDescriptor).getDescendants())// .describedAs("Too few test descriptors in classpath")// .hasSizeGreaterThan(150); List uniqueIds = uniqueIds(); assertThat(uniqueIds)// .describedAs("Failed to pick up DefaultPackageTestCase via classpath scanning")// .contains(uniqueIdForClass(ReflectionSupport.tryToLoadClass("DefaultPackageTestCase").getNonNull())); assertThat(uniqueIds).contains(uniqueIdForClass(Class1WithTestCases.class)); assertThat(uniqueIds).contains(uniqueIdForMethod(Class1WithTestCases.class, "test1()")); assertThat(uniqueIds).contains(uniqueIdForClass(Class2WithTestCases.class)); assertThat(uniqueIds).contains(uniqueIdForMethod(Class2WithTestCases.class, "test2()")); assertThat(uniqueIds).contains( uniqueIdForMethod(ClassWithStaticInnerTestCases.ShouldBeDiscovered.class, "test1()")); } @Test void classpathResolutionForJarFiles() throws Exception { URL jarUrl = requireNonNull(getClass().getResource("/jupiter-testjar.jar")); Path path = Path.of(jarUrl.toURI()); List selectors = selectClasspathRoots(Set.of(path)); ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); try (URLClassLoader classLoader = new URLClassLoader(new URL[] { jarUrl })) { Thread.currentThread().setContextClassLoader(classLoader); resolve(request().selectors(selectors)); assertThat(uniqueIds()) // .contains(uniqueIdForStaticClass("com.example.project.FirstTest")) // .contains(uniqueIdForStaticClass("com.example.project.SecondTest")); } finally { Thread.currentThread().setContextClassLoader(originalClassLoader); } } @Test void nestedTestResolutionFromBaseClass() { ClassSelector selector = selectClass(TestCaseWithNesting.class); resolve(request().selectors(selector)); List uniqueIds = uniqueIds(); assertThat(uniqueIds).hasSize(6); assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.class)); assertThat(uniqueIds).contains(uniqueIdForMethod(TestCaseWithNesting.class, "testA()")); assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.NestedTestCase.class)); assertThat(uniqueIds).contains(uniqueIdForMethod(TestCaseWithNesting.NestedTestCase.class, "testB()")); assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class)); assertThat(uniqueIds).contains( uniqueIdForMethod(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class, "testC()")); } @Test void nestedTestResolutionFromNestedTestClass() { ClassSelector selector = selectClass(TestCaseWithNesting.NestedTestCase.class); resolve(request().selectors(selector)); List uniqueIds = uniqueIds(); assertThat(uniqueIds).hasSize(5); assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.class)); assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.NestedTestCase.class)); assertThat(uniqueIds).contains(uniqueIdForMethod(TestCaseWithNesting.NestedTestCase.class, "testB()")); assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class)); assertThat(uniqueIds).contains( uniqueIdForMethod(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class, "testC()")); } @Test void nestedTestResolutionFromUniqueId() { UniqueIdSelector selector = selectUniqueId( uniqueIdForClass(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class).toString()); resolve(request().selectors(selector)); List uniqueIds = uniqueIds(); assertThat(uniqueIds).hasSize(4); assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.class)); assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.NestedTestCase.class)); assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class)); assertThat(uniqueIds).contains( uniqueIdForMethod(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class, "testC()")); } @Test void doubleNestedTestResolutionFromClass() { ClassSelector selector = selectClass(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class); resolve(request().selectors(selector)); List uniqueIds = uniqueIds(); assertThat(uniqueIds).hasSize(4); assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.class)); assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.NestedTestCase.class)); assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class)); assertThat(uniqueIds).contains( uniqueIdForMethod(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class, "testC()")); } @Test void methodResolutionInDoubleNestedTestClass() throws NoSuchMethodException { MethodSelector selector = selectMethod(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class, TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class.getDeclaredMethod("testC")); resolve(request().selectors(selector)); assertEquals(4, requireNonNull(engineDescriptor).getDescendants().size()); List uniqueIds = uniqueIds(); assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.class)); assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.NestedTestCase.class)); assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class)); assertThat(uniqueIds).contains( uniqueIdForMethod(TestCaseWithNesting.NestedTestCase.DoubleNestedTestCase.class, "testC()")); } @Test void nestedTestResolutionFromUniqueIdToMethod() { UniqueIdSelector selector = selectUniqueId( uniqueIdForMethod(TestCaseWithNesting.NestedTestCase.class, "testB()").toString()); resolve(request().selectors(selector)); List uniqueIds = uniqueIds(); assertThat(uniqueIds).hasSize(3); assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.class)); assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.NestedTestCase.class)); assertThat(uniqueIds).contains(uniqueIdForMethod(TestCaseWithNesting.NestedTestCase.class, "testB()")); } @Test void testFactoryMethodResolutionByUniqueId() { Class clazz = MyTestClass.class; UniqueId factoryUid = uniqueIdForTestFactoryMethod(clazz, "dynamicTest()"); resolve(request().selectors(selectUniqueId(factoryUid))); assertThat(requireNonNull(engineDescriptor).getDescendants()).hasSize(2); assertThat(uniqueIds()).containsSequence(uniqueIdForClass(clazz), factoryUid); } @Test void testTemplateMethodResolutionByUniqueId() { Class clazz = TestClassWithTemplate.class; UniqueId templateUid = uniqueIdForTestTemplateMethod(clazz, "testTemplate()"); resolve(request().selectors(selectUniqueId(templateUid))); assertThat(requireNonNull(engineDescriptor).getDescendants()).hasSize(2); assertThat(uniqueIds()).containsSequence(uniqueIdForClass(clazz), templateUid); } @Test void resolvingDynamicTestByUniqueIdResolvesUpToParentTestFactory() { Class clazz = MyTestClass.class; UniqueId factoryUid = uniqueIdForTestFactoryMethod(clazz, "dynamicTest()"); UniqueId dynamicTestUid = factoryUid.append(DYNAMIC_TEST_SEGMENT_TYPE, "#1"); UniqueId differentDynamicTestUid = factoryUid.append(DYNAMIC_TEST_SEGMENT_TYPE, "#2"); resolve(request().selectors(selectUniqueId(dynamicTestUid))); assertThat(requireNonNull(engineDescriptor).getDescendants()).hasSize(2); assertThat(uniqueIds()).containsSequence(uniqueIdForClass(clazz), factoryUid); TestDescriptor testClassDescriptor = getOnlyElement(requireNonNull(engineDescriptor).getChildren()); TestDescriptor testFactoryDescriptor = getOnlyElement(testClassDescriptor.getChildren()); DynamicDescendantFilter dynamicDescendantFilter = getDynamicDescendantFilter(testFactoryDescriptor); assertThat(dynamicDescendantFilter.test(dynamicTestUid, 42)).isTrue(); assertThat(dynamicDescendantFilter.test(differentDynamicTestUid, 42)).isFalse(); assertAllSelectorsResolved(); } @Test void resolvingDynamicContainerByUniqueIdResolvesUpToParentTestFactory() { Class clazz = MyTestClass.class; UniqueId factoryUid = uniqueIdForTestFactoryMethod(clazz, "dynamicTest()"); UniqueId dynamicContainerUid = factoryUid.append(DYNAMIC_CONTAINER_SEGMENT_TYPE, "#1"); UniqueId differentDynamicContainerUid = factoryUid.append(DYNAMIC_CONTAINER_SEGMENT_TYPE, "#2"); UniqueId dynamicTestUid = dynamicContainerUid.append(DYNAMIC_TEST_SEGMENT_TYPE, "#1"); UniqueId differentDynamicTestUid = dynamicContainerUid.append(DYNAMIC_TEST_SEGMENT_TYPE, "#2"); resolve(request().selectors(selectUniqueId(dynamicTestUid))); assertThat(requireNonNull(engineDescriptor).getDescendants()).hasSize(2); assertThat(uniqueIds()).containsSequence(uniqueIdForClass(clazz), factoryUid); TestDescriptor testClassDescriptor = getOnlyElement(requireNonNull(engineDescriptor).getChildren()); TestDescriptor testFactoryDescriptor = getOnlyElement(testClassDescriptor.getChildren()); DynamicDescendantFilter dynamicDescendantFilter = getDynamicDescendantFilter(testFactoryDescriptor); assertThat(dynamicDescendantFilter.test(dynamicTestUid, 42)).isTrue(); assertThat(dynamicDescendantFilter.test(differentDynamicContainerUid, 42)).isFalse(); assertThat(dynamicDescendantFilter.test(differentDynamicTestUid, 42)).isFalse(); assertAllSelectorsResolved(); } @Test void resolvingDynamicTestByUniqueIdAndTestFactoryByMethodSelectorResolvesTestFactory() { Class clazz = MyTestClass.class; UniqueId factoryUid = uniqueIdForTestFactoryMethod(clazz, "dynamicTest()"); UniqueId dynamicTestUid = factoryUid.append(DYNAMIC_TEST_SEGMENT_TYPE, "#1"); resolve(request().selectors(selectUniqueId(dynamicTestUid), selectMethod(clazz, "dynamicTest"))); assertThat(requireNonNull(engineDescriptor).getDescendants()).hasSize(2); assertThat(uniqueIds()).containsSequence(uniqueIdForClass(clazz), factoryUid); TestDescriptor testClassDescriptor = getOnlyElement(requireNonNull(engineDescriptor).getChildren()); TestDescriptor testFactoryDescriptor = getOnlyElement(testClassDescriptor.getChildren()); DynamicDescendantFilter dynamicDescendantFilter = getDynamicDescendantFilter(testFactoryDescriptor); assertThat(dynamicDescendantFilter.test(UniqueId.root("foo", "bar"), 42)).isTrue(); } private DynamicDescendantFilter getDynamicDescendantFilter(TestDescriptor testDescriptor) { assertThat(testDescriptor).isInstanceOf(JupiterTestDescriptor.class); return ((Filterable) testDescriptor).getDynamicDescendantFilter(); } @Test void resolvingTestTemplateInvocationByUniqueIdResolvesOnlyUpToParentTestTemplate() { Class clazz = TestClassWithTemplate.class; UniqueId templateUid = uniqueIdForTestTemplateMethod(clazz, "testTemplate()"); UniqueId invocationUid = templateUid.append(TestTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#1"); resolve(request().selectors(selectUniqueId(invocationUid))); assertThat(requireNonNull(engineDescriptor).getDescendants()).hasSize(2); assertThat(uniqueIds()).containsSequence(uniqueIdForClass(clazz), templateUid); } @Test void includingPackageNameFilterExcludesClassesInNonMatchingPackages() { resolve(request().selectors(selectClass(MatchingClass.class)).filters( includePackageNames("org.junit.jupiter.engine.unknown"))); assertThat(requireNonNull(engineDescriptor).getDescendants()).isEmpty(); } @Test void includingPackageNameFilterIncludesClassesInMatchingPackages() { resolve(request().selectors(selectClass(MatchingClass.class)).filters( includePackageNames("org.junit.jupiter.engine"))); assertThat(requireNonNull(engineDescriptor).getDescendants()).hasSize(3); } @Test void excludingPackageNameFilterExcludesClassesInMatchingPackages() { resolve(request().selectors(selectClass(MatchingClass.class)).filters( excludePackageNames("org.junit.jupiter.engine"))); assertThat(requireNonNull(engineDescriptor).getDescendants()).isEmpty(); } @Test void excludingPackageNameFilterIncludesClassesInNonMatchingPackages() { resolve(request().selectors(selectClass(MatchingClass.class)).filters( excludePackageNames("org.junit.jupiter.engine.unknown"))); assertThat(requireNonNull(engineDescriptor).getDescendants()).hasSize(3); } @Test void classNamePatternFilterExcludesNonMatchingClasses() { resolve(request().selectors(selectClass(MatchingClass.class), selectClass(OtherClass.class)).filters( includeClassNamePatterns(".*MatchingClass"))); assertThat(requireNonNull(engineDescriptor).getDescendants()).hasSize(3); } private void resolve(LauncherDiscoveryRequestBuilder builder) { engineDescriptor = discoverTests(builder.build()).getEngineDescriptor(); } private TestDescriptor descriptorByUniqueId(UniqueId uniqueId) { return requireNonNull(engineDescriptor).getDescendants().stream().filter( d -> d.getUniqueId().equals(uniqueId)).findFirst().orElseThrow(); } private List uniqueIds() { return requireNonNull(engineDescriptor).getDescendants().stream().map(TestDescriptor::getUniqueId).toList(); } private LauncherDiscoveryRequestBuilder request() { return defaultRequest() // .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // .listeners(discoveryListener); } private void assertAllSelectorsResolved() { ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(SelectorResolutionResult.class); verify(discoveryListener).selectorProcessed(eq(UniqueId.forEngine("junit-jupiter")), any(), resultCaptor.capture()); assertThat(resultCaptor.getAllValues()) // .flatExtracting(SelectorResolutionResult::getStatus) // .allMatch(Predicate.isEqual(RESOLVED)); } private void assertUnresolved(DiscoverySelector selector) { var result = verifySelectorProcessed(selector); assertThat(result.getStatus()).isEqualTo(UNRESOLVED); } private SelectorResolutionResult verifySelectorProcessed(DiscoverySelector selector) { ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(SelectorResolutionResult.class); verify(discoveryListener).selectorProcessed(eq(UniqueId.forEngine("junit-jupiter")), eq(selector), resultCaptor.capture()); return resultCaptor.getValue(); } } // ----------------------------------------------------------------------------- class NonTestClass { } abstract class AbstractTestClass { @SuppressWarnings("unused") @Test void test() { } } @SuppressWarnings("NewClassNamingConvention") class MyTestClass { @Test void test1() { } @Test void test2() { } void notATest() { } @TestFactory Stream dynamicTest() { return Stream.empty(); } } @SuppressWarnings("NewClassNamingConvention") class YourTestClass { @Test void test3() { } @Test void test4() { } } @SuppressWarnings("NewClassNamingConvention") class HerTestClass extends MyTestClass { @SuppressWarnings("JUnitMalformedDeclaration") @Test void test7(@SuppressWarnings("unused") String param) { } } class OtherTestClass { @SuppressWarnings("NewClassNamingConvention") static class NestedTestClass { @Test void test5() { } @Test void test6() { } } } class TestCaseWithNesting { @Test void testA() { } @Nested class NestedTestCase { @Test void testB() { } @Nested class DoubleNestedTestCase { @Test void testC() { } } } } class TestClassWithTemplate { @TestTemplate void testTemplate() { } } @SuppressWarnings("NewClassNamingConvention") class MatchingClass { @Nested class NestedClass { @Test void test() { } } } @SuppressWarnings("NewClassNamingConvention") class OtherClass { @Test void test() { } } @ClassTemplate class ClassTemplateTestCase { @Test void test() { } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.discovery; import static java.util.Comparator.comparing; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assumptions.assumeFalse; import static org.junit.jupiter.api.Named.named; import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.uniqueIdForTestTemplateMethod; import static org.junit.jupiter.params.provider.Arguments.argumentSet; import static org.junit.platform.commons.test.IdeUtils.runningInEclipse; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import static org.junit.platform.engine.discovery.ClassNameFilter.includeClassNamePatterns; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasses; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectNestedClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectNestedMethod; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Method; import java.util.List; import java.util.regex.Pattern; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Named; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.DisabledInEclipse; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.jupiter.engine.JupiterTestEngine; import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; import org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor; import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.platform.launcher.LauncherDiscoveryRequest; /** * Test correct test discovery in simple test classes for the {@link JupiterTestEngine}. * * @since 5.0 */ class DiscoveryTests extends AbstractJupiterTestEngineTests { @Test void discoverTestClass() { LauncherDiscoveryRequest request = defaultRequest().selectors(selectClass(LocalTestCase.class)).build(); TestDescriptor engineDescriptor = discoverTestsWithoutIssues(request); assertEquals(7, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @Test void doNotDiscoverAbstractTestClass() { LauncherDiscoveryRequest request = defaultRequest().selectors(selectClass(AbstractTestCase.class)).build(); TestDescriptor engineDescriptor = discoverTestsWithoutIssues(request); assertEquals(0, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @ParameterizedTest @ValueSource(strings = { "org.junit.jupiter.engine.discovery.DiscoveryTests$InterfaceTestCase", "org.junit.jupiter.engine.kotlin.KotlinInterfaceTestCase" }) void doNotDiscoverTestInterface(String className) { assumeFalse(runningInEclipse() && className.contains(".kotlin.")); LauncherDiscoveryRequest request = defaultRequest().selectors(selectClass(className)).build(); TestDescriptor engineDescriptor = discoverTestsWithoutIssues(request); assertEquals(0, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @Test @DisabledInEclipse void doNotDiscoverGeneratedKotlinDefaultImplsClass() { LauncherDiscoveryRequest request = defaultRequest() // .selectors(selectClass("org.junit.jupiter.engine.kotlin.KotlinInterfaceTestCase$DefaultImpls")) // .build(); TestDescriptor engineDescriptor = discoverTestsWithoutIssues(request); assertEquals(0, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @Test @DisabledInEclipse void discoverDeclaredKotlinDefaultImplsClass() { LauncherDiscoveryRequest request = defaultRequest().selectors( selectClass("org.junit.jupiter.engine.kotlin.KotlinDefaultImplsTestCase$DefaultImpls")).build(); TestDescriptor engineDescriptor = discoverTestsWithoutIssues(request); assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @ParameterizedTest @ValueSource(strings = { "org.junit.jupiter.engine.discovery.DiscoveryTests$ConcreteImplementationOfInterfaceTestCase", "org.junit.jupiter.engine.kotlin.KotlinInterfaceImplementationTestCase" }) void discoverTestClassInheritingTestsFromInterface(String className) { assumeFalse(runningInEclipse() && className.contains(".kotlin.")); LauncherDiscoveryRequest request = defaultRequest().selectors(selectClass(className)).build(); TestDescriptor engineDescriptor = discoverTestsWithoutIssues(request); assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @Test void discoverMethodByUniqueId() { LauncherDiscoveryRequest request = defaultRequest().selectors( selectUniqueId(JupiterUniqueIdBuilder.uniqueIdForMethod(LocalTestCase.class, "test1()"))).build(); TestDescriptor engineDescriptor = discoverTestsWithoutIssues(request); assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @Test void discoverMethodByUniqueIdForOverloadedMethod() { LauncherDiscoveryRequest request = defaultRequest().selectors( selectUniqueId(JupiterUniqueIdBuilder.uniqueIdForMethod(LocalTestCase.class, "test4()"))).build(); TestDescriptor engineDescriptor = discoverTestsWithoutIssues(request); assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @Test void discoverMethodByUniqueIdForOverloadedMethodVariantThatAcceptsArguments() { LauncherDiscoveryRequest request = defaultRequest().selectors( selectUniqueId(JupiterUniqueIdBuilder.uniqueIdForMethod(LocalTestCase.class, "test4(" + TestInfo.class.getName() + ")"))).build(); TestDescriptor engineDescriptor = discoverTestsWithoutIssues(request); assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @Test void discoverMethodByMethodReference() throws NoSuchMethodException { Method testMethod = LocalTestCase.class.getDeclaredMethod("test3"); LauncherDiscoveryRequest request = defaultRequest().selectors( selectMethod(LocalTestCase.class, testMethod)).build(); TestDescriptor engineDescriptor = discoverTestsWithoutIssues(request); assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @Test void discoverMultipleMethodsOfSameClass() { LauncherDiscoveryRequest request = defaultRequest().selectors(selectMethod(LocalTestCase.class, "test1"), selectMethod(LocalTestCase.class, "test2")).build(); TestDescriptor engineDescriptor = discoverTestsWithoutIssues(request); assertThat(engineDescriptor.getChildren()).hasSize(1); TestDescriptor classDescriptor = getOnlyElement(engineDescriptor.getChildren()); assertThat(classDescriptor.getChildren()).hasSize(2); } @Test void discoverCompositeSpec() { LauncherDiscoveryRequest spec = defaultRequest().selectors( selectUniqueId(JupiterUniqueIdBuilder.uniqueIdForMethod(LocalTestCase.class, "test2()")), selectClass(LocalTestCase.class)).build(); TestDescriptor engineDescriptor = discoverTests(spec).getEngineDescriptor(); assertEquals(7, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @Test void discoverTestTemplateMethodByUniqueId() { LauncherDiscoveryRequest spec = defaultRequest().selectors( selectUniqueId(uniqueIdForTestTemplateMethod(TestTemplateClass.class, "testTemplate()"))).build(); TestDescriptor engineDescriptor = discoverTests(spec).getEngineDescriptor(); assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @Test void discoverTestTemplateMethodByMethodSelector() { LauncherDiscoveryRequest spec = defaultRequest().selectors( selectMethod(TestTemplateClass.class, "testTemplate")).build(); TestDescriptor engineDescriptor = discoverTests(spec).getEngineDescriptor(); assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @Test void discoverDeeplyNestedTestMethodByNestedMethodSelector() throws Exception { var selector = selectNestedMethod( List.of(TestCaseWithExtendedNested.class, TestCaseWithExtendedNested.ConcreteInner1.class), AbstractSuperClass.NestedInAbstractClass.class, AbstractSuperClass.NestedInAbstractClass.class.getDeclaredMethod("test")); LauncherDiscoveryRequest spec = defaultRequest().selectors(selector).build(); TestDescriptor engineDescriptor = discoverTests(spec).getEngineDescriptor(); ClassTestDescriptor topLevelClassDescriptor = (ClassTestDescriptor) getOnlyElement( engineDescriptor.getChildren()); assertThat(topLevelClassDescriptor.getTestClass()).isEqualTo(TestCaseWithExtendedNested.class); NestedClassTestDescriptor firstLevelNestedClassDescriptor = (NestedClassTestDescriptor) getOnlyElement( topLevelClassDescriptor.getChildren()); assertThat(firstLevelNestedClassDescriptor.getTestClass()).isEqualTo( TestCaseWithExtendedNested.ConcreteInner1.class); NestedClassTestDescriptor secondLevelNestedClassDescriptor = (NestedClassTestDescriptor) getOnlyElement( firstLevelNestedClassDescriptor.getChildren()); assertThat(secondLevelNestedClassDescriptor.getTestClass()).isEqualTo( AbstractSuperClass.NestedInAbstractClass.class); TestMethodTestDescriptor methodDescriptor = (TestMethodTestDescriptor) getOnlyElement( secondLevelNestedClassDescriptor.getChildren()); assertThat(methodDescriptor.getTestMethod().getName()).isEqualTo("test"); } @ParameterizedTest @MethodSource("requestsForTestClassWithInvalidTestMethod") void reportsWarningForTestClassWithInvalidTestMethod(LauncherDiscoveryRequest request) throws Exception { var method = InvalidTestCases.InvalidTestMethodTestCase.class.getDeclaredMethod("test"); var results = discoverTests(request); var discoveryIssues = results.getDiscoveryIssues().stream().sorted(comparing(DiscoveryIssue::message)).toList(); assertThat(discoveryIssues).hasSize(3); assertThat(discoveryIssues.getFirst().message()) // .isEqualTo("@Test method '%s' must not be private. It will not be executed.", method.toGenericString()); assertThat(discoveryIssues.get(1).message()) // .isEqualTo("@Test method '%s' must not be static. It will not be executed.", method.toGenericString()); assertThat(discoveryIssues.getLast().message()) // .isEqualTo("@Test method '%s' must not return a value. It will not be executed.", method.toGenericString()); } static List> requestsForTestClassWithInvalidTestMethod() { return List.of( // named("directly selected", defaultRequest().selectors(selectClass(InvalidTestCases.InvalidTestMethodTestCase.class)).build()), // named("indirectly selected", defaultRequest() // .selectors(selectPackage(InvalidTestCases.InvalidTestMethodTestCase.class.getPackageName())) // .filters(includeClassNamePatterns( Pattern.quote(InvalidTestCases.InvalidTestMethodTestCase.class.getName()))).build()), // named("subclasses", defaultRequest() // .selectors(selectClasses(InvalidTestCases.InvalidTestMethodSubclass1TestCase.class, InvalidTestCases.InvalidTestMethodSubclass2TestCase.class)) // .build()) // ); } @ParameterizedTest @MethodSource("requestsForTestClassWithInvalidStandaloneTestClass") void reportsWarningForInvalidStandaloneTestClass(LauncherDiscoveryRequest request, Class testClass) { var results = discoverTests(request); var discoveryIssues = results.getDiscoveryIssues().stream().sorted(comparing(DiscoveryIssue::message)).toList(); assertThat(discoveryIssues).hasSize(2); assertThat(discoveryIssues.getFirst().message()) // .isEqualTo( "Test class '%s' must not be an inner class unless annotated with @Nested. It will not be executed.", testClass.getName()); assertThat(discoveryIssues.getLast().message()) // .isEqualTo("Test class '%s' must not be private. It will not be executed.", testClass.getName()); } static List requestsForTestClassWithInvalidStandaloneTestClass() { return List.of( // argumentSet("directly selected", defaultRequest().selectors(selectClass(InvalidTestCases.InvalidTestClassTestCase.class)).build(), InvalidTestCases.InvalidTestClassTestCase.class), // argumentSet("indirectly selected", defaultRequest() // .selectors(selectPackage(InvalidTestCases.InvalidTestClassTestCase.class.getPackageName())) // .filters(includeClassNamePatterns( Pattern.quote(InvalidTestCases.InvalidTestClassTestCase.class.getName()))).build(), // InvalidTestCases.InvalidTestClassTestCase.class), // argumentSet("subclass", defaultRequest() // .selectors(selectClass(InvalidTestCases.InvalidTestClassSubclassTestCase.class)) // .build(), // InvalidTestCases.InvalidTestClassSubclassTestCase.class) // ); } @ParameterizedTest @MethodSource("requestsForTestClassWithInvalidNestedTestClass") void reportsWarningForInvalidNestedTestClass(LauncherDiscoveryRequest request) { var results = discoverTests(request); var discoveryIssues = results.getDiscoveryIssues().stream().sorted(comparing(DiscoveryIssue::message)).toList(); assertThat(discoveryIssues).hasSize(2); assertThat(discoveryIssues.getFirst().message()) // .isEqualTo("@Nested class '%s' must not be private. It will not be executed.", InvalidTestCases.InvalidTestClassTestCase.Inner.class.getName()); assertThat(discoveryIssues.getLast().message()) // .startsWith("@Nested class '%s' must not be static.".formatted( InvalidTestCases.InvalidTestClassTestCase.Inner.class.getName())); } static List> requestsForTestClassWithInvalidNestedTestClass() { return List.of( // named("directly selected", defaultRequest().selectors(selectClass(InvalidTestCases.InvalidTestClassTestCase.Inner.class)).build()), // named("subclass", defaultRequest() // .selectors(selectNestedClass(List.of(InvalidTestCases.InvalidTestClassSubclassTestCase.class), InvalidTestCases.InvalidTestClassTestCase.Inner.class)) // .build()) // ); } @Test void reportsWarningForTestClassWithPotentialNestedTestClasses() { var results = discoverTestsForClass(InvalidTestCases.class); var discoveryIssues = results.getDiscoveryIssues().stream().sorted(comparing(DiscoveryIssue::message)).toList(); assertThat(discoveryIssues).hasSize(2); assertThat(discoveryIssues.getFirst().message()) // .isEqualTo( "Inner class '%s' looks like it was intended to be a test class but will not be executed. It must be static or annotated with @Nested.", InvalidTestCases.InvalidTestClassSubclassTestCase.class.getName()); assertThat(discoveryIssues.getLast().message()) // .isEqualTo( "Inner class '%s' looks like it was intended to be a test class but will not be executed. It must be static or annotated with @Nested.", InvalidTestCases.InvalidTestClassTestCase.class.getName()); } @Test void ignoresUnrelatedClassDefinitionCycles() { var results = discoverTestsForClass(UnrelatedRecursiveHierarchyTestCase.class); assertThat(results.getDiscoveryIssues()).isEmpty(); } @Test void ignoresRecursiveNonTestHierarchyCycles() { var results = discoverTestsForClass(NonTestRecursiveHierarchyTestCase.class); assertThat(results.getDiscoveryIssues()).isEmpty(); } @Test void reportsMissingNestedAnnotationOnRecursiveHierarchy() { var results = discoverTestsForClass(RecursiveHierarchyWithoutNestedTestCase.class); var discoveryIssues = results.getDiscoveryIssues(); assertThat(discoveryIssues).hasSize(1); assertThat(discoveryIssues.getFirst().severity()) // .isEqualTo(Severity.WARNING); assertThat(discoveryIssues.getFirst().message()) // .isEqualTo( "Inner class '%s' looks like it was intended to be a test class but will not be executed. It must be static or annotated with @Nested.", RecursiveHierarchyWithoutNestedTestCase.Inner.class.getName()); } @Test void reportsWarningsForInvalidTags() throws Exception { var results = discoverTestsForClass(InvalidTagsTestCase.class); var discoveryIssues = results.getDiscoveryIssues().stream().sorted(comparing(DiscoveryIssue::message)).toList(); assertThat(discoveryIssues).hasSize(2); assertThat(discoveryIssues.getFirst().message()) // .isEqualTo("Invalid tag syntax in @Tag(\"\") declaration on class '%s'. Tag will be ignored.", InvalidTagsTestCase.class.getName()); assertThat(discoveryIssues.getFirst().source()) // .contains(ClassSource.from(InvalidTagsTestCase.class)); var method = InvalidTagsTestCase.class.getDeclaredMethod("test"); assertThat(discoveryIssues.getLast().message()) // .isEqualTo("Invalid tag syntax in @Tag(\"|\") declaration on method '%s'. Tag will be ignored.", method.toGenericString()); assertThat(discoveryIssues.getLast().source()) // .contains(org.junit.platform.engine.support.descriptor.MethodSource.from(method)); } @Test void reportsWarningsForBlankDisplayNames() throws Exception { var results = discoverTestsForClass(BlankDisplayNamesTestCase.class); var discoveryIssues = results.getDiscoveryIssues().stream().sorted(comparing(DiscoveryIssue::message)).toList(); assertThat(discoveryIssues).hasSize(2); assertThat(discoveryIssues.getFirst().message()) // .isEqualTo("@DisplayName on class '%s' must be declared with a non-blank value.", BlankDisplayNamesTestCase.class.getName()); assertThat(discoveryIssues.getFirst().source()) // .contains(ClassSource.from(BlankDisplayNamesTestCase.class)); var method = BlankDisplayNamesTestCase.class.getDeclaredMethod("test"); assertThat(discoveryIssues.getLast().message()) // .isEqualTo("@DisplayName on method '%s' must be declared with a non-blank value.", method.toGenericString()); assertThat(discoveryIssues.getLast().source()) // .contains(org.junit.platform.engine.support.descriptor.MethodSource.from(method)); } // ------------------------------------------------------------------- @SuppressWarnings("unused") static abstract class AbstractTestCase { @Test void test() { } @Test abstract void abstractTest(); } static class LocalTestCase { @Test void test1() { } @Test void test2() { } @Test void test3() { } @Test void test4() { } @Test void test4(TestInfo testInfo) { } @CustomTestAnnotation void customTestAnnotation() { /* no-op */ } } @Test @Retention(RetentionPolicy.RUNTIME) @interface CustomTestAnnotation { } static class TestTemplateClass { @TestTemplate void testTemplate() { } } static abstract class AbstractSuperClass { @Nested class NestedInAbstractClass { @Test void test() { } } } static class TestCaseWithExtendedNested { @Nested class ConcreteInner1 extends AbstractSuperClass { } } static class InvalidTestCases { @SuppressWarnings("JUnitMalformedDeclaration") static class InvalidTestMethodTestCase { @Test private static int test() { return fail("should not be called"); } } static class InvalidTestMethodSubclass1TestCase extends InvalidTestMethodTestCase { } static class InvalidTestMethodSubclass2TestCase extends InvalidTestMethodTestCase { } @SuppressWarnings({ "JUnitMalformedDeclaration", "InnerClassMayBeStatic" }) private class InvalidTestClassTestCase { @SuppressWarnings("unused") @Test void test() { fail("should not be called"); } @Nested private static class Inner { @SuppressWarnings("unused") @Test void test() { fail("should not be called"); } } } private class InvalidTestClassSubclassTestCase extends InvalidTestClassTestCase { } } static class UnrelatedRecursiveHierarchyTestCase { @Test void test() { } @SuppressWarnings({ "InnerClassMayBeStatic", "unused" }) class Inner { class Recursive extends Inner { } } } static class RecursiveHierarchyWithoutNestedTestCase { @Test void test() { } @SuppressWarnings({ "InnerClassMayBeStatic", "unused" }) class Inner extends RecursiveHierarchyWithoutNestedTestCase { } } @SuppressWarnings("unused") static class NonTestRecursiveHierarchyTestCase { @SuppressWarnings("InnerClassMayBeStatic") class Inner extends NonTestRecursiveHierarchyTestCase { } } @Tag("") static class InvalidTagsTestCase { @Test @Tag("|") void test() { } } @DisplayName("") static class BlankDisplayNamesTestCase { @Test @DisplayName("\t") void test() { } } interface InterfaceTestCase { @Test default void test() { } } static class ConcreteImplementationOfInterfaceTestCase implements InterfaceTestCase { } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethodTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.discovery.predicates; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.function.Predicate; import java.util.stream.Stream; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DynamicContainer; import org.junit.jupiter.api.DynamicNode; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.support.descriptor.MethodSource; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter; /** * Unit tests for {@link IsTestFactoryMethod}. * * @since 5.0 */ class IsTestFactoryMethodTests { final List discoveryIssues = new ArrayList<>(); final Predicate isTestFactoryMethod = new IsTestFactoryMethod( DiscoveryIssueReporter.collecting(discoveryIssues)); @ParameterizedTest @ValueSource(strings = { "dynamicTestsFactoryFromCollection", "dynamicTestsFactoryFromStreamWithExtendsWildcard", "dynamicTestsFactoryFromNode", "dynamicTestsFactoryFromTest", "dynamicTestsFactoryFromContainer", "dynamicTestsFactoryFromNodeArray", "dynamicTestsFactoryFromTestArray", "dynamicTestsFactoryFromContainerArray" }) void validFactoryMethods(String methodName) { assertThat(isTestFactoryMethod).accepts(method(methodName)); assertThat(discoveryIssues).isEmpty(); } @ParameterizedTest @ValueSource(strings = { "bogusVoidFactory", "bogusStringsFactory", "bogusStringArrayFactory", "dynamicTestsFactoryFromStreamWithSuperWildcard" }) void invalidFactoryMethods(String methodName) { var method = method(methodName); assertThat(isTestFactoryMethod).rejects(method); var issue = getOnlyElement(discoveryIssues); assertThat(issue.severity()).isEqualTo(DiscoveryIssue.Severity.WARNING); assertThat(issue.message()).isEqualTo( "@TestFactory method '%s' must return a single org.junit.jupiter.api.DynamicNode or a " + "Stream, Collection, Iterable, Iterator, Iterator provider, or array of org.junit.jupiter.api.DynamicNode. " + "It will not be executed.", method.toGenericString()); assertThat(issue.source()).contains(MethodSource.from(method)); } @ParameterizedTest @ValueSource(strings = { "objectFactory", "objectArrayFactory", "rawCollectionFactory", "unboundStreamFactory" }) void suspiciousFactoryMethods(String methodName) { var method = method(methodName); assertThat(isTestFactoryMethod).accepts(method); var issue = getOnlyElement(discoveryIssues); assertThat(issue.severity()).isEqualTo(DiscoveryIssue.Severity.INFO); assertThat(issue.message()).isEqualTo( "The declared return type of @TestFactory method '%s' does not support static validation. " + "It must return a single org.junit.jupiter.api.DynamicNode or a " + "Stream, Collection, Iterable, Iterator, Iterator provider, or array of org.junit.jupiter.api.DynamicNode.", method.toGenericString()); assertThat(issue.source()).contains(MethodSource.from(method)); } private static Method method(String name) { return ReflectionSupport.findMethod(ClassWithTestFactoryMethods.class, name).orElseThrow(); } @SuppressWarnings("unused") private static class ClassWithTestFactoryMethods { @TestFactory Collection dynamicTestsFactoryFromCollection() { return new ArrayList<>(); } @TestFactory Stream dynamicTestsFactoryFromStreamWithExtendsWildcard() { return Stream.empty(); } @TestFactory DynamicTest dynamicTestsFactoryFromNode() { return dynamicTest("foo", Assertions::fail); } @TestFactory DynamicTest dynamicTestsFactoryFromTest() { return dynamicTest("foo", Assertions::fail); } @TestFactory DynamicNode dynamicTestsFactoryFromContainer() { return dynamicContainer("foo", Stream.empty()); } @TestFactory DynamicNode[] dynamicTestsFactoryFromNodeArray() { return new DynamicNode[0]; } @TestFactory DynamicTest[] dynamicTestsFactoryFromTestArray() { return new DynamicTest[0]; } @TestFactory DynamicContainer[] dynamicTestsFactoryFromContainerArray() { return new DynamicContainer[0]; } @TestFactory void bogusVoidFactory() { } @TestFactory Collection bogusStringsFactory() { return new ArrayList<>(); } @TestFactory String[] bogusStringArrayFactory() { return new String[0]; } @TestFactory Stream dynamicTestsFactoryFromStreamWithSuperWildcard() { return Stream.empty(); } @TestFactory Object objectFactory() { return dynamicTest("foo", Assertions::fail); } @TestFactory Object[] objectArrayFactory() { return new DynamicNode[0]; } @SuppressWarnings("rawtypes") @TestFactory Collection rawCollectionFactory() { return new ArrayList<>(); } @TestFactory Stream unboundStreamFactory() { return Stream.of(); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestMethodTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.discovery.predicates; import static java.util.Comparator.comparing; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.function.Predicate; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.commons.support.ModifierSupport; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.support.descriptor.MethodSource; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter; /** * Unit tests for {@link IsTestMethod}. * * @since 5.0 */ class IsTestMethodTests { final List discoveryIssues = new ArrayList<>(); final Predicate isTestMethod = new IsTestMethod(DiscoveryIssueReporter.collecting(discoveryIssues)); @Test void publicTestMethod() { Method method = method("publicTestMethod"); // Ensure that somebody doesn't accidentally delete the public modifier again. assertTrue(ModifierSupport.isPublic(method)); assertThat(isTestMethod).accepts(method); } @Test void publicTestMethodWithArgument() { Method method = method("publicTestMethodWithArgument", TestInfo.class); // Ensure that somebody doesn't accidentally delete the public modifier again. assertTrue(ModifierSupport.isPublic(method)); assertThat(isTestMethod).accepts(method); } @Test void protectedTestMethod() { assertThat(isTestMethod).accepts(method("protectedTestMethod")); } @Test void packageVisibleTestMethod() { assertThat(isTestMethod).accepts(method("packageVisibleTestMethod")); } @Test void bogusAbstractTestMethod() { var method = abstractMethod("bogusAbstractTestMethod"); assertThat(isTestMethod).rejects(method); assertThat(discoveryIssues).isEmpty(); } @Test void bogusAbstractNonVoidTestMethod() { var method = abstractMethod("bogusAbstractNonVoidTestMethod"); assertThat(isTestMethod).rejects(method); var issue = getOnlyElement(discoveryIssues); assertThat(issue.message()) // .isEqualTo("@Test method '%s' must not return a value. It will not be executed.", method.toGenericString()); } @Test void bogusStaticTestMethod() { var method = method("bogusStaticTestMethod"); assertThat(isTestMethod).rejects(method); var issue = getOnlyElement(discoveryIssues); assertThat(issue.severity()).isEqualTo(Severity.WARNING); assertThat(issue.message()).isEqualTo("@Test method '%s' must not be static. It will not be executed.", method.toGenericString()); assertThat(issue.source()).contains(MethodSource.from(method)); } @Test void bogusPrivateTestMethod() { var method = method("bogusPrivateTestMethod"); assertThat(isTestMethod).rejects(method); var issue = getOnlyElement(discoveryIssues); assertThat(issue.severity()).isEqualTo(Severity.WARNING); assertThat(issue.message()).isEqualTo("@Test method '%s' must not be private. It will not be executed.", method.toGenericString()); assertThat(issue.source()).contains(MethodSource.from(method)); } @ParameterizedTest @ValueSource(strings = { "bogusTestMethodReturningObject", "bogusTestMethodReturningVoidReference", "bogusTestMethodReturningPrimitive" }) void bogusNonVoidTestMethods(String methodName) { var method = method(methodName); assertThat(isTestMethod).rejects(method); var issue = getOnlyElement(discoveryIssues); assertThat(issue.severity()).isEqualTo(Severity.WARNING); assertThat(issue.message()).isEqualTo("@Test method '%s' must not return a value. It will not be executed.", method.toGenericString()); assertThat(issue.source()).contains(MethodSource.from(method)); } @Test void bogusStaticPrivateNonVoidTestMethod() { var method = method("bogusStaticPrivateNonVoidTestMethod"); assertThat(isTestMethod).rejects(method); assertThat(discoveryIssues).hasSize(3); discoveryIssues.sort(comparing(DiscoveryIssue::message)); assertThat(discoveryIssues.getFirst().message()) // .isEqualTo("@Test method '%s' must not be private. It will not be executed.", method.toGenericString()); assertThat(discoveryIssues.get(1).message()) // .isEqualTo("@Test method '%s' must not be static. It will not be executed.", method.toGenericString()); assertThat(discoveryIssues.getLast().message()) // .isEqualTo("@Test method '%s' must not return a value. It will not be executed.", method.toGenericString()); } private static Method method(String name, Class... parameterTypes) { return ReflectionSupport.findMethod(ClassWithTestMethods.class, name, parameterTypes).orElseThrow(); } private Method abstractMethod(String name) { return ReflectionSupport.findMethod(AbstractClassWithAbstractTestMethod.class, name).orElseThrow(); } @SuppressWarnings({ "JUnitMalformedDeclaration", "unused" }) private static abstract class AbstractClassWithAbstractTestMethod { @Test abstract void bogusAbstractTestMethod(); @Test abstract int bogusAbstractNonVoidTestMethod(); } @SuppressWarnings({ "JUnitMalformedDeclaration", "unused" }) private static class ClassWithTestMethods { @Test static void bogusStaticTestMethod() { } @Test private void bogusPrivateTestMethod() { } @Test private static int bogusStaticPrivateNonVoidTestMethod() { return 42; } @Test String bogusTestMethodReturningObject() { return ""; } @Test Void bogusTestMethodReturningVoidReference() { return null; } @Test int bogusTestMethodReturningPrimitive() { return 0; } @Test public void publicTestMethod() { } @Test public void publicTestMethodWithArgument(TestInfo info) { } @Test protected void protectedTestMethod() { } @Test void packageVisibleTestMethod() { } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestTemplateMethodTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.discovery.predicates; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestTemplate; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.support.descriptor.MethodSource; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter; /** * Unit tests for {@link IsTestTemplateMethod}. * * @since 5.0 */ class IsTestTemplateMethodTests { final List discoveryIssues = new ArrayList<>(); final IsTestTemplateMethod isTestTemplateMethod = new IsTestTemplateMethod( DiscoveryIssueReporter.collecting(discoveryIssues)); @Test void testTemplateMethodReturningVoid() { assertThat(isTestTemplateMethod).accepts(method("templateReturningVoid")); } @Test void bogusTestTemplateMethodReturningObject() { var method = method("bogusTemplateReturningObject"); assertThat(isTestTemplateMethod).rejects(method); var issue = getOnlyElement(discoveryIssues); assertThat(issue.severity()).isEqualTo(DiscoveryIssue.Severity.WARNING); assertThat(issue.message()).isEqualTo( "@TestTemplate method '%s' must not return a value. It will not be executed.", method.toGenericString()); assertThat(issue.source()).contains(MethodSource.from(method)); } private static Method method(String name) { return ReflectionSupport.findMethod(ClassWithTestTemplateMethods.class, name).orElseThrow(); } @SuppressWarnings("unused") private static class ClassWithTestTemplateMethods { @TestTemplate void templateReturningVoid() { } @TestTemplate String bogusTemplateReturningObject() { return ""; } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/TestClassPredicatesTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.discovery.predicates; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.TestTemplate; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter; public class TestClassPredicatesTests { private final List discoveryIssues = new ArrayList<>(); private final TestClassPredicates predicates = new TestClassPredicates( DiscoveryIssueReporter.collecting(discoveryIssues)); @Nested class StandaloneTestClasses { @Test void classWithTestMethodEvaluatesToTrue() { assertTrue(predicates.looksLikeIntendedTestClass(ClassWithTestMethod.class)); assertTrue(predicates.isValidStandaloneTestClass(ClassWithTestMethod.class)); } @Test void classWithTestFactoryEvaluatesToTrue() { assertTrue(predicates.looksLikeIntendedTestClass(ClassWithTestFactory.class)); assertTrue(predicates.isValidStandaloneTestClass(ClassWithTestFactory.class)); } @Test void classWithTestTemplateEvaluatesToTrue() { assertTrue(predicates.looksLikeIntendedTestClass(ClassWithTestTemplate.class)); assertTrue(predicates.isValidStandaloneTestClass(ClassWithTestTemplate.class)); } @Test void classWithNestedTestClassEvaluatesToTrue() { assertTrue(predicates.looksLikeIntendedTestClass(ClassWithNestedTestClass.class)); assertTrue(predicates.isValidStandaloneTestClass(ClassWithNestedTestClass.class)); } @Test void staticTestClassEvaluatesToTrue() { assertTrue(predicates.looksLikeIntendedTestClass(TestCases.StaticTestCase.class)); assertTrue(predicates.isValidStandaloneTestClass(TestCases.StaticTestCase.class)); } // ------------------------------------------------------------------------- @Test void abstractClassEvaluatesToFalse() { assertTrue(predicates.looksLikeIntendedTestClass(AbstractClass.class)); assertFalse(predicates.isValidStandaloneTestClass(AbstractClass.class)); assertThat(discoveryIssues).isEmpty(); } @Test void localClassEvaluatesToFalse() { @SuppressWarnings({ "JUnitMalformedDeclaration", "NewClassNamingConvention" }) class LocalClass { @SuppressWarnings("unused") @Test void test() { } } var candidate = LocalClass.class; assertTrue(predicates.looksLikeIntendedTestClass(candidate)); assertFalse(predicates.isValidStandaloneTestClass(candidate)); var issue = DiscoveryIssue.builder(Severity.WARNING, "Test class '%s' must not be a local class. It will not be executed.".formatted(candidate.getName())) // .source(ClassSource.from(candidate)) // .build(); assertThat(discoveryIssues).containsExactly(issue); } @Test void anonymousClassEvaluatesToFalse() { Object object = new Object() { @SuppressWarnings("unused") @Test void test() { } }; Class candidate = object.getClass(); assertTrue(predicates.looksLikeIntendedTestClass(candidate)); assertFalse(predicates.isValidStandaloneTestClass(candidate)); var issue = DiscoveryIssue.builder(Severity.WARNING, "Test class '%s' must not be anonymous. It will not be executed.".formatted(candidate.getName())) // .source(ClassSource.from(candidate)) // .build(); assertThat(discoveryIssues).containsExactly(issue); } @Test void privateClassWithTestMethodEvaluatesToFalse() { var candidate = TestCases.PrivateClassWithTestMethod.class; assertTrue(predicates.looksLikeIntendedTestClass(candidate)); assertFalse(predicates.isValidStandaloneTestClass(candidate)); var notPrivateIssue = DiscoveryIssue.builder(Severity.WARNING, "Test class '%s' must not be private. It will not be executed.".formatted(candidate.getName())) // .source(ClassSource.from(candidate)) // .build(); var notInnerClassIssue = DiscoveryIssue.builder(Severity.WARNING, "Test class '%s' must not be an inner class unless annotated with @Nested. It will not be executed.".formatted( candidate.getName())) // .source(ClassSource.from(candidate)) // .build(); assertThat(discoveryIssues).containsExactlyInAnyOrder(notPrivateIssue, notInnerClassIssue); } @Test void privateClassWithTestFactoryEvaluatesToFalse() { var candidate = TestCases.PrivateClassWithTestFactory.class; assertTrue(predicates.looksLikeIntendedTestClass(candidate)); assertFalse(predicates.isValidStandaloneTestClass(candidate)); var notPrivateIssue = DiscoveryIssue.builder(Severity.WARNING, "Test class '%s' must not be private. It will not be executed.".formatted(candidate.getName())) // .source(ClassSource.from(candidate)) // .build(); var notInnerClassIssue = DiscoveryIssue.builder(Severity.WARNING, "Test class '%s' must not be an inner class unless annotated with @Nested. It will not be executed.".formatted( candidate.getName())) // .source(ClassSource.from(candidate)) // .build(); assertThat(discoveryIssues).containsExactlyInAnyOrder(notPrivateIssue, notInnerClassIssue); } @Test void privateClassWithTestTemplateEvaluatesToFalse() { var candidate = TestCases.PrivateClassWithTestTemplate.class; assertTrue(predicates.looksLikeIntendedTestClass(candidate)); assertFalse(predicates.isValidStandaloneTestClass(candidate)); var notPrivateIssue = DiscoveryIssue.builder(Severity.WARNING, "Test class '%s' must not be private. It will not be executed.".formatted(candidate.getName())) // .source(ClassSource.from(candidate)) // .build(); var notInnerClassIssue = DiscoveryIssue.builder(Severity.WARNING, "Test class '%s' must not be an inner class unless annotated with @Nested. It will not be executed.".formatted( candidate.getName())) // .source(ClassSource.from(candidate)) // .build(); assertThat(discoveryIssues).containsExactlyInAnyOrder(notPrivateIssue, notInnerClassIssue); } @Test void privateClassWithNestedTestCasesEvaluatesToFalse() { var candidate = TestCases.PrivateClassWithNestedTestClass.class; assertTrue(predicates.looksLikeIntendedTestClass(candidate)); assertFalse(predicates.isValidStandaloneTestClass(candidate)); var notPrivateIssue = DiscoveryIssue.builder(Severity.WARNING, "Test class '%s' must not be private. It will not be executed.".formatted(candidate.getName())) // .source(ClassSource.from(candidate)) // .build(); var notInnerClassIssue = DiscoveryIssue.builder(Severity.WARNING, "Test class '%s' must not be an inner class unless annotated with @Nested. It will not be executed.".formatted( candidate.getName())) // .source(ClassSource.from(candidate)) // .build(); assertThat(discoveryIssues).containsExactlyInAnyOrder(notPrivateIssue, notInnerClassIssue); } @Test void privateStaticTestClassEvaluatesToFalse() { var candidate = TestCases.PrivateStaticTestCase.class; assertTrue(predicates.looksLikeIntendedTestClass(candidate)); assertFalse(predicates.isValidStandaloneTestClass(candidate)); var notPrivateIssue = DiscoveryIssue.builder(Severity.WARNING, "Test class '%s' must not be private. It will not be executed.".formatted(candidate.getName())) // .source(ClassSource.from(candidate)) // .build(); assertThat(discoveryIssues).containsExactly(notPrivateIssue); } /* * see https://github.com/junit-team/junit-framework/issues/2249 */ @Test void recursiveHierarchies() { assertTrue(predicates.looksLikeIntendedTestClass(TestCases.OuterClass.class)); assertTrue(predicates.isValidStandaloneTestClass(TestCases.OuterClass.class)); assertThat(discoveryIssues).isEmpty(); var candidate = TestCases.OuterClass.RecursiveInnerClass.class; assertTrue(predicates.looksLikeIntendedTestClass(candidate)); assertFalse(predicates.isValidStandaloneTestClass(candidate)); var notInnerClassIssue = DiscoveryIssue.builder(Severity.WARNING, "Test class '%s' must not be an inner class unless annotated with @Nested. It will not be executed.".formatted( candidate.getName())) // .source(ClassSource.from(candidate)) // .build(); assertThat(discoveryIssues).containsExactly(notInnerClassIssue); } } @Nested class NestedTestClasses { @Test void innerClassEvaluatesToTrue() { var candidate = TestCases.NestedClassesTestCase.InnerClass.class; assertThat(predicates.isAnnotatedWithNested).accepts(candidate); assertTrue(predicates.isValidNestedTestClass(candidate)); assertThat(predicates.isAnnotatedWithNestedAndValid).accepts(candidate); } @Test void staticNestedClassEvaluatesToFalse() { var candidate = TestCases.NestedClassesTestCase.StaticNestedClass.class; assertThat(predicates.isAnnotatedWithNested).accepts(candidate); assertFalse(predicates.isValidNestedTestClass(candidate)); assertThat(predicates.isAnnotatedWithNestedAndValid).rejects(candidate); var issue = DiscoveryIssue.builder(Severity.WARNING, "@Nested class '%s' must not be static. ".formatted(candidate.getName()) + "It will only be executed if discovered as a standalone test class. " + "You should remove the annotation or make it non-static to resolve this warning.") // .source(ClassSource.from(candidate)) // .build(); assertThat(discoveryIssues.stream().distinct()).containsExactly(issue); } @Test void topLevelClassEvaluatesToFalse() { var candidate = InvalidTopLevelNestedTestClass.class; assertThat(predicates.isAnnotatedWithNested).accepts(candidate); assertFalse(predicates.isValidNestedTestClass(candidate)); assertThat(predicates.isAnnotatedWithNestedAndValid).rejects(candidate); var issue = DiscoveryIssue.builder(Severity.WARNING, ("Top-level class '%s' must not be annotated with @Nested. ".formatted(candidate.getName()) + "It will be executed anyway for backward compatibility. " + "You should remove the @Nested annotation to resolve this warning.")) // .source(ClassSource.from(candidate)) // .build(); assertThat(discoveryIssues.stream().distinct()).containsExactly(issue); } @Test void privateNestedClassEvaluatesToFalse() { var candidate = TestCases.NestedClassesTestCase.PrivateInnerClass.class; assertThat(predicates.isAnnotatedWithNested).accepts(candidate); assertFalse(predicates.isValidNestedTestClass(candidate)); assertThat(predicates.isAnnotatedWithNestedAndValid).rejects(candidate); var issue = DiscoveryIssue.builder(Severity.WARNING, "@Nested class '%s' must not be private. It will not be executed.".formatted(candidate.getName())) // .source(ClassSource.from(candidate)) // .build(); assertThat(discoveryIssues.stream().distinct()).containsExactly(issue); } @Test void abstractInnerClassEvaluatesToFalse() { var candidate = TestCases.NestedClassesTestCase.AbstractInnerClass.class; assertThat(predicates.isAnnotatedWithNested).accepts(candidate); assertFalse(predicates.isValidNestedTestClass(candidate)); assertThat(predicates.isAnnotatedWithNestedAndValid).rejects(candidate); assertThat(discoveryIssues).isEmpty(); } @Test void localClassEvaluatesToFalse() { @Nested class LocalClass { } var candidate = LocalClass.class; assertThat(predicates.isAnnotatedWithNested).accepts(candidate); assertFalse(predicates.isValidNestedTestClass(candidate)); assertThat(predicates.isAnnotatedWithNestedAndValid).rejects(candidate); var issue = DiscoveryIssue.builder(Severity.WARNING, "@Nested class '%s' must not be static. ".formatted(candidate.getName()) + "It will only be executed if discovered as a standalone test class. " + "You should remove the annotation or make it non-static to resolve this warning.") // .source(ClassSource.from(candidate)) // .build(); assertThat(discoveryIssues.stream().distinct()).containsExactly(issue); } } // ------------------------------------------------------------------------- static class TestCases { @SuppressWarnings({ "JUnitMalformedDeclaration", "InnerClassMayBeStatic" }) private class PrivateClassWithTestMethod { @Test void test() { } } @SuppressWarnings("InnerClassMayBeStatic") private class PrivateClassWithTestFactory { @TestFactory Collection factory() { return new ArrayList<>(); } } @SuppressWarnings("InnerClassMayBeStatic") private class PrivateClassWithTestTemplate { @TestTemplate void template(int a) { } } @SuppressWarnings("InnerClassMayBeStatic") private class PrivateClassWithNestedTestClass { @Nested class InnerClass { @Test void first() { } @Test void second() { } } } // ------------------------------------------------------------------------- static class StaticTestCase { @Test void test() { } } private static class PrivateStaticTestCase { @Test void test() { } } @SuppressWarnings("NewClassNamingConvention") static class OuterClass { @Nested class InnerClass { @Test void test() { } } // Intentionally commented out so that RecursiveInnerClass is NOT a candidate test class // @Nested @SuppressWarnings("InnerClassMayBeStatic") class RecursiveInnerClass extends OuterClass { } } private static class NestedClassesTestCase { @Nested class InnerClass { } @SuppressWarnings("JUnitMalformedDeclaration") @Nested static class StaticNestedClass { } @SuppressWarnings("JUnitMalformedDeclaration") @Nested private class PrivateInnerClass { } @Nested private abstract class AbstractInnerClass { } } } } // ----------------------------------------------------------------------------- abstract class AbstractClass { @SuppressWarnings("unused") @Test void test() { } } @SuppressWarnings("NewClassNamingConvention") class ClassWithTestMethod { @Test void test() { } } @SuppressWarnings("NewClassNamingConvention") class ClassWithTestFactory { @TestFactory Collection factory() { return new ArrayList<>(); } } @SuppressWarnings("NewClassNamingConvention") class ClassWithTestTemplate { @TestTemplate void template(int a) { } } @SuppressWarnings("NewClassNamingConvention") class ClassWithNestedTestClass { @Nested class InnerClass { @Test void first() { } @Test void second() { } } } @SuppressWarnings("NewClassNamingConvention") @Nested class InvalidTopLevelNestedTestClass { @Test void test() { } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/AbstractExecutableInvokerTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.ParameterResolutionUtilsTests.ConfigurableParameterResolver; import org.junit.jupiter.engine.execution.ParameterResolutionUtilsTests.ConstructorInjectionTestCase; import org.junit.jupiter.engine.execution.ParameterResolutionUtilsTests.MethodSource; import org.junit.jupiter.engine.execution.ParameterResolutionUtilsTests.NumberParameterResolver; import org.junit.jupiter.engine.execution.ParameterResolutionUtilsTests.StringParameterResolver; import org.junit.jupiter.engine.extension.MutableExtensionRegistry; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.ReflectionUtils; /** * @since 5.9 */ abstract class AbstractExecutableInvokerTests { private static final String ENIGMA = "enigma"; protected final MethodSource instance = mock(); protected @Nullable Method method; protected final ExtensionContext extensionContext = mock(); private final JupiterConfiguration configuration = mock(); protected final MutableExtensionRegistry extensionRegistry = MutableExtensionRegistry.createRegistryWithDefaultExtensions( configuration); @Test void constructorInjection() { register(new StringParameterResolver(), new NumberParameterResolver()); Class outerClass = ConstructorInjectionTestCase.class; Constructor constructor = ReflectionUtils.getDeclaredConstructor(outerClass); ConstructorInjectionTestCase outer = invokeConstructor(constructor, null); assertNotNull(outer); assertEquals(ENIGMA, outer.str); Class innerClass = ConstructorInjectionTestCase.NestedTestCase.class; Constructor innerConstructor = ReflectionUtils.getDeclaredConstructor( innerClass); ConstructorInjectionTestCase.NestedTestCase inner = invokeConstructor(innerConstructor, outer); assertNotNull(inner); assertEquals(42, inner.num); } @Test void resolveArgumentsViaParameterResolver() { testMethodWithASingleStringParameter(); thereIsAParameterResolverThatResolvesTheParameterTo("argument"); invokeMethod(); verify(instance).singleStringParameter("argument"); } private void thereIsAParameterResolverThatResolvesTheParameterTo(Object argument) { register(ConfigurableParameterResolver.supportsAndResolvesTo(parameterContext -> argument)); } private void testMethodWithASingleStringParameter() { this.method = ReflectionSupport.findMethod(this.instance.getClass(), "singleStringParameter", String.class).orElseThrow(); } private void register(ParameterResolver... resolvers) { for (ParameterResolver resolver : resolvers) { extensionRegistry.registerExtension(resolver, this); } } abstract void invokeMethod(); abstract T invokeConstructor(Constructor constructor, @Nullable Object outerInstance); } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/DefaultExecutableInvokerTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution; import static java.util.Objects.requireNonNull; import java.lang.reflect.Constructor; import org.jspecify.annotations.Nullable; /** * Unit tests for {@link DefaultExecutableInvoker}. * * @since 5.9 */ class DefaultExecutableInvokerTests extends AbstractExecutableInvokerTests { @Override void invokeMethod() { newInvoker().invoke(requireNonNull(this.method), this.instance); } @Override T invokeConstructor(Constructor constructor, @Nullable Object outerInstance) { return newInvoker().invoke(constructor, outerInstance); } private DefaultExecutableInvoker newInvoker() { return new DefaultExecutableInvoker(this.extensionContext, this.extensionRegistry); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/DefaultTestInstancesTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution; import static org.assertj.core.api.Assertions.assertThat; import org.junit.jupiter.api.Test; class DefaultTestInstancesTests { @Test void topLevelClass() { DefaultTestInstances instances = DefaultTestInstances.of(this); assertThat(instances.getInnermostInstance()).isSameAs(this); assertThat(instances.getAllInstances()).containsExactly(this); assertThat(instances.getEnclosingInstances()).isEmpty(); assertThat(instances.findInstance(Object.class)).contains(this); assertThat(instances.findInstance(String.class)).isEmpty(); } @Test void nestedLevelClass() { DefaultTestInstancesTests outermost = this; Nested innermost = new Nested(); DefaultTestInstances instances = DefaultTestInstances.of(DefaultTestInstances.of(outermost), innermost); assertThat(instances.getInnermostInstance()).isSameAs(innermost); assertThat(instances.getAllInstances()).containsExactly(outermost, innermost); assertThat(instances.getEnclosingInstances()).containsExactly(outermost); assertThat(instances.findInstance(Object.class)).contains(innermost); assertThat(instances.findInstance(Nested.class)).contains(innermost); assertThat(instances.findInstance(DefaultTestInstancesTests.class)).contains(outermost); assertThat(instances.findInstance(String.class)).isEmpty(); } class Nested { } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/DynamicTestIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import java.util.stream.Stream; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.TestFactory; /** * Integration tests for dynamic tests. * * @since 5.5 */ class DynamicTestIntegrationTests { private static final int TEN_MB = 10 * 1024 * 1024; /** * Without the fix in {@code DynamicTestTestDescriptor}, setting the * {@code -mx200m} VM argument will cause an {@link OutOfMemoryError} before * the 200 limit is reached. * * @see Issue 1865 */ @TestFactory Stream generateDynamicTestsThatReferenceLargeAmountsOfMemory() { return Stream.generate(() -> new byte[TEN_MB])// // The lambda Executable in the following line *must* reference // the `bytes` array in order to hold onto the allocated memory. .map(bytes -> dynamicTest("test", () -> assertNotNull(bytes)))// .limit(200); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreConcurrencyTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution; import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.IntStream; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext.Store; import org.junit.platform.engine.support.store.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; /** * Concurrency tests for {@link NamespaceAwareStore} and {@link NamespacedHierarchicalStore}. * * @since 5.0 */ class ExtensionContextStoreConcurrencyTests { private final AtomicInteger count = new AtomicInteger(); @Test void concurrentAccessToDefaultStoreWithoutParentStore() { // Run the actual test 100 times "for good measure". IntStream.range(1, 100).forEach(i -> { Store store = reset(); // Simulate 100 extensions interacting concurrently with the Store. IntStream.range(1, 100).parallel().forEach(j -> store.computeIfAbsent("key", this::newValue)); assertEquals(1, count.get(), () -> "number of times newValue() was invoked in run #" + i); }); } private String newValue(String key) { count.incrementAndGet(); return "value"; } private Store reset() { count.set(0); return new NamespaceAwareStore(new NamespacedHierarchicalStore<>(null), Namespace.GLOBAL); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext.Store; import org.junit.jupiter.api.extension.ExtensionContextException; import org.junit.platform.engine.support.store.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.engine.support.store.NamespacedHierarchicalStoreException; /** * Unit tests for {@link NamespaceAwareStore} and {@link NamespacedHierarchicalStore}. * * @since 5.5 * @see ExtensionContextStoreConcurrencyTests * @see ExtensionValuesStoreTests */ class ExtensionContextStoreTests { private static final String KEY = "key"; private static final String VALUE = "value"; private final NamespacedHierarchicalStore parentStore = new NamespacedHierarchicalStore<>(null); private final NamespacedHierarchicalStore localStore = new NamespacedHierarchicalStore<>(parentStore); private final Store store = new NamespaceAwareStore(localStore, Namespace.GLOBAL); @Test void getOrDefaultWithNoValuePresent() { assertThat(store.get(KEY)).isNull(); assertThat(store.getOrDefault(KEY, boolean.class, true)).isTrue(); assertThat(store.getOrDefault(KEY, String.class, VALUE)).isEqualTo(VALUE); } @Test void getOrDefaultRequestingIncompatibleType() { localStore.put(Namespace.GLOBAL, KEY, VALUE); assertThat(store.get(KEY)).isEqualTo(VALUE); Exception exception = assertThrows(ExtensionContextException.class, () -> store.getOrDefault(KEY, boolean.class, true)); assertThat(exception) // .hasMessageContaining("is not of required type") // .hasCauseInstanceOf(NamespacedHierarchicalStoreException.class) // .hasStackTraceContaining(NamespacedHierarchicalStore.class.getName()); } @Test void getOrDefaultWithValueInLocalStore() { localStore.put(Namespace.GLOBAL, KEY, VALUE); assertThat(store.get(KEY)).isEqualTo(VALUE); assertThat(store.getOrDefault(KEY, String.class, VALUE)).isEqualTo(VALUE); } @Test void getOrDefaultWithValueInParentStore() { parentStore.put(Namespace.GLOBAL, KEY, VALUE); assertThat(store.get(KEY)).isEqualTo(VALUE); assertThat(store.getOrDefault(KEY, String.class, VALUE)).isEqualTo(VALUE); } @SuppressWarnings("deprecation") @Test void getOrComputeIfAbsentWithFailingCreator() { var invocations = new AtomicInteger(); var e1 = assertThrows(RuntimeException.class, () -> store.getOrComputeIfAbsent(KEY, __ -> { invocations.incrementAndGet(); throw new RuntimeException(); })); var e2 = assertThrows(RuntimeException.class, () -> store.get(KEY)); assertSame(e1, e2); assertDoesNotThrow(localStore::close); assertThat(invocations).hasValue(1); } @Test void computeIfAbsentWithFailingCreator() { assertThrows(RuntimeException.class, () -> store.computeIfAbsent(KEY, __ -> { throw new RuntimeException(); })); assertNull(store.get(KEY)); assertDoesNotThrow(localStore::close); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvokerTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution; import static java.util.Objects.requireNonNull; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.util.Optional; import org.jspecify.annotations.Nullable; import org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.ReflectiveInterceptorCall; /** * Unit tests for {@link InterceptingExecutableInvoker}. * * @since 5.0 */ class InterceptingExecutableInvokerTests extends AbstractExecutableInvokerTests { @Override void invokeMethod() { newInvoker().invoke(requireNonNull(this.method), this.instance, this.extensionContext, this.extensionRegistry, passthroughInterceptor()); } @Override T invokeConstructor(Constructor constructor, @Nullable Object outerInstance) { return newInvoker().invoke(constructor, Optional.ofNullable(outerInstance), __ -> extensionContext, extensionRegistry, passthroughInterceptor()); } private InterceptingExecutableInvoker newInvoker() { return new InterceptingExecutableInvoker(); } private static ReflectiveInterceptorCall passthroughInterceptor() { return (interceptor, invocation, invocationContext, extensionContext) -> invocation.proceed(); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContextTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.withSettings; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.extension.MutableExtensionRegistry; import org.junit.platform.engine.EngineExecutionListener; /** * Unit tests for {@link JupiterEngineExecutionContext}. * * @since 5.0 */ class JupiterEngineExecutionContextTests { private final JupiterConfiguration configuration = mock(); private final EngineExecutionListener engineExecutionListener = mock(); private final LauncherStoreFacade launcherStoreFacade = mock(); private final JupiterEngineExecutionContext originalContext = new JupiterEngineExecutionContext( engineExecutionListener, configuration, launcherStoreFacade); @Test void executionListenerIsHandedOnWhenContextIsExtended() { assertSame(engineExecutionListener, originalContext.getExecutionListener()); JupiterEngineExecutionContext newContext = originalContext.extend().build(); assertSame(engineExecutionListener, newContext.getExecutionListener()); } @Test void extendWithAllAttributes() { ExtensionContext extensionContext = mock(); MutableExtensionRegistry extensionRegistry = MutableExtensionRegistry.createRegistryWithDefaultExtensions( configuration); TestInstancesProvider testInstancesProvider = mock(); JupiterEngineExecutionContext newContext = originalContext.extend() // .withExtensionContext(extensionContext) // .withExtensionRegistry(extensionRegistry) // .withTestInstancesProvider(testInstancesProvider) // .build(); assertSame(extensionContext, newContext.getExtensionContext()); assertSame(extensionRegistry, newContext.getExtensionRegistry()); assertSame(testInstancesProvider, newContext.getTestInstancesProvider()); } @Test void canOverrideAttributeWhenContextIsExtended() { ExtensionContext extensionContext = mock(); MutableExtensionRegistry extensionRegistry = MutableExtensionRegistry.createRegistryWithDefaultExtensions( configuration); TestInstancesProvider testInstancesProvider = mock(); ExtensionContext newExtensionContext = mock(); JupiterEngineExecutionContext newContext = originalContext.extend() // .withExtensionContext(extensionContext) // .withExtensionRegistry(extensionRegistry) // .withTestInstancesProvider(testInstancesProvider) // .build() // .extend() // .withExtensionContext(newExtensionContext) // .build(); assertSame(newExtensionContext, newContext.getExtensionContext()); assertSame(extensionRegistry, newContext.getExtensionRegistry()); assertSame(testInstancesProvider, newContext.getTestInstancesProvider()); } @Test void closeAttemptExceptionWillBeThrownDownTheCallStack() throws Exception { ExtensionContext failingExtensionContext = mock(ExtensionContext.class, withSettings().extraInterfaces(AutoCloseable.class)); Exception expectedCause = new Exception("test message"); doThrow(expectedCause).when(((AutoCloseable) failingExtensionContext)).close(); JupiterEngineExecutionContext newContext = originalContext.extend() // .withExtensionContext(failingExtensionContext) // .build(); Exception actualException = assertThrows(Exception.class, newContext::close); assertThat(actualException) // .hasMessage("Failed to close extension context") // .cause().isSameAs(expectedCause); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/ParameterResolutionUtilsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.math.BigDecimal; import java.util.Optional; import java.util.function.Function; import java.util.function.Predicate; import java.util.regex.Pattern; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.extension.MutableExtensionRegistry; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.ReflectionUtils; /** * Unit tests for {@link ParameterResolutionUtils}. * * @since 5.9 */ class ParameterResolutionUtilsTests { private static final String ENIGMA = "enigma"; private final MethodSource instance = mock(); private @Nullable Method method; private final ExtensionContext extensionContext = mock(); private final JupiterConfiguration configuration = mock(); private final MutableExtensionRegistry extensionRegistry = MutableExtensionRegistry.createRegistryWithDefaultExtensions( configuration); @Test void resolveConstructorArguments() { register(new StringParameterResolver()); Class topLevelClass = ConstructorInjectionTestCase.class; @Nullable Object[] arguments = resolveConstructorParameters(topLevelClass, null); assertThat(arguments).containsExactly(ENIGMA); } @Test void resolveNestedConstructorArguments() { register(new NumberParameterResolver()); Class outerClass = ConstructorInjectionTestCase.class; ConstructorInjectionTestCase outer = ReflectionSupport.newInstance(outerClass, "str"); Class innerClass = ConstructorInjectionTestCase.NestedTestCase.class; @Nullable Object[] arguments = resolveConstructorParameters(innerClass, outer); assertThat(arguments).containsExactly(outer, 42); } @Test void resolveConstructorArgumentsWithMissingResolver() { Constructor constructor = ReflectionUtils.getDeclaredConstructor( ConstructorInjectionTestCase.class); Exception exception = assertThrows(ParameterResolutionException.class, () -> ParameterResolutionUtils.resolveParameters(constructor, Optional.empty(), Optional.empty(), extensionContext, extensionRegistry)); assertThat(exception.getMessage())// .contains("No ParameterResolver registered for parameter [java.lang.String")// .contains("in constructor")// .contains(ConstructorInjectionTestCase.class.getName()); } @Test void resolvingArgumentsForMethodsWithoutParameterDoesNotDependOnParameterResolvers() { testMethodWithNoParameters(); throwDuringParameterResolution(new RuntimeException("boom!")); @Nullable Object[] arguments = resolveMethodParameters(); assertThat(arguments).isEmpty(); } @Test void resolveArgumentsViaParameterResolver() { testMethodWithASingleStringParameter(); thereIsAParameterResolverThatResolvesTheParameterTo("argument"); @Nullable Object[] arguments = resolveMethodParameters(); assertThat(arguments).containsExactly("argument"); } @Test void resolveMultipleArguments() { testMethodWith("multipleParameters", String.class, Integer.class, Double.class); register(ConfigurableParameterResolver.supportsAndResolvesTo(parameterContext -> { return switch (parameterContext.getIndex()) { case 0 -> "0"; case 1 -> 1; default -> 2.0; }; })); @Nullable Object[] arguments = resolveMethodParameters(); assertThat(arguments).containsExactly("0", 1, 2.0); } @Test void onlyConsiderParameterResolversThatSupportAParticularParameter() { testMethodWithASingleStringParameter(); thereIsAParameterResolverThatDoesNotSupportThisParameter(); thereIsAParameterResolverThatResolvesTheParameterTo("something"); @Nullable Object[] arguments = resolveMethodParameters(); assertThat(arguments).containsExactly("something"); } @SuppressWarnings("DataFlowIssue") @Test void passContextInformationToParameterResolverMethods() { anyTestMethodWithAtLeastOneParameter(); ArgumentRecordingParameterResolver extension = new ArgumentRecordingParameterResolver(); register(extension); resolveMethodParameters(); assertSame(extensionContext, extension.supportsArguments.extensionContext); assertEquals(0, extension.supportsArguments.parameterContext.getIndex()); assertSame(instance, extension.supportsArguments.parameterContext.getTarget().orElseThrow()); assertSame(extensionContext, extension.resolveArguments.extensionContext); assertEquals(0, extension.resolveArguments.parameterContext.getIndex()); assertSame(instance, extension.resolveArguments.parameterContext.getTarget().orElseThrow()); assertThat(extension.resolveArguments.parameterContext.toString())// .contains("parameter", String.class.getTypeName(), "index", "0", "target", "Mock"); } @Test void resolvingArgumentsForMethodsWithPrimitiveTypesIsSupported() { testMethodWithASinglePrimitiveIntParameter(); thereIsAParameterResolverThatResolvesTheParameterTo(42); @Nullable Object[] arguments = resolveMethodParameters(); assertThat(arguments).containsExactly(42); } @Test void nullIsAViableArgumentIfAReferenceTypeParameterIsExpected() { testMethodWithASingleStringParameter(); thereIsAParameterResolverThatResolvesTheParameterTo(null); @Nullable Object[] arguments = resolveMethodParameters(); assertThat(arguments).hasSize(1); assertNull(arguments[0]); } @Test void reportThatNullIsNotAViableArgumentIfAPrimitiveTypeIsExpected() { testMethodWithASinglePrimitiveIntParameter(); thereIsAParameterResolverThatResolvesTheParameterTo(null); ParameterResolutionException caught = assertThrows(ParameterResolutionException.class, this::resolveMethodParameters); // @formatter:off assertThat(caught.getMessage()) .contains("in method") .contains("resolved a null value for parameter [int") .contains("but a primitive of type [int] is required."); // @formatter:on } @Test void reportIfThereIsNoParameterResolverThatSupportsTheParameter() { testMethodWithASingleStringParameter(); ParameterResolutionException caught = assertThrows(ParameterResolutionException.class, this::resolveMethodParameters); assertThat(caught.getMessage()).contains("parameter [java.lang.String").contains("in method"); } @Test void reportIfThereAreMultipleParameterResolversThatSupportTheParameter() { testMethodWithASingleStringParameter(); thereIsAParameterResolverThatResolvesTheParameterTo("one"); thereIsAParameterResolverThatResolvesTheParameterTo("two"); ParameterResolutionException caught = assertThrows(ParameterResolutionException.class, this::resolveMethodParameters); String className = Pattern.quote(ConfigurableParameterResolver.class.getName()); // @formatter:off assertThat(caught.getMessage()) .matches("Discovered multiple competing ParameterResolvers for parameter \\[java.lang.String .+?\\] " + "in method .+: " + className + "@.+, " + className + "@.+"); // @formatter:on } @Test void reportTypeMismatchBetweenParameterAndResolvedParameter() { testMethodWithASingleStringParameter(); thereIsAParameterResolverThatResolvesTheParameterTo(BigDecimal.ONE); ParameterResolutionException caught = assertThrows(ParameterResolutionException.class, this::resolveMethodParameters); // @formatter:off assertThat(caught.getMessage()) .contains("resolved a value of type [java.math.BigDecimal] for parameter [java.lang.String") .contains("in method") .contains("but a value assignment compatible with [java.lang.String] is required."); // @formatter:on } @Test void reportTypeMismatchBetweenParameterAndResolvedParameterWithArrayTypes() { testMethodWithASingleStringArrayParameter(); thereIsAParameterResolverThatResolvesTheParameterTo(new int[][] {}); assertThatExceptionOfType(ParameterResolutionException.class)// .isThrownBy(this::resolveMethodParameters)// .withMessageContaining(// "resolved a value of type [int[][]] for parameter [java.lang.String[]", // "in method", // "but a value assignment compatible with [java.lang.String[]] is required." // ); } @Test void wrapAllExceptionsThrownDuringParameterResolutionIntoAParameterResolutionException() { anyTestMethodWithAtLeastOneParameter(); IllegalArgumentException cause = anyExceptionButParameterResolutionException(); throwDuringParameterResolution(cause); ParameterResolutionException caught = assertThrows(ParameterResolutionException.class, this::resolveMethodParameters); assertSame(cause, caught.getCause(), () -> "cause should be present"); assertThat(caught.getMessage())// .matches("^Failed to resolve parameter \\[java.lang.String .+?\\] in method \\[.+?\\]$"); } @Test void exceptionMessageContainsMessageFromExceptionThrownDuringParameterResolution() { anyTestMethodWithAtLeastOneParameter(); RuntimeException cause = new RuntimeException("boom!"); throwDuringParameterResolution(cause); ParameterResolutionException caught = assertThrows(ParameterResolutionException.class, this::resolveMethodParameters); assertSame(cause, caught.getCause(), () -> "cause should be present"); assertThat(caught.getMessage())// .matches("^Failed to resolve parameter \\[java.lang.String .+?\\] in method \\[.+?\\]: boom!$"); } @Test void doNotWrapThrownExceptionIfItIsAlreadyAParameterResolutionException() { anyTestMethodWithAtLeastOneParameter(); ParameterResolutionException cause = new ParameterResolutionException("custom message"); throwDuringParameterResolution(cause); ParameterResolutionException caught = assertThrows(ParameterResolutionException.class, this::resolveMethodParameters); assertSame(cause, caught); } private IllegalArgumentException anyExceptionButParameterResolutionException() { return new IllegalArgumentException(); } private void throwDuringParameterResolution(RuntimeException parameterResolutionException) { register(ConfigurableParameterResolver.onAnyCallThrow(parameterResolutionException)); } private void thereIsAParameterResolverThatResolvesTheParameterTo(@Nullable Object argument) { register(ConfigurableParameterResolver.supportsAndResolvesTo(parameterContext -> argument)); } private void thereIsAParameterResolverThatDoesNotSupportThisParameter() { register(ConfigurableParameterResolver.withoutSupport()); } private void anyTestMethodWithAtLeastOneParameter() { testMethodWithASingleStringParameter(); } private void testMethodWithNoParameters() { testMethodWith("noParameter"); } private void testMethodWithASingleStringParameter() { testMethodWith("singleStringParameter", String.class); } private void testMethodWithASingleStringArrayParameter() { testMethodWith("singleStringArrayParameter", String[].class); } private void testMethodWithASinglePrimitiveIntParameter() { testMethodWith("primitiveParameterInt", int.class); } private void testMethodWith(String methodName, Class... parameterTypes) { this.method = ReflectionSupport.findMethod(this.instance.getClass(), methodName, parameterTypes).get(); } private void register(ParameterResolver... resolvers) { for (ParameterResolver resolver : resolvers) { extensionRegistry.registerExtension(resolver, this); } } private @Nullable Object[] resolveConstructorParameters(Class clazz, @Nullable Object outerInstance) { Constructor constructor = ReflectionUtils.getDeclaredConstructor(clazz); return ParameterResolutionUtils.resolveParameters(constructor, Optional.empty(), Optional.ofNullable(outerInstance), extensionContext, extensionRegistry); } private @Nullable Object[] resolveMethodParameters() { return ParameterResolutionUtils.resolveParameters(requireNonNull(this.method), Optional.of(this.instance), this.extensionContext, this.extensionRegistry); } // ------------------------------------------------------------------------- static class ArgumentRecordingParameterResolver implements ParameterResolver { ArgumentRecordingParameterResolver.@Nullable Arguments supportsArguments; ArgumentRecordingParameterResolver.@Nullable Arguments resolveArguments; record Arguments(ParameterContext parameterContext, ExtensionContext extensionContext) { } @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { supportsArguments = new ArgumentRecordingParameterResolver.Arguments(parameterContext, extensionContext); return true; } @Override public @Nullable Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { resolveArguments = new ArgumentRecordingParameterResolver.Arguments(parameterContext, extensionContext); return null; } } static class ConfigurableParameterResolver implements ParameterResolver { static ParameterResolver onAnyCallThrow(RuntimeException runtimeException) { return new ConfigurableParameterResolver(parameterContext -> { throw runtimeException; }, parameterContext -> { throw runtimeException; }); } static ParameterResolver supportsAndResolvesTo(Function resolve) { return new ConfigurableParameterResolver(parameterContext -> true, resolve); } static ParameterResolver withoutSupport() { return new ConfigurableParameterResolver(parameterContext -> false, parameter -> { throw new UnsupportedOperationException(); }); } private final Predicate supports; private final Function resolve; private ConfigurableParameterResolver(Predicate supports, Function resolve) { this.supports = supports; this.resolve = resolve; } @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return supports.test(parameterContext); } @Override public @Nullable Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return resolve.apply(parameterContext); } } @SuppressWarnings("unused") interface MethodSource { void noParameter(); void singleStringParameter(String parameter); void singleStringArrayParameter(String[] parameter); void primitiveParameterInt(int parameter); void multipleParameters(String first, Integer second, Double third); } static class StringParameterResolver implements ParameterResolver { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return parameterContext.getParameter().getType() == String.class; } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return ENIGMA; } } static class NumberParameterResolver implements ParameterResolver { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return parameterContext.getParameter().getType() == Number.class; } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return 42; } } static class ConstructorInjectionTestCase { final String str; @SuppressWarnings("unused") ConstructorInjectionTestCase(String str) { this.str = str; } class NestedTestCase { final Number num; @SuppressWarnings("unused") NestedTestCase(Number num) { this.num = num; } } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/UniqueIdParsingForArrayParameterIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.jupiter.engine.execution.injection.sample.PrimitiveArrayParameterResolver; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.testkit.engine.EngineExecutionResults; import org.junit.platform.testkit.engine.Event; /** * Integration tests for {@link UniqueId#parse(String)} for methods * with array type parameters. * * @see #810 * @see org.junit.platform.engine.UniqueIdTests * * @since 5.0 */ class UniqueIdParsingForArrayParameterIntegrationTests extends AbstractJupiterTestEngineTests { @Test void executeTestsForPrimitiveArrayMethodInjectionCases() { EngineExecutionResults executionResults = executeTestsForClass(PrimitiveArrayMethodInjectionTestCase.class); assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); // @formatter:off UniqueId uniqueId = executionResults.allEvents() .map(Event::getTestDescriptor) .distinct() .skip(2) .map(TestDescriptor::getUniqueId) .findFirst() .orElseThrow(AssertionError::new); // @formatter:on assertThat(UniqueId.parse(uniqueId.toString())).isEqualTo(uniqueId); } @ExtendWith(PrimitiveArrayParameterResolver.class) static class PrimitiveArrayMethodInjectionTestCase { @Test void primitiveArray(int... ints) { assertArrayEquals(new int[] { 1, 2, 3 }, ints); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotation.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution.injection.sample; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @since 5.0 */ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface CustomAnnotation { } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotationParameterResolver.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution.injection.sample; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.platform.commons.support.ReflectionSupport; /** * @since 5.0 */ public class CustomAnnotationParameterResolver implements ParameterResolver { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { // We invoke parameterContext.isAnnotated() instead of parameterContext.getParameter().isAnnotationPresent() // in order to verify support for the convenience method in the ParameterContext API. return parameterContext.isAnnotated(CustomAnnotation.class); } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return ReflectionSupport.newInstance(parameterContext.getParameter().getType()); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomType.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution.injection.sample; import java.util.Date; /** * @since 5.0 */ public class CustomType { private final Date date = new Date(); @Override public String toString() { return "CustomType: " + this.date; } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomTypeParameterResolver.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution.injection.sample; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; /** * @since 5.0 */ public class CustomTypeParameterResolver implements ParameterResolver { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return parameterContext.getParameter().getType().equals(CustomType.class); } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return new CustomType(); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/DoubleParameterResolver.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution.injection.sample; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; /** * Example {@link ParameterResolver} that always resolves a {@link Double} * parameter to {@code 42.0}. * * @since 5.0 */ public class DoubleParameterResolver implements ParameterResolver { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return parameterContext.getParameter().getType() == Double.class; } @Override public Double resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return 42.0; } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/LongParameterResolver.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution.injection.sample; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; /** * Example {@link ParameterResolver} that always resolves a {@link Long} * parameter to {@code 42}. * *

This resolver also attempts to support generic parameter type * declarations if the generic type is defined at the class level in a * superclass or interface (extended or implemented by the test class) and * is assignable from {@link Long}. * * @since 5.0 */ public class LongParameterResolver implements ParameterResolver { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { // Exact match? if (parameterContext.getParameter().getType() == Long.class) { return true; } Type typeInMethod = parameterContext.getParameter().getParameterizedType(); // Type variables in parameterized class for (TypeVariable typeVariable : parameterContext.getDeclaringExecutable().getDeclaringClass().getTypeParameters()) { boolean namesMatch = typeInMethod.getTypeName().equals(typeVariable.getName()); boolean typesAreCompatible = typeVariable.getBounds().length == 1 && // typeVariable.getBounds()[0] instanceof Class clazz && // clazz.isAssignableFrom(Long.class); if (namesMatch && typesAreCompatible) { return true; } } return false; } @Override public Long resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return 42L; } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfListsTypeBasedParameterResolver.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution.injection.sample; import java.util.List; import java.util.Map; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.support.TypeBasedParameterResolver; /** * @since 5.6 */ public class MapOfListsTypeBasedParameterResolver extends TypeBasedParameterResolver>> { @Override public Map> resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return Map.of("ids", List.of(1, 42)); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfStringsParameterResolver.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution.injection.sample; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Map; import java.util.TreeMap; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; /** * Example {@link ParameterResolver} that resolves {@code Map} types. * * @since 5.0 */ public class MapOfStringsParameterResolver implements ParameterResolver { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { Type type = parameterContext.getParameter().getParameterizedType(); if (!(type instanceof ParameterizedType parameterizedType)) { return false; } Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); if (actualTypeArguments.length != 2) { return false; } return actualTypeArguments[0] == String.class && actualTypeArguments[1] == String.class; } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { Map map = new TreeMap<>(); map.put("key", "value"); return map; } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/NullIntegerParameterResolver.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution.injection.sample; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; /** * Example {@link ParameterResolver} that always resolves an * {@link Integer} or {@code int} parameter to a {@code null} value. * * @since 5.0 */ public class NullIntegerParameterResolver implements ParameterResolver { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return Integer.class == parameterContext.getParameter().getType() || int.class == parameterContext.getParameter().getType(); } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return null; } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/NumberParameterResolver.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution.injection.sample; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; /** * This is a non-realistic {@link ParameterResolver} that claims to * resolve any {@link Number}, when in fact it always resolves an {@link Integer}. * *

This may appear nonsensical; however, there are use cases for which a * resolver may think that it can support a particular parameter only to * discover later that the actual resolved value is not assignment compatible. * *

For example, consider the case with Spring: the {@code SpringExtension} can * theoretically resolve any type of {@code ApplicationContext}, but if the * required parameter type is {@code WebApplicationContext} and the user * neglects to annotate the test class with {@code @WebAppConfiguration} then * the {@code ApplicationContext} loaded by Spring's testing support will in * fact be an {@code ApplicationContext} but not a {@code WebApplicationContext}. * Since Spring does not determine this in advance, such a scenario would lead to * an {@link IllegalArgumentException} with the message "argument type mismatch" * when JUnit attempts to invoke the test method. * * @since 5.0 */ public class NumberParameterResolver implements ParameterResolver { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return Number.class.isAssignableFrom(parameterContext.getParameter().getType()); } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return 42; } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/PrimitiveArrayParameterResolver.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution.injection.sample; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; /** * Example {@link ParameterResolver} that resolves arrays of primitive integers. * * @since 5.0 */ public class PrimitiveArrayParameterResolver implements ParameterResolver { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return int[].class == parameterContext.getParameter().getType(); } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return new int[] { 1, 2, 3 }; } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/PrimitiveIntegerParameterResolver.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.execution.injection.sample; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; /** * Example {@link ParameterResolver} that resolves primitive integers. * * @since 5.0 */ public class PrimitiveIntegerParameterResolver implements ParameterResolver { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return int.class == parameterContext.getParameter().getType(); } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return 42; } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/AutoCloseTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import java.io.InputStream; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.logging.Level; import java.util.logging.LogRecord; import org.jspecify.annotations.NullUnmarked; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AutoClose; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.fixtures.TrackLogRecords; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.testkit.engine.EngineExecutionResults; import org.junit.platform.testkit.engine.Events; import org.junit.platform.testkit.engine.Execution; /** * Integration tests for {@link AutoClose @AutoClose} and the {@link AutoCloseExtension}. * * @since 5.11 */ class AutoCloseTests extends AbstractJupiterTestEngineTests { private static final List recorder = new ArrayList<>(); @BeforeEach @AfterEach void resetTracking() { InstancePerClassTestCase.closed = false; recorder.clear(); } @Test void blankCloseMethodName() { Class testClass = BlankCloseMethodNameTestCase.class; String msg = "@AutoClose on field %s.field must specify a method name.".formatted(testClass.getCanonicalName()); Events tests = executeTestsForClass(testClass).testEvents(); assertFailingWithMessage(tests, msg); } @Test void primitiveTypeCannotBeClosed() { Class testClass = PrimitiveFieldTestCase.class; String msg = "@AutoClose is not supported on primitive field %s.x.".formatted(testClass.getCanonicalName()); Events tests = executeTestsForClass(testClass).testEvents(); assertFailingWithMessage(tests, msg); } @Test void arrayCannotBeClosed() { Class testClass = ArrayFieldTestCase.class; String msg = "@AutoClose is not supported on array field %s.x.".formatted(testClass.getCanonicalName()); Events tests = executeTestsForClass(testClass).testEvents(); assertFailingWithMessage(tests, msg); } @Test void nullCannotBeClosed(@TrackLogRecords LogRecordListener listener) { Class testClass = NullCloseableFieldTestCase.class; String msg = "Cannot @AutoClose field %s.field because it is null.".formatted(testClass.getCanonicalName()); Events tests = executeTestsForClass(testClass).testEvents(); tests.assertStatistics(stats -> stats.succeeded(1).failed(0)); assertThat(listener.stream(Level.WARNING)).map(LogRecord::getMessage).anyMatch(msg::equals); } @Test void noCloseMethod() { assertMissingCloseMethod(NoCloseMethodTestCase.class, "close"); } @Test void noShutdownMethod() { assertMissingCloseMethod(NoShutdownMethodTestCase.class, "shutdown"); } /** * Tests prerequisites for the {@link AutoCloseSpy} implementation. */ @Test void spyPermitsOnlyASingleAction() { @SuppressWarnings("resource") AutoCloseSpy spy = new AutoCloseSpy("preconditions"); spy.close(); assertThatIllegalStateException().isThrownBy(spy::run).withMessage("Already closed via close()"); assertThatIllegalStateException().isThrownBy(spy::close).withMessage("Already closed via close()"); assertThat(recorder).containsExactly("AutoCloseTests.preconditions.close()"); } /** * @see #3684 */ @Test void fieldsAreProperlyClosedViaInterfaceMethods() { // If the test method succeeds, that means there was no issue invoking // the @AutoClose fields. No need to assert anything else for this use case. executeTestsForClass(CloseMethodMustBeInvokedViaInterfaceTestCase.class)// .testEvents()// .assertStatistics(stats -> stats.succeeded(1)); } @Test void fieldsAreProperlyClosedWithInstancePerMethodTestClass() { Events tests = executeTestsForClass(InstancePerMethodTestCase.class).testEvents(); tests.assertStatistics(stats -> stats.succeeded(2)); assertThat(recorder).containsExactly(// // test1() "InstancePerMethodTestCase.runnable.run()", // "InstancePerMethodTestCase.closable.close()", // // test2() "InstancePerMethodTestCase.runnable.run()", // "InstancePerMethodTestCase.closable.close()", // // Class-level cleanup "InstancePerMethodTestCase.staticClosable.close()"// ); } @Test void fieldsAreProperlyClosedWithInstancePerClassTestClass() { Events tests = executeTestsForClass(InstancePerClassTestCase.class).testEvents(); tests.assertStatistics(stats -> stats.succeeded(2)); assertThat(InstancePerClassTestCase.closed).isTrue(); } @Test void fieldsAreProperlyClosedWithNestedTestClassesWithInstancePerMethod() { Events tests = executeTestsForClass(InstancePerMethodEnclosingTestCase.NestedTestCase.class).testEvents(); tests.assertStatistics(stats -> stats.succeeded(1)); assertThat(recorder).containsExactly(// "NestedTestCase.nestedClosable.close()", // "InstancePerMethodEnclosingTestCase.enclosingClosable.close()", // "NestedTestCase.nestedStaticClosable.close()", // "InstancePerMethodEnclosingTestCase.enclosingStaticClosable.close()"// ); // Reset tracking resetTracking(); tests = executeTestsForClass(InstancePerMethodEnclosingTestCase.class).testEvents(); tests.assertStatistics(stats -> stats.succeeded(2)); assertThat(recorder).containsExactly(// "InstancePerMethodEnclosingTestCase.enclosingClosable.close()", // "NestedTestCase.nestedClosable.close()", // "InstancePerMethodEnclosingTestCase.enclosingClosable.close()", // "NestedTestCase.nestedStaticClosable.close()", // "InstancePerMethodEnclosingTestCase.enclosingStaticClosable.close()" // ); } @Test void fieldsAreProperlyClosedWithNestedTestClassesWithInstancePerClass() { // With test instance lifecycle "per class" mode, we actually expect the // same behavior for the closing of all fields when the nested test class // is run standalone AND when it's run along with its enclosing class. String[] expected = { // "NestedTestCase.nestedStaticClosable.close()", // "NestedTestCase.nestedClosable.close()", // "InstancePerClassEnclosingTestCase.enclosingStaticClosable.close()", // "InstancePerClassEnclosingTestCase.enclosingClosable.close()" // }; Events tests = executeTestsForClass(InstancePerClassEnclosingTestCase.NestedTestCase.class).testEvents(); tests.assertStatistics(stats -> stats.succeeded(1)); assertThat(recorder).containsExactly(expected); // Reset tracking resetTracking(); tests = executeTestsForClass(InstancePerClassEnclosingTestCase.class).testEvents(); tests.assertStatistics(stats -> stats.succeeded(2)); assertThat(recorder).containsExactly(expected); } @Test void fieldsAreProperlyClosedWithinTestClassHierarchy() { Events tests = executeTestsForClass(SuperTestCase.class).testEvents(); tests.assertStatistics(stats -> stats.succeeded(1)); assertThat(recorder).containsExactly(// // superTest() "SuperTestCase.superClosable.close()", // // Class-level cleanup "SuperTestCase.superStaticClosable.close()" // ); // Reset tracking resetTracking(); tests = executeTestsForClass(SubTestCase.class).testEvents(); tests.assertStatistics(stats -> stats.succeeded(2)); assertThat(recorder).containsExactly(// // superTest() "SubTestCase.subClosable.close()", // "SuperTestCase.superClosable.close()", // // subTest() "SubTestCase.subClosable.close()", // "SuperTestCase.superClosable.close()", // // Class-level cleanup in subclass "SubTestCase.subStaticClosable.close()", // // Class-level cleanup in superclass "SuperTestCase.superStaticClosable.close()" // ); } @Test void allFieldsAreClosedIfAnyFieldThrowsAnException() { // Prerequisites to ensure fields are "ordered" as expected (based on the hash codes for their names). assertThat("staticField1".hashCode()).isLessThan("staticField2".hashCode()).isLessThan( "staticField3".hashCode()); assertThat("field1".hashCode()).isLessThan("field2".hashCode()).isLessThan("field3".hashCode()); Class testClass = FailingFieldsTestCase.class; EngineExecutionResults allEvents = executeTestsForClass(testClass); Events tests = allEvents.testEvents(); tests.assertStatistics(stats -> stats.succeeded(0).failed(1)); // Verify that ALL fields were closed in the proper order. assertThat(recorder).containsExactly(// "FailingFieldsTestCase.field1.close()", // "FailingFieldsTestCase.field2.close()", // "FailingFieldsTestCase.field3.close()", // "FailingFieldsTestCase.staticField1.close()", // "FailingFieldsTestCase.staticField2.close()", // "FailingFieldsTestCase.staticField3.close()" // ); // Test-level failures assertThat(findFailure(tests, "test()")) // .isExactlyInstanceOf(RuntimeException.class) // .hasMessage("FailingFieldsTestCase.field1.close()")// .hasNoCause()// .hasSuppressedException(new RuntimeException("FailingFieldsTestCase.field2.close()")); Events containers = allEvents.containerEvents(); containers.assertStatistics(stats -> stats.succeeded(1).failed(1)); // Container-level failures assertThat(findFailure(containers, testClass.getSimpleName())) // .isExactlyInstanceOf(RuntimeException.class) // .hasMessage("FailingFieldsTestCase.staticField1.close()")// .hasNoCause()// .hasSuppressedException(new RuntimeException("FailingFieldsTestCase.staticField2.close()")); } @Test void allFieldsAreClosedIfAnyFieldThrowsAnExceptionWithNestedTestClassesWithInstancePerMethod() { Class enclosingTestClass = FailingFieldsEnclosingTestCase.class; Class nestedTestClass = FailingFieldsEnclosingTestCase.NestedTestCase.class; EngineExecutionResults allEvents = executeTestsForClass(nestedTestClass); Events tests = allEvents.testEvents(); tests.assertStatistics(stats -> stats.succeeded(0).failed(1)); // Verify that ALL fields were closed in the proper order. assertThat(recorder).containsExactly(// // Results from NestedTestCase instance "NestedTestCase.nestedField1.close()", // "NestedTestCase.nestedField2.close()", // // Results from FailingFieldsEnclosingTestCase instance "FailingFieldsEnclosingTestCase.enclosingField1.close()", // "FailingFieldsEnclosingTestCase.enclosingField2.close()", // // Results from NestedTestCase class "NestedTestCase.nestedStaticField1.close()", // "NestedTestCase.nestedStaticField2.close()", // // Results from FailingFieldsEnclosingTestCase class "FailingFieldsEnclosingTestCase.enclosingStaticField1.close()", // "FailingFieldsEnclosingTestCase.enclosingStaticField2.close()"// ); // Test-level failures assertThat(findFailure(tests, "nestedTest()"))// .isExactlyInstanceOf(RuntimeException.class)// .hasMessage("NestedTestCase.nestedField1.close()")// .hasNoCause()// .hasSuppressedException(new RuntimeException("FailingFieldsEnclosingTestCase.enclosingField1.close()")); Events containers = allEvents.containerEvents(); containers.assertStatistics(stats -> stats.succeeded(1).failed(2)); // Container-level failures assertThat(findFailure(containers, nestedTestClass.getSimpleName()))// .isExactlyInstanceOf(RuntimeException.class)// .hasMessage("NestedTestCase.nestedStaticField1.close()")// .hasNoCause()// .hasNoSuppressedExceptions(); assertThat(findFailure(containers, enclosingTestClass.getSimpleName()))// .isExactlyInstanceOf(RuntimeException.class)// .hasMessage("FailingFieldsEnclosingTestCase.enclosingStaticField1.close()")// .hasNoCause()// .hasNoSuppressedExceptions(); // Reset tracking resetTracking(); allEvents = executeTestsForClass(enclosingTestClass); tests = allEvents.testEvents(); tests.assertStatistics(stats -> stats.succeeded(0).failed(2)); // Verify that ALL fields were closed in the proper order. assertThat(recorder).containsExactly(// // Results from FailingFieldsEnclosingTestCase instance "FailingFieldsEnclosingTestCase.enclosingField1.close()", // "FailingFieldsEnclosingTestCase.enclosingField2.close()", // // Results from NestedTestCase instance "NestedTestCase.nestedField1.close()", // "NestedTestCase.nestedField2.close()", // // Results from FailingFieldsEnclosingTestCase instance "FailingFieldsEnclosingTestCase.enclosingField1.close()", // "FailingFieldsEnclosingTestCase.enclosingField2.close()", // // Results from NestedTestCase class "NestedTestCase.nestedStaticField1.close()", // "NestedTestCase.nestedStaticField2.close()", // // Results from FailingFieldsEnclosingTestCase class "FailingFieldsEnclosingTestCase.enclosingStaticField1.close()", // "FailingFieldsEnclosingTestCase.enclosingStaticField2.close()"// ); // Test-level failures assertThat(findFailure(tests, "enclosingTest()"))// .isExactlyInstanceOf(RuntimeException.class)// .hasMessage("FailingFieldsEnclosingTestCase.enclosingField1.close()")// .hasNoCause()// .hasNoSuppressedExceptions(); assertThat(findFailure(tests, "nestedTest()"))// .isExactlyInstanceOf(RuntimeException.class)// .hasMessage("NestedTestCase.nestedField1.close()")// .hasNoCause()// .hasSuppressedException(new RuntimeException("FailingFieldsEnclosingTestCase.enclosingField1.close()")); containers = allEvents.containerEvents(); containers.assertStatistics(stats -> stats.succeeded(1).failed(2)); // Container-level failures assertThat(findFailure(containers, nestedTestClass.getSimpleName()))// .isExactlyInstanceOf(RuntimeException.class)// .hasMessage("NestedTestCase.nestedStaticField1.close()")// .hasNoCause()// .hasNoSuppressedExceptions(); assertThat(findFailure(containers, enclosingTestClass.getSimpleName()))// .isExactlyInstanceOf(RuntimeException.class)// .hasMessage("FailingFieldsEnclosingTestCase.enclosingStaticField1.close()")// .hasNoCause()// .hasNoSuppressedExceptions(); } private Throwable findFailure(Events tests, String displayName) { return findExecution(tests, displayName)// .getTerminationInfo().getExecutionResult().getThrowable().orElseThrow(); } private static Execution findExecution(Events events, String displayName) { return events.executions()// .filter(execution -> execution.getTestDescriptor().getDisplayName().contains(displayName))// .findFirst().get(); } private static void assertFailingWithMessage(Events testEvents, String msg) { testEvents// .assertStatistics(stats -> stats.failed(1))// .assertThatEvents().haveExactly(1, finishedWithFailure(message(msg))); } private void assertMissingCloseMethod(Class testClass, String methodName) { String msg = "Cannot @AutoClose field %s.field because %s does not define method %s().".formatted( testClass.getCanonicalName(), String.class.getName(), methodName); Events tests = executeTestsForClass(testClass).testEvents(); assertFailingWithMessage(tests, msg); } interface TestInterface { @Test default void test() { } } static class BlankCloseMethodNameTestCase implements TestInterface { @AutoClose("") final String field = "blank"; } static class PrimitiveFieldTestCase implements TestInterface { @AutoClose final int x = 0; } static class ArrayFieldTestCase implements TestInterface { @AutoClose final int[] x = {}; } @NullUnmarked static class NullCloseableFieldTestCase implements TestInterface { @AutoClose final AutoCloseable field = null; } static class NoCloseMethodTestCase implements TestInterface { @AutoClose private final String field = ""; } @Retention(RetentionPolicy.RUNTIME) @AutoClose("shutdown") @interface AutoShutdown { } static class NoShutdownMethodTestCase implements TestInterface { @AutoShutdown private final String field = ""; } static class CloseMethodMustBeInvokedViaInterfaceTestCase implements TestInterface { @AutoClose final InputStream inputStream = InputStream.nullInputStream(); @AutoClose("shutdown") final ExecutorService service = Executors.newSingleThreadExecutor(); } @TestInstance(PER_METHOD) @NullUnmarked static class InstancePerMethodTestCase { @AutoClose private static AutoCloseable staticClosable; @AutoClose private static final AutoCloseable nullStatic = null; @AutoClose private final AutoCloseable closable = new AutoCloseSpy("closable"); @AutoClose(" run ") // intentionally contains extra whitespace. private final Runnable runnable = new AutoCloseSpy("runnable"); @AutoClose private final AutoCloseable nullField = null; @BeforeAll static void setup() { staticClosable = new AutoCloseSpy("staticClosable"); } @Test void test1() { } @Test void test2() { } } @TestInstance(PER_CLASS) static class InstancePerClassTestCase { static boolean closed = false; @AutoClose final AutoCloseable field = () -> closed = true; @Test void test1() { assertThat(closed).isFalse(); } @Test void test2() { assertThat(closed).isFalse(); } } @TestInstance(PER_METHOD) static class InstancePerMethodEnclosingTestCase implements TestInterface { @AutoClose static AutoCloseSpy enclosingStaticClosable; @AutoClose final AutoCloseable enclosingClosable = new AutoCloseSpy("enclosingClosable"); @BeforeAll static void setup() { enclosingStaticClosable = new AutoCloseSpy("enclosingStaticClosable"); } @Nested @TestInstance(PER_METHOD) class NestedTestCase implements TestInterface { @AutoClose static AutoCloseSpy nestedStaticClosable; @AutoClose final AutoCloseable nestedClosable = new AutoCloseSpy("nestedClosable"); @BeforeAll static void setup() { nestedStaticClosable = new AutoCloseSpy("nestedStaticClosable"); } } } @TestInstance(PER_CLASS) static class InstancePerClassEnclosingTestCase implements TestInterface { @AutoClose static AutoCloseSpy enclosingStaticClosable; @AutoClose final AutoCloseable enclosingClosable = new AutoCloseSpy("enclosingClosable"); @BeforeAll static void setup() { enclosingStaticClosable = new AutoCloseSpy("enclosingStaticClosable"); } @Nested @TestInstance(PER_CLASS) class NestedTestCase implements TestInterface { @AutoClose static AutoCloseSpy nestedStaticClosable; @AutoClose final AutoCloseable nestedClosable = new AutoCloseSpy("nestedClosable"); @BeforeAll static void setup() { nestedStaticClosable = new AutoCloseSpy("nestedStaticClosable"); } } } static class SuperTestCase { @AutoClose static AutoCloseable superStaticClosable; @AutoClose final AutoCloseable superClosable = new AutoCloseSpy("superClosable"); @BeforeAll // WARNING: if this method is named setup() AND the @BeforeAll method in // SubTestCase is also named setup(), the latter will "hide" the former. static void superSetup() { superStaticClosable = new AutoCloseSpy("superStaticClosable"); } @Test void superTest() { } } static class SubTestCase extends SuperTestCase { @AutoClose static AutoCloseable subStaticClosable; @AutoClose final AutoCloseable subClosable = new AutoCloseSpy("subClosable"); @BeforeAll static void subSetup() { subStaticClosable = new AutoCloseSpy("subStaticClosable"); } @Test void subTest() { } } static class FailingFieldsTestCase { @AutoClose static AutoCloseable staticField1; @AutoClose static AutoCloseable staticField2; @AutoClose static AutoCloseable staticField3; @AutoClose final AutoCloseable field1 = new AutoCloseSpy("field1", true); @AutoClose final AutoCloseable field2 = new AutoCloseSpy("field2", true); @AutoClose final AutoCloseable field3 = new AutoCloseSpy("field3", false); @BeforeAll static void setup() { staticField1 = new AutoCloseSpy("staticField1", true); staticField2 = new AutoCloseSpy("staticField2", true); staticField3 = new AutoCloseSpy("staticField3", false); } @Test void test() { } } static class FailingFieldsEnclosingTestCase { @AutoClose static AutoCloseable enclosingStaticField1; @AutoClose static AutoCloseable enclosingStaticField2; @AutoClose final AutoCloseable enclosingField1 = new AutoCloseSpy("enclosingField1", true); @AutoClose final AutoCloseable enclosingField2 = new AutoCloseSpy("enclosingField2", false); @BeforeAll static void setup() { enclosingStaticField1 = new AutoCloseSpy("enclosingStaticField1", true); enclosingStaticField2 = new AutoCloseSpy("enclosingStaticField2", false); } @Test void enclosingTest() { } @Nested class NestedTestCase { @AutoClose static AutoCloseable nestedStaticField1; @AutoClose static AutoCloseable nestedStaticField2; @AutoClose final AutoCloseable nestedField1 = new AutoCloseSpy("nestedField1", true); @AutoClose final AutoCloseable nestedField2 = new AutoCloseSpy("nestedField2", false); @BeforeAll static void setup() { nestedStaticField1 = new AutoCloseSpy("nestedStaticField1", true); nestedStaticField2 = new AutoCloseSpy("nestedStaticField2", false); } @Test void nestedTest() { } } } @NullUnmarked static class AutoCloseSpy implements AutoCloseable, Runnable { private final String prefix; private final boolean fail; private String invokedMethod = null; AutoCloseSpy(String prefix) { Class callerClass = StackWalker.getInstance(RETAIN_CLASS_REFERENCE).getCallerClass(); this.fail = false; this.prefix = callerClass.getSimpleName() + "." + prefix + "."; } AutoCloseSpy(String prefix, boolean fail) { Class callerClass = StackWalker.getInstance(RETAIN_CLASS_REFERENCE).getCallerClass(); this.prefix = callerClass.getSimpleName() + "." + prefix + "."; this.fail = fail; } @Override public void run() { recordInvocation("run()"); } @Override public void close() { recordInvocation("close()"); } private void recordInvocation(String methodName) { if (this.invokedMethod != null) { throw new IllegalStateException("Already closed via " + this.invokedMethod); } this.invokedMethod = methodName; String invocation = this.prefix + this.invokedMethod; recorder.add(invocation); if (this.fail) { throw new RuntimeException(invocation); } } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterAllTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.ArrayList; import java.util.List; import java.util.Optional; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.jupiter.engine.JupiterTestEngine; /** * Integration tests that verify support for {@link BeforeAll}, {@link AfterAll}, * {@link BeforeAllCallback}, and {@link AfterAllCallback} in the {@link JupiterTestEngine}. * * @since 5.0 */ class BeforeAndAfterAllTests extends AbstractJupiterTestEngineTests { private static final List callSequence = new ArrayList<>(); private static @Nullable Optional actualExceptionInAfterAllCallback; @Test void beforeAllAndAfterAllCallbacks() { // @formatter:off assertBeforeAllAndAfterAllCallbacks(TopLevelTestCase.class, "fooBeforeAllCallback", "barBeforeAllCallback", "beforeAllMethod-1", "test-1", "afterAllMethod-1", "barAfterAllCallback", "fooAfterAllCallback" ); // @formatter:on assertThat(actualExceptionInAfterAllCallback).isEmpty(); } @Test void beforeAllAndAfterAllCallbacksInSubclass() { // @formatter:off assertBeforeAllAndAfterAllCallbacks(SecondLevelTestCase.class, "fooBeforeAllCallback", "barBeforeAllCallback", "bazBeforeAllCallback", "beforeAllMethod-1", "beforeAllMethod-2", "test-2", "afterAllMethod-2", "afterAllMethod-1", "bazAfterAllCallback", "barAfterAllCallback", "fooAfterAllCallback" ); // @formatter:on assertThat(actualExceptionInAfterAllCallback).isEmpty(); } @Test void beforeAllAndAfterAllCallbacksInSubSubclass() { // @formatter:off assertBeforeAllAndAfterAllCallbacks(ThirdLevelTestCase.class, "fooBeforeAllCallback", "barBeforeAllCallback", "bazBeforeAllCallback", "quuxBeforeAllCallback", "beforeAllMethod-1", "beforeAllMethod-2", "beforeAllMethod-3", "test-3", "afterAllMethod-3", "afterAllMethod-2", "afterAllMethod-1", "quuxAfterAllCallback", "bazAfterAllCallback", "barAfterAllCallback", "fooAfterAllCallback" ); // @formatter:on assertThat(actualExceptionInAfterAllCallback).isEmpty(); } /** * Since static methods cannot be overridden, "static hiding" no longer occurs since 5.11. */ @Test void beforeAllAndAfterAllCallbacksInSubSubclassWithoutStaticMethodHiding() { // NOTE: both the @BeforeAll AND the @AfterAll methods in level 3 are // executed as 1/2/3 due to the "stable" method sort order on the Platform. // @formatter:off assertBeforeAllAndAfterAllCallbacks(ThirdLevelStaticHidingTestCase.class, "fooBeforeAllCallback", "barBeforeAllCallback", "bazBeforeAllCallback", "quuxBeforeAllCallback", "beforeAllMethod-1", "beforeAllMethod-2", "beforeAllMethod-1-level3", "beforeAllMethod-2-level3", "beforeAllMethod-3-level3", "test-3", "afterAllMethod-1-level3", "afterAllMethod-2-level3", "afterAllMethod-3-level3", "afterAllMethod-2", "afterAllMethod-1", "quuxAfterAllCallback", "bazAfterAllCallback", "barAfterAllCallback", "fooAfterAllCallback" ); // @formatter:on assertThat(actualExceptionInAfterAllCallback).isEmpty(); } @Test void beforeAllMethodThrowsAnException() { // @formatter:off assertBeforeAllAndAfterAllCallbacks(ExceptionInBeforeAllMethodTestCase.class, 0, 0, "fooBeforeAllCallback", "beforeAllMethod", // throws an exception. // test should not get invoked. "afterAllMethod", "fooAfterAllCallback" ); // @formatter:on assertThat(actualExceptionInAfterAllCallback).containsInstanceOf(EnigmaException.class); } @Test void beforeAllCallbackThrowsAnException() { // @formatter:off assertBeforeAllAndAfterAllCallbacks(ExceptionInBeforeAllCallbackTestCase.class, 0, 0, "fooBeforeAllCallback", "exceptionThrowingBeforeAllCallback", // throws an exception. // beforeAllMethod should not get invoked. // test should not get invoked. // afterAllMethod should not get invoked. "fooAfterAllCallback" ); // @formatter:on assertThat(actualExceptionInAfterAllCallback).containsInstanceOf(EnigmaException.class); } private void assertBeforeAllAndAfterAllCallbacks(Class testClass, String... expectedCalls) { assertBeforeAllAndAfterAllCallbacks(testClass, 1, 1, expectedCalls); } private void assertBeforeAllAndAfterAllCallbacks(Class testClass, int testsStarted, int testsSuccessful, String... expectedCalls) { callSequence.clear(); executeTestsForClass(testClass).testEvents()// .assertStatistics(stats -> stats.started(testsStarted).succeeded(testsSuccessful)); assertEquals(asList(expectedCalls), callSequence, () -> "wrong call sequence for " + testClass.getName()); } // ------------------------------------------------------------------------- // Must NOT be private; otherwise, the @Test method gets discovered but never executed. @ExtendWith({ FooClassLevelCallbacks.class, BarClassLevelCallbacks.class }) static class TopLevelTestCase { @BeforeAll static void beforeAll1() { callSequence.add("beforeAllMethod-1"); } @AfterAll static void afterAll1() { callSequence.add("afterAllMethod-1"); } @Test void test() { callSequence.add("test-1"); } } // Must NOT be private; otherwise, the @Test method gets discovered but never executed. @ExtendWith(BazClassLevelCallbacks.class) static class SecondLevelTestCase extends TopLevelTestCase { @BeforeAll static void beforeAll2() { callSequence.add("beforeAllMethod-2"); } @AfterAll static void afterAll2() { callSequence.add("afterAllMethod-2"); } @Test @Override void test() { callSequence.add("test-2"); } } @ExtendWith(QuuxClassLevelCallbacks.class) static class ThirdLevelTestCase extends SecondLevelTestCase { @BeforeAll static void beforeAll3() { callSequence.add("beforeAllMethod-3"); } @AfterAll static void afterAll3() { callSequence.add("afterAllMethod-3"); } @Test @Override void test() { callSequence.add("test-3"); } } @ExtendWith(QuuxClassLevelCallbacks.class) static class ThirdLevelStaticHidingTestCase extends SecondLevelTestCase { @BeforeAll static void beforeAll1() { callSequence.add("beforeAllMethod-1-level3"); } @BeforeAll static void beforeAll2() { callSequence.add("beforeAllMethod-2-level3"); } @BeforeAll static void beforeAll3() { callSequence.add("beforeAllMethod-3-level3"); } @AfterAll static void afterAll1() { callSequence.add("afterAllMethod-1-level3"); } @AfterAll static void afterAll2() { callSequence.add("afterAllMethod-2-level3"); } @AfterAll static void afterAll3() { callSequence.add("afterAllMethod-3-level3"); } @Test @Override void test() { callSequence.add("test-3"); } } @ExtendWith(FooClassLevelCallbacks.class) static class ExceptionInBeforeAllMethodTestCase { @BeforeAll static void beforeAll() { callSequence.add("beforeAllMethod"); throw new EnigmaException("@BeforeAll"); } @Test void test() { callSequence.add("test"); } @AfterAll static void afterAll() { callSequence.add("afterAllMethod"); } } @ExtendWith({ FooClassLevelCallbacks.class, ExceptionThrowingBeforeAllCallback.class }) static class ExceptionInBeforeAllCallbackTestCase { @BeforeAll static void beforeAll() { callSequence.add("beforeAllMethod"); } @Test void test() { callSequence.add("test"); } @AfterAll static void afterAll() { callSequence.add("afterAllMethod"); } } // ------------------------------------------------------------------------- static class FooClassLevelCallbacks implements BeforeAllCallback, AfterAllCallback { @Override public void beforeAll(ExtensionContext context) { callSequence.add("fooBeforeAllCallback"); } @Override public void afterAll(ExtensionContext context) { callSequence.add("fooAfterAllCallback"); actualExceptionInAfterAllCallback = context.getExecutionException(); } } static class BarClassLevelCallbacks implements BeforeAllCallback, AfterAllCallback { @Override public void beforeAll(ExtensionContext context) { callSequence.add("barBeforeAllCallback"); } @Override public void afterAll(ExtensionContext context) { callSequence.add("barAfterAllCallback"); } } static class BazClassLevelCallbacks implements BeforeAllCallback, AfterAllCallback { @Override public void beforeAll(ExtensionContext context) { callSequence.add("bazBeforeAllCallback"); } @Override public void afterAll(ExtensionContext context) { callSequence.add("bazAfterAllCallback"); } } static class QuuxClassLevelCallbacks implements BeforeAllCallback, AfterAllCallback { @Override public void beforeAll(ExtensionContext context) { callSequence.add("quuxBeforeAllCallback"); } @Override public void afterAll(ExtensionContext context) { callSequence.add("quuxAfterAllCallback"); } } static class ExceptionThrowingBeforeAllCallback implements BeforeAllCallback { @Override public void beforeAll(ExtensionContext context) { callSequence.add("exceptionThrowingBeforeAllCallback"); throw new EnigmaException("BeforeAllCallback"); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterEachTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.ArrayList; import java.util.List; import java.util.Optional; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.jupiter.engine.JupiterTestEngine; import org.junit.platform.testkit.engine.Events; /** * Integration tests that verify support for {@link BeforeEach}, {@link AfterEach}, * {@link BeforeEachCallback}, and {@link AfterEachCallback} in the {@link JupiterTestEngine}. * * @since 5.0 * @see BeforeAndAfterTestExecutionCallbackTests */ class BeforeAndAfterEachTests extends AbstractJupiterTestEngineTests { private static final List callSequence = new ArrayList<>(); private static final List beforeEachMethodCallSequence = new ArrayList<>(); private static @Nullable Optional actualExceptionInAfterEachCallback; @SuppressWarnings("OptionalAssignedToNull") @BeforeEach void resetCallSequence() { callSequence.clear(); beforeEachMethodCallSequence.clear(); actualExceptionInAfterEachCallback = null; } @Test void beforeEachAndAfterEachCallbacks() { Events testEvents = executeTestsForClass(OuterTestCase.class).testEvents(); assertEquals(2, testEvents.started().count(), "# tests started"); assertEquals(2, testEvents.succeeded().count(), "# tests succeeded"); assertEquals(0, testEvents.skipped().count(), "# tests skipped"); assertEquals(0, testEvents.aborted().count(), "# tests aborted"); assertEquals(0, testEvents.failed().count(), "# tests failed"); // @formatter:off assertEquals(asList( // OuterTestCase "fooBeforeEachCallback", "barBeforeEachCallback", "beforeEachMethod", "testOuter", "afterEachMethod", "barAfterEachCallback", "fooAfterEachCallback", // InnerTestCase "fooBeforeEachCallback", "barBeforeEachCallback", "fizzBeforeEachCallback", "beforeEachMethod", "beforeEachInnerMethod", "testInner", "afterEachInnerMethod", "afterEachMethod", "fizzAfterEachCallback", "barAfterEachCallback", "fooAfterEachCallback" ), callSequence, "wrong call sequence"); // @formatter:on } @Test void beforeEachAndAfterEachCallbacksDeclaredOnSuperclassAndSubclass() { Events testEvents = executeTestsForClass(ChildTestCase.class).testEvents(); assertEquals(1, testEvents.started().count(), "# tests started"); assertEquals(1, testEvents.succeeded().count(), "# tests succeeded"); assertEquals(0, testEvents.skipped().count(), "# tests skipped"); assertEquals(0, testEvents.aborted().count(), "# tests aborted"); assertEquals(0, testEvents.failed().count(), "# tests failed"); // @formatter:off assertEquals(asList( "fooBeforeEachCallback", "barBeforeEachCallback", "testChild", "barAfterEachCallback", "fooAfterEachCallback" ), callSequence, "wrong call sequence"); // @formatter:on } @Test void beforeEachAndAfterEachCallbacksDeclaredOnInterfaceAndClass() { Events testEvents = executeTestsForClass(TestInterfaceTestCase.class).testEvents(); assertEquals(2, testEvents.started().count(), "# tests started"); assertEquals(2, testEvents.succeeded().count(), "# tests succeeded"); assertEquals(0, testEvents.skipped().count(), "# tests skipped"); assertEquals(0, testEvents.aborted().count(), "# tests aborted"); assertEquals(0, testEvents.failed().count(), "# tests failed"); // @formatter:off assertEquals(asList( // Test Interface "fooBeforeEachCallback", "barBeforeEachCallback", "defaultTestMethod", "barAfterEachCallback", "fooAfterEachCallback", // Test Class "fooBeforeEachCallback", "barBeforeEachCallback", "localTestMethod", "barAfterEachCallback", "fooAfterEachCallback" ), callSequence, "wrong call sequence"); // @formatter:on } @Test void beforeEachCallbackThrowsAnException() { Events testEvents = executeTestsForClass(ExceptionInBeforeEachCallbackTestCase.class).testEvents(); assertEquals(1, testEvents.started().count(), "# tests started"); assertEquals(0, testEvents.succeeded().count(), "# tests succeeded"); assertEquals(0, testEvents.skipped().count(), "# tests skipped"); assertEquals(0, testEvents.aborted().count(), "# tests aborted"); assertEquals(1, testEvents.failed().count(), "# tests failed"); // @formatter:off assertEquals(asList( "fooBeforeEachCallback", "exceptionThrowingBeforeEachCallback", // throws an exception. // barBeforeEachCallback should not get invoked. // beforeEachMethod should not get invoked. // test should not get invoked. // afterEachMethod should not get invoked. "barAfterEachCallback", "fooAfterEachCallback" ), callSequence, "wrong call sequence"); // @formatter:on assertThat(actualExceptionInAfterEachCallback).containsInstanceOf(EnigmaException.class); } @Test void afterEachCallbackThrowsAnException() { Events testEvents = executeTestsForClass(ExceptionInAfterEachCallbackTestCase.class).testEvents(); assertEquals(1, testEvents.started().count(), "# tests started"); assertEquals(0, testEvents.succeeded().count(), "# tests succeeded"); assertEquals(0, testEvents.skipped().count(), "# tests skipped"); assertEquals(0, testEvents.aborted().count(), "# tests aborted"); assertEquals(1, testEvents.failed().count(), "# tests failed"); // @formatter:off assertEquals(asList( "fooBeforeEachCallback", "barBeforeEachCallback", "beforeEachMethod", "test", "afterEachMethod", "barAfterEachCallback", "exceptionThrowingAfterEachCallback", // throws an exception. "fooAfterEachCallback" ), callSequence, "wrong call sequence"); // @formatter:on assertThat(actualExceptionInAfterEachCallback).containsInstanceOf(EnigmaException.class); } @Test void beforeEachMethodThrowsAnException() { Events testEvents = executeTestsForClass(ExceptionInBeforeEachMethodTestCase.class).testEvents(); assertEquals(1, testEvents.started().count(), "# tests started"); assertEquals(0, testEvents.succeeded().count(), "# tests succeeded"); assertEquals(0, testEvents.skipped().count(), "# tests skipped"); assertEquals(0, testEvents.aborted().count(), "# tests aborted"); assertEquals(1, testEvents.failed().count(), "# tests failed"); // Since the JVM does not guarantee the order in which methods are // returned via reflection (and since JUnit Jupiter does not yet // support ordering of @BeforeEach methods), we have to figure out // which @BeforeEach method got executed first in order to determine // the expected call sequence. // @formatter:off List list1 = asList( "fooBeforeEachCallback", "beforeEachMethod1", // throws an exception. // "beforeEachMethod2" should not get invoked // test should not get invoked. "afterEachMethod", "fooAfterEachCallback" ); List list2 = asList( "fooBeforeEachCallback", "beforeEachMethod2", "beforeEachMethod1", // throws an exception. // test should not get invoked. "afterEachMethod", "fooAfterEachCallback" ); // @formatter:on List expected = beforeEachMethodCallSequence.getFirst().equals("beforeEachMethod1") ? list1 : list2; assertEquals(expected, callSequence, "wrong call sequence"); assertThat(actualExceptionInAfterEachCallback).containsInstanceOf(EnigmaException.class); } @Test void afterEachMethodThrowsAnException() { Events testEvents = executeTestsForClass(ExceptionInAfterEachMethodTestCase.class).testEvents(); assertEquals(1, testEvents.started().count(), "# tests started"); assertEquals(0, testEvents.succeeded().count(), "# tests succeeded"); assertEquals(0, testEvents.skipped().count(), "# tests skipped"); assertEquals(0, testEvents.aborted().count(), "# tests aborted"); assertEquals(1, testEvents.failed().count(), "# tests failed"); // @formatter:off assertEquals(asList( "fooBeforeEachCallback", "beforeEachMethod", "test", "afterEachMethod", // throws an exception. "fooAfterEachCallback" ), callSequence, "wrong call sequence"); // @formatter:on assertThat(actualExceptionInAfterEachCallback).containsInstanceOf(EnigmaException.class); } @Test void testMethodThrowsAnException() { Events testEvents = executeTestsForClass(ExceptionInTestMethodTestCase.class).testEvents(); assertEquals(1, testEvents.started().count(), "# tests started"); assertEquals(0, testEvents.succeeded().count(), "# tests succeeded"); assertEquals(0, testEvents.skipped().count(), "# tests skipped"); assertEquals(0, testEvents.aborted().count(), "# tests aborted"); assertEquals(1, testEvents.failed().count(), "# tests failed"); // @formatter:off assertEquals(asList( "fooBeforeEachCallback", "beforeEachMethod", "test", // throws an exception. "afterEachMethod", "fooAfterEachCallback" ), callSequence, "wrong call sequence"); // @formatter:on assertThat(actualExceptionInAfterEachCallback).containsInstanceOf(EnigmaException.class); } // ------------------------------------------------------------------------- @ExtendWith(FooMethodLevelCallbacks.class) static class ParentTestCase { } @ExtendWith(BarMethodLevelCallbacks.class) static class ChildTestCase extends ParentTestCase { @Test void test() { callSequence.add("testChild"); } } @ExtendWith(FooMethodLevelCallbacks.class) private interface TestInterface { @Test default void defaultTest() { callSequence.add("defaultTestMethod"); } } @ExtendWith(BarMethodLevelCallbacks.class) static class TestInterfaceTestCase implements TestInterface { @Test void localTest() { callSequence.add("localTestMethod"); } } @ExtendWith({ FooMethodLevelCallbacks.class, BarMethodLevelCallbacks.class }) static class OuterTestCase { @BeforeEach void beforeEach() { callSequence.add("beforeEachMethod"); } @Test void testOuter() { callSequence.add("testOuter"); } @AfterEach void afterEach() { callSequence.add("afterEachMethod"); } @Nested @ExtendWith(FizzMethodLevelCallbacks.class) class InnerTestCase { @BeforeEach void beforeEachInnerMethod() { callSequence.add("beforeEachInnerMethod"); } @Test void testInner() { callSequence.add("testInner"); } @AfterEach void afterEachInnerMethod() { callSequence.add("afterEachInnerMethod"); } } } @ExtendWith({ FooMethodLevelCallbacks.class, ExceptionThrowingBeforeEachCallback.class, BarMethodLevelCallbacks.class }) static class ExceptionInBeforeEachCallbackTestCase { @BeforeEach void beforeEach() { callSequence.add("beforeEachMethod"); } @Test void test() { callSequence.add("test"); } @AfterEach void afterEach() { callSequence.add("afterEachMethod"); } } @ExtendWith({ FooMethodLevelCallbacks.class, ExceptionThrowingAfterEachCallback.class, BarMethodLevelCallbacks.class }) static class ExceptionInAfterEachCallbackTestCase { @BeforeEach void beforeEach() { callSequence.add("beforeEachMethod"); } @Test void test() { callSequence.add("test"); } @AfterEach void afterEach() { callSequence.add("afterEachMethod"); } } @ExtendWith(FooMethodLevelCallbacks.class) static class ExceptionInBeforeEachMethodTestCase { @BeforeEach void beforeEach1() { beforeEachMethodCallSequence.add("beforeEachMethod1"); callSequence.add("beforeEachMethod1"); throw new EnigmaException("@BeforeEach"); } @BeforeEach void beforeEach2() { beforeEachMethodCallSequence.add("beforeEachMethod2"); callSequence.add("beforeEachMethod2"); } @Test void test() { callSequence.add("test"); } @AfterEach void afterEach() { callSequence.add("afterEachMethod"); } } @ExtendWith(FooMethodLevelCallbacks.class) static class ExceptionInAfterEachMethodTestCase { @BeforeEach void beforeEach() { callSequence.add("beforeEachMethod"); } @Test void test() { callSequence.add("test"); } @AfterEach void afterEach() { callSequence.add("afterEachMethod"); throw new EnigmaException("@AfterEach"); } } @ExtendWith(FooMethodLevelCallbacks.class) static class ExceptionInTestMethodTestCase { @BeforeEach void beforeEach() { callSequence.add("beforeEachMethod"); } @Test void test() { callSequence.add("test"); throw new EnigmaException("@Test"); } @AfterEach void afterEach() { callSequence.add("afterEachMethod"); } } // ------------------------------------------------------------------------- static class FooMethodLevelCallbacks implements BeforeEachCallback, AfterEachCallback { @Override public void beforeEach(ExtensionContext context) { callSequence.add("fooBeforeEachCallback"); } @Override public void afterEach(ExtensionContext context) { callSequence.add("fooAfterEachCallback"); actualExceptionInAfterEachCallback = context.getExecutionException(); } } static class BarMethodLevelCallbacks implements BeforeEachCallback, AfterEachCallback { @Override public void beforeEach(ExtensionContext context) { callSequence.add("barBeforeEachCallback"); } @Override public void afterEach(ExtensionContext context) { callSequence.add("barAfterEachCallback"); } } static class FizzMethodLevelCallbacks implements BeforeEachCallback, AfterEachCallback { @Override public void beforeEach(ExtensionContext context) { callSequence.add("fizzBeforeEachCallback"); } @Override public void afterEach(ExtensionContext context) { callSequence.add("fizzAfterEachCallback"); } } static class ExceptionThrowingBeforeEachCallback implements BeforeEachCallback { @Override public void beforeEach(ExtensionContext context) { callSequence.add("exceptionThrowingBeforeEachCallback"); throw new EnigmaException("BeforeEachCallback"); } } static class ExceptionThrowingAfterEachCallback implements AfterEachCallback { @Override public void afterEach(ExtensionContext context) { callSequence.add("exceptionThrowingAfterEachCallback"); throw new EnigmaException("AfterEachCallback"); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterTestExecutionCallbackTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static java.util.Arrays.asList; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.ArrayList; import java.util.List; import java.util.Optional; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.AfterTestExecutionCallback; import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.jupiter.engine.JupiterTestEngine; import org.junit.platform.testkit.engine.EngineExecutionResults; /** * Integration tests that verify support for {@link BeforeTestExecutionCallback}, * {@link AfterTestExecutionCallback}, {@link BeforeEach}, and {@link AfterEach} * in the {@link JupiterTestEngine}. * * @since 5.0 * @see BeforeAndAfterEachTests */ class BeforeAndAfterTestExecutionCallbackTests extends AbstractJupiterTestEngineTests { private static final List callSequence = new ArrayList<>(); private static @Nullable Optional actualExceptionInAfterTestExecution; @SuppressWarnings("OptionalAssignedToNull") @BeforeEach void resetCallSequence() { callSequence.clear(); actualExceptionInAfterTestExecution = null; } @Test void beforeAndAfterTestExecutionCallbacks() { EngineExecutionResults executionResults = executeTestsForClass(OuterTestCase.class); assertEquals(2, executionResults.testEvents().started().count(), "# tests started"); assertEquals(2, executionResults.testEvents().succeeded().count(), "# tests succeeded"); assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); // @formatter:off assertEquals(asList( // OuterTestCase "beforeEachMethodOuter", "fooBeforeTestExecutionCallback", "barBeforeTestExecutionCallback", "testOuter", "barAfterTestExecutionCallback", "fooAfterTestExecutionCallback", "afterEachMethodOuter", // InnerTestCase "beforeEachMethodOuter", "beforeEachMethodInner", "fooBeforeTestExecutionCallback", "barBeforeTestExecutionCallback", "fizzBeforeTestExecutionCallback", "testInner", "fizzAfterTestExecutionCallback", "barAfterTestExecutionCallback", "fooAfterTestExecutionCallback", "afterEachMethodInner", "afterEachMethodOuter" ), callSequence, "wrong call sequence"); // @formatter:on } @Test void beforeAndAfterTestExecutionCallbacksDeclaredOnSuperclassAndSubclass() { EngineExecutionResults executionResults = executeTestsForClass(ChildTestCase.class); assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); // @formatter:off assertEquals(asList( "fooBeforeTestExecutionCallback", "barBeforeTestExecutionCallback", "testChild", "barAfterTestExecutionCallback", "fooAfterTestExecutionCallback" ), callSequence, "wrong call sequence"); // @formatter:on } @Test void beforeAndAfterTestExecutionCallbacksDeclaredOnInterfaceAndClass() { EngineExecutionResults executionResults = executeTestsForClass(TestInterfaceTestCase.class); assertEquals(2, executionResults.testEvents().started().count(), "# tests started"); assertEquals(2, executionResults.testEvents().succeeded().count(), "# tests succeeded"); assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); // @formatter:off assertEquals(asList( // Test Interface "fooBeforeTestExecutionCallback", "barBeforeTestExecutionCallback", "defaultTestMethod", "barAfterTestExecutionCallback", "fooAfterTestExecutionCallback", // Test Class "fooBeforeTestExecutionCallback", "barBeforeTestExecutionCallback", "localTestMethod", "barAfterTestExecutionCallback", "fooAfterTestExecutionCallback" ), callSequence, "wrong call sequence"); // @formatter:on } @Test void beforeEachMethodThrowsAnException() { EngineExecutionResults executionResults = executeTestsForClass(ExceptionInBeforeEachMethodTestCase.class); assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); assertEquals(0, executionResults.testEvents().succeeded().count(), "# tests succeeded"); assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); assertEquals(1, executionResults.testEvents().failed().count(), "# tests failed"); // @formatter:off assertEquals(asList( "beforeEachMethod", // throws an exception. // fooBeforeTestExecutionCallback should not get invoked. // test should not get invoked. // fooAfterTestExecutionCallback should not get invoked. "afterEachMethod" ), callSequence, "wrong call sequence"); // @formatter:on assertNull(actualExceptionInAfterTestExecution, "test exception (fooAfterTestExecutionCallback should not have been called)"); } @Test void beforeTestExecutionCallbackThrowsAnException() { EngineExecutionResults executionResults = executeTestsForClass( ExceptionInBeforeTestExecutionCallbackTestCase.class); assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); assertEquals(0, executionResults.testEvents().succeeded().count(), "# tests succeeded"); assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); assertEquals(1, executionResults.testEvents().failed().count(), "# tests failed"); // @formatter:off assertEquals(asList( "beforeEachMethod", "fooBeforeTestExecutionCallback", "exceptionThrowingBeforeTestExecutionCallback", // throws an exception. // barBeforeTestExecutionCallback should not get invoked. // test() should not get invoked. "barAfterTestExecutionCallback", "fooAfterTestExecutionCallback", "afterEachMethod" ), callSequence, "wrong call sequence"); // @formatter:on assertNotNull(actualExceptionInAfterTestExecution, "test exception"); assertTrue(actualExceptionInAfterTestExecution.isPresent(), "test exception should be present"); assertEquals(EnigmaException.class, actualExceptionInAfterTestExecution.get().getClass()); } @Test void afterTestExecutionCallbackThrowsAnException() { EngineExecutionResults executionResults = executeTestsForClass( ExceptionInAfterTestExecutionCallbackTestCase.class); assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); assertEquals(0, executionResults.testEvents().succeeded().count(), "# tests succeeded"); assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); assertEquals(1, executionResults.testEvents().failed().count(), "# tests failed"); // @formatter:off assertEquals(asList( "beforeEachMethod", "fooBeforeTestExecutionCallback", "barBeforeTestExecutionCallback", "test", "barAfterTestExecutionCallback", "exceptionThrowingAfterTestExecutionCallback", // throws an exception. "fooAfterTestExecutionCallback", "afterEachMethod" ), callSequence, "wrong call sequence"); // @formatter:on assertNotNull(actualExceptionInAfterTestExecution, "test exception"); assertTrue(actualExceptionInAfterTestExecution.isPresent(), "test exception should be present"); assertEquals(EnigmaException.class, actualExceptionInAfterTestExecution.get().getClass()); } @Test void testMethodThrowsAnException() { EngineExecutionResults executionResults = executeTestsForClass(ExceptionInTestMethodTestCase.class); assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); assertEquals(0, executionResults.testEvents().succeeded().count(), "# tests succeeded"); assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); assertEquals(1, executionResults.testEvents().failed().count(), "# tests failed"); // @formatter:off assertEquals(asList( "beforeEachMethod", "fooBeforeTestExecutionCallback", "test", // throws an exception. "fooAfterTestExecutionCallback", "afterEachMethod" ), callSequence, "wrong call sequence"); // @formatter:on assertNotNull(actualExceptionInAfterTestExecution, "test exception"); assertTrue(actualExceptionInAfterTestExecution.isPresent(), "test exception should be present"); assertEquals(EnigmaException.class, actualExceptionInAfterTestExecution.get().getClass()); } // ------------------------------------------------------------------------- @ExtendWith(FooTestExecutionCallbacks.class) static class ParentTestCase { } @ExtendWith(BarTestExecutionCallbacks.class) static class ChildTestCase extends ParentTestCase { @Test void test() { callSequence.add("testChild"); } } @ExtendWith(FooTestExecutionCallbacks.class) private interface TestInterface { @Test default void defaultTest() { callSequence.add("defaultTestMethod"); } } @ExtendWith(BarTestExecutionCallbacks.class) static class TestInterfaceTestCase implements TestInterface { @Test void localTest() { callSequence.add("localTestMethod"); } } @ExtendWith({ FooTestExecutionCallbacks.class, BarTestExecutionCallbacks.class }) static class OuterTestCase { @BeforeEach void beforeEach() { callSequence.add("beforeEachMethodOuter"); } @Test void testOuter() { callSequence.add("testOuter"); } @AfterEach void afterEach() { callSequence.add("afterEachMethodOuter"); } @Nested @ExtendWith(FizzTestExecutionCallbacks.class) class InnerTestCase { @BeforeEach void beforeInnerMethod() { callSequence.add("beforeEachMethodInner"); } @Test void testInner() { callSequence.add("testInner"); } @AfterEach void afterInnerMethod() { callSequence.add("afterEachMethodInner"); } } } @ExtendWith({ FooTestExecutionCallbacks.class, ExceptionThrowingBeforeTestExecutionCallback.class, BarTestExecutionCallbacks.class }) static class ExceptionInBeforeTestExecutionCallbackTestCase { @BeforeEach void beforeEach() { callSequence.add("beforeEachMethod"); } @Test void test() { callSequence.add("test"); } @AfterEach void afterEach() { callSequence.add("afterEachMethod"); } } @ExtendWith({ FooTestExecutionCallbacks.class, ExceptionThrowingAfterTestExecutionCallback.class, BarTestExecutionCallbacks.class }) static class ExceptionInAfterTestExecutionCallbackTestCase { @BeforeEach void beforeEach() { callSequence.add("beforeEachMethod"); } @Test void test() { callSequence.add("test"); } @AfterEach void afterEach() { callSequence.add("afterEachMethod"); } } @ExtendWith(FooTestExecutionCallbacks.class) static class ExceptionInBeforeEachMethodTestCase { @BeforeEach void beforeEach() { callSequence.add("beforeEachMethod"); throw new EnigmaException("@BeforeEach"); } @Test void test() { callSequence.add("test"); } @AfterEach void afterEach() { callSequence.add("afterEachMethod"); } } @ExtendWith(FooTestExecutionCallbacks.class) static class ExceptionInTestMethodTestCase { @BeforeEach void beforeEach() { callSequence.add("beforeEachMethod"); } @Test void test() { callSequence.add("test"); throw new EnigmaException("@Test"); } @AfterEach void afterEach() { callSequence.add("afterEachMethod"); } } // ------------------------------------------------------------------------- static class FooTestExecutionCallbacks implements BeforeTestExecutionCallback, AfterTestExecutionCallback { @Override public void beforeTestExecution(ExtensionContext context) { callSequence.add("fooBeforeTestExecutionCallback"); } @Override public void afterTestExecution(ExtensionContext context) { callSequence.add("fooAfterTestExecutionCallback"); actualExceptionInAfterTestExecution = context.getExecutionException(); } } static class BarTestExecutionCallbacks implements BeforeTestExecutionCallback, AfterTestExecutionCallback { @Override public void beforeTestExecution(ExtensionContext context) { callSequence.add("barBeforeTestExecutionCallback"); } @Override public void afterTestExecution(ExtensionContext context) { callSequence.add("barAfterTestExecutionCallback"); } } static class FizzTestExecutionCallbacks implements BeforeTestExecutionCallback, AfterTestExecutionCallback { @Override public void beforeTestExecution(ExtensionContext context) { callSequence.add("fizzBeforeTestExecutionCallback"); } @Override public void afterTestExecution(ExtensionContext context) { callSequence.add("fizzAfterTestExecutionCallback"); } } static class ExceptionThrowingBeforeTestExecutionCallback implements BeforeTestExecutionCallback { @Override public void beforeTestExecution(ExtensionContext context) { callSequence.add("exceptionThrowingBeforeTestExecutionCallback"); throw new EnigmaException("BeforeTestExecutionCallback"); } } static class ExceptionThrowingAfterTestExecutionCallback implements AfterTestExecutionCallback { @Override public void afterTestExecution(ExtensionContext context) { callSequence.add("exceptionThrowingAfterTestExecutionCallback"); throw new EnigmaException("AfterTestExecutionCallback"); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/CloseablePathTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static com.google.common.jimfs.Configuration.unix; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; import static java.nio.file.Files.createDirectory; import static java.nio.file.Files.createFile; import static java.nio.file.Files.createSymbolicLink; import static java.nio.file.Files.createTempDirectory; import static java.nio.file.Files.delete; import static java.nio.file.Files.deleteIfExists; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.junit.jupiter.api.condition.OS.WINDOWS; import static org.junit.jupiter.api.io.CleanupMode.ALWAYS; import static org.junit.jupiter.api.io.CleanupMode.NEVER; import static org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.io.File; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.Optional; import java.util.logging.Level; import java.util.logging.LogRecord; import com.google.common.jimfs.Jimfs; import org.assertj.core.api.ThrowableAssert.ThrowingCallable; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.condition.DisabledOnOs; import org.junit.jupiter.api.extension.AnnotatedElementContext; import org.junit.jupiter.api.extension.ExtensionConfigurationException; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.fixtures.TrackLogRecords; import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDirDeletionStrategy; import org.junit.jupiter.api.io.TempDirFactory; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.jupiter.engine.execution.NamespaceAwareStore; import org.junit.jupiter.engine.extension.TempDirectory.CloseablePath; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.engine.support.store.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; /** * Integration tests for the creation and cleanup of the {@link TempDirectory}. * * @since 5.9 */ @DisplayName("Temporary directory") class CloseablePathTests extends AbstractJupiterTestEngineTests { private final AnnotatedElementContext elementContext = mock(); private final ExtensionContext extensionContext = mock(); private @Nullable CloseablePath closeablePath; @Target(METHOD) @Retention(RUNTIME) @ValueSource(classes = { File.class, Path.class }) private @interface ElementTypeSource { } @BeforeEach void setUpExtensionContext() { var store = new NamespaceAwareStore(new NamespacedHierarchicalStore<>(null), Namespace.GLOBAL); when(extensionContext.getStore(any())).thenReturn(store); } /** * Integration tests for the creation of the {@link TempDirectory} based on the different result * that {@link TempDirFactory#createTempDirectory(AnnotatedElementContext, ExtensionContext)} may provide. * * @since 5.11 * @see TempDirFactory */ @Nested @DisplayName("creation") class Creation { private Path root; @BeforeEach void setUpRootFolder() throws IOException { root = createTempDirectory("root"); } @AfterEach void cleanupRoot() throws IOException { delete(root); } @DisplayName("succeeds if the factory returns a directory") @ParameterizedTest @ElementTypeSource void factoryReturnsDirectoryDynamic(Class elementType) throws IOException { TempDirFactory factory = (_, _) -> createDirectory(root.resolve("directory")); closeablePath = TempDirectory.createTempDir(factory, cleanup(ALWAYS), elementType, elementContext, extensionContext); assertThat(closeablePath.get()).isDirectory(); delete(closeablePath.get()); } @DisplayName("succeeds if the factory returns a symbolic link to a directory") @ParameterizedTest @ElementTypeSource @DisabledOnOs(WINDOWS) void factoryReturnsSymbolicLinkToDirectory(Class elementType) throws IOException { Path directory = createDirectory(root.resolve("directory")); TempDirFactory factory = (_, _) -> createSymbolicLink(root.resolve("symbolicLink"), directory); closeablePath = TempDirectory.createTempDir(factory, cleanup(ALWAYS), elementType, elementContext, extensionContext); assertThat(closeablePath.get()).isDirectory(); delete(closeablePath.get()); delete(directory); } @DisplayName("succeeds if the factory returns a directory on a non-default file system for a Path annotated element") @Test void factoryReturnsDirectoryOnNonDefaultFileSystemWithPath() throws IOException { TempDirFactory factory = new JimfsFactory(); closeablePath = TempDirectory.createTempDir(factory, cleanup(ALWAYS), Path.class, elementContext, extensionContext); assertThat(closeablePath.get()).isDirectory(); delete(closeablePath.get()); } @SuppressWarnings("DataFlowIssue") @DisplayName("fails if the factory returns null") @ParameterizedTest @ElementTypeSource void factoryReturnsNull(Class elementType) throws IOException { TempDirFactory factory = spy(new Factory(null)); assertThatExtensionConfigurationExceptionIsThrownBy(() -> TempDirectory.createTempDir(factory, cleanup(ALWAYS), elementType, elementContext, extensionContext)); verify(factory).close(); } @DisplayName("fails if the factory returns a file") @ParameterizedTest @ElementTypeSource void factoryReturnsFile(Class elementType) throws IOException { Path file = createFile(root.resolve("file")); TempDirFactory factory = spy(new Factory(file)); assertThatExtensionConfigurationExceptionIsThrownBy(() -> TempDirectory.createTempDir(factory, cleanup(ALWAYS), elementType, elementContext, extensionContext)); verify(factory).close(); assertThat(file).doesNotExist(); } @DisplayName("fails if the factory returns a symbolic link to a file") @ParameterizedTest @ElementTypeSource @DisabledOnOs(WINDOWS) void factoryReturnsSymbolicLinkToFile(Class elementType) throws IOException { Path file = createFile(root.resolve("file")); Path symbolicLink = createSymbolicLink(root.resolve("symbolicLink"), file); TempDirFactory factory = spy(new Factory(symbolicLink)); assertThatExtensionConfigurationExceptionIsThrownBy(() -> TempDirectory.createTempDir(factory, cleanup(ALWAYS), elementType, elementContext, extensionContext)); verify(factory).close(); assertThat(symbolicLink).doesNotExist(); delete(file); } @DisplayName("fails if the factory returns a directory on a non-default file system for a File annotated element") @Test void factoryReturnsDirectoryOnNonDefaultFileSystemWithFile() throws IOException { TempDirFactory factory = spy(new JimfsFactory()); assertThatExceptionOfType(ExtensionConfigurationException.class)// .isThrownBy(() -> TempDirectory.createTempDir(factory, cleanup(ALWAYS), File.class, elementContext, extensionContext))// .withMessage("Failed to create default temp directory")// .withCauseInstanceOf(PreconditionViolationException.class)// .havingCause().withMessage("temp directory with non-default file system cannot be injected into " + File.class.getName() + " target"); verify(factory).close(); } // Mockito spying a lambda fails with: VM does not support modification of given type @NullMarked private record Factory(Path path) implements TempDirFactory { @Override public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) { return path; } } @NullMarked private static class JimfsFactory implements TempDirFactory { private final FileSystem fileSystem = Jimfs.newFileSystem(unix()); @Override public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) throws Exception { return createDirectory(fileSystem.getPath("/").resolve("directory")); } @Override public void close() throws IOException { fileSystem.close(); } } private static void assertThatExtensionConfigurationExceptionIsThrownBy(ThrowingCallable callable) { assertThatExceptionOfType(ExtensionConfigurationException.class)// .isThrownBy(callable)// .withMessage("Failed to create default temp directory")// .withCauseInstanceOf(PreconditionViolationException.class)// .havingCause().withMessage("temp directory must be a directory"); } } /** * Integration tests for cleanup of the {@link TempDirectory} when the {@link CleanupMode} is * set to {@link CleanupMode#ALWAYS}, {@link CleanupMode#NEVER}, or {@link CleanupMode#ON_SUCCESS}. * * @since 5.9 * @see TempDir * @see CleanupMode */ @Nested @DisplayName("cleanup") class Cleanup { private final TempDirFactory factory = spy(TempDirFactory.Standard.INSTANCE); @AfterEach void cleanupTempDirectory() throws IOException { if (closeablePath != null) { deleteIfExists(closeablePath.get()); } } @DisplayName("is done for a cleanup mode of ALWAYS") @ParameterizedTest @ElementTypeSource void always(Class elementType, @TrackLogRecords LogRecordListener listener) throws IOException { reset(factory); closeablePath = TempDirectory.createTempDir(factory, cleanup(ALWAYS), elementType, elementContext, extensionContext); assertThat(closeablePath.get()).isDirectory(); closeablePath.close(); verify(factory).close(); assertThat(closeablePath.get()).doesNotExist(); assertThat(listener.stream(Level.INFO)).map(LogRecord::getMessage)// .noneMatch(m -> m.startsWith("Skipping cleanup of temp dir")); } @DisplayName("is not done for a cleanup mode of NEVER") @ParameterizedTest @ElementTypeSource void never(Class elementType, @TrackLogRecords LogRecordListener listener) throws Exception { reset(factory); when(elementContext.getAnnotatedElement()).thenReturn(TestCase.class.getDeclaredField("tempDir")); closeablePath = TempDirectory.createTempDir(factory, cleanup(NEVER), elementType, elementContext, extensionContext); assertThat(closeablePath.get()).isDirectory(); closeablePath.close(); verify(factory).close(); assertThat(closeablePath.get()).exists(); assertThat(listener.stream(Level.INFO)).map(LogRecord::getMessage)// .anyMatch(m -> m.startsWith("Skipping cleanup of temp dir ") && m.endsWith(" for field TestCase.tempDir due to CleanupMode.NEVER.")); } @DisplayName("is not done for a cleanup mode of ON_SUCCESS, if there is an exception (for annotated field)") @ParameterizedTest @ElementTypeSource void onSuccessWithExceptionForAnnotatedField(Class elementType, @TrackLogRecords LogRecordListener listener) throws Exception { Field field = TestCase.class.getDeclaredField("tempDir"); onSuccessWithException(elementType, listener, field, " for field TestCase.tempDir due to CleanupMode.ON_SUCCESS."); } @DisplayName("is not done for a cleanup mode of ON_SUCCESS, if there is an exception (for annotated method parameter)") @ParameterizedTest @ElementTypeSource void onSuccessWithExceptionForAnnotatedMethodParameter(Class elementType, @TrackLogRecords LogRecordListener listener) throws Exception { Method method = TestCase.class.getDeclaredMethod("test", TestInfo.class, Path.class); Parameter parameter = method.getParameters()[1]; onSuccessWithException(elementType, listener, parameter, "for parameter 'tempDir' in method test(TestInfo, Path) due to CleanupMode.ON_SUCCESS."); } @DisplayName("is not done for a cleanup mode of ON_SUCCESS, if there is an exception (for annotated constructor parameter)") @ParameterizedTest @ElementTypeSource void onSuccessWithExceptionForAnnotatedConstructorParameter(Class elementType, @TrackLogRecords LogRecordListener listener) throws Exception { Constructor constructor = TestCase.class.getDeclaredConstructor(TestInfo.class, Path.class); Parameter parameter = constructor.getParameters()[1]; onSuccessWithException(elementType, listener, parameter, "for parameter 'tempDir' in constructor TestCase(TestInfo, Path) due to CleanupMode.ON_SUCCESS."); } private void onSuccessWithException(Class elementType, @TrackLogRecords LogRecordListener listener, AnnotatedElement annotatedElement, String expectedMessage) throws Exception { reset(factory); when(extensionContext.getExecutionException()).thenReturn(Optional.of(new Exception())); when(elementContext.getAnnotatedElement()).thenReturn(annotatedElement); closeablePath = TempDirectory.createTempDir(factory, cleanup(ON_SUCCESS), elementType, elementContext, extensionContext); assertThat(closeablePath.get()).isDirectory(); closeablePath.close(); verify(factory).close(); assertThat(closeablePath.get()).exists(); assertThat(listener.stream(Level.INFO)).map(LogRecord::getMessage)// .anyMatch(m -> m.startsWith("Skipping cleanup of temp dir ") && m.endsWith(expectedMessage)); } @DisplayName("is done for a cleanup mode of ON_SUCCESS, if there is no exception") @ParameterizedTest @ElementTypeSource void onSuccessWithNoException(Class elementType, @TrackLogRecords LogRecordListener listener) throws IOException { reset(factory); when(extensionContext.getExecutionException()).thenReturn(Optional.empty()); closeablePath = TempDirectory.createTempDir(factory, cleanup(ON_SUCCESS), elementType, elementContext, extensionContext); assertThat(closeablePath.get()).isDirectory(); closeablePath.close(); verify(factory).close(); assertThat(closeablePath.get()).doesNotExist(); assertThat(listener.stream(Level.INFO)).map(LogRecord::getMessage)// .noneMatch(m -> m.startsWith("Skipping cleanup of temp dir")); } @DisplayName("deletes symbolic links targeting directory inside temp dir") @ParameterizedTest @ElementTypeSource @DisabledOnOs(WINDOWS) void deletesSymbolicLinksTargetingDirInsideTempDir(Class elementType, @TrackLogRecords LogRecordListener listener) throws IOException { reset(factory); closeablePath = TempDirectory.createTempDir(factory, cleanup(ON_SUCCESS), elementType, elementContext, extensionContext); var rootDir = closeablePath.get(); assertThat(rootDir).isDirectory(); var subDir = createDirectory(rootDir.resolve("subDir")); Files.createFile(subDir.resolve("file")); Files.createSymbolicLink(rootDir.resolve("symbolicLink"), subDir); closeablePath.close(); verify(factory).close(); assertThat(rootDir).doesNotExist(); assertThat(listener.stream(Level.WARNING)).map(LogRecord::getMessage).isEmpty(); } @DisplayName("deletes symbolic links targeting directory outside temp dir") @ParameterizedTest @ElementTypeSource @DisabledOnOs(WINDOWS) void deletesSymbolicLinksTargetingDirOutsideTempDir(Class elementType, @TrackLogRecords LogRecordListener listener) throws IOException { reset(factory); closeablePath = TempDirectory.createTempDir(factory, cleanup(ON_SUCCESS), elementType, elementContext, extensionContext); var rootDir = closeablePath.get(); assertThat(rootDir).isDirectory(); var directoryOutsideTempDir = createTempDirectory("junit-"); try { var symbolicLink = createSymbolicLink(rootDir.resolve("symbolicLink"), directoryOutsideTempDir); closeablePath.close(); verify(factory).close(); assertThat(rootDir).doesNotExist(); assertThat(directoryOutsideTempDir).isDirectory(); assertThat(listener.stream(Level.WARNING)) // .map(LogRecord::getMessage) // .contains(("Deleting symbolic link from location inside of temp dir (%s) " + "to location outside of temp dir (%s) but not the target file/directory").formatted( symbolicLink, directoryOutsideTempDir.toRealPath())); } finally { Files.deleteIfExists(directoryOutsideTempDir); } } } private static TempDirectory.Cleanup cleanup(CleanupMode cleanupMode) { return new TempDirectory.Cleanup(cleanupMode, () -> TempDirDeletionStrategy.Standard.INSTANCE); } @NullUnmarked static class TestCase { Path tempDir; TestCase(TestInfo testInfo, Path tempDir) { } void test(TestInfo testInfo, Path tempDir) { } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ConfigLoaderExtension.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; /** * Demo extension for auto-detection of extensions loaded via Java's * {@link java.util.ServiceLoader} mechanism. * * @since 5.11 */ public class ConfigLoaderExtension implements BeforeAllCallback { @Override public void beforeAll(ExtensionContext context) { } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/DefaultTestReporterTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullFor; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import org.junit.jupiter.api.MediaType; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.function.ThrowingConsumer; import org.junit.jupiter.api.io.TempDir; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoSettings; @MockitoSettings class DefaultTestReporterTests { @TempDir Path tempDir; @Mock ExtensionContext extensionContext; @Captor ArgumentCaptor> actionCaptor; @InjectMocks DefaultTestReporter testReporter; @Test void copiesExistingFileToTarget() throws Throwable { testReporter.publishFile(Files.writeString(tempDir.resolve("source"), "content"), MediaType.TEXT_PLAIN_UTF_8); verify(extensionContext).publishFile(eq("source"), eq(MediaType.TEXT_PLAIN_UTF_8), actionCaptor.capture()); actionCaptor.getValue().accept(tempDir.resolve("target")); assertThat(tempDir.resolve("target")).usingCharset(UTF_8).hasContent("content"); } @Test void executesCustomActionWithTargetFile() throws Throwable { testReporter.publishFile("target", MediaType.APPLICATION_OCTET_STREAM, file -> Files.write(file, "content".getBytes())); verify(extensionContext).publishFile(eq("target"), eq(MediaType.APPLICATION_OCTET_STREAM), actionCaptor.capture()); actionCaptor.getValue().accept(tempDir.resolve("target")); assertThat(tempDir.resolve("target")).hasContent("content"); } @Test void copiesExistingDirectoryToTarget() throws Throwable { var source = Files.createDirectory(tempDir.resolve("source")); Files.writeString(source.resolve("source1"), "content1"); var sourceSubDir = Files.createDirectory(source.resolve("subDir")); Files.writeString(sourceSubDir.resolve("source2"), "content2"); Files.writeString(Files.createDirectory(sourceSubDir.resolve("subSubDir")).resolve("source3"), "content3"); testReporter.publishDirectory(source); verify(extensionContext).publishDirectory(eq("source"), actionCaptor.capture()); var target = tempDir.resolve("target"); actionCaptor.getValue().accept(target); assertThat(target).isDirectory(); assertThat(target.resolve("source1")).usingCharset(UTF_8).hasContent("content1"); var targetSubDir = target.resolve("subDir"); assertThat(targetSubDir.resolve("source2")).usingCharset(UTF_8).hasContent("content2"); assertThat(targetSubDir.resolve("subSubDir").resolve("source3")).usingCharset(UTF_8).hasContent("content3"); } @Test void executesCustomActionWithTargetDirectory() throws Throwable { testReporter.publishDirectory("target", dir -> Files.writeString(Files.createDirectory(dir).resolve("file"), "content", Charset.defaultCharset())); verify(extensionContext).publishDirectory(eq("target"), actionCaptor.capture()); var target = tempDir.resolve("target"); actionCaptor.getValue().accept(target); assertThat(target.resolve("file")).hasContent("content"); } @Test @SuppressWarnings("DataFlowIssue") // publishFile() parameters are not @Nullable void failsWhenPublishingNullFile() { assertPreconditionViolationNotNullFor("file", () -> testReporter.publishFile(null, MediaType.TEXT_PLAIN)); } @Test @SuppressWarnings("DataFlowIssue") // publishFile() parameters are not @Nullable void failsWhenPublishingFileWithNullMediaType() { assertPreconditionViolationNotNullFor("mediaType", () -> testReporter.publishFile(Path.of("test"), (MediaType) null)); } @Test void failsWhenPublishingMissingFile() { var missingFile = tempDir.resolve("missingFile"); assertPreconditionViolationFor(() -> testReporter.publishFile(missingFile, MediaType.APPLICATION_OCTET_STREAM))// .withMessage("file must exist: " + missingFile); } @Test void failsWhenPublishingDirectoryAsFile() { assertPreconditionViolationFor(() -> testReporter.publishFile(tempDir, MediaType.APPLICATION_OCTET_STREAM))// .withMessage("file must be a regular file: " + tempDir); } @Test @SuppressWarnings("DataFlowIssue") // publishDirectory() parameters are not @Nullable void failsWhenPublishingNullDirectory() { assertPreconditionViolationNotNullFor("directory", () -> testReporter.publishDirectory(null)); } @Test void failsWhenPublishingMissingDirectory() { var missingDir = tempDir.resolve("missingDir"); assertPreconditionViolationFor(() -> testReporter.publishDirectory(missingDir))// .withMessage("directory must exist: " + missingDir); } @Test void failsWhenPublishingFileAsDirectory() throws Exception { var dir = Files.createFile(tempDir.resolve("source")); assertPreconditionViolationFor(() -> testReporter.publishDirectory(dir))// .withMessage("path must represent a directory: " + dir); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/EnigmaException.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; @SuppressWarnings("serial") class EnigmaException extends RuntimeException { EnigmaException(String message) { super(message); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/EventuallyInterruptibleInvocation.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static org.assertj.core.api.Assertions.assertThat; import java.util.stream.IntStream; import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; /** * @since 5.5 */ class EventuallyInterruptibleInvocation implements Invocation { @Override public Void proceed() { while (!Thread.currentThread().isInterrupted()) { assertThat(IntStream.range(1, 1_000_000).sum()).isGreaterThan(0); } return null; } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ExecutionConditionTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Constants.DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.util.SetSystemProperty; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.jupiter.engine.JupiterTestEngine; import org.junit.jupiter.engine.extension.sub.AlwaysDisabledCondition; import org.junit.jupiter.engine.extension.sub.AnotherAlwaysDisabledCondition; import org.junit.jupiter.engine.extension.sub.SystemPropertyCondition; import org.junit.jupiter.engine.extension.sub.SystemPropertyCondition.SystemProperty; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.testkit.engine.EngineExecutionResults; import org.junit.platform.testkit.engine.Events; /** * Integration tests that verify support for the {@link ExecutionCondition} * extension point in the {@link JupiterTestEngine}. * * @since 5.0 */ @SetSystemProperty(key = ExecutionConditionTests.FOO, value = ExecutionConditionTests.BAR) class ExecutionConditionTests extends AbstractJupiterTestEngineTests { static final String FOO = "DisabledTests.foo"; static final String BAR = "DisabledTests.bar"; private static final String BOGUS = "DisabledTests.bogus"; private static final String DEACTIVATE = "*AnotherAlwaysDisable*, org.junit.jupiter.engine.extension.sub.AlwaysDisable*"; @Test void conditionWorksOnContainer() { EngineExecutionResults executionResults = executeTestsForClass(TestCaseWithExecutionConditionOnClass.class); executionResults.containerEvents().assertStatistics(stats -> stats.skipped(1)); executionResults.testEvents().assertStatistics(stats -> stats.started(0)); } @Test void conditionWorksOnTest() { Events tests = executeTestsForClass(TestCaseWithExecutionConditionOnMethods.class).testEvents(); tests.assertStatistics(stats -> stats.started(2).succeeded(2).skipped(3)); } @Test void overrideConditionsUsingFullyQualifiedClassName() { String deactivatePattern = SystemPropertyCondition.class.getName() + "," + DEACTIVATE; assertExecutionConditionOverride(deactivatePattern, 1, 1); assertExecutionConditionOverride(deactivatePattern, 4, 2, 2); } @Test void overrideConditionsUsingStar() { // "*" should deactivate DisabledCondition and SystemPropertyCondition String deactivatePattern = "*"; assertExecutionConditionOverride(deactivatePattern, 2, 2); assertExecutionConditionOverride(deactivatePattern, 5, 2, 3); } @Test void overrideConditionsUsingStarPlusSimpleClassName() { // DisabledCondition should remain activated String deactivatePattern = "*" + SystemPropertyCondition.class.getSimpleName() + ", " + DEACTIVATE; assertExecutionConditionOverride(deactivatePattern, 1, 1); assertExecutionConditionOverride(deactivatePattern, 4, 2, 2); } @Test void overrideConditionsUsingPackageNamePlusDotStar() { // DisabledCondition should remain activated String deactivatePattern = DEACTIVATE + ", " + SystemPropertyCondition.class.getPackage().getName() + ".*"; assertExecutionConditionOverride(deactivatePattern, 1, 1); assertExecutionConditionOverride(deactivatePattern, 4, 2, 2); } @Test void overrideConditionsUsingMultipleWildcards() { // DisabledCondition should remain activated String deactivatePattern = "org.junit.jupiter.*.System*Condition" + "," + DEACTIVATE; assertExecutionConditionOverride(deactivatePattern, 1, 1); assertExecutionConditionOverride(deactivatePattern, 4, 2, 2); } @Test void deactivateAllConditions() { // DisabledCondition should remain activated String deactivatePattern = "org.junit.jupiter.*.System*Condition" + ", " + DEACTIVATE; assertExecutionConditionOverride(deactivatePattern, 1, 1); assertExecutionConditionOverride(deactivatePattern, 4, 2, 2); } private void assertExecutionConditionOverride(String deactivatePattern, int testStartedCount, int testFailedCount) { // @formatter:off LauncherDiscoveryRequest request = request() .selectors(selectClass(TestCaseWithExecutionConditionOnClass.class)) .configurationParameter(DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME, deactivatePattern) .build(); // @formatter:on EngineExecutionResults executionResults = executeTests(request); Events containers = executionResults.containerEvents(); Events tests = executionResults.testEvents(); containers.assertStatistics(stats -> stats.skipped(0).started(2)); tests.assertStatistics(stats -> stats.started(testStartedCount).failed(testFailedCount)); } private void assertExecutionConditionOverride(String deactivatePattern, int started, int succeeded, int failed) { // @formatter:off LauncherDiscoveryRequest request = request() .selectors(selectClass(TestCaseWithExecutionConditionOnMethods.class)) .configurationParameter(DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME, deactivatePattern) .build(); // @formatter:on executeTests(request).testEvents().assertStatistics( stats -> stats.started(started).succeeded(succeeded).failed(failed)); } // ------------------------------------------------------------------- @SystemProperty(key = FOO, value = BOGUS) @DeactivatedConditions static class TestCaseWithExecutionConditionOnClass { @Test void disabledTest() { fail("this should be disabled"); } @Test @Disabled void atDisabledTest() { fail("this should be @Disabled"); } } static class TestCaseWithExecutionConditionOnMethods { @Test void enabledTest() { } @Test @Disabled @DeactivatedConditions void atDisabledTest() { fail("this should be @Disabled"); } @Test @SystemProperty(key = FOO, value = BAR) void systemPropertyEnabledTest() { } @Test @DeactivatedConditions @SystemProperty(key = FOO, value = BOGUS) void systemPropertyWithIncorrectValueTest() { fail("this should be disabled"); } @Test @DeactivatedConditions @SystemProperty(key = BOGUS, value = "doesn't matter") void systemPropertyNotSetTest() { fail("this should be disabled"); } } @Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @ExtendWith({ AlwaysDisabledCondition.class, AnotherAlwaysDisabledCondition.class }) @interface DeactivatedConditions { } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ExtensionContextExecutionTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import java.lang.reflect.Method; import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; import org.jspecify.annotations.NullMarked; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContextParameterResolver; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; class ExtensionContextExecutionTests extends AbstractJupiterTestEngineTests { @Test @ExtendWith(ExtensionContextParameterResolver.class) void extensionContextHierarchy(ExtensionContext methodExtensionContext) { assertThat(methodExtensionContext).isNotNull(); assertThat(methodExtensionContext.getElement()).containsInstanceOf(Method.class); Optional classExtensionContext = methodExtensionContext.getParent(); assertThat(classExtensionContext).isNotEmpty(); assertThat(classExtensionContext.orElseThrow().getElement()).contains(ExtensionContextExecutionTests.class); Optional engineExtensionContext = classExtensionContext.orElseThrow().getParent(); assertThat(engineExtensionContext).isNotEmpty(); assertThat(engineExtensionContext.orElseThrow().getElement()).isEmpty(); assertThat(engineExtensionContext.orElseThrow().getParent()).isEmpty(); } @Test void twoTestClassesCanShareStateViaEngineExtensionContext() { Parent.counter.set(0); executeTests(selectClass(A.class), selectClass(B.class)).testEvents()// .assertStatistics(stats -> stats.started(2)); assertThat(Parent.counter).hasValue(1); } @SuppressWarnings("NewClassNamingConvention") @ExtendWith(OnlyIncrementCounterOnce.class) static class Parent { static final AtomicInteger counter = new AtomicInteger(0); @Test void test() { } } @SuppressWarnings("NewClassNamingConvention") static class A extends Parent { } @SuppressWarnings("NewClassNamingConvention") static class B extends Parent { } @NullMarked static class OnlyIncrementCounterOnce implements BeforeAllCallback { @Override public void beforeAll(ExtensionContext context) { ExtensionContext.Store store = getRoot(context).getStore(ExtensionContext.Namespace.GLOBAL); store.computeIfAbsent("counter", key -> Parent.counter.incrementAndGet()); } private ExtensionContext getRoot(ExtensionContext context) { return context.getParent().map(this::getRoot).orElse(context); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistrationViaParametersAndFieldsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotatedFields; import static org.junit.platform.commons.support.ReflectionSupport.makeAccessible; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.List; import java.util.function.Predicate; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.stream.IntStream; import java.util.stream.Stream; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; 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.Constants; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.TestInstancePostProcessor; import org.junit.jupiter.api.extension.TestTemplateInvocationContext; import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; import org.junit.jupiter.api.fixtures.TrackLogRecords; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.jupiter.engine.execution.injection.sample.LongParameterResolver; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.commons.support.ModifierSupport; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.engine.support.hierarchical.ParallelHierarchicalTestExecutorServiceFactory; import org.junit.platform.engine.support.hierarchical.ParallelHierarchicalTestExecutorServiceFactory.ParallelExecutorServiceType; import org.junit.platform.testkit.engine.EngineExecutionResults; /** * Integration tests that verify support for extension registration via * {@link ExtendWith @ExtendWith} on parameters and fields. * * @since 5.8 */ class ExtensionRegistrationViaParametersAndFieldsTests extends AbstractJupiterTestEngineTests { @Test void constructorParameter() { assertOneTestSucceeded(ConstructorParameterTestCase.class); } @Test void constructorParameterForNestedTestClass() { assertTestsSucceeded(NestedConstructorParameterTestCase.class, 2); } @Test void beforeAllMethodParameter() { assertOneTestSucceeded(BeforeAllParameterTestCase.class); } @Test void afterAllMethodParameter() { assertOneTestSucceeded(AfterAllParameterTestCase.class); } @Test void beforeEachMethodParameter() { assertOneTestSucceeded(BeforeEachParameterTestCase.class); } @Test void afterEachMethodParameter() { assertOneTestSucceeded(AfterEachParameterTestCase.class); } @Test void testMethodParameter() { assertOneTestSucceeded(TestMethodParameterTestCase.class); } @Test void testFactoryMethodParameter() { assertTestsSucceeded(TestFactoryMethodParameterTestCase.class, 2); } @Test void testTemplateMethodParameter() { assertTestsSucceeded(TestTemplateMethodParameterTestCase.class, 2); } @Test void multipleRegistrationsViaParameter(@TrackLogRecords LogRecordListener listener) { assertOneTestSucceeded(MultipleRegistrationsViaParameterTestCase.class); assertThat(getRegisteredLocalExtensions(listener)).containsExactly("LongParameterResolver", "DummyExtension"); } @Test void staticField() { assertOneTestSucceeded(StaticFieldTestCase.class); } @Test void instanceField() { assertOneTestSucceeded(InstanceFieldTestCase.class); } @Test void fieldsWithTestInstancePerClass() { assertOneTestSucceeded(TestInstancePerClassFieldTestCase.class); } @ParameterizedTest @ValueSource(classes = { MultipleMixedRegistrationsViaFieldTestCase.class, MultipleExtendWithRegistrationsViaFieldTestCase.class }) void multipleRegistrationsViaField(Class testClass, @TrackLogRecords LogRecordListener listener) { assertOneTestSucceeded(testClass); assertThat(getRegisteredLocalExtensions(listener)).containsExactly("LongParameterResolver", "DummyExtension"); } @Test void duplicateRegistrationViaField() { Class testClass = DuplicateRegistrationViaFieldTestCase.class; String expectedMessage = "Failed to register extension via field " + "[org.junit.jupiter.api.extension.Extension " + "org.junit.jupiter.engine.extension.ExtensionRegistrationViaParametersAndFieldsTests$DuplicateRegistrationViaFieldTestCase.dummy]. " + "The field registers an extension of type [org.junit.jupiter.engine.extension.DummyExtension] " + "via @RegisterExtension and @ExtendWith, but only one registration of a given extension type is permitted."; executeTestsForClass(testClass).testEvents().assertThatEvents().haveExactly(1, finishedWithFailure(instanceOf(PreconditionViolationException.class), message(expectedMessage))); } @ParameterizedTest(name = "{0}") @ValueSource(classes = { AllInOneWithTestInstancePerMethodTestCase.class, AllInOneWithTestInstancePerClassTestCase.class }) void registrationOrder(Class testClass, @TrackLogRecords LogRecordListener listener) { assertOneTestSucceeded(testClass); assertThat(getRegisteredLocalExtensions(listener))// .containsExactly(// "ClassLevelExtension2", // @RegisterExtension on static field "StaticField2", // @ExtendWith on static field "ClassLevelExtension1", // @RegisterExtension on static field "StaticField1", // @ExtendWith on static field "ConstructorParameter", // @ExtendWith on parameter in constructor "BeforeAllParameter", // @ExtendWith on parameter in static @BeforeAll method "BeforeEachParameter", // @ExtendWith on parameter in @BeforeEach method "AfterEachParameter", // @ExtendWith on parameter in @AfterEach method "AfterAllParameter", // @ExtendWith on parameter in static @AfterAll method "InstanceLevelExtension1", // @RegisterExtension on instance field "InstanceField1", // @ExtendWith on instance field "InstanceLevelExtension2", // @RegisterExtension on instance field "InstanceField2", // @ExtendWith on instance field "TestParameter" // @ExtendWith on parameter in @Test method ); } @Test void registersProgrammaticTestInstancePostProcessors() { assertOneTestSucceeded(ProgrammaticTestInstancePostProcessorTestCase.class); } @ParameterizedTest @EnumSource(ParallelExecutorServiceType.class) void createsExtensionPerInstance( ParallelHierarchicalTestExecutorServiceFactory.ParallelExecutorServiceType executorServiceType) { var results = executeTests(request() // .selectors(selectClass(InitializationPerInstanceTestCase.class)) // .configurationParameter(Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, "true") // .configurationParameter(Constants.PARALLEL_CONFIG_EXECUTOR_SERVICE_PROPERTY_NAME, executorServiceType.name()) // ); assertTestsSucceeded(results, 100); } private List getRegisteredLocalExtensions(LogRecordListener listener) { return listener.stream(MutableExtensionRegistry.class, Level.FINER) // .map(LogRecord::getMessage) // .filter(message -> message.contains("local extension")) // .map(message -> { message = message.replaceAll(" from source .+", ""); int beginIndex = message.lastIndexOf('.') + 1; if (message.contains("late-init")) { return message.substring(beginIndex, message.indexOf("]")); } else { int indexOfDollarSign = message.indexOf("$"); int indexOfAtSign = message.indexOf("@"); int endIndex = (indexOfDollarSign > 1 ? indexOfDollarSign : indexOfAtSign); return message.substring(beginIndex, endIndex); } }) // .toList(); } private void assertOneTestSucceeded(Class testClass) { assertTestsSucceeded(testClass, 1); } private void assertTestsSucceeded(Class testClass, int expected) { assertTestsSucceeded(executeTestsForClass(testClass), expected); } private static void assertTestsSucceeded(EngineExecutionResults results, int expected) { results.testEvents().assertStatistics( stats -> stats.started(expected).succeeded(expected).skipped(0).aborted(0).failed(0)); } // ------------------------------------------------------------------- /** * The {@link MagicParameter.Extension} is first registered for the constructor * and then used for lifecycle and test methods. */ @ExtendWith(LongParameterResolver.class) static class ConstructorParameterTestCase { ConstructorParameterTestCase(@MagicParameter("constructor") String text) { assertThat(text).isEqualTo("ConstructorParameterTestCase-0-constructor"); } @BeforeEach void beforeEach(String text, TestInfo testInfo) { assertThat(text).isEqualTo("beforeEach-0-enigma"); assertThat(testInfo).isNotNull(); } @Test void test(TestInfo testInfo, String text) { assertThat(testInfo).isNotNull(); assertThat(text).isEqualTo("test-1-enigma"); } /** * Redeclaring {@code @MagicParameter} should not result in a * {@link ParameterResolutionException}. */ @AfterEach void afterEach(Long number, TestInfo testInfo, @MagicParameter("method") String text) { assertThat(number).isEqualTo(42L); assertThat(testInfo).isNotNull(); assertThat(text).isEqualTo("afterEach-2-method"); } } /** * The {@link MagicParameter.Extension} is first registered for the constructor * and then used for lifecycle and test methods. */ @Nested @ExtendWith(LongParameterResolver.class) class NestedConstructorParameterTestCase { NestedConstructorParameterTestCase(TestInfo testInfo, @MagicParameter("constructor") String text) { assertThat(testInfo).isNotNull(); // Index is 2 instead of 1, since constructors for non-static nested classes // receive a reference to the enclosing instance as the first argument: this$0 assertThat(text).isEqualTo("NestedConstructorParameterTestCase-2-constructor"); } @BeforeEach void beforeEach(String text, TestInfo testInfo) { assertThat(text).isEqualTo("beforeEach-0-enigma"); assertThat(testInfo).isNotNull(); } @Test void test(TestInfo testInfo, String text) { assertThat(testInfo).isNotNull(); assertThat(text).isEqualTo("test-1-enigma"); } /** * Redeclaring {@code @MagicParameter} should not result in a * {@link ParameterResolutionException}. */ @AfterEach void afterEach(Long number, TestInfo testInfo, @MagicParameter("method") String text) { assertThat(number).isEqualTo(42L); assertThat(testInfo).isNotNull(); assertThat(text).isEqualTo("afterEach-2-method"); } @Nested class DoublyNestedConstructorParameterTestCase { DoublyNestedConstructorParameterTestCase(TestInfo testInfo, String text) { assertThat(testInfo).isNotNull(); // Index is 2 instead of 1, since constructors for non-static nested classes // receive a reference to the enclosing instance as the first argument: this$0 assertThat(text).isEqualTo("DoublyNestedConstructorParameterTestCase-2-enigma"); } @BeforeEach void beforeEach(String text, TestInfo testInfo) { assertThat(text).isEqualTo("beforeEach-0-enigma"); assertThat(testInfo).isNotNull(); } @Test void test(TestInfo testInfo, String text) { assertThat(testInfo).isNotNull(); assertThat(text).isEqualTo("test-1-enigma"); } /** * Redeclaring {@code @MagicParameter} should not result in a * {@link ParameterResolutionException}. */ @AfterEach void afterEach(Long number, TestInfo testInfo, @MagicParameter("method") String text) { assertThat(number).isEqualTo(42L); assertThat(testInfo).isNotNull(); assertThat(text).isEqualTo("afterEach-2-method"); } } } /** * The {@link MagicParameter.Extension} is first registered for the {@code @BeforeAll} * method and then used for other lifecycle methods and test methods. */ @ExtendWith(LongParameterResolver.class) static class BeforeAllParameterTestCase { @BeforeAll static void beforeAll(@MagicParameter("method") String text, TestInfo testInfo) { assertThat(text).isEqualTo("beforeAll-0-method"); assertThat(testInfo).isNotNull(); } @BeforeEach void beforeEach(String text, TestInfo testInfo) { assertThat(text).isEqualTo("beforeEach-0-enigma"); assertThat(testInfo).isNotNull(); } @Test void test(TestInfo testInfo, String text) { assertThat(testInfo).isNotNull(); assertThat(text).isEqualTo("test-1-enigma"); } /** * Redeclaring {@code @MagicParameter} should not result in a * {@link ParameterResolutionException}. */ @AfterEach void afterEach(Long number, TestInfo testInfo, @MagicParameter("method") String text) { assertThat(number).isEqualTo(42L); assertThat(testInfo).isNotNull(); assertThat(text).isEqualTo("afterEach-2-method"); } @AfterAll static void afterAll(String text, TestInfo testInfo) { assertThat(text).isEqualTo("afterAll-0-enigma"); assertThat(testInfo).isNotNull(); } } /** * The {@link MagicParameter.Extension} is first registered for the {@code @AfterAll} * method, but that registration occurs before the test method is invoked, which * allows the string parameters in the after-each and test methods to be resolved * by the {@link MagicParameter.Extension} as well. */ @ExtendWith(LongParameterResolver.class) static class AfterAllParameterTestCase { @Test void test(TestInfo testInfo, String text) { assertThat(testInfo).isNotNull(); assertThat(text).isEqualTo("test-1-enigma"); } @AfterEach void afterEach(Long number, TestInfo testInfo, String text) { assertThat(number).isEqualTo(42L); assertThat(testInfo).isNotNull(); assertThat(text).isEqualTo("afterEach-2-enigma"); } @AfterAll static void afterAll(Long number, TestInfo testInfo, @MagicParameter("method") String text) { assertThat(number).isEqualTo(42L); assertThat(testInfo).isNotNull(); assertThat(text).isEqualTo("afterAll-2-method"); } } /** * The {@link MagicParameter.Extension} is first registered for the {@code @BeforeEach} * method and then used for other lifecycle methods and test methods. */ @ExtendWith(LongParameterResolver.class) static class BeforeEachParameterTestCase { @BeforeEach void beforeEach(@MagicParameter("method") String text, TestInfo testInfo) { assertThat(text).isEqualTo("beforeEach-0-method"); assertThat(testInfo).isNotNull(); } @Test void test(TestInfo testInfo, String text) { assertThat(testInfo).isNotNull(); assertThat(text).isEqualTo("test-1-enigma"); } /** * Redeclaring {@code @MagicParameter} should not result in a * {@link ParameterResolutionException}. */ @AfterEach void afterEach(Long number, TestInfo testInfo, @MagicParameter("method") String text) { assertThat(number).isEqualTo(42L); assertThat(testInfo).isNotNull(); assertThat(text).isEqualTo("afterEach-2-method"); } } /** * The {@link MagicParameter.Extension} is first registered for the {@code @AfterEach} * method, but that registration occurs before the test method is invoked, which * allows the test method's parameter to be resolved by the {@link MagicParameter.Extension} * as well. */ @ExtendWith(LongParameterResolver.class) static class AfterEachParameterTestCase { @Test void test(TestInfo testInfo, String text) { assertThat(testInfo).isNotNull(); assertThat(text).isEqualTo("test-1-enigma"); } @AfterEach void afterEach(Long number, TestInfo testInfo, @MagicParameter("method") String text) { assertThat(number).isEqualTo(42L); assertThat(testInfo).isNotNull(); assertThat(text).isEqualTo("afterEach-2-method"); } } /** * The {@link MagicParameter.Extension} is first registered for the {@code @Test} * method and then used for after-each lifecycle methods. */ @ExtendWith(LongParameterResolver.class) static class TestMethodParameterTestCase { @Test void test(TestInfo testInfo, @MagicParameter("method") String text) { assertThat(testInfo).isNotNull(); assertThat(text).isEqualTo("test-1-method"); } @AfterEach void afterEach(Long number, TestInfo testInfo, String text) { assertThat(number).isEqualTo(42L); assertThat(testInfo).isNotNull(); assertThat(text).isEqualTo("afterEach-2-enigma"); } } /** * The {@link MagicParameter.Extension} is first registered for the {@code @TestFactory} * method and then used for after-each lifecycle methods. */ @ExtendWith(LongParameterResolver.class) static class TestFactoryMethodParameterTestCase { @SuppressWarnings("ConstantValue") @TestFactory Stream testFactory(TestInfo testInfo, @MagicParameter("method") String text) { assertThat(testInfo).isNotNull(); assertThat(text).isEqualTo("testFactory-1-method"); return IntStream.of(2, 4).mapToObj(num -> dynamicTest("" + num, () -> assertEquals(0, num % 2))); } @AfterEach void afterEach(Long number, TestInfo testInfo, String text) { assertThat(number).isEqualTo(42L); assertThat(testInfo).isNotNull(); assertThat(text).isEqualTo("afterEach-2-enigma"); } } /** * The {@link MagicParameter.Extension} is first registered for the {@code @TestTemplate} * method and then used for after-each lifecycle methods. */ @ExtendWith(LongParameterResolver.class) static class TestTemplateMethodParameterTestCase { @TestTemplate @ExtendWith(TwoInvocationsContextProvider.class) void testTemplate(TestInfo testInfo, @MagicParameter("method") String text) { assertThat(testInfo).isNotNull(); assertThat(text).isEqualTo("testTemplate-1-method"); } @AfterEach void afterEach(Long number, TestInfo testInfo, String text) { assertThat(number).isEqualTo(42L); assertThat(testInfo).isNotNull(); assertThat(text).isEqualTo("afterEach-2-enigma"); } } @NullMarked private static class TwoInvocationsContextProvider implements TestTemplateInvocationContextProvider { @Override public boolean supportsTestTemplate(ExtensionContext context) { return true; } @Override public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { return Stream.of(emptyTestTemplateInvocationContext(), emptyTestTemplateInvocationContext()); } private static TestTemplateInvocationContext emptyTestTemplateInvocationContext() { return new TestTemplateInvocationContext() { }; } } @ExtendWith(LongParameterResolver.class) static class MultipleRegistrationsViaParameterTestCase { @Test void test(@ExtendWith(DummyExtension.class) @ExtendWith(LongParameterResolver.class) Long number) { assertThat(number).isEqualTo(42L); } } static class MultipleMixedRegistrationsViaFieldTestCase { @ExtendWith(LongParameterResolver.class) @RegisterExtension DummyExtension dummy = new DummyExtension(); @SuppressWarnings("JUnitMalformedDeclaration") @Test void test(Long number) { assertThat(number).isEqualTo(42L); } } @NullUnmarked static class MultipleExtendWithRegistrationsViaFieldTestCase { @SuppressWarnings("unused") @ExtendWith(LongParameterResolver.class) @ExtendWith(DummyExtension.class) Object field; @SuppressWarnings("JUnitMalformedDeclaration") @Test void test(Long number) { assertThat(number).isEqualTo(42L); } } static class DuplicateRegistrationViaFieldTestCase { @ExtendWith(DummyExtension.class) @RegisterExtension Extension dummy = new DummyExtension(); @Test void test() { } } /** * The {@link MagicField.Extension} is registered via a static field. */ @NullUnmarked static class StaticFieldTestCase { @SuppressWarnings("unused") @MagicField private static String staticField1; @MagicField static String staticField2; @BeforeAll static void beforeAll() { assertThat(staticField1).isEqualTo("beforeAll - staticField1"); assertThat(staticField2).isEqualTo("beforeAll - staticField2"); } @Test void test() { assertThat(staticField1).isEqualTo("beforeAll - staticField1"); assertThat(staticField2).isEqualTo("beforeAll - staticField2"); } } /** * The {@link MagicField.Extension} is registered via an instance field. */ @NullUnmarked static class InstanceFieldTestCase { @MagicField String instanceField1; @MagicField private String instanceField2; @Test void test() { assertThat(instanceField1).isEqualTo("postProcessTestInstance - instanceField1"); assertThat(instanceField2).isEqualTo("postProcessTestInstance - instanceField2"); } } /** * The {@link MagicField.Extension} is registered via a static field and * an instance field. */ @NullUnmarked @TestInstance(Lifecycle.PER_CLASS) static class TestInstancePerClassFieldTestCase { @MagicField static String staticField; @MagicField String instanceField; @BeforeAll void beforeAll() { assertThat(staticField).isEqualTo("beforeAll - staticField"); assertThat(instanceField).isEqualTo("postProcessTestInstance - instanceField"); } @Test void test() { assertThat(staticField).isEqualTo("beforeAll - staticField"); assertThat(instanceField).isEqualTo("postProcessTestInstance - instanceField"); } } @NullUnmarked @SuppressWarnings("JUnitMalformedDeclaration") @TestInstance(Lifecycle.PER_METHOD) static class AllInOneWithTestInstancePerMethodTestCase { @StaticField1 @Order(Integer.MAX_VALUE) static String staticField1; @StaticField2 @ExtendWith(StaticField2.Extension.class) @Order(3) static String staticField2; @RegisterExtension private static final Extension classLevelExtension1 = new ClassLevelExtension1(); @RegisterExtension @Order(1) static Extension classLevelExtension2 = new ClassLevelExtension2(); @InstanceField1 @Order(2) String instanceField1; @InstanceField2 @ExtendWith(InstanceField2.Extension.class) String instanceField2; @RegisterExtension @Order(1) private final InstanceLevelExtension1 instanceLevelExtension1 = new InstanceLevelExtension1(); @RegisterExtension @Order(3) InstanceLevelExtension2 instanceLevelExtension2 = new InstanceLevelExtension2(); AllInOneWithTestInstancePerMethodTestCase(@ConstructorParameter String text) { assertThat(text).isEqualTo("enigma"); } @BeforeAll static void beforeAll(@ExtendWith(BeforeAllParameter.Extension.class) @BeforeAllParameter String text) { assertThat(text).isEqualTo("enigma"); assertThat(staticField1).isEqualTo("beforeAll - staticField1"); assertThat(staticField2).isEqualTo("beforeAll - staticField2"); } @BeforeEach void beforeEach(@BeforeEachParameter String text) { assertThat(text).isEqualTo("enigma"); assertThat(staticField1).isEqualTo("beforeAll - staticField1"); assertThat(staticField2).isEqualTo("beforeAll - staticField2"); assertThat(instanceField1).isEqualTo("postProcessTestInstance - instanceField1"); assertThat(instanceField2).isEqualTo("postProcessTestInstance - instanceField2"); } @Test void test(@TestParameter String text) { assertThat(text).isEqualTo("enigma"); assertThat(staticField1).isEqualTo("beforeAll - staticField1"); assertThat(staticField2).isEqualTo("beforeAll - staticField2"); assertThat(instanceField1).isEqualTo("postProcessTestInstance - instanceField1"); assertThat(instanceField2).isEqualTo("postProcessTestInstance - instanceField2"); } @AfterEach void afterEach(@AfterEachParameter String text) { assertThat(text).isEqualTo("enigma"); assertThat(staticField1).isEqualTo("beforeAll - staticField1"); assertThat(staticField2).isEqualTo("beforeAll - staticField2"); assertThat(instanceField1).isEqualTo("postProcessTestInstance - instanceField1"); assertThat(instanceField2).isEqualTo("postProcessTestInstance - instanceField2"); } @AfterAll static void afterAll(@AfterAllParameter String text) { assertThat(text).isEqualTo("enigma"); assertThat(staticField1).isEqualTo("beforeAll - staticField1"); assertThat(staticField2).isEqualTo("beforeAll - staticField2"); } } @TestInstance(Lifecycle.PER_CLASS) static class AllInOneWithTestInstancePerClassTestCase extends AllInOneWithTestInstancePerMethodTestCase { AllInOneWithTestInstancePerClassTestCase(@ConstructorParameter String text) { super(text); } } @NullUnmarked static class ProgrammaticTestInstancePostProcessorTestCase { @RegisterExtension static Extension resolver = new InstanceField2.Extension(); @InstanceField2 String instanceField2; @Test void test() { assertThat(instanceField2).isEqualTo("postProcessTestInstance - instanceField2"); } } @Execution(CONCURRENT) static class InitializationPerInstanceTestCase { @RegisterExtension Extension extension = new InstanceParameterResolver<>(this); @Nested class Wrapper { @RegisterExtension Extension extension = new InstanceParameterResolver<>(this); @RepeatedTest(100) void test(InitializationPerInstanceTestCase outerInstance, Wrapper innerInstance) { assertSame(InitializationPerInstanceTestCase.this, outerInstance); assertSame(Wrapper.this, innerInstance); } } @NullMarked private record InstanceParameterResolver(T instance) implements ParameterResolver { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return instance.getClass().equals(parameterContext.getParameter().getType()); } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return instance; } } } } @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @ExtendWith(MagicParameter.Extension.class) @interface MagicParameter { String value(); @NullMarked class Extension implements ParameterResolver { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return parameterContext.getParameter().getType() == String.class; } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { String text = parameterContext.findAnnotation(MagicParameter.class)// .map(MagicParameter::value)// .orElse("enigma"); Executable declaringExecutable = parameterContext.getDeclaringExecutable(); String name = declaringExecutable instanceof Constructor ? declaringExecutable.getDeclaringClass().getSimpleName() : declaringExecutable.getName(); return "%s-%d-%s".formatted(name, parameterContext.getIndex(), text); } } } @SuppressWarnings("unused") @NullMarked class BaseParameterExtension implements ParameterResolver { private final Class annotationType; @SuppressWarnings("unchecked") BaseParameterExtension() { Type genericSuperclass = getClass().getGenericSuperclass(); this.annotationType = (Class) ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0]; } @Override public final boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return parameterContext.isAnnotated(this.annotationType) && parameterContext.getParameter().getType() == String.class; } @Override public final Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return "enigma"; } } @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @ExtendWith(ConstructorParameter.Extension.class) @interface ConstructorParameter { class Extension extends BaseParameterExtension { } } @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) // Intentionally NOT annotated as follows // @ExtendWith(BeforeAllParameter.Extension.class) @interface BeforeAllParameter { class Extension extends BaseParameterExtension { } } @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @ExtendWith(AfterAllParameter.Extension.class) @interface AfterAllParameter { class Extension extends BaseParameterExtension { } } @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @ExtendWith(BeforeEachParameter.Extension.class) @interface BeforeEachParameter { class Extension extends BaseParameterExtension { } } @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @ExtendWith(AfterEachParameter.Extension.class) @interface AfterEachParameter { class Extension extends BaseParameterExtension { } } @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @ExtendWith(TestParameter.Extension.class) @interface TestParameter { class Extension extends BaseParameterExtension { } } class DummyExtension implements Extension { } @NullMarked class BaseFieldExtension implements BeforeAllCallback, TestInstancePostProcessor { private final Class annotationType; @SuppressWarnings("unchecked") BaseFieldExtension() { Type genericSuperclass = getClass().getGenericSuperclass(); this.annotationType = (Class) ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0]; } @Override public final void beforeAll(ExtensionContext context) { injectFields("beforeAll", context.getRequiredTestClass(), null, ModifierSupport::isStatic); } @Override public final void postProcessTestInstance(Object testInstance, ExtensionContext context) { injectFields("postProcessTestInstance", context.getRequiredTestClass(), testInstance, ModifierSupport::isNotStatic); } private void injectFields(String trigger, Class testClass, @Nullable Object instance, Predicate predicate) { findAnnotatedFields(testClass, this.annotationType, predicate).forEach(field -> { try { makeAccessible(field).set(instance, trigger + " - " + field.getName()); } catch (Throwable t) { throw ExceptionUtils.throwAsUncheckedException(t); } }); } } @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @ExtendWith(MagicField.Extension.class) @interface MagicField { class Extension extends BaseFieldExtension { } } @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @ExtendWith(InstanceField1.Extension.class) @interface InstanceField1 { class Extension extends BaseFieldExtension { } } @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) // Intentionally NOT annotated as follows // @ExtendWith(InstanceField2.Extension.class) @interface InstanceField2 { class Extension extends BaseFieldExtension { } } @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @ExtendWith(StaticField1.Extension.class) @interface StaticField1 { class Extension extends BaseFieldExtension { } } @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) // Intentionally NOT annotated as follows // @ExtendWith(StaticField2.Extension.class) @interface StaticField2 { class Extension extends BaseFieldExtension { } } class ClassLevelExtension1 implements Extension { } class ClassLevelExtension2 implements Extension { } class InstanceLevelExtension1 implements Extension { } class InstanceLevelExtension2 implements Extension { } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistryTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.engine.extension.MutableExtensionRegistry.createRegistryFrom; import static org.junit.jupiter.engine.extension.MutableExtensionRegistry.createRegistryWithDefaultExtensions; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Predicate; import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.InvocationInterceptor; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.platform.commons.util.ClassNamePatternFilterUtils; /** * Tests for the {@link MutableExtensionRegistry}. * * @since 5.0 */ class ExtensionRegistryTests { private static final int NUM_CORE_EXTENSIONS = 7; private static final int NUM_DEMO_EXTENSIONS = 2; private final JupiterConfiguration configuration = mock(); private MutableExtensionRegistry registry = createRegistryWithDefaultExtensions(configuration); @Test void newRegistryWithoutParentHasDefaultExtensions() { List extensions = registry.getExtensions(Extension.class); assertEquals(NUM_CORE_EXTENSIONS, extensions.size()); assertDefaultGlobalExtensionsAreRegistered(); } @Test void newRegistryWithoutParentHasDefaultExtensionsPlusAutodetectedExtensionsLoadedViaServiceLoader() { when(configuration.isExtensionAutoDetectionEnabled()).thenReturn(true); when(configuration.getFilterForAutoDetectedExtensions()).thenReturn(__ -> true); registry = createRegistryWithDefaultExtensions(configuration); List extensions = registry.getExtensions(Extension.class); assertEquals(NUM_CORE_EXTENSIONS + NUM_DEMO_EXTENSIONS, extensions.size()); assertDefaultGlobalExtensionsAreRegistered(4); assertExtensionRegistered(registry, ServiceLoaderExtension.class); assertEquals(4, countExtensions(registry, BeforeAllCallback.class)); } @Test void registryIncludesAndExcludesSpecificAutoDetectedExtensions() { when(configuration.isExtensionAutoDetectionEnabled()).thenReturn(true); when(configuration.getFilterForAutoDetectedExtensions()).thenReturn( extensionFilter(ServiceLoaderExtension.class.getName(), ConfigLoaderExtension.class.getName())); registry = createRegistryWithDefaultExtensions(configuration); List extensions = registry.getExtensions(Extension.class); assertEquals(NUM_CORE_EXTENSIONS + NUM_DEMO_EXTENSIONS - 1 /* exlcuded ConfigLoaderExtension */, extensions.size()); assertDefaultGlobalExtensionsAreRegistered(3); assertExtensionRegistered(registry, ServiceLoaderExtension.class); assertEquals(3, countExtensions(registry, BeforeAllCallback.class)); } @Test void registryIncludesAllAutoDetectedExtensionsAndExcludesNone() { when(configuration.isExtensionAutoDetectionEnabled()).thenReturn(true); when(configuration.getFilterForAutoDetectedExtensions()).thenReturn(extensionFilter("*", "")); registry = createRegistryWithDefaultExtensions(configuration); List extensions = registry.getExtensions(Extension.class); assertEquals(NUM_CORE_EXTENSIONS + NUM_DEMO_EXTENSIONS, extensions.size()); assertDefaultGlobalExtensionsAreRegistered(4); assertExtensionRegistered(registry, ServiceLoaderExtension.class); assertExtensionRegistered(registry, ConfigLoaderExtension.class); assertEquals(4, countExtensions(registry, BeforeAllCallback.class)); } @Test void registryIncludesSpecificAutoDetectedExtensionsAndExcludesAll() { when(configuration.isExtensionAutoDetectionEnabled()).thenReturn(true); when(configuration.getFilterForAutoDetectedExtensions()).thenReturn( extensionFilter(ServiceLoaderExtension.class.getName(), "*")); registry = createRegistryWithDefaultExtensions(configuration); List extensions = registry.getExtensions(Extension.class); assertEquals(NUM_CORE_EXTENSIONS, extensions.size()); assertDefaultGlobalExtensionsAreRegistered(2); assertExtensionNotRegistered(registry, ServiceLoaderExtension.class); assertEquals(2, countExtensions(registry, BeforeAllCallback.class)); } @Test void registryIncludesAndExcludesSameAutoDetectedExtension() { when(configuration.isExtensionAutoDetectionEnabled()).thenReturn(true); when(configuration.getFilterForAutoDetectedExtensions()).thenReturn( extensionFilter(ServiceLoaderExtension.class.getName(), ServiceLoaderExtension.class.getName())); registry = createRegistryWithDefaultExtensions(configuration); List extensions = registry.getExtensions(Extension.class); assertEquals(NUM_CORE_EXTENSIONS, extensions.size()); assertDefaultGlobalExtensionsAreRegistered(2); assertExtensionNotRegistered(registry, ServiceLoaderExtension.class); assertEquals(2, countExtensions(registry, BeforeAllCallback.class)); } @Test void registerExtensionByImplementingClass() { registry.registerExtension(MyExtension.class); assertExtensionRegistered(registry, MyExtension.class); registry.registerExtension(MyExtension.class); registry.registerExtension(MyExtension.class); registry.registerExtension(MyExtension.class); assertEquals(1, registry.getExtensions(MyExtension.class).size()); assertExtensionRegistered(registry, MyExtension.class); assertEquals(1, countExtensions(registry, MyExtensionApi.class)); registry.registerExtension(YourExtension.class); assertExtensionRegistered(registry, YourExtension.class); assertEquals(2, countExtensions(registry, MyExtensionApi.class)); } @Test void registerExtensionThatImplementsMultipleExtensionApis() { registry.registerExtension(MultipleExtension.class); assertExtensionRegistered(registry, MultipleExtension.class); assertEquals(1, countExtensions(registry, MyExtensionApi.class)); assertEquals(1, countExtensions(registry, AnotherExtensionApi.class)); } @Test void extensionsAreInheritedFromParent() { MutableExtensionRegistry parent = registry; parent.registerExtension(MyExtension.class); MutableExtensionRegistry child = createRegistryFrom(parent, Stream.of(YourExtension.class)); assertExtensionRegistered(child, MyExtension.class); assertExtensionRegistered(child, YourExtension.class); assertEquals(2, countExtensions(child, MyExtensionApi.class)); ExtensionRegistry grandChild = createRegistryFrom(child, Stream.empty()); assertExtensionRegistered(grandChild, MyExtension.class); assertExtensionRegistered(grandChild, YourExtension.class); assertEquals(2, countExtensions(grandChild, MyExtensionApi.class)); } @Test void registeringSameExtensionImplementationInParentAndChildDoesNotResultInDuplicate() { MutableExtensionRegistry parent = registry; parent.registerExtension(MyExtension.class); assertEquals(1, countExtensions(parent, MyExtensionApi.class)); MutableExtensionRegistry child = createRegistryFrom(parent, Stream.of(MyExtension.class, YourExtension.class)); assertExtensionRegistered(child, MyExtension.class); assertExtensionRegistered(child, YourExtension.class); assertEquals(2, countExtensions(child, MyExtensionApi.class)); ExtensionRegistry grandChild = createRegistryFrom(child, Stream.of(MyExtension.class, YourExtension.class)); assertExtensionRegistered(grandChild, MyExtension.class); assertExtensionRegistered(grandChild, YourExtension.class); assertEquals(2, countExtensions(grandChild, MyExtensionApi.class)); } @Test void canStreamOverRegisteredExtension() { registry.registerExtension(MyExtension.class); AtomicBoolean hasRun = new AtomicBoolean(false); registry.getExtensions(MyExtensionApi.class).forEach(extension -> { assertEquals(MyExtension.class.getName(), extension.getClass().getName()); hasRun.set(true); }); assertTrue(hasRun.get()); } private long countExtensions(ExtensionRegistry registry, Class extensionType) { return registry.stream(extensionType).count(); } private void assertExtensionRegistered(ExtensionRegistry registry, Class extensionType) { assertFalse(registry.getExtensions(extensionType).isEmpty(), () -> extensionType.getSimpleName() + " should be present"); } private void assertExtensionNotRegistered(ExtensionRegistry registry, Class extensionType) { assertTrue(registry.getExtensions(extensionType).isEmpty(), () -> extensionType.getSimpleName() + " should not be present"); } private void assertDefaultGlobalExtensionsAreRegistered() { assertDefaultGlobalExtensionsAreRegistered(2); } private void assertDefaultGlobalExtensionsAreRegistered(long bacCount) { assertExtensionRegistered(registry, DisabledCondition.class); assertExtensionRegistered(registry, TempDirectory.class); assertExtensionRegistered(registry, TimeoutExtension.class); assertExtensionRegistered(registry, RepeatedTestExtension.class); assertExtensionRegistered(registry, TestInfoParameterResolver.class); assertExtensionRegistered(registry, TestReporterParameterResolver.class); assertEquals(bacCount, countExtensions(registry, BeforeAllCallback.class)); assertEquals(2, countExtensions(registry, BeforeEachCallback.class)); assertEquals(3, countExtensions(registry, ParameterResolver.class)); assertEquals(1, countExtensions(registry, ExecutionCondition.class)); assertEquals(1, countExtensions(registry, TestTemplateInvocationContextProvider.class)); assertEquals(1, countExtensions(registry, InvocationInterceptor.class)); } private static Predicate> extensionFilter(String includes, String excludes) { var nameFilter = ClassNamePatternFilterUtils.includeMatchingClassNames(includes) // .and(ClassNamePatternFilterUtils.excludeMatchingClassNames(excludes)); return clazz -> nameFilter.test(clazz.getName()); } // ------------------------------------------------------------------------- interface MyExtensionApi extends Extension { void doNothing(String test); } interface AnotherExtensionApi extends Extension { void doMore(); } static class MyExtension implements MyExtensionApi { @Override public void doNothing(String test) { } } static class YourExtension implements MyExtensionApi { @Override public void doNothing(String test) { } } static class MultipleExtension implements MyExtensionApi, AnotherExtensionApi { @Override public void doNothing(String test) { } @Override public void doMore() { } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/InvocationInterceptorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static java.util.function.Function.identity; import static java.util.function.Predicate.isEqual; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.EventConditions.test; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.function.UnaryOperator; import java.util.stream.Stream; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; 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.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.TestReporter; import org.junit.jupiter.api.extension.DynamicTestInvocationContext; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.InvocationInterceptor; import org.junit.jupiter.api.extension.ReflectiveInvocationContext; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.platform.commons.JUnitException; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.testkit.engine.EngineExecutionResults; class InvocationInterceptorTests extends AbstractJupiterTestEngineTests { @Test void failsTestWhenInterceptorChainDoesNotCallInvocation() { var results = executeTestsForClass(InvocationIgnoringInterceptorTestCase.class); var tests = results.testEvents().assertStatistics(stats -> stats.failed(1).succeeded(0)); tests.failed().assertEventsMatchExactly( event(test("test"), finishedWithFailure(instanceOf(JUnitException.class), message(it -> it.startsWith("Chain of InvocationInterceptors never called invocation"))))); } @NullMarked static class InvocationIgnoringInterceptorTestCase { @RegisterExtension Extension interceptor = new InvocationInterceptor() { @Override public void interceptTestMethod(Invocation<@Nullable Void> invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) { // do nothing } }; @Test void test() { // never called } } @Test void successTestWhenInterceptorChainSkippedInvocation() { var results = executeTestsForClass(InvocationSkippedTestCase.class); results.testEvents().assertStatistics(stats -> stats.failed(0).succeeded(1)); } @NullMarked static class InvocationSkippedTestCase { @RegisterExtension Extension interceptor = new InvocationInterceptor() { @Override public void interceptTestMethod(Invocation<@Nullable Void> invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) { invocation.skip(); } }; @Test void test() { fail("should not be called"); } } @Test void failsTestWhenInterceptorChainCallsInvocationMoreThanOnce() { var results = executeTestsForClass(DoubleInvocationInterceptorTestCase.class); var tests = results.testEvents().assertStatistics(stats -> stats.failed(1).succeeded(0)); tests.failed().assertEventsMatchExactly( event(test("test"), finishedWithFailure(instanceOf(JUnitException.class), message(it -> it.startsWith( "Chain of InvocationInterceptors called invocation multiple times instead of just once"))))); } @NullMarked static class DoubleInvocationInterceptorTestCase { @RegisterExtension Extension interceptor = new InvocationInterceptor() { @Override public void interceptTestMethod(Invocation<@Nullable Void> invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { invocation.proceed(); invocation.proceed(); } }; @Test void test() { // called twice } } @TestFactory Stream callsInterceptors() { var results = executeTestsForClass(TestCaseWithThreeInterceptors.class); results.testEvents().assertStatistics(stats -> stats.failed(0).succeeded(3)); return Arrays.stream(InvocationType.values()) // .map(it -> dynamicTest(it.name(), () -> verifyEvents(results, it))); } private void verifyEvents(EngineExecutionResults results, InvocationType invocationType) { var beforeEvents = List.of("before:foo", "before:bar", "before:baz"); var testEvent = List.of("test"); var afterEvents = List.of("after:baz", "after:bar", "after:foo"); var allEvents = Stream.of(beforeEvents, testEvent, afterEvents).flatMap(Collection::stream).toList(); String testClassName = TestCaseWithThreeInterceptors.class.getName(); var expectedElements = switch (invocationType) { case BEFORE_ALL, AFTER_ALL -> prefixed(allEvents, testClassName); case CONSTRUCTOR -> concatStreams( prefixed(allEvents, it -> it.endsWith(":bar") ? testClassName : "test(TestReporter)"), prefixed(allEvents, it -> it.endsWith(":bar") ? testClassName : "testTemplate(TestReporter)[1]"), prefixed(allEvents, it -> it.endsWith(":bar") ? testClassName : "testFactory(TestReporter)")); case BEFORE_EACH, AFTER_EACH -> concatStreams(prefixed(allEvents, "test(TestReporter)"), prefixed(allEvents, "testTemplate(TestReporter)[1]"), prefixed(allEvents, "testFactory(TestReporter)")); case TEST_METHOD -> prefixed(allEvents, "test(TestReporter)"); case TEST_TEMPLATE_METHOD -> prefixed(allEvents, "testTemplate(TestReporter)[1]"); case TEST_FACTORY_METHOD -> prefixed(allEvents, "testFactory(TestReporter)"); case DYNAMIC_TEST -> concatStreams(prefixed(beforeEvents, "testFactory(TestReporter)[1]"), prefixed(testEvent, "testFactory(TestReporter)"), prefixed(afterEvents, "testFactory(TestReporter)[1]")); }; assertThat(getEvents(results, invocationType)) // .containsExactlyElementsOf(expectedElements.toList()); } @SafeVarargs @SuppressWarnings("varargs") private static Stream concatStreams(Stream... items) { return Stream.of(items).flatMap(identity()); } private static Stream prefixed(List values, String prefix) { return prefixed(values, __ -> prefix); } private static Stream prefixed(List values, UnaryOperator prefixGenerator) { return values.stream() // .map(it -> "[%s] %s".formatted(prefixGenerator.apply(it), it)); } private Stream getEvents(EngineExecutionResults results, InvocationType invocationType) { return results.allEvents().reportingEntryPublished().stream() // .flatMap(event -> { var reportEntry = event.getPayload(ReportEntry.class).orElseThrow(); var keyValuePairs = reportEntry.getKeyValuePairs(); if (keyValuePairs.keySet().stream() // .map(InvocationType::valueOf) // .anyMatch(isEqual(invocationType))) { return keyValuePairs.values().stream() // .map(it -> "[%s] %s".formatted(event.getTestDescriptor().getLegacyReportingName(), it)); } return Stream.empty(); }); } @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith({ FooInvocationInterceptor.class, BarInvocationInterceptor.class, BazInvocationInterceptor.class }) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) static class TestCaseWithThreeInterceptors { TestCaseWithThreeInterceptors(TestReporter reporter) { publish(reporter, InvocationType.CONSTRUCTOR); } @SuppressWarnings("JUnitMalformedDeclaration") @BeforeAll static void beforeAll(TestReporter reporter) { publish(reporter, InvocationType.BEFORE_ALL); } @BeforeEach void beforeEach(TestReporter reporter) { publish(reporter, InvocationType.BEFORE_EACH); } @Order(1) @Test void test(TestReporter reporter) { publish(reporter, InvocationType.TEST_METHOD); } @Order(2) @RepeatedTest(1) void testTemplate(TestReporter reporter) { publish(reporter, InvocationType.TEST_TEMPLATE_METHOD); } @Order(3) @TestFactory DynamicTest testFactory(TestReporter reporter) { publish(reporter, InvocationType.TEST_FACTORY_METHOD); return dynamicTest("dynamicTest", () -> publish(reporter, InvocationType.DYNAMIC_TEST)); } @AfterEach void afterEach(TestReporter reporter) { publish(reporter, InvocationType.AFTER_EACH); } @SuppressWarnings("JUnitMalformedDeclaration") @AfterAll static void afterAll(TestReporter reporter) { publish(reporter, InvocationType.AFTER_ALL); } static void publish(TestReporter reporter, InvocationType type) { reporter.publishEntry(type.name(), "test"); } } enum InvocationType { BEFORE_ALL, CONSTRUCTOR, BEFORE_EACH, TEST_METHOD, TEST_TEMPLATE_METHOD, TEST_FACTORY_METHOD, DYNAMIC_TEST, AFTER_EACH, AFTER_ALL } @NullMarked abstract static class ReportingInvocationInterceptor implements InvocationInterceptor { private final Class testClass = TestCaseWithThreeInterceptors.class; private final String name; ReportingInvocationInterceptor(String name) { this.name = name; } @Override public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { return ExtensionContextScope.TEST_METHOD; } @Override public void interceptBeforeAllMethod(Invocation<@Nullable Void> invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { assertEquals(testClass, invocationContext.getTargetClass()); assertThat(invocationContext.getTarget()).isEmpty(); assertEquals(testClass.getDeclaredMethod("beforeAll", TestReporter.class), invocationContext.getExecutable()); assertThat(invocationContext.getArguments()).hasSize(1).hasOnlyElementsOfType(TestReporter.class); this.<@Nullable Void> reportAndProceed(invocation, extensionContext, InvocationType.BEFORE_ALL); } @Override public T interceptTestClassConstructor(Invocation invocation, ReflectiveInvocationContext> invocationContext, ExtensionContext extensionContext) throws Throwable { assertEquals(testClass, invocationContext.getTargetClass()); assertEquals(testClass.getDeclaredConstructor(TestReporter.class), invocationContext.getExecutable()); assertThat(invocationContext.getArguments()).hasSize(1).hasOnlyElementsOfType(TestReporter.class); return reportAndProceed(invocation, extensionContext, InvocationType.CONSTRUCTOR); } @Override public void interceptBeforeEachMethod(Invocation<@Nullable Void> invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { assertEquals(testClass, invocationContext.getTargetClass()); assertThat(invocationContext.getTarget()).containsInstanceOf(testClass); assertEquals(testClass.getDeclaredMethod("beforeEach", TestReporter.class), invocationContext.getExecutable()); assertThat(invocationContext.getArguments()).hasSize(1).hasOnlyElementsOfType(TestReporter.class); this.<@Nullable Void> reportAndProceed(invocation, extensionContext, InvocationType.BEFORE_EACH); } @Override public void interceptTestMethod(Invocation<@Nullable Void> invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { assertEquals(testClass, invocationContext.getTargetClass()); assertThat(invocationContext.getTarget()).containsInstanceOf(testClass); assertEquals(testClass.getDeclaredMethod("test", TestReporter.class), invocationContext.getExecutable()); assertThat(invocationContext.getArguments()).hasSize(1).hasOnlyElementsOfType(TestReporter.class); this.<@Nullable Void> reportAndProceed(invocation, extensionContext, InvocationType.TEST_METHOD); } @Override public void interceptTestTemplateMethod(Invocation<@Nullable Void> invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { assertEquals(testClass, invocationContext.getTargetClass()); assertThat(invocationContext.getTarget()).containsInstanceOf(testClass); assertEquals(testClass.getDeclaredMethod("testTemplate", TestReporter.class), invocationContext.getExecutable()); assertThat(invocationContext.getArguments()).hasSize(1).hasOnlyElementsOfType(TestReporter.class); this.<@Nullable Void> reportAndProceed(invocation, extensionContext, InvocationType.TEST_TEMPLATE_METHOD); } @Override public T interceptTestFactoryMethod(Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { assertEquals(testClass, invocationContext.getTargetClass()); assertThat(invocationContext.getTarget()).containsInstanceOf(testClass); assertEquals(testClass.getDeclaredMethod("testFactory", TestReporter.class), invocationContext.getExecutable()); assertThat(invocationContext.getArguments()).hasSize(1).hasOnlyElementsOfType(TestReporter.class); return reportAndProceed(invocation, extensionContext, InvocationType.TEST_FACTORY_METHOD); } @Override public void interceptDynamicTest(Invocation<@Nullable Void> invocation, DynamicTestInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { assertThat(invocationContext.getExecutable()).isNotNull(); assertThat(extensionContext.getUniqueId()).isNotBlank(); assertThat(extensionContext.getElement()).isEmpty(); assertThat(extensionContext.getParent().flatMap(ExtensionContext::getTestMethod)) // .contains(testClass.getDeclaredMethod("testFactory", TestReporter.class)); this.<@Nullable Void> reportAndProceed(invocation, extensionContext, InvocationType.DYNAMIC_TEST); } @Override public void interceptAfterEachMethod(Invocation<@Nullable Void> invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { assertEquals(testClass, invocationContext.getTargetClass()); assertThat(invocationContext.getTarget()).containsInstanceOf(testClass); assertEquals(testClass.getDeclaredMethod("afterEach", TestReporter.class), invocationContext.getExecutable()); assertThat(invocationContext.getArguments()).hasSize(1).hasOnlyElementsOfType(TestReporter.class); this.<@Nullable Void> reportAndProceed(invocation, extensionContext, InvocationType.AFTER_EACH); } @Override public void interceptAfterAllMethod(Invocation<@Nullable Void> invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { assertEquals(testClass, invocationContext.getTargetClass()); assertThat(invocationContext.getTarget()).isEmpty(); assertEquals(testClass.getDeclaredMethod("afterAll", TestReporter.class), invocationContext.getExecutable()); assertThat(invocationContext.getArguments()).hasSize(1).hasOnlyElementsOfType(TestReporter.class); this.<@Nullable Void> reportAndProceed(invocation, extensionContext, InvocationType.AFTER_ALL); } private T reportAndProceed(Invocation invocation, ExtensionContext extensionContext, InvocationType type) throws Throwable { extensionContext.publishReportEntry(type.name(), "before:" + name); try { return invocation.proceed(); } finally { extensionContext.publishReportEntry(type.name(), "after:" + name); } } } @NullMarked static class FooInvocationInterceptor extends ReportingInvocationInterceptor { FooInvocationInterceptor() { super("foo"); } } @NullMarked static class BarInvocationInterceptor extends ReportingInvocationInterceptor { BarInvocationInterceptor() { super("bar"); } @SuppressWarnings("deprecation") @Override public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { return ExtensionContextScope.DEFAULT; } } @NullMarked static class BazInvocationInterceptor extends ReportingInvocationInterceptor { BazInvocationInterceptor() { super("baz"); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/LifecycleMethodExecutionExceptionHandlerTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.junit.platform.testkit.engine.EventConditions.container; import static org.junit.platform.testkit.engine.EventConditions.engine; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.EventConditions.started; import static org.junit.platform.testkit.engine.EventConditions.test; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; 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.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.testkit.engine.EngineExecutionResults; /** * Tests that verify the support for lifecycle method execution exception handling * via {@link LifecycleMethodExecutionExceptionHandler} * * @since 5.5 */ class LifecycleMethodExecutionExceptionHandlerTests extends AbstractJupiterTestEngineTests { private static List handlerCalls = new ArrayList<>(); private static boolean throwExceptionBeforeAll; private static boolean throwExceptionBeforeEach; private static boolean throwExceptionAfterEach; private static boolean throwExceptionAfterAll; @BeforeEach void resetStatics() { throwExceptionBeforeAll = true; throwExceptionBeforeEach = true; throwExceptionAfterEach = true; throwExceptionAfterAll = true; handlerCalls.clear(); SwallowExceptionHandler.callCounter.reset(); RethrowExceptionHandler.callCounter.reset(); ConvertExceptionHandler.callCounter.reset(); UnrecoverableExceptionHandler.callCounter.reset(); ShouldNotBeCalledHandler.callCounter.reset(); } @Test void classLevelExceptionHandlersRethrowException() { LauncherDiscoveryRequest request = request().selectors(selectClass(RethrowingTestCase.class)).build(); EngineExecutionResults executionResults = executeTests(request); assertEquals(1, RethrowExceptionHandler.callCounter.beforeAllCalls, "Exception should handled in @BeforeAll"); assertEquals(1, RethrowExceptionHandler.callCounter.afterAllCalls, "Exception should handled in @AfterAll"); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(RethrowingTestCase.class), started()), // event(container(RethrowingTestCase.class), finishedWithFailure(instanceOf(RuntimeException.class))), // event(engine(), finishedSuccessfully())); } @Test void testLevelExceptionHandlersRethrowException() { throwExceptionBeforeAll = false; throwExceptionAfterAll = false; LauncherDiscoveryRequest request = request().selectors(selectClass(RethrowingTestCase.class)).build(); EngineExecutionResults executionResults = executeTests(request); assertEquals(1, RethrowExceptionHandler.callCounter.beforeEachCalls, "Exception should be handled in @BeforeEach"); assertEquals(1, RethrowExceptionHandler.callCounter.afterEachCalls, "Exception should be handled in @AfterEach"); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(RethrowingTestCase.class), started()), // event(test("aTest"), started()), // event(test("aTest"), finishedWithFailure(instanceOf(RuntimeException.class))), // event(container(RethrowingTestCase.class), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void classLevelExceptionHandlersConvertException() { LauncherDiscoveryRequest request = request().selectors(selectClass(ConvertingTestCase.class)).build(); EngineExecutionResults executionResults = executeTests(request); assertEquals(1, ConvertExceptionHandler.callCounter.beforeAllCalls, "Exception should handled in @BeforeAll"); assertEquals(1, ConvertExceptionHandler.callCounter.afterAllCalls, "Exception should handled in @AfterAll"); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(ConvertingTestCase.class), started()), // event(container(ConvertingTestCase.class), finishedWithFailure(instanceOf(IOException.class))), // event(engine(), finishedSuccessfully())); } @Test void testLevelExceptionHandlersConvertException() { throwExceptionBeforeAll = false; throwExceptionAfterAll = false; LauncherDiscoveryRequest request = request().selectors(selectClass(ConvertingTestCase.class)).build(); EngineExecutionResults executionResults = executeTests(request); assertEquals(1, ConvertExceptionHandler.callCounter.beforeEachCalls, "Exception should be handled in @BeforeEach"); assertEquals(1, ConvertExceptionHandler.callCounter.afterEachCalls, "Exception should be handled in @AfterEach"); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(ConvertingTestCase.class), started()), // event(test("aTest"), started()), // event(test("aTest"), finishedWithFailure(instanceOf(IOException.class))), // event(container(ConvertingTestCase.class), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void exceptionHandlersSwallowException() { LauncherDiscoveryRequest request = request().selectors(selectClass(SwallowingTestCase.class)).build(); EngineExecutionResults executionResults = executeTests(request); assertEquals(1, SwallowExceptionHandler.callCounter.beforeAllCalls, "Exception should be handled in @BeforeAll"); assertEquals(1, SwallowExceptionHandler.callCounter.beforeEachCalls, "Exception should be handled in @BeforeEach"); assertEquals(1, SwallowExceptionHandler.callCounter.afterEachCalls, "Exception should be handled in @AfterEach"); assertEquals(1, SwallowExceptionHandler.callCounter.afterAllCalls, "Exception should be handled in @AfterAll"); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(SwallowingTestCase.class), started()), // event(test("aTest"), started()), // event(test("aTest"), finishedSuccessfully()), // event(container(SwallowingTestCase.class), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void perClassLifecycleMethodsAreHandled() { LauncherDiscoveryRequest request = request().selectors(selectClass(PerClassLifecycleTestCase.class)).build(); EngineExecutionResults executionResults = executeTests(request); assertEquals(2, SwallowExceptionHandler.callCounter.beforeAllCalls, "Exception should be handled in @BeforeAll"); assertEquals(1, SwallowExceptionHandler.callCounter.beforeEachCalls, "Exception should be handled in @BeforeEach"); assertEquals(1, SwallowExceptionHandler.callCounter.afterEachCalls, "Exception should be handled in @AfterEach"); assertEquals(2, SwallowExceptionHandler.callCounter.afterAllCalls, "Exception should be handled in @AfterAll"); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(PerClassLifecycleTestCase.class), started()), // event(test("aTest"), started()), // event(test("aTest"), finishedSuccessfully()), // event(container(PerClassLifecycleTestCase.class), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void multipleHandlersAreCalledInOrder() { LauncherDiscoveryRequest request = request().selectors(selectClass(MultipleHandlersTestCase.class)).build(); EngineExecutionResults executionResults = executeTests(request); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(MultipleHandlersTestCase.class), started()), // event(test("aTest"), started()), // event(test("aTest"), finishedSuccessfully()), // event(test("aTest2"), started()), // event(test("aTest2"), finishedSuccessfully()), // event(container(MultipleHandlersTestCase.class), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); // assertEquals(Arrays.asList( // BeforeAll chain (class level only) "RethrowExceptionBeforeAll", "SwallowExceptionBeforeAll", // BeforeEach chain for aTest (test + class level) "ConvertExceptionBeforeEach", "RethrowExceptionBeforeEach", "SwallowExceptionBeforeEach", // AfterEach chain for aTest (test + class level) "ConvertExceptionAfterEach", "RethrowExceptionAfterEach", "SwallowExceptionAfterEach", // BeforeEach chain for aTest2 (class level only) "RethrowExceptionBeforeEach", "SwallowExceptionBeforeEach", // AfterEach chain for aTest2 (class level only) "RethrowExceptionAfterEach", "SwallowExceptionAfterEach", // AfterAll chain (class level only) "RethrowExceptionAfterAll", "SwallowExceptionAfterAll" // ), handlerCalls, "Wrong order of handler calls"); } @Test void unrecoverableExceptionsAreNotPropagatedInBeforeAll() { throwExceptionBeforeAll = true; throwExceptionBeforeEach = false; throwExceptionAfterEach = false; throwExceptionAfterAll = false; boolean unrecoverableExceptionThrown = executeThrowingOutOfMemoryException(); assertTrue(unrecoverableExceptionThrown, "Unrecoverable Exception should be thrown"); assertEquals(1, UnrecoverableExceptionHandler.callCounter.beforeAllCalls, "Exception should be handled in @BeforeAll"); assertEquals(0, ShouldNotBeCalledHandler.callCounter.beforeAllCalls, "Exception should not propagate in @BeforeAll"); } @Test void unrecoverableExceptionsAreNotPropagatedInBeforeEach() { throwExceptionBeforeAll = false; throwExceptionBeforeEach = true; throwExceptionAfterEach = false; throwExceptionAfterAll = false; boolean unrecoverableExceptionThrown = executeThrowingOutOfMemoryException(); assertTrue(unrecoverableExceptionThrown, "Unrecoverable Exception should be thrown"); assertEquals(1, UnrecoverableExceptionHandler.callCounter.beforeEachCalls, "Exception should be handled in @BeforeEach"); assertEquals(0, ShouldNotBeCalledHandler.callCounter.beforeEachCalls, "Exception should not propagate in @BeforeEach"); } @Test void unrecoverableExceptionsAreNotPropagatedInAfterEach() { throwExceptionBeforeAll = false; throwExceptionBeforeEach = false; throwExceptionAfterEach = true; throwExceptionAfterAll = false; boolean unrecoverableExceptionThrown = executeThrowingOutOfMemoryException(); assertTrue(unrecoverableExceptionThrown, "Unrecoverable Exception should be thrown"); assertEquals(1, UnrecoverableExceptionHandler.callCounter.afterEachCalls, "Exception should be handled in @AfterEach"); assertEquals(0, ShouldNotBeCalledHandler.callCounter.afterEachCalls, "Exception should not propagate in @AfterEach"); } @Test void unrecoverableExceptionsAreNotPropagatedInAfterAll() { throwExceptionBeforeAll = false; throwExceptionBeforeEach = false; throwExceptionAfterEach = false; throwExceptionAfterAll = true; boolean unrecoverableExceptionThrown = executeThrowingOutOfMemoryException(); assertTrue(unrecoverableExceptionThrown, "Unrecoverable Exception should be thrown"); assertEquals(1, UnrecoverableExceptionHandler.callCounter.afterAllCalls, "Exception should be handled in @AfterAll"); assertEquals(0, ShouldNotBeCalledHandler.callCounter.afterAllCalls, "Exception should not propagate in @AfterAll"); } private boolean executeThrowingOutOfMemoryException() { LauncherDiscoveryRequest request = request().selectors( selectClass(UnrecoverableExceptionTestCase.class)).build(); try { executeTests(request); } catch (OutOfMemoryError expected) { return true; } return false; } // ------------------------------------------ static class BaseTestCase { @BeforeAll static void throwBeforeAll() { if (throwExceptionBeforeAll) { throw new RuntimeException("BeforeAllEx"); } } @BeforeEach void throwBeforeEach() { if (throwExceptionBeforeEach) { throw new RuntimeException("BeforeEachEx"); } } @Test void aTest() { } @AfterEach void throwAfterEach() { if (throwExceptionAfterEach) { throw new RuntimeException("AfterEachEx"); } } @AfterAll static void throwAfterAll() { if (throwExceptionAfterAll) { throw new RuntimeException("AfterAllEx"); } } } @ExtendWith(RethrowExceptionHandler.class) static class RethrowingTestCase extends BaseTestCase { } @ExtendWith(ConvertExceptionHandler.class) static class ConvertingTestCase extends BaseTestCase { } @ExtendWith(SwallowExceptionHandler.class) static class SwallowingTestCase extends BaseTestCase { } @ExtendWith(ShouldNotBeCalledHandler.class) @ExtendWith(UnrecoverableExceptionHandler.class) static class UnrecoverableExceptionTestCase extends BaseTestCase { } @ExtendWith(ShouldNotBeCalledHandler.class) @ExtendWith(SwallowExceptionHandler.class) @ExtendWith(RethrowExceptionHandler.class) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) static class MultipleHandlersTestCase extends BaseTestCase { @Override @ExtendWith(ConvertExceptionHandler.class) @Order(1) @Test void aTest() { } @Order(2) @Test void aTest2() { } } @ExtendWith(SwallowExceptionHandler.class) @TestInstance(TestInstance.Lifecycle.PER_CLASS) static class PerClassLifecycleTestCase extends BaseTestCase { @BeforeAll void beforeAll() { throw new RuntimeException("nonStaticBeforeAllEx"); } @AfterAll void afterAll() { throw new RuntimeException("nonStaticAfterAllEx"); } } // ------------------------------------------ static class RethrowExceptionHandler implements LifecycleMethodExecutionExceptionHandler { static HandlerCallCounter callCounter = new HandlerCallCounter(); @Override public void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { callCounter.incrementBeforeAllCalls(); handlerCalls.add("RethrowExceptionBeforeAll"); throw throwable; } @Override public void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { callCounter.incrementBeforeEachCalls(); handlerCalls.add("RethrowExceptionBeforeEach"); throw throwable; } @Override public void handleAfterEachMethodExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { callCounter.incrementAfterEachCalls(); handlerCalls.add("RethrowExceptionAfterEach"); throw throwable; } @Override public void handleAfterAllMethodExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { callCounter.incrementAfterAllCalls(); handlerCalls.add("RethrowExceptionAfterAll"); throw throwable; } } static class SwallowExceptionHandler implements LifecycleMethodExecutionExceptionHandler { static HandlerCallCounter callCounter = new HandlerCallCounter(); @Override public void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable throwable) { callCounter.incrementBeforeAllCalls(); handlerCalls.add("SwallowExceptionBeforeAll"); // Do not rethrow } @Override public void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable throwable) { callCounter.incrementBeforeEachCalls(); handlerCalls.add("SwallowExceptionBeforeEach"); // Do not rethrow } @Override public void handleAfterEachMethodExecutionException(ExtensionContext context, Throwable throwable) { callCounter.incrementAfterEachCalls(); handlerCalls.add("SwallowExceptionAfterEach"); // Do not rethrow } @Override public void handleAfterAllMethodExecutionException(ExtensionContext context, Throwable throwable) { callCounter.incrementAfterAllCalls(); handlerCalls.add("SwallowExceptionAfterAll"); // Do not rethrow } } static class ConvertExceptionHandler implements LifecycleMethodExecutionExceptionHandler { static HandlerCallCounter callCounter = new HandlerCallCounter(); @Override public void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { callCounter.incrementBeforeAllCalls(); handlerCalls.add("ConvertExceptionBeforeAll"); throw new IOException(throwable); } @Override public void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { callCounter.incrementBeforeEachCalls(); handlerCalls.add("ConvertExceptionBeforeEach"); throw new IOException(throwable); } @Override public void handleAfterEachMethodExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { callCounter.incrementAfterEachCalls(); handlerCalls.add("ConvertExceptionAfterEach"); throw new IOException(throwable); } @Override public void handleAfterAllMethodExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { callCounter.incrementAfterAllCalls(); handlerCalls.add("ConvertExceptionAfterAll"); throw new IOException(throwable); } } static class UnrecoverableExceptionHandler implements LifecycleMethodExecutionExceptionHandler { static HandlerCallCounter callCounter = new HandlerCallCounter(); @Override public void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable throwable) { callCounter.incrementBeforeAllCalls(); handlerCalls.add("UnrecoverableExceptionBeforeAll"); throw new OutOfMemoryError(); } @Override public void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable throwable) { callCounter.incrementBeforeEachCalls(); handlerCalls.add("UnrecoverableExceptionBeforeEach"); throw new OutOfMemoryError(); } @Override public void handleAfterEachMethodExecutionException(ExtensionContext context, Throwable throwable) { callCounter.incrementAfterEachCalls(); handlerCalls.add("UnrecoverableExceptionAfterEach"); throw new OutOfMemoryError(); } @Override public void handleAfterAllMethodExecutionException(ExtensionContext context, Throwable throwable) { callCounter.incrementAfterAllCalls(); handlerCalls.add("UnrecoverableExceptionAfterAll"); throw new OutOfMemoryError(); } } static class ShouldNotBeCalledHandler implements LifecycleMethodExecutionExceptionHandler { static HandlerCallCounter callCounter = new HandlerCallCounter(); @Override public void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { callCounter.incrementBeforeAllCalls(); handlerCalls.add("ShouldNotBeCalledBeforeAll"); throw throwable; } @Override public void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { ShouldNotBeCalledHandler.callCounter.incrementBeforeEachCalls(); handlerCalls.add("ShouldNotBeCalledBeforeEach"); throw throwable; } @Override public void handleAfterEachMethodExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { callCounter.incrementAfterEachCalls(); handlerCalls.add("ShouldNotBeCalledAfterEach"); throw throwable; } @Override public void handleAfterAllMethodExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { callCounter.incrementAfterAllCalls(); handlerCalls.add("ShouldNotBeCalledAfterAll"); throw throwable; } } static class HandlerCallCounter { private int beforeAllCalls; private int beforeEachCalls; private int afterEachCalls; private int afterAllCalls; HandlerCallCounter() { reset(); } public void reset() { this.beforeAllCalls = 0; this.beforeEachCalls = 0; this.afterEachCalls = 0; this.afterAllCalls = 0; } public void incrementBeforeAllCalls() { beforeAllCalls++; } public void incrementBeforeEachCalls() { beforeEachCalls++; } public void incrementAfterEachCalls() { afterEachCalls++; } public void incrementAfterAllCalls() { afterAllCalls++; } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/OrderedClassTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Constants.DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasses; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.stream.Stream; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.ClassOrderer; import org.junit.jupiter.api.ClassTemplate; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestClassOrder; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.extension.ClassTemplateInvocationContext; import org.junit.jupiter.api.extension.ClassTemplateInvocationContextProvider; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.fixtures.TrackLogRecords; import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.platform.testkit.engine.EngineDiscoveryResults; import org.junit.platform.testkit.engine.EngineTestKit; import org.junit.platform.testkit.engine.Events; /** * Integration tests for {@link ClassOrderer} support. * * @since 5.8 */ class OrderedClassTests { private static final List callSequence = Collections.synchronizedList(new ArrayList<>()); @BeforeEach @AfterEach void clearCallSequence() { callSequence.clear(); } @Test void noOrderer() { var discoveryIssues = discoverTests(null).getDiscoveryIssues(); assertIneffectiveOrderAnnotationIssues(discoveryIssues); executeTests(null)// .assertStatistics(stats -> stats.succeeded(callSequence.size())); assertThat(callSequence)// .containsExactlyInAnyOrder("A_TestCase", "B_TestCase", "C_TestCase"); } @Test void className() { var discoveryIssues = discoverTests(ClassOrderer.ClassName.class).getDiscoveryIssues(); assertIneffectiveOrderAnnotationIssues(discoveryIssues); executeTests(ClassOrderer.ClassName.class)// .assertStatistics(stats -> stats.succeeded(callSequence.size())); assertThat(callSequence)// .containsExactly("A_TestCase", "B_TestCase", "C_TestCase"); } @Test void classNameAcrossPackages() { try { example.B_TestCase.callSequence = callSequence; // @formatter:off executeTests(ClassOrderer.ClassName.class, selectClasses(B_TestCase.class, example.B_TestCase.class)) .assertStatistics(stats -> stats.succeeded(callSequence.size())); // @formatter:on assertThat(callSequence)// .containsExactly("example.B_TestCase", "B_TestCase"); } finally { example.B_TestCase.callSequence = null; } } @Test void displayName() { var discoveryIssues = discoverTests(ClassOrderer.DisplayName.class).getDiscoveryIssues(); assertIneffectiveOrderAnnotationIssues(discoveryIssues); executeTests(ClassOrderer.DisplayName.class)// .assertStatistics(stats -> stats.succeeded(callSequence.size())); assertThat(callSequence)// .containsExactly("C_TestCase", "B_TestCase", "A_TestCase"); } @Test void orderAnnotation() { var discoveryIssues = discoverTests(ClassOrderer.OrderAnnotation.class).getDiscoveryIssues(); assertThat(discoveryIssues).isEmpty(); executeTests(ClassOrderer.OrderAnnotation.class)// .assertStatistics(stats -> stats.succeeded(callSequence.size())); assertThat(callSequence)// .containsExactly("A_TestCase", "C_TestCase", "B_TestCase"); } @Test void orderAnnotationOnNestedTestClassesWithGlobalConfig() { executeTests(ClassOrderer.OrderAnnotation.class, selectClass(OuterWithGlobalConfig.class))// .assertStatistics(stats -> stats.succeeded(callSequence.size())); assertThat(callSequence)// .containsExactly("Inner2", "Inner1", "Inner0", "Inner3"); } @Test void orderAnnotationOnNestedTestClassesWithLocalConfig(@TrackLogRecords LogRecordListener listener) { executeTests(ClassOrderer.class, selectClass(OuterWithLocalConfig.class))// .assertStatistics(stats -> stats.succeeded(callSequence.size())); // Ensure that supplying the ClassOrderer interface instead of an implementation // class results in a WARNING log message. This also lets us know the local // config is used. assertTrue(listener.stream(Level.WARNING)// .map(LogRecord::getMessage)// .anyMatch(m -> m.startsWith( "Failed to load default class orderer class 'org.junit.jupiter.api.ClassOrderer'"))); assertThat(callSequence)// .containsExactly("Inner2", "Inner1", "Inner1Inner1", "Inner1Inner0", "Inner0", "Inner3"); } @Test void random() { var discoveryIssues = discoverTests(ClassOrderer.Random.class).getDiscoveryIssues(); assertIneffectiveOrderAnnotationIssues(discoveryIssues); executeTests(ClassOrderer.Random.class)// .assertStatistics(stats -> stats.succeeded(callSequence.size())); } @Test void classTemplateWithLocalConfig() { var classTemplate = ClassTemplateWithLocalConfigTestCase.class; var inner0 = ClassTemplateWithLocalConfigTestCase.Inner0.class; var inner1 = ClassTemplateWithLocalConfigTestCase.Inner1.class; var inner1Inner1 = ClassTemplateWithLocalConfigTestCase.Inner1.Inner1Inner1.class; var inner1Inner0 = ClassTemplateWithLocalConfigTestCase.Inner1.Inner1Inner0.class; executeTests(ClassOrderer.Random.class, selectClass(classTemplate))// .assertStatistics(stats -> stats.succeeded(callSequence.size())); var inner1InvocationCallSequence = Stream.of(inner1, inner1Inner1, inner1Inner0, inner1Inner0).toList(); var inner1CallSequence = twice(inner1InvocationCallSequence).toList(); var outerCallSequence = Stream.concat(Stream.of(classTemplate), Stream.concat(inner1CallSequence.stream(), Stream.of(inner0))).toList(); var expectedCallSequence = twice(outerCallSequence).map(Class::getSimpleName).toList(); assertThat(callSequence).containsExactlyElementsOf(expectedCallSequence); } private static Stream twice(List values) { return Stream.concat(values.stream(), values.stream()); } @Test void classTemplateWithGlobalConfig() { var classTemplate = ClassTemplateWithLocalConfigTestCase.class; var otherClass = A_TestCase.class; executeTests(ClassOrderer.OrderAnnotation.class, selectClasses(otherClass, classTemplate))// .assertStatistics(stats -> stats.succeeded(callSequence.size())); assertThat(callSequence)// .containsSubsequence(classTemplate.getSimpleName(), otherClass.getSimpleName()); } @Test void nestedClassedCanUseDefaultOrder(@TrackLogRecords LogRecordListener logRecords) { executeTests(null, selectClass(RevertingBackToDefaultOrderTestCase.Inner.class)); assertThat(callSequence).containsExactly("Test1", "Test2", "Test3", "Test4"); callSequence.clear(); executeTests(ClassOrderer.OrderAnnotation.class, selectClass(RevertingBackToDefaultOrderTestCase.Inner.class)); assertThat(callSequence).containsExactly("Test4", "Test2", "Test1", "Test3"); callSequence.clear(); executeTests(ClassOrderer.Default.class, selectClass(RevertingBackToDefaultOrderTestCase.Inner.class)); assertThat(callSequence).containsExactly("Test1", "Test2", "Test3", "Test4"); assertThat(logRecords.stream()) // .filteredOn(it -> it.getLevel().intValue() >= Level.WARNING.intValue()) // .map(LogRecord::getMessage) // .isEmpty(); } private static void assertIneffectiveOrderAnnotationIssues(List discoveryIssues) { assertThat(discoveryIssues).hasSize(2); assertThat(discoveryIssues).extracting(DiscoveryIssue::severity).containsOnly(Severity.INFO); assertThat(discoveryIssues).extracting(DiscoveryIssue::message) // .allMatch(it -> it.startsWith("Ineffective @Order annotation on class") && it.contains("It will not be applied because ClassOrderer.OrderAnnotation is not in use.") && it.endsWith( "Note that the annotation may be either directly present or meta-present on the class.")); assertThat(discoveryIssues).extracting(DiscoveryIssue::source).extracting(Optional::orElseThrow) // .containsExactlyInAnyOrder(ClassSource.from(A_TestCase.class), ClassSource.from(C_TestCase.class)); } private Events executeTests(@Nullable Class classOrderer) { return executeTests(classOrderer, selectClasses(A_TestCase.class, B_TestCase.class, C_TestCase.class)); } private Events executeTests(@Nullable Class classOrderer, DiscoverySelector... selectors) { return executeTests(classOrderer, List.of(selectors)); } private Events executeTests(@Nullable Class classOrderer, List selectors) { // @formatter:off return testKit(classOrderer, selectors) .execute() .testEvents(); // @formatter:on } private EngineDiscoveryResults discoverTests(@Nullable Class classOrderer) { return discoverTests(classOrderer, selectClasses(A_TestCase.class, B_TestCase.class, C_TestCase.class)); } private EngineDiscoveryResults discoverTests(@Nullable Class classOrderer, List selectors) { return testKit(classOrderer, selectors).discover(); } private static EngineTestKit.Builder testKit(@Nullable Class classOrderer, List selectors) { var testKit = EngineTestKit.engine("junit-jupiter"); if (classOrderer != null) { testKit.configurationParameter(DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME, classOrderer.getName()); } return testKit.selectors(selectors); } static abstract class BaseTestCase { @BeforeEach void trackInvocations(TestInfo testInfo) { var testClass = testInfo.getTestClass().orElseThrow(); callSequence.add(testClass.getSimpleName()); } @Test void a() { } } @SuppressWarnings("NewClassNamingConvention") @Order(2) @DisplayName("Z") static class A_TestCase extends BaseTestCase { } @SuppressWarnings("NewClassNamingConvention") static class B_TestCase extends BaseTestCase { } @SuppressWarnings("NewClassNamingConvention") @Order(10) @DisplayName("A") static class C_TestCase extends BaseTestCase { } @SuppressWarnings("NewClassNamingConvention") static class OuterWithGlobalConfig { @Nested class Inner0 { @Test void test() { callSequence.add(getClass().getSimpleName()); } } @Nested @Order(2) class Inner1 { @Test void test() { callSequence.add(getClass().getSimpleName()); } } @Nested @Order(1) class Inner2 { @Test void test() { callSequence.add(getClass().getSimpleName()); } } @Nested @Order(Integer.MAX_VALUE) class Inner3 { @Test void test() { callSequence.add(getClass().getSimpleName()); } } } @SuppressWarnings("NewClassNamingConvention") @TestClassOrder(ClassOrderer.OrderAnnotation.class) static class OuterWithLocalConfig { @Nested class Inner0 { @Test void test() { callSequence.add(getClass().getSimpleName()); } } @Nested @Order(2) class Inner1 { @Test void test() { callSequence.add(getClass().getSimpleName()); } @Nested @Order(2) class Inner1Inner0 { @Test void test() { callSequence.add(getClass().getSimpleName()); } } @Nested @Order(1) class Inner1Inner1 { @Test void test() { callSequence.add(getClass().getSimpleName()); } } } @Nested @Order(1) class Inner2 { @Test void test() { callSequence.add(getClass().getSimpleName()); } } @Nested @Order(Integer.MAX_VALUE) class Inner3 { @Test void test() { callSequence.add(getClass().getSimpleName()); } } } @Order(1) @TestClassOrder(ClassOrderer.OrderAnnotation.class) @ClassTemplate @ExtendWith(ClassTemplateWithLocalConfigTestCase.Twice.class) static class ClassTemplateWithLocalConfigTestCase { @Test void test() { callSequence.add(ClassTemplateWithLocalConfigTestCase.class.getSimpleName()); } @Nested @Order(1) class Inner0 { @Test void test() { callSequence.add(getClass().getSimpleName()); } } @Nested @ClassTemplate @Order(0) class Inner1 { @Test void test() { callSequence.add(getClass().getSimpleName()); } @Nested @ClassTemplate @Order(2) class Inner1Inner0 { @Test void test() { callSequence.add(getClass().getSimpleName()); } } @Nested @Order(1) class Inner1Inner1 { @Test void test() { callSequence.add(getClass().getSimpleName()); } } } @NullMarked private static class Twice implements ClassTemplateInvocationContextProvider { @Override public boolean supportsClassTemplate(ExtensionContext context) { return true; } @Override public Stream provideClassTemplateInvocationContexts( ExtensionContext context) { return Stream.of(new Ctx(), new Ctx()); } private record Ctx() implements ClassTemplateInvocationContext { } } } @TestClassOrder(ClassOrderer.DisplayName.class) static class RevertingBackToDefaultOrderTestCase { @Nested @TestClassOrder(ClassOrderer.Default.class) class Inner { @Nested @Order(3) class Test1 { @Test void test() { callSequence.add(getClass().getSimpleName()); } } @Nested @Order(2) class Test2 { @Test void test() { callSequence.add(getClass().getSimpleName()); } } @Nested @Order(4) class Test3 { @Test void test() { callSequence.add(getClass().getSimpleName()); } } @Nested @Order(1) class Test4 { @Test void test() { callSequence.add(getClass().getSimpleName()); } } } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/OrderedMethodTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static java.util.Comparator.comparing; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Constants.DEFAULT_EXECUTION_MODE_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.PARALLEL_CONFIG_EXECUTOR_SERVICE_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.junit.jupiter.api.MethodOrderer.Random.RANDOM_SEED_PROPERTY_NAME; import static org.junit.jupiter.api.Order.DEFAULT; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.launcher.LauncherConstants.CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.regex.Pattern; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.MethodDescriptor; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.MethodOrderer.Default; import org.junit.jupiter.api.MethodOrderer.MethodName; import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; import org.junit.jupiter.api.MethodOrderer.Random; import org.junit.jupiter.api.MethodOrdererContext; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.TestReporter; import org.junit.jupiter.api.fixtures.TrackLogRecords; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.jupiter.engine.JupiterTestEngine; import org.junit.jupiter.params.ParameterizedClass; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.commons.util.ClassUtils; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.platform.engine.support.descriptor.MethodSource; import org.junit.platform.engine.support.hierarchical.ParallelHierarchicalTestExecutorServiceFactory.ParallelExecutorServiceType; import org.junit.platform.testkit.engine.EngineDiscoveryResults; import org.junit.platform.testkit.engine.EngineTestKit; import org.junit.platform.testkit.engine.Events; import org.mockito.Mockito; /** * Integration tests that verify support for custom test method execution order * in the {@link JupiterTestEngine}. * * @since 5.4 */ @ParameterizedClass @EnumSource(ParallelExecutorServiceType.class) record OrderedMethodTests(ParallelExecutorServiceType executorServiceType) { private static final Set callSequence = Collections.synchronizedSet(new LinkedHashSet<>()); private static final Set threadNames = Collections.synchronizedSet(new LinkedHashSet<>()); @BeforeEach void clearCallSequence() { callSequence.clear(); threadNames.clear(); } @Test void methodName() { Class testClass = AMethodNameTestCase.class; // The name of the base class MUST start with a letter alphanumerically // greater than "A" so that BaseTestCase comes after AMethodNameTestCase // if methods are sorted by class name for the fallback ordering if two // methods have the same name but different parameter lists. Note, however, // that MethodName actually does not order methods like that, but we want // this check to remain in place to ensure that the ordering does not rely // on the class names. assertThat(testClass.getSuperclass().getName()).isGreaterThan(testClass.getName()); var tests = executeTestsInParallel(testClass, Random.class); tests.assertStatistics(stats -> stats.succeeded(callSequence.size())); assertThat(callSequence).containsExactly("$()", "AAA()", "AAA(org.junit.jupiter.api.TestInfo)", "AAA(org.junit.jupiter.api.TestReporter)", "ZZ_Top()", "___()", "a1()", "a2()", "b()", "c()", "zzz()"); assertThat(threadNames).hasSize(1); } @Test void displayName() { var tests = executeTestsInParallel(DisplayNameTestCase.class, Random.class); tests.assertStatistics(stats -> stats.succeeded(callSequence.size())); assertThat(callSequence)// .containsExactly("$", "AAA", "No_display_name_attribute_1_caps()", "No_display_name_attribute_2_caps()", "ZZ_Top", "___", "a1", "a2", "b()", "no_display_name_attribute_1()", "no_display_name_attribute_2()", "repetition 1 of 1", "⑦ϼ\uD83D\uDC69\u200D⚕\uD83E\uDDD8\u200D♂"); assertThat(threadNames).hasSize(1); } @Test void orderAnnotation() { assertOrderAnnotationSupport(OrderAnnotationTestCase.class); } @Test void orderAnnotationInNestedTestClass() { assertOrderAnnotationSupport(OuterTestCase.class); } @Test void orderAnnotationWithNestedTestClass() { var tests = executeTestsInParallel(OrderAnnotationWithNestedClassTestCase.class, Random.class); tests.assertStatistics(stats -> stats.succeeded(callSequence.size())); assertThat(callSequence)// .containsExactly("test1", "test2", "test3", "test4", "test5", "test6", "test7", "test8", "nestedTest1", "nestedTest2"); assertThat(threadNames).hasSize(1); } private void assertOrderAnnotationSupport(Class testClass) { var tests = executeTestsInParallel(testClass, Random.class); tests.assertStatistics(stats -> stats.succeeded(callSequence.size())); assertThat(callSequence)// .containsExactly("test1", "test2", "test3", "test4", "test5", "test6", "test7", "test8"); assertThat(threadNames).hasSize(1); } @Test void random() { var tests = executeTestsInParallel(RandomTestCase.class, Random.class); tests.assertStatistics(stats -> stats.succeeded(callSequence.size())); assertThat(threadNames).hasSize(1); } @ParameterizedTest @ValueSource(classes = { WithoutTestMethodOrderTestCase.class, ClassTemplateTestCase.class }) void defaultOrderer(Class testClass) { var tests = executeTestsInParallel(testClass, OrderAnnotation.class); tests.assertStatistics(stats -> stats.succeeded(callSequence.size())); assertThat(callSequence).containsExactly("test1()", "test2()", "test3()"); assertThat(threadNames).hasSize(1); } @Test void randomWithBogusSeedRepeatedly(@TrackLogRecords LogRecordListener listener) { var seed = "explode"; var expectedMessagePattern = Pattern.compile( "Failed to convert configuration parameter \\[" + Pattern.quote(Random.RANDOM_SEED_PROPERTY_NAME) + "] with value \\[" + seed + "] to a long\\. Using default seed \\[\\d+] as fallback\\."); Set uniqueSequences = new HashSet<>(); for (var i = 0; i < 10; i++) { callSequence.clear(); listener.clear(); var tests = executeRandomTestCaseInParallelWithRandomSeed(seed); tests.assertStatistics(stats -> stats.succeeded(callSequence.size())); uniqueSequences.add(String.join(",", callSequence)); // @formatter:off assertThat(listener.stream(Random.class, Level.WARNING) .map(LogRecord::getMessage)) .anyMatch(expectedMessagePattern.asMatchPredicate()); // @formatter:on } assertThat(uniqueSequences).size().isEqualTo(1); } @Test void randomWithDifferentSeedConsecutively(@TrackLogRecords LogRecordListener listener) { Set uniqueSequences = new HashSet<>(); for (var i = 0; i < 10; i++) { var seed = String.valueOf(i); var expectedMessage = "Using custom seed for configuration parameter [" + Random.RANDOM_SEED_PROPERTY_NAME + "] with value [" + seed + "]."; callSequence.clear(); listener.clear(); var tests = executeRandomTestCaseInParallelWithRandomSeed(seed); tests.assertStatistics(stats -> stats.succeeded(callSequence.size())); uniqueSequences.add(String.join(",", callSequence)); // @formatter:off assertThat(listener.stream(Random.class, Level.CONFIG) .map(LogRecord::getMessage)) .contains(expectedMessage); // @formatter:on assertThat(threadNames).hasSize(i + 1); } // We assume that at least 3 out of 10 are different... assertThat(uniqueSequences).size().isGreaterThanOrEqualTo(3); } @Test void randomWithCustomSeed(@TrackLogRecords LogRecordListener listener) { var seed = "42"; var expectedMessage = "Using custom seed for configuration parameter [" + Random.RANDOM_SEED_PROPERTY_NAME + "] with value [" + seed + "]."; for (var i = 0; i < 10; i++) { callSequence.clear(); listener.clear(); var tests = executeRandomTestCaseInParallelWithRandomSeed(seed); tests.assertStatistics(stats -> stats.succeeded(callSequence.size())); // With a custom seed, the "randomness" must be the same for every iteration. assertThat(callSequence).containsExactly("test2()", "test3()", "test4()", "repetition 1 of 1", "test1()"); // @formatter:off assertThat(listener.stream(Random.class, Level.CONFIG) .map(LogRecord::getMessage)) .contains(expectedMessage); // @formatter:on } assertThat(threadNames).size().isGreaterThanOrEqualTo(3); } @Test void reportsDiscoveryIssuesForIneffectiveOrderAnnotations() throws Exception { var results = discoverTests(WithoutTestMethodOrderTestCase.class, OrderAnnotation.class); assertThat(results.getDiscoveryIssues()).isEmpty(); results = discoverTests(WithoutTestMethodOrderTestCase.class, null); assertIneffectiveOrderAnnotationIssues(results.getDiscoveryIssues()); results = discoverTests(WithoutTestMethodOrderTestCase.class, Random.class); assertIneffectiveOrderAnnotationIssues(results.getDiscoveryIssues()); } @Test void misbehavingMethodOrdererThatAddsElements() { Class testClass = MisbehavingByAddingTestCase.class; var discoveryIssues = discoverTests(testClass, null).getDiscoveryIssues(); assertThat(discoveryIssues).hasSize(1); var issue = discoveryIssues.getFirst(); assertThat(issue.severity()).isEqualTo(Severity.WARNING); assertThat(issue.message()).isEqualTo( "MethodOrderer [%s] added 2 MethodDescriptor(s) for test class [%s] which will be ignored.", MisbehavingByAdding.class.getName(), testClass.getName()); assertThat(issue.source()).contains(ClassSource.from(testClass)); executeTestsInParallel(testClass, null, Severity.ERROR) // .assertStatistics(stats -> stats.succeeded(2)); assertThat(callSequence).containsExactly("test1()", "test2()"); } @Test void misbehavingMethodOrdererThatImpersonatesElements() { Class testClass = MisbehavingByImpersonatingTestCase.class; executeTestsInParallel(testClass, Random.class).assertStatistics(stats -> stats.succeeded(2)); assertThat(callSequence).containsExactlyInAnyOrder("test1()", "test2()"); } @Test void misbehavingMethodOrdererThatRemovesElements() { Class testClass = MisbehavingByRemovingTestCase.class; var discoveryIssues = discoverTests(testClass, null).getDiscoveryIssues(); assertThat(discoveryIssues).hasSize(1); var issue = discoveryIssues.getFirst(); assertThat(issue.severity()).isEqualTo(Severity.WARNING); assertThat(issue.message()).isEqualTo( "MethodOrderer [%s] removed 2 MethodDescriptor(s) for test class [%s] which will be retained with arbitrary ordering.", MisbehavingByRemoving.class.getName(), testClass.getName()); assertThat(issue.source()).contains(ClassSource.from(testClass)); executeTestsInParallel(testClass, null, Severity.ERROR) // .assertStatistics(stats -> stats.succeeded(4)); assertThat(callSequence) // .containsExactlyInAnyOrder("test1()", "test2()", "test3()", "test4()") // .containsSubsequence("test3()", "test4()") // ordered in MisbehavingByRemoving .containsSubsequence("test1()", "test3()") // removed item is re-added before ordered item .containsSubsequence("test1()", "test4()") // removed item is re-added before ordered item .containsSubsequence("test2()", "test3()") // removed item is re-added before ordered item .containsSubsequence("test2()", "test4()");// removed item is re-added before ordered item } @Test void nestedClassedCanUseDefaultOrder(@TrackLogRecords LogRecordListener logRecords) { executeTestsInParallel(NestedClassWithDefaultOrderTestCase.NestedTests.class, null, Severity.WARNING); assertThat(callSequence).containsExactly("test1()", "test2()", "test3()", "test4()"); callSequence.clear(); executeTestsInParallel(NestedClassWithDefaultOrderTestCase.NestedTests.class, OrderAnnotation.class); assertThat(callSequence).containsExactly("test4()", "test2()", "test1()", "test3()"); callSequence.clear(); executeTestsInParallel(NestedClassWithDefaultOrderTestCase.NestedTests.class, Default.class, Severity.WARNING); assertThat(callSequence).containsExactly("test1()", "test2()", "test3()", "test4()"); assertThat(logRecords.stream()) // .filteredOn(it -> it.getLevel().intValue() >= Level.WARNING.intValue()) // .map(LogRecord::getMessage) // .isEmpty(); } private EngineDiscoveryResults discoverTests(Class testClass, @Nullable Class defaultOrderer) { return testKit(testClass, defaultOrderer, Severity.INFO).discover(); } private Events executeTestsInParallel(Class testClass, @Nullable Class defaultOrderer) { return executeTestsInParallel(testClass, defaultOrderer, Severity.INFO); } private Events executeTestsInParallel(Class testClass, @Nullable Class defaultOrderer, Severity criticalSeverity) { return testKit(testClass, defaultOrderer, criticalSeverity) // .execute() // .testEvents(); } private EngineTestKit.Builder testKit(Class testClass, @Nullable Class defaultOrderer, Severity criticalSeverity) { var testKit = EngineTestKit.engine("junit-jupiter") // .configurationParameter(PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, "true") // .configurationParameter(DEFAULT_EXECUTION_MODE_PROPERTY_NAME, "concurrent") // .configurationParameter(PARALLEL_CONFIG_EXECUTOR_SERVICE_PROPERTY_NAME, executorServiceType.name()) // .configurationParameter(CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME, criticalSeverity.name()); if (defaultOrderer != null) { testKit.configurationParameter(DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME, defaultOrderer.getName()); } return testKit.selectors(selectClass(testClass)); } private Events executeRandomTestCaseInParallelWithRandomSeed(String seed) { var configurationParameters = Map.of(// PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, "true", // DEFAULT_EXECUTION_MODE_PROPERTY_NAME, "concurrent", // RANDOM_SEED_PROPERTY_NAME, seed // ); // @formatter:off return EngineTestKit .engine("junit-jupiter") .configurationParameters(configurationParameters) .selectors(selectClass(RandomTestCase.class)) .execute() .testEvents(); // @formatter:on } private static void assertIneffectiveOrderAnnotationIssues(List discoveryIssues) throws Exception { assertThat(discoveryIssues).hasSize(3); assertThat(discoveryIssues).extracting(DiscoveryIssue::severity).containsOnly(Severity.INFO); assertThat(discoveryIssues).extracting(DiscoveryIssue::message) // .allMatch(it -> it.startsWith("Ineffective @Order annotation on method") && it.contains("It will not be applied because MethodOrderer.OrderAnnotation is not in use.") && it.endsWith( "Note that the annotation may be either directly present or meta-present on the method.")); var testClass = WithoutTestMethodOrderTestCase.class; assertThat(discoveryIssues).extracting(DiscoveryIssue::source).extracting(Optional::orElseThrow) // .containsExactlyInAnyOrder(MethodSource.from(testClass.getDeclaredMethod("test1")), MethodSource.from(testClass.getDeclaredMethod("test2")), MethodSource.from(testClass.getDeclaredMethod("test3"))); } // ------------------------------------------------------------------------- static class BaseTestCase { @Test void AAA() { } @Test void c() { } } @SuppressWarnings("unused") @TestMethodOrder(MethodName.class) static class AMethodNameTestCase extends BaseTestCase { @BeforeEach void trackInvocations(TestInfo testInfo) { var method = testInfo.getTestMethod().orElseThrow(AssertionError::new); var signature = "%s(%s)".formatted(method.getName(), ClassUtils.nullSafeToString(method.getParameterTypes())); callSequence.add(signature); threadNames.add(Thread.currentThread().getName()); } @TestFactory DynamicTest b() { return dynamicTest("dynamic", () -> { }); } @Test void $() { } @Test void ___() { } @Test void AAA(TestReporter testReporter) { } @Test void AAA(TestInfo testInfo) { } @Test void ZZ_Top() { } @Test void a1() { } @Test void a2() { } @RepeatedTest(1) void zzz() { } } @TestMethodOrder(MethodOrderer.DisplayName.class) static class DisplayNameTestCase { @BeforeEach void trackInvocations(TestInfo testInfo) { callSequence.add(testInfo.getDisplayName()); threadNames.add(Thread.currentThread().getName()); } @TestFactory DynamicTest b() { return dynamicTest("dynamic", () -> { }); } @DisplayName("$") @Test void $() { } @DisplayName("___") @Test void ___() { } @DisplayName("AAA") @Test void AAA() { } @DisplayName("ZZ_Top") @Test void ZZ_Top() { } @DisplayName("a1") @Test void a1() { } @DisplayName("a2") @Test void a2() { } @DisplayName("zzz") @RepeatedTest(1) void zzz() { } @Test @DisplayName("⑦ϼ\uD83D\uDC69\u200D⚕\uD83E\uDDD8\u200D♂") void special_characters() { } @Test void no_display_name_attribute_1() { } @Test void no_display_name_attribute_2() { } @Test void No_display_name_attribute_1_caps() { } @Test void No_display_name_attribute_2_caps() { } } @TestMethodOrder(OrderAnnotation.class) static class OrderAnnotationTestCase { @BeforeEach void trackInvocations(TestInfo testInfo) { callSequence.add(testInfo.getDisplayName()); threadNames.add(Thread.currentThread().getName()); } @Test @DisplayName("test8") @Order(Integer.MAX_VALUE) void maxInteger() { } @Test @DisplayName("test7") @Order(DEFAULT + 1) void defaultOrderValuePlusOne() { } @Test @DisplayName("test6") // @Order(DEFAULT) void defaultOrderValue() { } @Test @DisplayName("test3") @Order(3) void $() { } @Test @DisplayName("test5") @Order(5) void AAA() { } @TestFactory @DisplayName("test4") @Order(4) DynamicTest aaa() { return dynamicTest("test4", () -> { }); } @Test @DisplayName("test1") @Order(1) void zzz() { } @RepeatedTest(value = 1, name = "{displayName}") @DisplayName("test2") @Order(2) void ___() { } } static class OuterTestCase { @Nested class InnerTestCase { @Nested class NestedOrderAnnotationTestCase extends OrderAnnotationTestCase { } } } @TestMethodOrder(Random.class) static class RandomTestCase { @BeforeEach void trackInvocations(TestInfo testInfo) { callSequence.add(testInfo.getDisplayName()); threadNames.add(Thread.currentThread().getName()); } @Test void test1() { } @Test void test2() { } @Test void test3() { } @TestFactory DynamicTest test4() { return dynamicTest("dynamic", () -> { }); } @RepeatedTest(1) void test5() { } } @TestMethodOrder(MisbehavingByAdding.class) static class MisbehavingByAddingTestCase { @BeforeEach void trackInvocations(TestInfo testInfo) { callSequence.add(testInfo.getDisplayName()); } @Test void test2() { } @Test void test1() { } } @TestMethodOrder(MisbehavingByImpersonating.class) static class MisbehavingByImpersonatingTestCase { @BeforeEach void trackInvocations(TestInfo testInfo) { callSequence.add(testInfo.getDisplayName()); } @Test void test2() { } @Test void test1() { } } @TestMethodOrder(MisbehavingByRemoving.class) static class MisbehavingByRemovingTestCase { @BeforeEach void trackInvocations(TestInfo testInfo) { callSequence.add(testInfo.getDisplayName()); } @Test void test1() { } @Test void test4() { } @Test void test2() { } @Test void test3() { } } static class OrderAnnotationWithNestedClassTestCase extends OrderAnnotationTestCase { @Nested class NestedTests { @BeforeEach void trackInvocations(TestInfo testInfo) { callSequence.add(testInfo.getDisplayName()); } @Test @Order(1) @DisplayName("nestedTest1") void nestedTest1() { } @Test @Order(2) @DisplayName("nestedTest2") void nestedTest2() { } } } static class MisbehavingByAdding implements MethodOrderer { @Override public void orderMethods(MethodOrdererContext context) { context.getMethodDescriptors().sort(comparing(MethodDescriptor::getDisplayName)); context.getMethodDescriptors().add(mockMethodDescriptor()); context.getMethodDescriptors().add(mockMethodDescriptor()); } @SuppressWarnings("unchecked") static T mockMethodDescriptor() { return (T) Mockito.mock((Class) MethodDescriptor.class); } } static class MisbehavingByImpersonating implements MethodOrderer { @Override public void orderMethods(MethodOrdererContext context) { context.getMethodDescriptors().sort(comparing(MethodDescriptor::getDisplayName)); MethodDescriptor method1 = context.getMethodDescriptors().get(0); MethodDescriptor method2 = context.getMethodDescriptors().get(1); context.getMethodDescriptors().set(0, createMethodDescriptorImpersonator(method1)); context.getMethodDescriptors().set(1, createMethodDescriptorImpersonator(method2)); } @SuppressWarnings("unchecked") static T createMethodDescriptorImpersonator(MethodDescriptor method) { @NullMarked class Stub implements MethodDescriptor { @Override public Method getMethod() { throw new UnsupportedOperationException(); } @Override public String getDisplayName() { throw new UnsupportedOperationException(); } @Override public boolean isAnnotated(Class annotationType) { return false; } @Override public Optional findAnnotation(Class annotationType) { return Optional.empty(); } @Override public List findRepeatableAnnotations(Class annotationType) { return List.of(); } @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") @Override public boolean equals(Object obj) { return method.equals(obj); } @Override public int hashCode() { return method.hashCode(); } } return (T) new Stub(); } } static class MisbehavingByRemoving implements MethodOrderer { @Override public void orderMethods(MethodOrdererContext context) { context.getMethodDescriptors().sort(comparing(MethodDescriptor::getDisplayName)); context.getMethodDescriptors().removeFirst(); context.getMethodDescriptors().removeFirst(); } } static class WithoutTestMethodOrderTestCase { @BeforeEach void trackInvocations(TestInfo testInfo) { callSequence.add(testInfo.getDisplayName()); threadNames.add(Thread.currentThread().getName()); } @Test @Order(2) void test2() { } @Test @Order(3) void test3() { } @Test @Order(1) void test1() { } } static class ClassTemplateTestCase extends WithoutTestMethodOrderTestCase { } static class NestedClassWithDefaultOrderTestCase extends OrderAnnotationTestCase { @Nested @TestMethodOrder(Default.class) @Execution(ExecutionMode.SAME_THREAD) class NestedTests { @BeforeEach void trackInvocations(TestInfo testInfo) { callSequence.add(testInfo.getDisplayName()); } @Test @Order(3) void test1() { } @Test @Order(2) void test2() { } @Test @Order(4) void test3() { } @Test @Order(1) void test4() { } } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/OrderedProgrammaticExtensionRegistrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Order.DEFAULT; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.jupiter.engine.JupiterTestEngine; /** * Integration tests that verify support for {@linkplain Order ordered} programmatic * extension registration via {@link RegisterExtension @RegisterExtension} in the * {@link JupiterTestEngine}. * * @since 5.4 * @see ProgrammaticExtensionRegistrationTests */ class OrderedProgrammaticExtensionRegistrationTests extends AbstractJupiterTestEngineTests { private static final List callSequence = new ArrayList<>(); /** * This method basically verifies the implementation of * {@link java.lang.String#hashCode()} (which needn't really be tested) * in order to make reasonable assumptions about how fields are sorted * in {@link org.junit.platform.commons.util.ReflectionUtils#defaultFieldSorter(Field, Field)}. * *

In other words, this method is just a sanity check for the chosen * field names in the test cases used in these tests. */ @BeforeAll static void assertAssumptionsAboutDefaultOrderingAlgorithm() { String fieldName1 = "extension1"; String fieldName2 = "extension2"; String fieldName3 = "extension3"; assertThat(fieldName1.hashCode()).isLessThan(fieldName2.hashCode()); assertThat(fieldName2.hashCode()).isLessThan(fieldName3.hashCode()); } @BeforeEach void clearCallSequence() { callSequence.clear(); } @Test void instanceLevelWithDefaultOrder() { Class testClass = DefaultOrderInstanceLevelExtensionRegistrationTestCase.class; String testClassName = testClass.getSimpleName(); assertOutcome(testClass, // testClassName + " :: extension1 :: before test", // testClassName + " :: extension2 :: before test", // testClassName + " :: extension3 :: before test" // ); } @Test void instanceLevelWithExplicitOrder() { Class testClass = ExplicitOrderInstanceLevelExtensionRegistrationTestCase.class; String testClassName = testClass.getSimpleName(); assertOutcome(testClass, // testClassName + " :: extension3 :: before test", // testClassName + " :: extension2 :: before test", // testClassName + " :: extension1 :: before test" // ); } @Test void instanceLevelWithDefaultOrderAndExplicitOrder() { Class testClass = DefaultOrderAndExplicitOrderInstanceLevelExtensionRegistrationTestCase.class; String testClassName = testClass.getSimpleName(); assertOutcome(testClass, // testClassName + " :: extension3 :: before test", // testClassName + " :: extension1 :: before test", // testClassName + " :: extension2 :: before test" // ); } /** * Verify that an "after" callback can be registered first relative to other * non-annotated "after" callbacks. * * @since 5.6 * @see gh-1924 */ @Test void instanceLevelWithDefaultOrderPlusOneAndDefaultOrder() { Class testClass = DefaultOrderPlusOneAndDefaultOrderInstanceLevelExtensionRegistrationTestCase.class; String testClassName = testClass.getSimpleName(); assertOutcome(testClass, // testClassName + " :: extension1 :: after test", // testClassName + " :: extension3 :: after test", // testClassName + " :: extension2 :: after test" // ); } @Test void instanceLevelWithDefaultOrderAndExplicitOrderWithTestInstancePerClassLifecycle() { Class testClass = DefaultOrderAndExplicitOrderInstanceLevelExtensionRegistrationWithTestInstancePerClassLifecycleTestCase.class; String testClassName = testClass.getSimpleName(); assertOutcome(testClass, // testClassName + " :: extension3 :: before test", // testClassName + " :: extension1 :: before test", // testClassName + " :: extension2 :: before test" // ); } @Test void classLevelWithDefaultOrderAndExplicitOrder() { Class testClass = DefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase.class; String testClassName = testClass.getSimpleName(); assertOutcome(testClass, // testClassName + " :: extension3 :: before test", // testClassName + " :: extension1 :: before test", // testClassName + " :: extension2 :: before test" // ); } @Test void classLevelWithDefaultOrderAndExplicitOrderInheritedFromSuperclass() { Class testClass = InheritedDefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase.class; Class parent = DefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase.class; String parentName = parent.getSimpleName(); assertOutcome(testClass, // parentName + " :: extension3 :: before test", // parentName + " :: extension1 :: before test", // parentName + " :: extension2 :: before test" // ); } @Test void classLevelWithDefaultOrderDoesNotShadowExtensionFromSuperclass() { Class testClass = DefaultOrderShadowingDefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase.class; String testClassName = testClass.getSimpleName(); Class parent = DefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase.class; String parentName = parent.getSimpleName(); assertOutcome(testClass, // parentName + " :: extension3 :: before test", // parentName + " :: extension1 :: before test", // parentName + " :: extension2 :: before test", // testClassName + " :: extension3 :: before test" // ); } @Test void classLevelWithExplicitOrderDoesNotShadowExtensionFromSuperclass() { Class testClass = ExplicitOrderShadowingDefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase.class; String testClassName = testClass.getSimpleName(); Class parent = DefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase.class; String parentName = parent.getSimpleName(); assertOutcome(testClass, // parentName + " :: extension3 :: before test", // testClassName + " :: extension2 :: before test", // parentName + " :: extension1 :: before test", // parentName + " :: extension2 :: before test" // ); } @Test void classLevelWithDefaultOrderAndExplicitOrderFromInterface() { Class testClass = DefaultOrderAndExplicitOrderExtensionRegistrationFromInterfaceTestCase.class; Class testInterface = DefaultOrderAndExplicitOrderClassLevelExtensionRegistrationInterface.class; String interfaceName = testInterface.getSimpleName(); assertOutcome(testClass, // interfaceName + " :: extension3 :: before test", // interfaceName + " :: extension1 :: before test", // interfaceName + " :: extension2 :: before test" // ); } private void assertOutcome(Class testClass, String... values) { executeTestsForClass(testClass).testEvents().assertStatistics(stats -> stats.succeeded(1)); assertThat(callSequence).containsExactly(values); } // ------------------------------------------------------------------- private static class AbstractTestCase { @Test void test() { } } static class DefaultOrderInstanceLevelExtensionRegistrationTestCase extends AbstractTestCase { @RegisterExtension Extension extension1 = new BeforeEachExtension(1); @RegisterExtension Extension extension3 = new BeforeEachExtension(3); @RegisterExtension Extension extension2 = new BeforeEachExtension(2); } static class ExplicitOrderInstanceLevelExtensionRegistrationTestCase extends AbstractTestCase { @Order(3) @RegisterExtension Extension extension1 = new BeforeEachExtension(1); @Order(2) @RegisterExtension Extension extension2 = new BeforeEachExtension(2); @Order(1) @RegisterExtension Extension extension3 = new BeforeEachExtension(3); } static class DefaultOrderAndExplicitOrderInstanceLevelExtensionRegistrationTestCase extends AbstractTestCase { // @Order(3) @RegisterExtension Extension extension1 = new BeforeEachExtension(1); // @Order(2) @RegisterExtension Extension extension2 = new BeforeEachExtension(2); @Order(1) @RegisterExtension Extension extension3 = new BeforeEachExtension(3); } static class DefaultOrderPlusOneAndDefaultOrderInstanceLevelExtensionRegistrationTestCase extends AbstractTestCase { @Order(DEFAULT + 1) @RegisterExtension Extension extension1 = new AfterEachExtension(1); @RegisterExtension Extension extension2 = new AfterEachExtension(2); @RegisterExtension Extension extension3 = new AfterEachExtension(3); } @TestInstance(PER_CLASS) static class DefaultOrderAndExplicitOrderInstanceLevelExtensionRegistrationWithTestInstancePerClassLifecycleTestCase extends AbstractTestCase { // @Order(3) @RegisterExtension Extension extension1 = new BeforeEachExtension(1); // @Order(2) @RegisterExtension Extension extension2 = new BeforeEachExtension(2); @Order(1) @RegisterExtension Extension extension3 = new BeforeEachExtension(3); } static class DefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase extends AbstractTestCase { // @Order(3) @RegisterExtension static Extension extension1 = new BeforeEachExtension(1); // @Order(2) @RegisterExtension static Extension extension2 = new BeforeEachExtension(2); @Order(1) @RegisterExtension static Extension extension3 = new BeforeEachExtension(3); } static class InheritedDefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase extends DefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase { } static class DefaultOrderShadowingDefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase extends DefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase { // @Order(1) @RegisterExtension static Extension extension3 = new BeforeEachExtension(3); } static class ExplicitOrderShadowingDefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase extends DefaultOrderAndExplicitOrderClassLevelExtensionRegistrationTestCase { @Order(2) @RegisterExtension static Extension extension2 = new BeforeEachExtension(2); } interface DefaultOrderAndExplicitOrderClassLevelExtensionRegistrationInterface { // @Order(3) @RegisterExtension Extension extension1 = new BeforeEachExtension(1); // @Order(2) @RegisterExtension Extension extension2 = new BeforeEachExtension(2); @Order(1) @RegisterExtension Extension extension3 = new BeforeEachExtension(3); } static class DefaultOrderAndExplicitOrderExtensionRegistrationFromInterfaceTestCase extends AbstractTestCase implements DefaultOrderAndExplicitOrderClassLevelExtensionRegistrationInterface { } private static class BeforeEachExtension implements BeforeEachCallback { private final String prefix; BeforeEachExtension(int id) { Class callerClass = StackWalker.getInstance(RETAIN_CLASS_REFERENCE).getCallerClass(); this.prefix = callerClass.getSimpleName() + " :: extension" + id + " :: before "; } @Override public void beforeEach(ExtensionContext context) { callSequence.add(this.prefix + context.getRequiredTestMethod().getName()); } } private static class AfterEachExtension implements AfterEachCallback { private final String prefix; AfterEachExtension(int id) { Class callerClass = StackWalker.getInstance(RETAIN_CLASS_REFERENCE).getCallerClass(); this.prefix = callerClass.getSimpleName() + " :: extension" + id + " :: after "; } @Override public void afterEach(ExtensionContext context) { callSequence.add(this.prefix + context.getRequiredTestMethod().getName()); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ParameterResolverTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static java.util.Arrays.asList; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.EventConditions.test; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import java.lang.reflect.Method; import java.util.List; import java.util.Map; import java.util.function.Predicate; 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.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.jupiter.engine.JupiterTestEngine; import org.junit.jupiter.engine.execution.injection.sample.CustomAnnotation; import org.junit.jupiter.engine.execution.injection.sample.CustomAnnotationParameterResolver; import org.junit.jupiter.engine.execution.injection.sample.CustomType; import org.junit.jupiter.engine.execution.injection.sample.CustomTypeParameterResolver; import org.junit.jupiter.engine.execution.injection.sample.MapOfListsTypeBasedParameterResolver; import org.junit.jupiter.engine.execution.injection.sample.MapOfStringsParameterResolver; import org.junit.jupiter.engine.execution.injection.sample.NullIntegerParameterResolver; import org.junit.jupiter.engine.execution.injection.sample.NumberParameterResolver; import org.junit.jupiter.engine.execution.injection.sample.PrimitiveArrayParameterResolver; import org.junit.jupiter.engine.execution.injection.sample.PrimitiveIntegerParameterResolver; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.testkit.engine.EngineExecutionResults; import org.junit.platform.testkit.engine.Events; /** * Integration tests that verify support for {@link ParameterResolver} * extensions in the {@link JupiterTestEngine}. * * @since 5.0 */ class ParameterResolverTests extends AbstractJupiterTestEngineTests { @Test void constructorInjection() { EngineExecutionResults executionResults = executeTestsForClass(ConstructorInjectionTestCase.class); assertEquals(2, executionResults.testEvents().started().count(), "# tests started"); assertEquals(2, executionResults.testEvents().succeeded().count(), "# tests succeeded"); assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); } @Test void constructorInjectionWithAnnotatedParameter() { EngineExecutionResults executionResults = executeTestsForClass( AnnotatedParameterConstructorInjectionTestCase.class); assertEquals(2, executionResults.testEvents().started().count(), "# tests started"); assertEquals(2, executionResults.testEvents().succeeded().count(), "# tests succeeded"); assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); } @Test void executeTestsForMethodInjectionCases() { EngineExecutionResults executionResults = executeTestsForClass(MethodInjectionTestCase.class); assertEquals(7, executionResults.testEvents().started().count(), "# tests started"); assertEquals(6, executionResults.testEvents().succeeded().count(), "# tests succeeded"); assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); assertEquals(1, executionResults.testEvents().failed().count(), "# tests failed"); } @Test void executeTestsForNullValuedMethodInjectionCases() { EngineExecutionResults executionResults = executeTestsForClass(NullMethodInjectionTestCase.class); Events tests = executionResults.testEvents(); assertEquals(2, executionResults.testEvents().started().count(), "# tests started"); assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); assertEquals(1, executionResults.testEvents().failed().count(), "# tests failed"); // @formatter:off Predicate expectations = s -> s.contains("NullIntegerParameterResolver") && s.contains("resolved a null value for parameter") && s.contains("but a primitive of type [int] is required"); tests.failed().assertEventsMatchExactly( event( test("injectPrimitive"), finishedWithFailure(instanceOf(ParameterResolutionException.class), message(expectations)) )); // @formatter:on } @Test void executeTestsForPrimitiveIntegerMethodInjectionCases() { EngineExecutionResults executionResults = executeTestsForClass(PrimitiveIntegerMethodInjectionTestCase.class); assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); } @Test void executeTestsForPrimitiveArrayMethodInjectionCases() { EngineExecutionResults executionResults = executeTestsForClass(PrimitiveArrayMethodInjectionTestCase.class); assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); } @Test void executeTestsForPotentiallyIncompatibleTypeMethodInjectionCases() { EngineExecutionResults executionResults = executeTestsForClass( PotentiallyIncompatibleTypeMethodInjectionTestCase.class); Events tests = executionResults.testEvents(); assertEquals(3, executionResults.testEvents().started().count(), "# tests started"); assertEquals(2, executionResults.testEvents().succeeded().count(), "# tests succeeded"); assertEquals(1, executionResults.testEvents().failed().count(), "# tests failed"); // @formatter:off Predicate expectations = s -> s.contains("NumberParameterResolver") && s.contains("resolved a value of type [java.lang.Integer]") && s.contains("but a value assignment compatible with [java.lang.Double] is required"); tests.failed().assertEventsMatchExactly( event( test("doubleParameterInjection"), finishedWithFailure(instanceOf(ParameterResolutionException.class), message(expectations) ))); // @formatter:on } @Test void executeTestsForMethodInjectionInBeforeAndAfterEachMethods() { EngineExecutionResults executionResults = executeTestsForClass(BeforeAndAfterMethodInjectionTestCase.class); assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); } @Test void executeTestsForMethodInjectionInBeforeAndAfterAllMethods() { EngineExecutionResults executionResults = executeTestsForClass(BeforeAndAfterAllMethodInjectionTestCase.class); assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); } @Test void executeTestsForMethodWithExtendWithAnnotation() { EngineExecutionResults executionResults = executeTestsForClass(ExtendWithOnMethodTestCase.class); assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); } @Test void executeTestsForParameterizedTypesSelectingByClass() { assertEventsForParameterizedTypes(executeTestsForClass(ParameterizedTypeTestCase.class)); } @Test void executeTestsForParameterizedTypesSelectingByFullyQualifiedMethodName() { String fqmn = ReflectionUtils.getFullyQualifiedMethodName(ParameterizedTypeTestCase.class, "testMapOfStrings", Map.class); assertEventsForParameterizedTypes(executeTests(selectMethod(fqmn))); } @Test void executeTestsForTypeBasedParameterResolverTestCaseSelectingByClass() { assertEventsForParameterizedTypes(executeTestsForClass(TypeBasedParameterResolverTestCase.class)); } @Test void executeTestsForTypeBasedParameterResolverTestCaseSelectingByFullyQualifiedMethodName() { String fqmn = ReflectionUtils.getFullyQualifiedMethodName(TypeBasedParameterResolverTestCase.class, "testMapOfLists", Map.class); assertEventsForParameterizedTypes(executeTests(selectMethod(fqmn))); } @Disabled("Disabled until a decision has been made regarding #956") @Test void executeTestsForParameterizedTypesSelectingByFullyQualifiedMethodNameContainingGenericInfo() throws Exception { Method method = ParameterizedTypeTestCase.class.getDeclaredMethod("testMapOfStrings", Map.class); String genericParameterTypeName = method.getGenericParameterTypes()[0].getTypeName(); String fqmn = "%s#%s(%s)".formatted(ParameterizedTypeTestCase.class.getName(), "testMapOfStrings", genericParameterTypeName); assertEventsForParameterizedTypes(executeTests(selectMethod(fqmn))); } private void assertEventsForParameterizedTypes(EngineExecutionResults executionResults) { assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); assertEquals(0, executionResults.testEvents().skipped().count(), "# tests skipped"); assertEquals(0, executionResults.testEvents().aborted().count(), "# tests aborted"); assertEquals(0, executionResults.testEvents().failed().count(), "# tests failed"); } // ------------------------------------------------------------------- @ExtendWith(CustomTypeParameterResolver.class) static class ConstructorInjectionTestCase { private final TestInfo outerTestInfo; private final CustomType outerCustomType; ConstructorInjectionTestCase(TestInfo testInfo, CustomType customType) { this.outerTestInfo = testInfo; this.outerCustomType = customType; } @Test void test() { assertNotNull(this.outerTestInfo); assertNotNull(this.outerCustomType); } @Nested class NestedTestCase { private final TestInfo innerTestInfo; private final CustomType innerCustomType; NestedTestCase(TestInfo testInfo, CustomType customType) { this.innerTestInfo = testInfo; this.innerCustomType = customType; } @Test void test() { assertNotNull(outerTestInfo); assertNotNull(outerCustomType); assertNotNull(this.innerTestInfo); assertNotNull(this.innerCustomType); } } } @ExtendWith(CustomAnnotationParameterResolver.class) static class AnnotatedParameterConstructorInjectionTestCase { private final TestInfo outerTestInfo; private final CustomType outerCustomType; AnnotatedParameterConstructorInjectionTestCase(TestInfo testInfo, @CustomAnnotation CustomType customType) { this.outerTestInfo = testInfo; this.outerCustomType = customType; } @Test void test() { assertNotNull(this.outerTestInfo); assertNotNull(this.outerCustomType); } @Nested // See https://github.com/junit-team/junit-framework/issues/1345 class AnnotatedConstructorParameterNestedTestCase { private final TestInfo innerTestInfo; private final CustomType innerCustomType; AnnotatedConstructorParameterNestedTestCase(TestInfo testInfo, @CustomAnnotation CustomType customType) { this.innerTestInfo = testInfo; this.innerCustomType = customType; } @Test void test() { assertNotNull(outerTestInfo); assertNotNull(outerCustomType); assertNotNull(this.innerTestInfo); assertNotNull(this.innerCustomType); } } } @ExtendWith({ CustomTypeParameterResolver.class, CustomAnnotationParameterResolver.class }) static class MethodInjectionTestCase { @Test void parameterInjectionOfTestInfo(TestInfo testInfo) { assertNotNull(testInfo); } @Test void parameterInjectionWithCompetingResolversFail(@CustomAnnotation CustomType customType) { // should fail } @Test void parameterInjectionByType(CustomType customType) { assertNotNull(customType); } @Test void parameterInjectionByAnnotation(@CustomAnnotation String value) { assertNotNull(value); } // some overloaded methods @Test void overloadedName() { assertTrue(true); } @Test void overloadedName(CustomType customType) { assertNotNull(customType); } @Test void overloadedName(CustomType customType, @CustomAnnotation String value) { assertNotNull(customType); assertNotNull(value); } } @ExtendWith(NullIntegerParameterResolver.class) static class NullMethodInjectionTestCase { @Test void injectWrapper(Integer number) { assertNull(number); } @Test void injectPrimitive(int number) { // should never be invoked since an int cannot be null } } @ExtendWith(PrimitiveIntegerParameterResolver.class) static class PrimitiveIntegerMethodInjectionTestCase { @Test void intPrimitive(int i) { assertEquals(42, i); } } @ExtendWith(PrimitiveArrayParameterResolver.class) static class PrimitiveArrayMethodInjectionTestCase { @Test void primitiveArray(int... ints) { assertArrayEquals(new int[] { 1, 2, 3 }, ints); } } @ExtendWith(NumberParameterResolver.class) static class PotentiallyIncompatibleTypeMethodInjectionTestCase { @Test void numberParameterInjection(Number number) { assertEquals(Integer.valueOf(42), number); } @Test void integerParameterInjection(Integer number) { assertEquals(Integer.valueOf(42), number); } /** * This test must fail, since {@link Double} is a {@link Number} but not an {@link Integer}. * @see NumberParameterResolver */ @Test void doubleParameterInjection(Double number) { /* no-op */ } } static class BeforeAndAfterMethodInjectionTestCase { @BeforeEach void before(TestInfo testInfo) { assertEquals("custom name", testInfo.getDisplayName()); } @Test @DisplayName("custom name") void customNamedTest() { } @AfterEach void after(TestInfo testInfo) { assertEquals("custom name", testInfo.getDisplayName()); } } @DisplayName("custom class name") static class BeforeAndAfterAllMethodInjectionTestCase { @BeforeAll static void beforeAll(TestInfo testInfo) { assertEquals("custom class name", testInfo.getDisplayName()); } @Test void aTest() { } @AfterAll static void afterAll(TestInfo testInfo) { assertEquals("custom class name", testInfo.getDisplayName()); } } @SuppressWarnings("JUnitMalformedDeclaration") static class ExtendWithOnMethodTestCase { /** * This set-up / tear-down method is here to verify that {@code @BeforeEach} * and {@code @AfterEach} methods are properly invoked using the same * {@code ExtensionRegistry} as the one used for the corresponding * {@code @Test} method. * * @see #523 */ @SuppressWarnings("JUnitMalformedDeclaration") @BeforeEach @AfterEach void setUpAndTearDown(CustomType customType, @CustomAnnotation String value) { assertNotNull(customType); assertNotNull(value); } @Test @ExtendWith(CustomTypeParameterResolver.class) @ExtendWith(CustomAnnotationParameterResolver.class) void testMethodWithExtensionAnnotation(CustomType customType, @CustomAnnotation String value) { assertNotNull(customType); assertNotNull(value); } } static class ParameterizedTypeTestCase { @Test @ExtendWith(MapOfStringsParameterResolver.class) void testMapOfStrings(Map map) { assertNotNull(map); assertEquals("value", map.get("key")); } } static class TypeBasedParameterResolverTestCase { @Test @ExtendWith(MapOfListsTypeBasedParameterResolver.class) void testMapOfLists(Map> map) { assertNotNull(map); assertEquals(asList(1, 42), map.get("ids")); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/PreInterruptCallbackTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.condition.OS.WINDOWS; import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ_WRITE; import static org.junit.jupiter.api.parallel.Resources.SYSTEM_OUT; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.EventConditions.test; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.suppressed; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.nio.charset.StandardCharsets; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.UnaryOperator; import org.assertj.core.api.Condition; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Constants; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.PreInterruptCallback; import org.junit.jupiter.api.extension.PreInterruptContext; import org.junit.jupiter.api.parallel.Isolated; import org.junit.jupiter.api.parallel.ResourceLock; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; import org.junit.platform.testkit.engine.EngineExecutionResults; import org.junit.platform.testkit.engine.Events; /** * @since 5.12 */ @Isolated class PreInterruptCallbackTests extends AbstractJupiterTestEngineTests { private static final String TC = "test"; private static final String TIMEOUT_ERROR_MSG = TC + "() timed out after 1 microsecond"; private static final AtomicBoolean interruptedTest = new AtomicBoolean(); private static final CompletableFuture testThreadExecutionDone = new CompletableFuture<>(); private static final AtomicReference<@Nullable Thread> interruptedTestThread = new AtomicReference<>(); private static final AtomicBoolean interruptCallbackShallThrowException = new AtomicBoolean(); private static final AtomicReference<@Nullable PreInterruptContext> calledPreInterruptContext = new AtomicReference<>(); @BeforeEach void setUp() { interruptedTest.set(false); interruptCallbackShallThrowException.set(false); calledPreInterruptContext.set(null); } @AfterEach void tearDown() { calledPreInterruptContext.set(null); interruptedTestThread.set(null); } @Test @ResourceLock(value = SYSTEM_OUT, mode = READ_WRITE) void testCaseWithDefaultInterruptCallbackEnabled() { PrintStream orgOutStream = System.out; EngineExecutionResults results; String output; try { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); PrintStream outStream = new PrintStream(buffer, false, StandardCharsets.UTF_8); System.setOut(outStream); // Use larger timeout to increase likelihood of the test being started when the timeout is reached var timeout = WINDOWS.isCurrentOs() ? "1 s" : "100 ms"; results = executeDefaultPreInterruptCallbackTimeoutOnMethodTestCase(timeout, request -> request // .configurationParameter(Constants.EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_PROPERTY_NAME, "true")); output = buffer.toString(StandardCharsets.UTF_8); } finally { System.setOut(orgOutStream); } assertTestHasTimedOut(results.testEvents(), message(it -> it.startsWith(TC + "() timed out after"))); assertTrue(interruptedTest.get()); Thread thread = Thread.currentThread(); assertThat(output) // .containsSubsequence( "Thread \"%s\" prio=%d Id=%d %s will be interrupted.".formatted(thread.getName(), thread.getPriority(), thread.threadId(), Thread.State.TIMED_WAITING), // "java.lang.Thread.sleep", // "%s.test(PreInterruptCallbackTests.java".formatted( DefaultPreInterruptCallbackTimeoutOnMethodTestCase.class.getName())); assertThat(output) // .containsSubsequence( // "junit-jupiter-timeout-watcher", // "%s.beforeThreadInterrupt".formatted(PreInterruptThreadDumpPrinter.class.getName())); } @Test void testCaseWithNoInterruptCallbackEnabled() { Events tests = executeDefaultPreInterruptCallbackTimeoutOnMethodTestCase("1 μs", UnaryOperator.identity()) // .testEvents(); assertTestHasTimedOut(tests); assertTrue(interruptedTest.get()); } private EngineExecutionResults executeDefaultPreInterruptCallbackTimeoutOnMethodTestCase(String timeout, UnaryOperator configurer) { return executeTests(request -> configurer.apply(request // .selectors(selectClass(DefaultPreInterruptCallbackTimeoutOnMethodTestCase.class)) // .configurationParameter(Constants.DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME, timeout))); } @Test void testCaseWithDeclaredInterruptCallbackEnabled() { Events tests = executeTestsForClass(DefaultPreInterruptCallbackWithExplicitCallbackTestCase.class).testEvents(); assertTestHasTimedOut(tests); assertTrue(interruptedTest.get()); PreInterruptContext preInterruptContext = calledPreInterruptContext.get(); assertNotNull(preInterruptContext); assertNotNull(preInterruptContext.getThreadToInterrupt()); assertEquals(preInterruptContext.getThreadToInterrupt(), interruptedTestThread.get()); } @Test void testCaseWithDeclaredInterruptCallbackEnabledWithSeparateThread() throws Exception { Events tests = executeTestsForClass( DefaultPreInterruptCallbackWithExplicitCallbackWithSeparateThreadTestCase.class).testEvents(); assertOneFailedTest(tests); tests.failed().assertEventsMatchExactly( event(test(TC), finishedWithFailure(instanceOf(TimeoutException.class)))); //Wait until the real test thread was interrupted due to executor.shutdown(), otherwise the asserts below will be flaky. testThreadExecutionDone.get(1, TimeUnit.SECONDS); assertTrue(interruptedTest.get()); PreInterruptContext preInterruptContext = calledPreInterruptContext.get(); assertNotNull(preInterruptContext); assertNotNull(preInterruptContext.getThreadToInterrupt()); assertEquals(preInterruptContext.getThreadToInterrupt(), interruptedTestThread.get()); } @Test void testCaseWithDeclaredInterruptCallbackThrowsException() { interruptCallbackShallThrowException.set(true); Events tests = executeTestsForClass(DefaultPreInterruptCallbackWithExplicitCallbackTestCase.class).testEvents(); tests.failed().assertEventsMatchExactly(event(test(TC), finishedWithFailure(instanceOf(TimeoutException.class), message(TIMEOUT_ERROR_MSG), suppressed(0, instanceOf(InterruptedException.class)), suppressed(1, instanceOf(IllegalStateException.class))))); assertTrue(interruptedTest.get()); PreInterruptContext preInterruptContext = calledPreInterruptContext.get(); assertNotNull(preInterruptContext); assertNotNull(preInterruptContext.getThreadToInterrupt()); assertEquals(preInterruptContext.getThreadToInterrupt(), interruptedTestThread.get()); } private static void assertTestHasTimedOut(Events tests) { assertTestHasTimedOut(tests, message(TIMEOUT_ERROR_MSG)); } private static void assertTestHasTimedOut(Events tests, Condition messageCondition) { assertOneFailedTest(tests); tests.failed().assertEventsMatchExactly( event(test(TC), finishedWithFailure(instanceOf(TimeoutException.class), messageCondition, // suppressed(0, instanceOf(InterruptedException.class))// ))); } private static void assertOneFailedTest(Events tests) { tests.assertStatistics(stats -> stats.started(1).succeeded(0).failed(1)); } static class TestPreInterruptCallback implements PreInterruptCallback { @Override public void beforeThreadInterrupt(PreInterruptContext preInterruptContext, ExtensionContext extensionContext) { assertNotNull(extensionContext); calledPreInterruptContext.set(preInterruptContext); if (interruptCallbackShallThrowException.get()) { throw new IllegalStateException("Test-Ex"); } } } static class DefaultPreInterruptCallbackTimeoutOnMethodTestCase { @Test void test() throws InterruptedException { try { Thread.sleep(5_000); } catch (InterruptedException ex) { interruptedTest.set(true); interruptedTestThread.set(Thread.currentThread()); throw ex; } } } @ExtendWith(TestPreInterruptCallback.class) static class DefaultPreInterruptCallbackWithExplicitCallbackTestCase { @Test @Timeout(value = 1, unit = TimeUnit.MICROSECONDS) void test() throws InterruptedException { try { Thread.sleep(1000); } catch (InterruptedException ex) { interruptedTest.set(true); interruptedTestThread.set(Thread.currentThread()); throw ex; } } } @ExtendWith(TestPreInterruptCallback.class) static class DefaultPreInterruptCallbackWithExplicitCallbackWithSeparateThreadTestCase { @Test @Timeout(value = 200, unit = TimeUnit.MILLISECONDS, threadMode = Timeout.ThreadMode.SEPARATE_THREAD) void test() throws InterruptedException { try { Thread.sleep(2000); } catch (InterruptedException ex) { interruptedTest.set(true); interruptedTestThread.set(Thread.currentThread()); throw ex; } finally { testThreadExecutionDone.complete(null); } } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ProgrammaticExtensionRegistrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE; import static org.assertj.core.api.Assertions.allOf; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotatedFields; import static org.junit.platform.commons.support.ReflectionSupport.makeAccessible; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.cause; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import java.util.function.Predicate; import org.assertj.core.api.Condition; import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; 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.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.TestInstancePostProcessor; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.jupiter.engine.JupiterTestEngine; import org.junit.jupiter.engine.execution.injection.sample.LongParameterResolver; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.testkit.engine.EngineExecutionResults; /** * Integration tests that verify support for programmatic extension registration * via {@link RegisterExtension @RegisterExtension} in the {@link JupiterTestEngine}. * * @since 5.1 * @see OrderedProgrammaticExtensionRegistrationTests */ class ProgrammaticExtensionRegistrationTests extends AbstractJupiterTestEngineTests { private static final List callSequence = new ArrayList<>(); @BeforeEach void clearCallSequence() { callSequence.clear(); } @Test void instanceLevel() { assertOneTestSucceeded(InstanceLevelExtensionRegistrationTestCase.class); } @Test void instanceLevelWithInjectedExtension() { assertOneTestSucceeded(InstanceLevelExtensionRegistrationWithInjectedExtensionTestCase.class); } @Test void instanceLevelWithTestInstancePerClassLifecycle() { assertOneTestSucceeded(InstanceLevelExtensionRegistrationWithTestInstancePerClassLifecycleTestCase.class); } @Test void classLevel() { assertOneTestSucceeded(ClassLevelExtensionRegistrationTestCase.class); } @Test void classLevelFromSuperclass() { assertOneTestSucceeded(SubClassLevelExtensionRegistrationTestCase.class); } @Test void classLevelFromInterface() { assertOneTestSucceeded(ExtensionRegistrationFromInterfaceTestCase.class); } @Test void instanceLevelWithInheritedExtensions() { Class testClass = InstanceLevelExtensionRegistrationParentTestCase.class; String parent = testClass.getSimpleName(); assertOneTestSucceeded(testClass); assertThat(callSequence).containsExactly( // parent + " :: extension1 :: before test", // parent + " :: extension2 :: before test" // ); callSequence.clear(); testClass = InstanceLevelExtensionRegistrationChildTestCase.class; String child = testClass.getSimpleName(); assertOneTestSucceeded(testClass); assertThat(callSequence).containsExactly( // parent + " :: extension1 :: before test", // parent + " :: extension2 :: before test", // child + " :: extension2 :: before test", // child + " :: extension3 :: before test" // ); } @Test void classLevelWithInheritedExtensions() { Class testClass = ClassLevelExtensionRegistrationParentTestCase.class; String parent = testClass.getSimpleName(); assertOneTestSucceeded(testClass); assertThat(callSequence).containsExactly( // parent + " :: extension1 :: before test", // parent + " :: extension2 :: before test" // ); callSequence.clear(); testClass = ClassLevelExtensionRegistrationChildTestCase.class; String child = testClass.getSimpleName(); assertOneTestSucceeded(testClass); assertThat(callSequence).containsExactly( // parent + " :: extension1 :: before test", // parent + " :: extension2 :: before test", // child + " :: extension2 :: before test", // child + " :: extension3 :: before test" // ); } /** * @since 5.5 */ @Test void instanceLevelWithFieldThatDoesNotImplementAnExtensionApi() { assertOneTestSucceeded(InstanceLevelCustomExtensionApiTestCase.class); assertThat(callSequence).containsExactly( // CustomExtensionImpl.class.getSimpleName() + " :: before test", // CustomExtensionImpl.class.getSimpleName() + " :: doSomething()" // ); } /** * @since 5.5 */ @Test void classLevelWithFieldThatDoesNotImplementAnExtensionApi() { assertOneTestSucceeded(ClassLevelCustomExtensionApiTestCase.class); assertThat(callSequence).containsExactly( // CustomExtensionImpl.class.getSimpleName() + " :: before test", // CustomExtensionImpl.class.getSimpleName() + " :: doSomething()" // ); } /** * @since 5.5 */ @Test void instanceLevelWithPrivateField() { Class testClass = InstanceLevelExtensionRegistrationWithPrivateFieldTestCase.class; executeTestsForClass(testClass).testEvents().assertStatistics(stats -> stats.succeeded(1)); } /** * @since 5.5 */ @Test void classLevelWithPrivateField() { Class testClass = ClassLevelExtensionRegistrationWithPrivateFieldTestCase.class; executeTestsForClass(testClass).testEvents().assertStatistics(stats -> stats.succeeded(1)); } @Test void instanceLevelWithNullField() { Class testClass = InstanceLevelExtensionRegistrationWithNullFieldTestCase.class; executeTestsForClass(testClass).testEvents().debug().assertThatEvents().haveExactly(1, finishedWithFailure( instanceOf(PreconditionViolationException.class), message(expectedMessage(testClass, null)))); } @Test void classLevelWithNullField() { Class testClass = ClassLevelExtensionRegistrationWithNullFieldTestCase.class; executeTestsForClass(testClass).containerEvents().assertThatEvents().haveExactly(1, finishedWithFailure( instanceOf(PreconditionViolationException.class), message(expectedMessage(testClass, null)))); } /** * @since 5.5 */ @Test void instanceLevelWithNonExtensionFieldValue() { Class testClass = InstanceLevelExtensionRegistrationWithNonExtensionFieldValueTestCase.class; executeTestsForClass(testClass).testEvents().assertThatEvents().haveExactly(1, finishedWithFailure( instanceOf(PreconditionViolationException.class), message(expectedMessage(testClass, String.class)))); } /** * @since 5.5 */ @Test void classLevelWithNonExtensionFieldValue() { Class testClass = ClassLevelExtensionRegistrationWithNonExtensionFieldValueTestCase.class; executeTestsForClass(testClass).containerEvents().assertThatEvents().haveExactly(1, finishedWithFailure( instanceOf(PreconditionViolationException.class), message(expectedMessage(testClass, String.class)))); } private String expectedMessage(Class testClass, @Nullable Class valueType) { return "Failed to register extension via @RegisterExtension field [" + field(testClass) + "]: field value's type [" + (valueType != null ? valueType.getName() : null) + "] must implement an [" + Extension.class.getName() + "] API."; } private Field field(Class testClass) { try { return testClass.getDeclaredField("extension"); } catch (Exception ex) { throw new RuntimeException(ex); } } @Test void propagatesCheckedExceptionThrownDuringInitializationOfStaticField() { assertClassFails(ClassLevelExplosiveCheckedExceptionTestCase.class, allOf(instanceOf(ExceptionInInitializerError.class), cause(instanceOf(Exception.class), message("boom")))); } @Test void propagatesUncheckedExceptionThrownDuringInitializationOfStaticField() { assertClassFails(ClassLevelExplosiveUncheckedExceptionTestCase.class, allOf( instanceOf(ExceptionInInitializerError.class), cause(instanceOf(RuntimeException.class), message("boom")))); } @Test void propagatesErrorThrownDuringInitializationOfStaticField() { assertClassFails(ClassLevelExplosiveErrorTestCase.class, allOf(instanceOf(Error.class), message("boom"))); } @Test void propagatesCheckedExceptionThrownDuringInitializationOfInstanceField() { assertTestFails(InstanceLevelExplosiveCheckedExceptionTestCase.class, allOf(instanceOf(Exception.class), message("boom"))); } @Test void propagatesUncheckedExceptionThrownDuringInitializationOfInstanceField() { assertTestFails(InstanceLevelExplosiveUncheckedExceptionTestCase.class, allOf(instanceOf(RuntimeException.class), message("boom"))); } @Test void propagatesErrorThrownDuringInitializationOfInstanceField() { assertTestFails(InstanceLevelExplosiveErrorTestCase.class, allOf(instanceOf(Error.class), message("boom"))); } @Test void storesExtensionInRegistryOfNestedTestMethods() { var results = executeTestsForClass(TwoNestedClassesTestCase.class); results.testEvents().assertStatistics(stats -> stats.succeeded(4)); } private void assertClassFails(Class testClass, Condition causeCondition) { EngineExecutionResults executionResults = executeTestsForClass(testClass); executionResults.containerEvents().assertThatEvents().haveExactly(1, finishedWithFailure(causeCondition)); } private void assertTestFails(Class testClass, Condition causeCondition) { executeTestsForClass(testClass).testEvents().assertThatEvents().haveExactly(1, finishedWithFailure(causeCondition)); } private void assertOneTestSucceeded(Class testClass) { executeTestsForClass(testClass).testEvents().assertStatistics( stats -> stats.started(1).succeeded(1).skipped(0).aborted(0).failed(0)); } // ------------------------------------------------------------------- private static void assertWisdom(CrystalBall crystalBall, String wisdom, String useCase) { assertNotNull(crystalBall, useCase); assertEquals("Outlook good", wisdom, useCase); } static class InstanceLevelExtensionRegistrationTestCase { @RegisterExtension final CrystalBall crystalBall = new CrystalBall("Outlook good"); @BeforeEach void beforeEach(String wisdom) { assertWisdom(crystalBall, wisdom, "@BeforeEach"); } @Test void test(String wisdom) { assertWisdom(crystalBall, wisdom, "@Test"); } @AfterEach void afterEach(String wisdom) { assertWisdom(crystalBall, wisdom, "@AfterEach"); } } @ExtendWith(ExtensionInjector.class) @NullUnmarked static class InstanceLevelExtensionRegistrationWithInjectedExtensionTestCase { @RegisterExtension protected CrystalBall crystalBall; // Injected by ExtensionInjector. @BeforeEach void beforeEach(String wisdom) { assertWisdom(crystalBall, wisdom, "@BeforeEach"); } @Test void test(String wisdom) { assertWisdom(crystalBall, wisdom, "@Test"); } @AfterEach void afterEach(String wisdom) { assertWisdom(crystalBall, wisdom, "@AfterEach"); } } @TestInstance(PER_CLASS) static class InstanceLevelExtensionRegistrationWithTestInstancePerClassLifecycleTestCase { @RegisterExtension final CrystalBall crystalBall = new CrystalBall("Outlook good"); @BeforeAll void beforeAll(String wisdom) { assertWisdom(crystalBall, wisdom, "@BeforeAll"); } @BeforeEach void beforeEach(String wisdom) { assertWisdom(crystalBall, wisdom, "@BeforeEach"); } @Test void test(String wisdom) { assertWisdom(crystalBall, wisdom, "@Test"); } @AfterEach void afterEach(String wisdom) { assertWisdom(crystalBall, wisdom, "@AfterEach"); } @AfterAll void afterAll(String wisdom) { assertWisdom(crystalBall, wisdom, "@AfterAll"); } } static class ClassLevelExtensionRegistrationTestCase { @RegisterExtension static final CrystalBall crystalBall = new CrystalBall("Outlook good"); @BeforeAll static void beforeAll(String wisdom) { assertWisdom(crystalBall, wisdom, "@BeforeAll"); } @BeforeEach void beforeEach(String wisdom) { assertWisdom(crystalBall, wisdom, "@BeforeEach"); } @Test void test(String wisdom) { assertWisdom(crystalBall, wisdom, "@Test"); } @AfterEach void afterEach(String wisdom) { assertWisdom(crystalBall, wisdom, "@AfterEach"); } @AfterAll static void afterAll(String wisdom) { assertWisdom(crystalBall, wisdom, "@AfterAll"); } } static class SubClassLevelExtensionRegistrationTestCase extends ClassLevelExtensionRegistrationTestCase { @SuppressWarnings("JUnitMalformedDeclaration") @Test @Override void test(String wisdom) { assertWisdom(crystalBall, wisdom, "Overridden @Test"); } } interface ClassLevelExtensionRegistrationInterface { @RegisterExtension CrystalBall crystalBall = new CrystalBall("Outlook good"); @BeforeAll static void beforeAll(String wisdom) { assertWisdom(crystalBall, wisdom, "@BeforeAll"); } @BeforeEach default void beforeEach(String wisdom) { assertWisdom(crystalBall, wisdom, "@BeforeEach"); } @AfterEach default void afterEach(String wisdom) { assertWisdom(crystalBall, wisdom, "@AfterEach"); } @AfterAll static void afterAll(String wisdom) { assertWisdom(crystalBall, wisdom, "@AfterAll"); } } static class ExtensionRegistrationFromInterfaceTestCase implements ClassLevelExtensionRegistrationInterface { @SuppressWarnings("JUnitMalformedDeclaration") @Test void test(String wisdom) { assertWisdom(crystalBall, wisdom, "@Test"); } } private record CrystalBall(String wisdom) implements ParameterResolver { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return parameterContext.getParameter().getType() == String.class; } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return this.wisdom; } } static class ClassLevelExtensionRegistrationParentTestCase { @RegisterExtension static Extension extension1 = new BeforeEachExtension(1); @RegisterExtension static Extension extension2 = new BeforeEachExtension(2); @Test void test() { } } static class ClassLevelExtensionRegistrationChildTestCase extends ClassLevelExtensionRegistrationParentTestCase { // "Hides" ClassLevelExtensionRegistrationParentTestCase.extension2 in legacy mode @RegisterExtension static Extension extension2 = new BeforeEachExtension(2); @RegisterExtension static Extension extension3 = new BeforeEachExtension(3); } static class InstanceLevelExtensionRegistrationParentTestCase { @RegisterExtension Extension extension1 = new BeforeEachExtension(1); @RegisterExtension Extension extension2 = new BeforeEachExtension(2); @Test void test() { } } static class InstanceLevelExtensionRegistrationChildTestCase extends InstanceLevelExtensionRegistrationParentTestCase { // "Hides" InstanceLevelExtensionRegistrationParentTestCase.extension2 in legacy mode @RegisterExtension Extension extension2 = new BeforeEachExtension(2); @RegisterExtension Extension extension3 = new BeforeEachExtension(3); } private static class BeforeEachExtension implements BeforeEachCallback { private final String prefix; BeforeEachExtension(int id) { Class callerClass = StackWalker.getInstance(RETAIN_CLASS_REFERENCE).getCallerClass(); this.prefix = callerClass.getSimpleName() + " :: extension" + id + " :: before "; } @Override public void beforeEach(ExtensionContext context) { callSequence.add(this.prefix + context.getRequiredTestMethod().getName()); } } /** * This interface intentionally does not implement a supported {@link Extension} API. */ interface CustomExtension { void doSomething(); } static class CustomExtensionImpl implements CustomExtension, BeforeEachCallback { @Override public void doSomething() { callSequence.add(getClass().getSimpleName() + " :: doSomething()"); } @Override public void beforeEach(ExtensionContext context) throws Exception { callSequence.add(getClass().getSimpleName() + " :: before " + context.getRequiredTestMethod().getName()); } } static class InstanceLevelCustomExtensionApiTestCase { @SuppressWarnings("JUnitMalformedDeclaration") @RegisterExtension CustomExtension extension = new CustomExtensionImpl(); @Test void test() { this.extension.doSomething(); } } static class ClassLevelCustomExtensionApiTestCase { @SuppressWarnings("JUnitMalformedDeclaration") @RegisterExtension static CustomExtension extension = new CustomExtensionImpl(); @Test void test() { extension.doSomething(); } } static class AbstractTestCase { @Test void test() { } } static class InstanceLevelExtensionRegistrationWithPrivateFieldTestCase extends AbstractTestCase { @SuppressWarnings("JUnitMalformedDeclaration") @RegisterExtension private Extension extension = new Extension() { }; } static class ClassLevelExtensionRegistrationWithPrivateFieldTestCase extends AbstractTestCase { @SuppressWarnings("JUnitMalformedDeclaration") @RegisterExtension private static Extension extension = new Extension() { }; } @NullUnmarked static class InstanceLevelExtensionRegistrationWithNullFieldTestCase extends AbstractTestCase { @RegisterExtension Extension extension; } @NullUnmarked static class ClassLevelExtensionRegistrationWithNullFieldTestCase extends AbstractTestCase { @RegisterExtension static Extension extension; } static class InstanceLevelExtensionRegistrationWithNonExtensionFieldValueTestCase extends AbstractTestCase { @SuppressWarnings("JUnitMalformedDeclaration") @RegisterExtension Object extension = "not an extension type"; } static class ClassLevelExtensionRegistrationWithNonExtensionFieldValueTestCase extends AbstractTestCase { @SuppressWarnings("JUnitMalformedDeclaration") @RegisterExtension static Object extension = "not an extension type"; } static class ClassLevelExplosiveCheckedExceptionTestCase extends AbstractTestCase { @RegisterExtension static Extension field = new ExplosiveExtension(new Exception("boom")); } static class ClassLevelExplosiveUncheckedExceptionTestCase extends AbstractTestCase { @RegisterExtension static Extension field = new ExplosiveExtension(new RuntimeException("boom")); } static class ClassLevelExplosiveErrorTestCase extends AbstractTestCase { @RegisterExtension static Extension field = new ExplosiveExtension(new Error("boom")); } static class InstanceLevelExplosiveCheckedExceptionTestCase extends AbstractTestCase { @RegisterExtension Extension field = new ExplosiveExtension(new Exception("boom")); } static class InstanceLevelExplosiveUncheckedExceptionTestCase extends AbstractTestCase { @RegisterExtension Extension field = new ExplosiveExtension(new RuntimeException("boom")); } static class InstanceLevelExplosiveErrorTestCase extends AbstractTestCase { @RegisterExtension Extension field = new ExplosiveExtension(new Error("boom")); } static class ExplosiveExtension implements Extension { ExplosiveExtension(Throwable t) { throw ExceptionUtils.throwAsUncheckedException(t); } } /** * Mimics a dependency injection framework such as Spring, Guice, CDI, etc., * where the instance of the extension registered via * {@link RegisterExtension @RegisterExtension} is managed by the DI * framework and injected into the test instance. */ private static class ExtensionInjector implements TestInstancePostProcessor { private static final Predicate isCrystalBall = field -> CrystalBall.class.isAssignableFrom( field.getType()); @Override public void postProcessTestInstance(Object testInstance, ExtensionContext context) { // @formatter:off findAnnotatedFields(testInstance.getClass(), RegisterExtension.class, isCrystalBall).stream() .findFirst() .ifPresent(field -> { try { makeAccessible(field).set(testInstance, new CrystalBall("Outlook good")); } catch (Throwable t) { throw ExceptionUtils.throwAsUncheckedException(t); } }); // @formatter:on } } static class TwoNestedClassesTestCase { @RegisterExtension Extension extension = new LongParameterResolver(); @Nested class A { @SuppressWarnings("JUnitMalformedDeclaration") @Test void first(Long n) { assertEquals(42L, n); } @SuppressWarnings("JUnitMalformedDeclaration") @Test void second(Long n) { assertEquals(42L, n); } } @Nested class B { @SuppressWarnings("JUnitMalformedDeclaration") @Test void first(Long n) { assertEquals(42L, n); } @SuppressWarnings("JUnitMalformedDeclaration") @Test void second(Long n) { assertEquals(42L, n); } } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/RepeatedTestTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Constants.DEFAULT_EXECUTION_MODE_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.PARALLEL_CONFIG_EXECUTOR_SERVICE_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.PARALLEL_CONFIG_STRATEGY_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.launcher.LauncherConstants.CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.junit.platform.testkit.engine.EventConditions.container; import static org.junit.platform.testkit.engine.EventConditions.displayName; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.EventConditions.skippedWithReason; import static org.junit.platform.testkit.engine.EventConditions.started; import static org.junit.platform.testkit.engine.EventConditions.test; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import java.lang.reflect.Method; import java.util.concurrent.atomic.AtomicInteger; import org.assertj.core.api.Condition; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.RepetitionInfo; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.support.hierarchical.ParallelHierarchicalTestExecutorServiceFactory.ParallelExecutorServiceType; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.testkit.engine.Events; /** * Integration tests for {@link RepeatedTest @RepeatedTest} and supporting * infrastructure. * * @since 5.0 */ class RepeatedTestTests extends AbstractJupiterTestEngineTests { @RepeatedTest(1) @DisplayName("Repeat!") void customDisplayName(TestInfo testInfo) { assertThat(testInfo.getDisplayName()).isEqualTo("repetition 1 of 1"); } @Test void customDisplayNameWithBlankName() { executeTests(request -> request // .selectors(selectClass(BlankDisplayNameTestCase.class)) // .configurationParameter(CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME, Severity.ERROR.name())) // .testEvents() // .assertStatistics(stats -> stats.started(1).succeeded(1)); } @RepeatedTest(value = 1, name = "{displayName}") @DisplayName("Repeat!") void customDisplayNameWithPatternIncludingDisplayName(TestInfo testInfo) { assertThat(testInfo.getDisplayName()).isEqualTo("Repeat!"); } @RepeatedTest(value = 1, name = "#{currentRepetition}") @DisplayName("Repeat!") void customDisplayNameWithPatternIncludingCurrentRepetition(TestInfo testInfo) { assertThat(testInfo.getDisplayName()).isEqualTo("#1"); } @RepeatedTest(value = 1, name = "Repetition #{currentRepetition} for {displayName}") @DisplayName("Repeat!") void customDisplayNameWithPatternIncludingDisplayNameAndCurrentRepetition(TestInfo testInfo) { assertThat(testInfo.getDisplayName()).isEqualTo("Repetition #1 for Repeat!"); } @RepeatedTest(value = 1, name = RepeatedTest.LONG_DISPLAY_NAME) @DisplayName("Repeat!") void customDisplayNameWithPredefinedLongPattern(TestInfo testInfo) { assertThat(testInfo.getDisplayName()).isEqualTo("Repeat! :: repetition 1 of 1"); } @RepeatedTest(value = 1, name = "{displayName} {currentRepetition}/{totalRepetitions}") @DisplayName("Repeat!") void customDisplayNameWithPatternIncludingDisplayNameCurrentRepetitionAndTotalRepetitions(TestInfo testInfo) { assertThat(testInfo.getDisplayName()).isEqualTo("Repeat! 1/1"); } @RepeatedTest(value = 1, name = "Repetition #{currentRepetition} for {displayName}") void defaultDisplayNameWithPatternIncludingDisplayNameAndCurrentRepetition(TestInfo testInfo) { assertThat(testInfo.getDisplayName()).isEqualTo( "Repetition #1 for defaultDisplayNameWithPatternIncludingDisplayNameAndCurrentRepetition(TestInfo)"); } @RepeatedTest(value = 5, name = "{displayName}") void injectRepetitionInfo(TestInfo testInfo, RepetitionInfo repetitionInfo) { assertThat(testInfo.getDisplayName()).isEqualTo("injectRepetitionInfo(TestInfo, RepetitionInfo)"); assertThat(repetitionInfo.getCurrentRepetition()).isBetween(1, 5); assertThat(repetitionInfo.getTotalRepetitions()).isEqualTo(5); } @Nested class LifecycleMethodTests { private static int fortyTwo = 0; @AfterAll static void afterAll() { assertEquals(42, fortyTwo); } // Can be injected into test class constructors if the test class only has @RepeatedTest methods LifecycleMethodTests(RepetitionInfo repetitionInfo) { assertNotNull(repetitionInfo); } @BeforeEach @AfterEach void beforeAndAfterEach(TestInfo testInfo, RepetitionInfo repetitionInfo) { switch (testInfo.getTestMethod().orElseThrow().getName()) { case "repeatedOnce" -> { assertThat(repetitionInfo.getCurrentRepetition()).isEqualTo(1); assertThat(repetitionInfo.getTotalRepetitions()).isEqualTo(1); } case "repeatedFortyTwoTimes" -> { assertThat(repetitionInfo.getCurrentRepetition()).isBetween(1, 42); assertThat(repetitionInfo.getTotalRepetitions()).isEqualTo(42); } } } @RepeatedTest(1) void repeatedOnce(TestInfo testInfo) { assertThat(testInfo.getDisplayName()).isEqualTo("repetition 1 of 1"); } @RepeatedTest(42) void repeatedFortyTwoTimes(TestInfo testInfo) { assertThat(testInfo.getDisplayName()).matches("repetition \\d+ of 42"); fortyTwo++; } } @Nested class FailureTests { private static final Condition emptyPattern = message( value -> value.contains("must be declared with a non-empty name")); private static final Condition invalidRepetitionCount = message( value -> value.contains("must be declared with a positive 'value'")); private static final Condition invalidThreshold = message(value -> value.endsWith( "must declare a 'failureThreshold' greater than zero and less than the total number of repetitions [10].")); @BeforeEach void resetCounter() { TestCase.counter.set(0); } @Test void failsContainerForEmptyPattern() { executeTest("testWithEmptyPattern").assertThatEvents() // .haveExactly(1, event(container(), displayName("testWithEmptyPattern()"), // finishedWithFailure(emptyPattern))); } @Test void failsContainerForBlankPattern() { executeTest("testWithBlankPattern").assertThatEvents() // .haveExactly(1, event(container(), displayName("testWithBlankPattern()"), // finishedWithFailure(emptyPattern))); } @Test void failsContainerForNegativeRepeatCount() { executeTest("negativeRepeatCount").assertThatEvents() // .haveExactly(1, event(container(), displayName("negativeRepeatCount()"), // finishedWithFailure(invalidRepetitionCount))); } @Test void failsContainerForZeroRepeatCount() { executeTest("zeroRepeatCount").assertThatEvents() // .haveExactly(1, event(container(), displayName("zeroRepeatCount()"), // finishedWithFailure(invalidRepetitionCount))); } @Test void failsContainerForFailureThresholdSetToNegativeValue() { executeTest("failureThresholdSetToNegativeValue").assertThatEvents() // .haveExactly(1, event(container(), displayName("failureThresholdSetToNegativeValue()"), // finishedWithFailure(invalidThreshold))); } @Test void failsContainerForFailureThresholdSetToZero() { executeTest("failureThresholdSetToZero").assertThatEvents() // .haveExactly(1, event(container(), displayName("failureThresholdSetToZero()"), // finishedWithFailure(invalidThreshold))); } @Test void failsContainerForFailureThresholdGreaterThanRepetitionCount() { executeTest("failureThresholdGreaterThanRepetitionCount").assertThatEvents() // .haveExactly(1, event(container(), displayName("failureThresholdGreaterThanRepetitionCount()"), // finishedWithFailure(invalidThreshold))); } @Test void failsContainerForFailureThresholdEqualToRepetitionCount() { executeTest("failureThresholdEqualToRepetitionCount").assertThatEvents() // .haveExactly(1, event(container(), displayName("failureThresholdEqualToRepetitionCount()"), // finishedWithFailure(invalidThreshold))); } @Test void failureThresholdEqualToRepetitionCountMinusOne() { String methodName = "failureThresholdEqualToRepetitionCountMinusOne"; // @formatter:off executeTest(methodName).assertEventsMatchLooselyInOrder( event(container(methodName), started()), event(test("test-template-invocation:#1"), finishedWithFailure(message("Boom!"))), event(test("test-template-invocation:#2"), finishedWithFailure(message("Boom!"))), event(test("test-template-invocation:#3"), skippedWithReason("Failure threshold [2] exceeded")), event(container(methodName), finishedSuccessfully())); // @formatter:on } @Test void failureThreshold1() { String methodName = "failureThreshold1"; // @formatter:off executeTest(methodName).assertEventsMatchLooselyInOrder( event(container(methodName), started()), event(test("test-template-invocation:#1"), finishedSuccessfully()), event(test("test-template-invocation:#2"), finishedWithFailure(message("Boom!"))), event(test("test-template-invocation:#3"), skippedWithReason("Failure threshold [1] exceeded")), event(container(methodName), finishedSuccessfully())); // @formatter:on } @Test void failureThreshold2() { String methodName = "failureThreshold2"; // @formatter:off executeTest(methodName).assertEventsMatchLooselyInOrder( event(container(methodName), started()), event(test("test-template-invocation:#1"), finishedSuccessfully()), event(test("test-template-invocation:#2"), finishedWithFailure(message("Boom!"))), event(test("test-template-invocation:#3"), finishedWithFailure(message("Boom!"))), event(test("test-template-invocation:#4"), skippedWithReason("Failure threshold [2] exceeded")), event(container(methodName), finishedSuccessfully())); // @formatter:on } @Test void failureThreshold3() { String methodName = "failureThreshold3"; // @formatter:off executeTest(methodName).assertEventsMatchLooselyInOrder( event(container(methodName), started()), event(test("test-template-invocation:#1"), finishedSuccessfully()), event(test("test-template-invocation:#2"), finishedWithFailure(message("Boom!"))), event(test("test-template-invocation:#3"), finishedSuccessfully()), event(test("test-template-invocation:#4"), finishedWithFailure(message("Boom!"))), event(test("test-template-invocation:#5"), finishedSuccessfully()), event(test("test-template-invocation:#6"), finishedWithFailure(message("Boom!"))), event(test("test-template-invocation:#7"), skippedWithReason("Failure threshold [3] exceeded")), event(test("test-template-invocation:#8"), skippedWithReason("Failure threshold [3] exceeded")), event(container(methodName), finishedSuccessfully())); // @formatter:on } @ParameterizedTest @EnumSource(ParallelExecutorServiceType.class) void failureThresholdWithConcurrentExecution(ParallelExecutorServiceType executorServiceType) { Class testClass = TestCase.class; String methodName = "failureThresholdWithConcurrentExecution"; Method method = ReflectionSupport.findMethod(testClass, methodName).orElseThrow(); LauncherDiscoveryRequest request = request()// .selectors(selectMethod(testClass, method))// .configurationParameter(PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, "true")// .configurationParameter(PARALLEL_CONFIG_EXECUTOR_SERVICE_PROPERTY_NAME, executorServiceType.name()) // .configurationParameter(DEFAULT_EXECUTION_MODE_PROPERTY_NAME, "concurrent")// .configurationParameter(PARALLEL_CONFIG_STRATEGY_PROPERTY_NAME, "fixed")// .configurationParameter(PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME, "4")// .build(); Events tests = executeTests(request).testEvents(); // There are 20 repetitions/tests in total. assertThat(tests.dynamicallyRegistered().count()).as("registered").isEqualTo(20); assertThat(tests.started().count() + tests.skipped().count()).as("started or skipped").isEqualTo(20); // Would be 3 successful tests without parallel execution, but with race conditions // and multiple threads we may encounter more; and yet we still should not // encounter too many. assertThat(tests.succeeded().count()).as("succeeded").isBetween(3L, 10L); // Would be 3 failed tests without parallel execution, but with race conditions // and multiple threads we may encounter more. assertThat(tests.failed().count()).as("failed").isGreaterThanOrEqualTo(3); // Would be 14 skipped tests without parallel execution, but with race conditions // and multiple threads we may not encounter many. assertThat(tests.skipped().count()).as("skipped").isGreaterThan(0); } private Events executeTest(String methodName) { Class testClass = TestCase.class; Method method = ReflectionSupport.findMethod(testClass, methodName).orElseThrow(); return executeTests(selectMethod(testClass, method)).allEvents(); } } static class TestCase { static final AtomicInteger counter = new AtomicInteger(); @RepeatedTest(value = 1, name = "") void testWithEmptyPattern() { } @RepeatedTest(value = 1, name = " \t ") void testWithBlankPattern() { } @SuppressWarnings("JUnitMalformedDeclaration") @RepeatedTest(-99) void negativeRepeatCount() { } @SuppressWarnings("JUnitMalformedDeclaration") @RepeatedTest(0) void zeroRepeatCount() { } @RepeatedTest(value = 10, failureThreshold = -1) void failureThresholdSetToNegativeValue() { fail("Boom!"); } @RepeatedTest(value = 10, failureThreshold = 0) void failureThresholdSetToZero() { fail("Boom!"); } @RepeatedTest(value = 10, failureThreshold = 11) void failureThresholdGreaterThanRepetitionCount() { fail("Boom!"); } @RepeatedTest(value = 10, failureThreshold = 10) void failureThresholdEqualToRepetitionCount() { fail("Boom!"); } @RepeatedTest(value = 3, failureThreshold = 2) void failureThresholdEqualToRepetitionCountMinusOne() { fail("Boom!"); } @RepeatedTest(value = 3, failureThreshold = 1) void failureThreshold1() { int count = counter.incrementAndGet(); if (count > 1) { fail("Boom!"); } } @RepeatedTest(value = 4, failureThreshold = 2) void failureThreshold2() { int count = counter.incrementAndGet(); if (count > 1) { fail("Boom!"); } } @RepeatedTest(value = 8, failureThreshold = 3) void failureThreshold3() { int count = counter.incrementAndGet(); if ((count > 1) && (count % 2 == 0)) { fail("Boom!"); } } @RepeatedTest(value = 20, failureThreshold = 3) void failureThresholdWithConcurrentExecution() { int count = counter.incrementAndGet(); if ((count > 1) && (count % 2 == 0)) { fail("Boom!"); } } } static class BlankDisplayNameTestCase { @RepeatedTest(1) @DisplayName(" \t ") void test(TestInfo testInfo) { assertThat(testInfo.getDisplayName()).isEqualTo("repetition 1 of 1"); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/SameThreadTimeoutInvocationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeoutException; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.ThrowingConsumer; /** * @since 5.5 */ class SameThreadTimeoutInvocationTests { @Test void resetsInterruptFlag() { var exception = assertThrows(TimeoutException.class, () -> withExecutor(executor -> { var delegate = new EventuallyInterruptibleInvocation(); var duration = new TimeoutDuration(1, NANOSECONDS); var timeoutInvocation = new SameThreadTimeoutInvocation<>(delegate, duration, executor, () -> "execution", PreInterruptCallbackInvocation.NOOP); timeoutInvocation.proceed(); })); assertFalse(Thread.currentThread().isInterrupted()); assertThat(exception).hasMessage("execution timed out after 1 nanosecond"); } private void withExecutor(ThrowingConsumer consumer) throws Throwable { ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); try { consumer.accept(executor); } finally { executor.shutdown(); assertTrue(executor.awaitTermination(5, SECONDS)); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.condition.OS.WINDOWS; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout.ThreadMode; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; import org.junit.jupiter.engine.execution.NamespaceAwareStore; import org.junit.jupiter.engine.extension.TimeoutInvocationFactory.TimeoutInvocationParameters; import org.junit.platform.engine.support.store.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; /** * @since 5.9 */ @DisplayName("SeparateThreadTimeoutInvocation") class SeparateThreadTimeoutInvocationTests { private static final long PREEMPTIVE_TIMEOUT_MILLIS = WINDOWS.isCurrentOs() ? 1000 : 100; @Test @DisplayName("throws timeout exception when timeout duration is exceeded") void throwsTimeoutException() { AtomicReference threadName = new AtomicReference<>(); var invocation = aSeparateThreadInvocation(() -> { threadName.set(Thread.currentThread().getName()); Thread.sleep(PREEMPTIVE_TIMEOUT_MILLIS * 2); return "ignored"; }); assertThatThrownBy(invocation::proceed) // .hasMessage("method() timed out after " + PREEMPTIVE_TIMEOUT_MILLIS + " milliseconds") // .isInstanceOf(TimeoutException.class) // .hasRootCauseMessage("Execution timed out in thread " + threadName.get()); } @Test @DisplayName("executes invocation in a separate thread") void runsInvocationUsingSeparateThread() throws Throwable { var invocationThreadName = aSeparateThreadInvocation(() -> Thread.currentThread().getName()).proceed(); assertThat(invocationThreadName).isNotEqualTo(Thread.currentThread().getName()); } @Test @DisplayName("throws invocation exception") void shouldThrowInvocationException() { var invocation = aSeparateThreadInvocation(() -> { throw new RuntimeException("hi!"); }); assertThatThrownBy(invocation::proceed) // .isInstanceOf(RuntimeException.class) // .hasMessage("hi!"); } private static SeparateThreadTimeoutInvocation aSeparateThreadInvocation( Invocation invocation) { var namespace = ExtensionContext.Namespace.create(SeparateThreadTimeoutInvocationTests.class); var store = new NamespaceAwareStore(new NamespacedHierarchicalStore<>(null), Namespace.create(namespace.getParts())); var parameters = new TimeoutInvocationParameters<>(invocation, new TimeoutDuration(PREEMPTIVE_TIMEOUT_MILLIS, MILLISECONDS), () -> "method()", PreInterruptCallbackInvocation.NOOP); return (SeparateThreadTimeoutInvocation) new TimeoutInvocationFactory(store) // .create(ThreadMode.SEPARATE_THREAD, parameters); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ServiceLoaderExtension.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; /** * Demo extension for auto-detection of extensions loaded via Java's * {@link java.util.ServiceLoader} mechanism. * * @since 5.0 */ public class ServiceLoaderExtension implements BeforeAllCallback { @Override public void beforeAll(ExtensionContext context) { } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static java.nio.file.Files.deleteIfExists; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Constants.DEFAULT_TEMP_DIR_CLEANUP_MODE_PROPERTY_NAME; import static org.junit.jupiter.api.condition.OS.WINDOWS; import static org.junit.jupiter.api.io.CleanupMode.ALWAYS; import static org.junit.jupiter.api.io.CleanupMode.NEVER; import static org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasses; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.logging.Level; import java.util.logging.LogRecord; import org.jspecify.annotations.NullUnmarked; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.condition.EnabledOnOs; import org.junit.jupiter.api.fixtures.TrackLogRecords; import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; /** * Test that {@linkplain TempDir temporary directories} are not deleted with * {@link CleanupMode#NEVER}, are deleted with {@link CleanupMode#ON_SUCCESS} * but only if the test passes, and are always deleted with {@link CleanupMode#ALWAYS}. * * @see CleanupMode * @see TempDir * @since 5.9 */ class TempDirectoryCleanupTests extends AbstractJupiterTestEngineTests { @Nested @NullUnmarked class TempDirFieldTests { private static Path defaultFieldDir; private static Path neverFieldDir; private static Path alwaysFieldDir; private static Path onSuccessFailingFieldDir; private static Path onSuccessPassingFieldDir; private static Path onSuccessPassingParameterDir; /** * Ensure the cleanup mode defaults to ALWAYS for fields. *

* Expect the TempDir to be cleaned up. */ @Test void cleanupModeDefaultField() { LauncherDiscoveryRequest request = request()// .selectors(selectMethod(DefaultFieldCase.class, "testDefaultField"))// .build(); executeTests(request); assertThat(defaultFieldDir).doesNotExist(); } /** * Ensure that a custom, global cleanup mode is used for fields. *

* Expect the TempDir NOT to be cleaned up if set to NEVER. */ @Test void cleanupModeCustomDefaultField() { LauncherDiscoveryRequest request = request()// .configurationParameter(DEFAULT_TEMP_DIR_CLEANUP_MODE_PROPERTY_NAME, "never")// .selectors(selectMethod(DefaultFieldCase.class, "testDefaultField"))// .build(); executeTests(request); assertThat(defaultFieldDir).exists(); } /** * Ensure that NEVER cleanup modes are obeyed for fields. *

* Expect the TempDir not to be cleaned up. */ @Test void cleanupModeNeverField() { LauncherDiscoveryRequest request = request()// .selectors(selectMethod(NeverFieldCase.class, "testNeverField"))// .build(); executeTests(request); assertThat(neverFieldDir).exists(); } /** * Ensure that ALWAYS cleanup modes are obeyed for fields. *

* Expect the TempDir to be cleaned up. */ @Test void cleanupModeAlwaysField() { LauncherDiscoveryRequest request = request()// .selectors(selectMethod(AlwaysFieldCase.class, "testAlwaysField"))// .build(); executeTests(request); assertThat(alwaysFieldDir).doesNotExist(); } /** * Ensure that ON_SUCCESS cleanup modes are obeyed for passing field tests. *

* Expect the TempDir to be cleaned up. */ @Test void cleanupModeOnSuccessPassingField() { LauncherDiscoveryRequest request = request()// .selectors(selectMethod(OnSuccessPassingFieldCase.class, "testOnSuccessPassingField"))// .build(); executeTests(request); assertThat(onSuccessPassingFieldDir).doesNotExist(); } /** * Ensure that ON_SUCCESS cleanup modes are obeyed for failing field tests. *

* Expect the TempDir not to be cleaned up. */ @Test void cleanupModeOnSuccessFailingField() { LauncherDiscoveryRequest request = request()// .selectors(selectMethod(OnSuccessFailingFieldCase.class, "testOnSuccessFailingField"))// .build(); executeTests(request); assertThat(onSuccessFailingFieldDir).exists(); } @Test void cleanupModeOnSuccessFailingThenPassingField() { executeTests(selectClasses(OnSuccessFailingFieldCase.class, OnSuccessPassingFieldCase.class)); assertThat(onSuccessFailingFieldDir).exists(); assertThat(onSuccessPassingFieldDir).doesNotExist(); } /** * Ensure that ON_SUCCESS cleanup modes are obeyed for static fields when tests are failing. *

* Expect the TempDir not to be cleaned up. */ @Test void cleanupModeOnSuccessFailingStaticField() { LauncherDiscoveryRequest request = request()// .selectors(selectClass(OnSuccessFailingStaticFieldCase.class))// .build(); executeTests(request); assertThat(onSuccessFailingFieldDir).exists(); } /** * Ensure that ON_SUCCESS cleanup modes are obeyed for static fields when nested tests are failing. *

* Expect the TempDir not to be cleaned up. */ @Test void cleanupModeOnSuccessFailingStaticFieldWithNesting() { executeTestsForClass(OnSuccessFailingStaticFieldWithNestingCase.class); assertThat(onSuccessFailingFieldDir).exists(); assertThat(onSuccessPassingParameterDir).doesNotExist(); } @AfterEach void deleteTempDirs() throws IOException { deleteIfNotNullAndExists(defaultFieldDir); deleteIfNotNullAndExists(neverFieldDir); deleteIfNotNullAndExists(alwaysFieldDir); deleteIfNotNullAndExists(onSuccessFailingFieldDir); deleteIfNotNullAndExists(onSuccessPassingFieldDir); deleteIfNotNullAndExists(onSuccessPassingParameterDir); } static void deleteIfNotNullAndExists(Path dir) throws IOException { if (dir != null) { deleteIfExists(dir); } } // ------------------------------------------------------------------- @SuppressWarnings("NewClassNamingConvention") static class DefaultFieldCase { @TempDir Path defaultFieldDir; @Test void testDefaultField() { TempDirFieldTests.defaultFieldDir = defaultFieldDir; } } @SuppressWarnings("NewClassNamingConvention") static class NeverFieldCase { @TempDir(cleanup = NEVER) Path neverFieldDir; @Test void testNeverField() { TempDirFieldTests.neverFieldDir = neverFieldDir; } } @SuppressWarnings("NewClassNamingConvention") static class AlwaysFieldCase { @TempDir(cleanup = ALWAYS) Path alwaysFieldDir; @Test void testAlwaysField() { TempDirFieldTests.alwaysFieldDir = alwaysFieldDir; } } @SuppressWarnings("NewClassNamingConvention") static class OnSuccessPassingFieldCase { @TempDir(cleanup = ON_SUCCESS) Path onSuccessPassingFieldDir; @Test void testOnSuccessPassingField() { TempDirFieldTests.onSuccessPassingFieldDir = onSuccessPassingFieldDir; } } @SuppressWarnings("NewClassNamingConvention") static class OnSuccessFailingFieldCase { @TempDir(cleanup = ON_SUCCESS) Path onSuccessFailingFieldDir; @Test void testOnSuccessFailingField() { TempDirFieldTests.onSuccessFailingFieldDir = onSuccessFailingFieldDir; fail(); } } @SuppressWarnings("NewClassNamingConvention") @TestMethodOrder(MethodOrderer.OrderAnnotation.class) static class OnSuccessFailingStaticFieldCase { @TempDir(cleanup = ON_SUCCESS) static Path onSuccessFailingFieldDir; @Test @Order(1) void failing() { TempDirFieldTests.onSuccessFailingFieldDir = onSuccessFailingFieldDir; fail(); } @Test @Order(2) void passing() { } } @SuppressWarnings("NewClassNamingConvention") static class OnSuccessFailingStaticFieldWithNestingCase { @TempDir(cleanup = ON_SUCCESS) static Path onSuccessFailingFieldDir; @Nested @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class NestedTestCase { @Test @Order(1) void failingTest() { TempDirFieldTests.onSuccessFailingFieldDir = onSuccessFailingFieldDir; fail(); } @Test @Order(2) void passingTest(@TempDir(cleanup = ON_SUCCESS) Path tempDir) { TempDirFieldTests.onSuccessPassingParameterDir = tempDir; } } } } @Nested @NullUnmarked class TempDirParameterTests { private static Path defaultParameterDir; private static Path neverParameterDir; private static Path alwaysParameterDir; private static Path onSuccessFailingParameterDir; private static Path onSuccessPassingParameterDir; /** * Ensure the cleanup mode defaults to ALWAYS for parameters. *

* Expect the TempDir to be cleaned up. */ @Test void cleanupModeDefaultParameter() { LauncherDiscoveryRequest request = request()// .selectors(selectMethod(DefaultParameterCase.class, "testDefaultParameter", "java.nio.file.Path"))// .build(); executeTests(request); assertThat(defaultParameterDir).doesNotExist(); } /** * Ensure that a custom, global cleanup mode is used for parameters. *

* Expect the TempDir NOT to be cleaned up if set to NEVER. */ @Test void cleanupModeCustomDefaultParameter() { LauncherDiscoveryRequest request = request()// .configurationParameter(DEFAULT_TEMP_DIR_CLEANUP_MODE_PROPERTY_NAME, "never")// .selectors(selectMethod(DefaultParameterCase.class, "testDefaultParameter", "java.nio.file.Path"))// .build(); executeTests(request); assertThat(defaultParameterDir).exists(); } /** * Ensure that NEVER cleanup modes are obeyed for parameters. *

* Expect the TempDir not to be cleaned up. */ @Test void cleanupModeNeverParameter() { LauncherDiscoveryRequest request = request()// .selectors(selectMethod(NeverParameterCase.class, "testNeverParameter", "java.nio.file.Path"))// .build(); executeTests(request); assertThat(neverParameterDir).exists(); } /** * Ensure that ALWAYS cleanup modes are obeyed for parameters. *

* Expect the TempDir to be cleaned up. */ @Test void cleanupModeAlwaysParameter() { LauncherDiscoveryRequest request = request()// .selectors(selectMethod(AlwaysParameterCase.class, "testAlwaysParameter", "java.nio.file.Path"))// .build(); executeTests(request); assertThat(alwaysParameterDir).doesNotExist(); } /** * Ensure that ON_SUCCESS cleanup modes are obeyed for passing parameter tests. *

* Expect the TempDir to be cleaned up. */ @Test void cleanupModeOnSuccessPassingParameter() { LauncherDiscoveryRequest request = request()// .selectors(selectMethod(OnSuccessPassingParameterCase.class, "testOnSuccessPassingParameter", "java.nio.file.Path"))// .build(); executeTests(request); assertThat(onSuccessPassingParameterDir).doesNotExist(); } /** * Ensure that ON_SUCCESS cleanup modes are obeyed for failing parameter tests. *

* Expect the TempDir not to be cleaned up. */ @Test void cleanupModeOnSuccessFailingParameter() { LauncherDiscoveryRequest request = request()// .selectors(selectMethod(OnSuccessFailingParameterCase.class, "testOnSuccessFailingParameter", "java.nio.file.Path"))// .build(); executeTests(request); assertThat(onSuccessFailingParameterDir).exists(); } @Test void cleanupModeOnSuccessFailingThenPassingParameter() { executeTestsForClass(OnSuccessFailingThenPassingParameterCase.class); assertThat(onSuccessFailingParameterDir).exists(); assertThat(onSuccessPassingParameterDir).doesNotExist(); } @AfterEach void deleteTempDirs() throws IOException { TempDirFieldTests.deleteIfNotNullAndExists(defaultParameterDir); TempDirFieldTests.deleteIfNotNullAndExists(neverParameterDir); TempDirFieldTests.deleteIfNotNullAndExists(alwaysParameterDir); TempDirFieldTests.deleteIfNotNullAndExists(onSuccessFailingParameterDir); TempDirFieldTests.deleteIfNotNullAndExists(onSuccessPassingParameterDir); } // ------------------------------------------------------------------- @SuppressWarnings("NewClassNamingConvention") static class DefaultParameterCase { @Test void testDefaultParameter(@TempDir Path defaultParameterDir) { TempDirParameterTests.defaultParameterDir = defaultParameterDir; } } @SuppressWarnings("NewClassNamingConvention") static class NeverParameterCase { @Test void testNeverParameter(@TempDir(cleanup = NEVER) Path neverParameterDir) { TempDirParameterTests.neverParameterDir = neverParameterDir; } } @SuppressWarnings("NewClassNamingConvention") static class AlwaysParameterCase { @Test void testAlwaysParameter(@TempDir(cleanup = ALWAYS) Path alwaysParameterDir) { TempDirParameterTests.alwaysParameterDir = alwaysParameterDir; } } @SuppressWarnings("NewClassNamingConvention") static class OnSuccessPassingParameterCase { @Test void testOnSuccessPassingParameter(@TempDir(cleanup = ON_SUCCESS) Path onSuccessPassingParameterDir) { TempDirParameterTests.onSuccessPassingParameterDir = onSuccessPassingParameterDir; } } @SuppressWarnings("NewClassNamingConvention") static class OnSuccessFailingParameterCase { @Test void testOnSuccessFailingParameter(@TempDir(cleanup = ON_SUCCESS) Path onSuccessFailingParameterDir) { TempDirParameterTests.onSuccessFailingParameterDir = onSuccessFailingParameterDir; fail(); } } @SuppressWarnings("NewClassNamingConvention") @TestMethodOrder(MethodOrderer.OrderAnnotation.class) static class OnSuccessFailingThenPassingParameterCase { @Test @Order(1) void testOnSuccessFailingParameter(@TempDir(cleanup = ON_SUCCESS) Path onSuccessFailingParameterDir) { TempDirParameterTests.onSuccessFailingParameterDir = onSuccessFailingParameterDir; fail(); } @Test @Order(2) void testOnSuccessPassingParameter(@TempDir(cleanup = ON_SUCCESS) Path onSuccessPassingParameterDir) { TempDirParameterTests.onSuccessPassingParameterDir = onSuccessPassingParameterDir; } } } @Nested @EnabledOnOs(WINDOWS) class WindowsTests { @Test void deletesBrokenJunctions(@TempDir Path dir) throws Exception { var test = Files.createDirectory(dir.resolve("test")); createWindowsJunction(dir.resolve("link"), test); // The error might also occur without the source folder being deleted // but it depends on the order that the TempDir cleanup does its work, // so the following line forces the error to occur always Files.delete(test); } @Test void doesNotFollowJunctions(@TempDir Path tempDir, @TrackLogRecords LogRecordListener listener) throws IOException { var outsideDir = Files.createDirectory(tempDir.resolve("outside")); var testFile = Files.writeString(outsideDir.resolve("test.txt"), "test"); JunctionTestCase.target = outsideDir; try { executeTestsForClass(JunctionTestCase.class).testEvents() // .assertStatistics(stats -> stats.started(1).succeeded(1)); } finally { JunctionTestCase.target = null; } assertThat(outsideDir).exists(); assertThat(testFile).exists(); assertThat(listener.stream(Level.WARNING)) // .map(LogRecord::getMessage) // .anyMatch(m -> m.matches( "Deleting link from location inside of temp dir \\(.+\\) to location outside of temp dir \\(.+\\) but not the target file/directory")); } @NullUnmarked static class JunctionTestCase { public static Path target; @Test void createJunctionToTarget(@TempDir Path dir) throws Exception { var link = createWindowsJunction(dir.resolve("link"), target); try (var files = Files.list(link)) { files.forEach(it -> System.out.println("- " + it)); } } } private static Path createWindowsJunction(Path link, Path target) throws Exception { // This creates a Windows "junction" which you can't do with Java code String[] command = { "cmd.exe", "/c", "mklink", "/j", link.toString(), target.toString() }; Runtime.getRuntime().exec(command).waitFor(); return link; } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryMetaAnnotationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static org.junit.jupiter.api.Assertions.assertTrue; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.nio.file.Files; import java.nio.file.Path; import org.jspecify.annotations.NullUnmarked; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; /** * Integration tests for the use of {@link TempDir} as a meta-annotation. * * @since 5.10 */ @DisplayName("@TempDir as a meta-annotation") class TempDirectoryMetaAnnotationTests extends AbstractJupiterTestEngineTests { @Test void annotationOnField() { executeTestsForClass(AnnotationOnFieldTestCase.class).testEvents()// .assertStatistics(stats -> stats.started(1).succeeded(1)); } @Test void annotationOnParameter() { executeTestsForClass(AnnotationOnParameterTestCase.class).testEvents()// .assertStatistics(stats -> stats.started(1).succeeded(1)); } @NullUnmarked static class AnnotationOnFieldTestCase { @CustomTempDir private Path tempDir; @Test void test() { assertTrue(Files.exists(tempDir)); } } static class AnnotationOnParameterTestCase { @Test void test(@CustomTempDir Path tempDir) { assertTrue(Files.exists(tempDir)); } } @TempDir @Target({ ElementType.FIELD, ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) @interface CustomTempDir { } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPreconditionTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static org.junit.platform.testkit.engine.EventConditions.container; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.EventConditions.test; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import java.io.File; import java.nio.file.Path; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionConfigurationException; import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.platform.testkit.engine.EngineExecutionResults; import org.junit.platform.testkit.engine.Events; /** * Integration tests for preconditions and assertions in the {@link TempDirectory} * extension. * * @since 5.9 */ class TempDirectoryPreconditionTests extends AbstractJupiterTestEngineTests { @Test @DisplayName("Valid and invalid @TempDir parameter types") void parameterTypes() { EngineExecutionResults executionResults = executeTestsForClass(ParameterTypeTestCase.class); Events tests = executionResults.testEvents(); tests.assertStatistics(stats -> stats.started(2).failed(1).succeeded(1)); tests.succeeded().assertEventsMatchExactly(event(test("validTempDirType"), finishedSuccessfully())); // @formatter:off tests.failed().assertEventsMatchExactly(event(test("invalidTempDirType"), finishedWithFailure(instanceOf(ParameterResolutionException.class), message(""" Failed to resolve parameter [java.lang.String text] in method \ [void org.junit.jupiter.engine.extension.TempDirectoryPreconditionTests$ParameterTypeTestCase.invalidTempDirType(java.lang.String)]: \ Can only resolve @TempDir parameter of type java.nio.file.Path or java.io.File but was: java.lang.String\ """)))); // @formatter:on } @Test @DisplayName("final static @TempDir fields are not supported") void finalStaticFieldIsNotSupported() { EngineExecutionResults executionResults = executeTestsForClass(FinalStaticFieldTestCase.class); Events containers = executionResults.containerEvents(); containers.assertStatistics(stats -> stats.started(2).failed(1).succeeded(1)); containers.succeeded().assertEventsMatchExactly(event(container("junit-jupiter"), finishedSuccessfully())); containers.failed().assertEventsMatchExactly(event(container("FinalStaticFieldTestCase"), finishedWithFailure(instanceOf(ExtensionConfigurationException.class), message(""" @TempDir field [static final java.nio.file.Path \ org.junit.jupiter.engine.extension.TempDirectoryPreconditionTests$FinalStaticFieldTestCase.path] \ must not be declared as final.\ """)))); } @Test @DisplayName("final instance @TempDir fields are not supported") void finalInstanceFieldIsNotSupported() { EngineExecutionResults executionResults = executeTestsForClass(FinalInstanceFieldTestCase.class); Events tests = executionResults.testEvents(); tests.assertStatistics(stats -> stats.started(1).failed(1).succeeded(0)); tests.failed().assertEventsMatchExactly( event(test("test()"), finishedWithFailure(instanceOf(ExtensionConfigurationException.class), message(""" @TempDir field [final java.nio.file.Path \ org.junit.jupiter.engine.extension.TempDirectoryPreconditionTests$FinalInstanceFieldTestCase.path] \ must not be declared as final.\ """)))); } // ------------------------------------------------------------------- static class ParameterTypeTestCase { @Test void validTempDirType(@TempDir File file, @TempDir Path path) { } @Test void invalidTempDirType(@TempDir String text) { } } static class FinalStaticFieldTestCase { static final @TempDir Path path = Path.of("."); @Test void test() { } } static class FinalInstanceFieldTestCase { final @TempDir Path path = Path.of("."); @Test void test() { } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static java.lang.annotation.ElementType.ANNOTATION_TYPE; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.RetentionPolicy.RUNTIME; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.allOf; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.junit.jupiter.api.Constants.DEFAULT_TEMP_DIR_DELETION_STRATEGY_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_TEMP_DIR_FACTORY_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME; import static org.junit.jupiter.api.io.FailingTempDirDeletionStrategy.UNDELETABLE_PATH; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.cause; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.suppressed; import java.io.File; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; import java.lang.reflect.Parameter; import java.nio.file.DirectoryNotEmptyException; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; import com.github.marschall.memoryfilesystem.MemoryFileSystemBuilder; import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; import org.assertj.core.api.Condition; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; 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.DisplayName; import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.TestReporter; import org.junit.jupiter.api.condition.DisabledOnOs; import org.junit.jupiter.api.condition.OS; import org.junit.jupiter.api.extension.AnnotatedElementContext; import org.junit.jupiter.api.extension.ExtensionConfigurationException; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.io.FailingTempDirDeletionStrategy; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDirDeletionStrategy.DeletionException; import org.junit.jupiter.api.io.TempDirFactory; import org.junit.jupiter.api.io.TempDirFactory.Standard; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.testkit.engine.EngineExecutionResults; /** * Integration tests for the new behavior of the {@link TempDirectory} extension * to create separate temp directories for each {@link TempDir} declaration. * * @since 5.8 */ @DisplayName("TempDirectory extension") class TempDirectoryTests extends AbstractJupiterTestEngineTests { private EngineExecutionResults executeTestsForClassWithDefaultFactory(Class testClass, Class factoryClass) { return executeTests(request() // .selectors(selectClass(testClass)) // .configurationParameter(DEFAULT_TEMP_DIR_FACTORY_PROPERTY_NAME, factoryClass.getName()) // .build()); } @BeforeEach @AfterEach void resetStaticVariables() { AllPossibleDeclarationLocationsTestCase.tempDirs.clear(); } @SuppressWarnings("NullAway") @ParameterizedTest(name = "{0}") @EnumSource(TestInstance.Lifecycle.class) @DisplayName("resolves separate temp dirs for each annotation declaration") void resolvesSeparateTempDirsForEachAnnotationDeclaration(TestInstance.Lifecycle lifecycle) { var results = executeTests(request() // .selectors(selectClass(AllPossibleDeclarationLocationsTestCase.class)) // .configurationParameter(DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME, lifecycle.name()).build()); results.containerEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); results.testEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); assertThat(AllPossibleDeclarationLocationsTestCase.tempDirs).hasSize(3); var classTempDirs = AllPossibleDeclarationLocationsTestCase.tempDirs.get("class"); assertThat(classTempDirs) // .containsOnlyKeys("staticField1", "staticField2", "beforeAll1", "beforeAll2", "afterAll1", "afterAll2") // .values().hasSize(6).doesNotHaveDuplicates().allMatch(Files::notExists); var testATempDirs = AllPossibleDeclarationLocationsTestCase.tempDirs.get("testA"); assertThat(testATempDirs) // .containsOnlyKeys("staticField1", "staticField2", "instanceFieldSetViaConstructorInjection", "instanceField1", "instanceField2", "beforeEach1", "beforeEach2", "test1", "test2", "afterEach1", "afterEach2") // .values().hasSize(11).doesNotHaveDuplicates().allMatch(Files::notExists); var testBTempDirs = AllPossibleDeclarationLocationsTestCase.tempDirs.get("testB"); assertThat(testBTempDirs) // .containsOnlyKeys("staticField1", "staticField2", "instanceFieldSetViaConstructorInjection", "instanceField1", "instanceField2", "beforeEach1", "beforeEach2", "test1", "test2", "afterEach1", "afterEach2") // .values().hasSize(11).doesNotHaveDuplicates().allMatch(Files::notExists); assertThat(testATempDirs).containsEntry("staticField1", classTempDirs.get("staticField1")); assertThat(testBTempDirs).containsEntry("staticField1", classTempDirs.get("staticField1")); assertThat(testATempDirs).containsEntry("staticField2", classTempDirs.get("staticField2")); assertThat(testBTempDirs).containsEntry("staticField2", classTempDirs.get("staticField2")); switch (lifecycle) { case PER_CLASS -> assertThat(testATempDirs) // .containsEntry("instanceFieldSetViaConstructorInjection", testBTempDirs.get("instanceFieldSetViaConstructorInjection")); case PER_METHOD -> assertThat(testATempDirs) // .doesNotContainEntry("instanceFieldSetViaConstructorInjection", testBTempDirs.get("instanceFieldSetViaConstructorInjection")); } assertThat(testATempDirs).doesNotContainEntry("instanceField1", testBTempDirs.get("instanceField1")); assertThat(testATempDirs).doesNotContainEntry("instanceField2", testBTempDirs.get("instanceField2")); assertThat(testATempDirs).doesNotContainEntry("beforeEach1", testBTempDirs.get("beforeEach1")); assertThat(testATempDirs).doesNotContainEntry("beforeEach2", testBTempDirs.get("beforeEach2")); assertThat(testATempDirs).doesNotContainEntry("test1", testBTempDirs.get("test1")); assertThat(testATempDirs).doesNotContainEntry("test2", testBTempDirs.get("test2")); assertThat(testATempDirs).doesNotContainEntry("afterEach1", testBTempDirs.get("afterEach1")); assertThat(testATempDirs).doesNotContainEntry("afterEach2", testBTempDirs.get("afterEach2")); } @Test void supportsConstructorInjectionOnRecords() { executeTestsForClass(TempDirRecordTestCase.class).testEvents()// .assertStatistics(stats -> stats.started(1).succeeded(1)); } @Test @DisplayName("does not prevent constructor parameter resolution") void tempDirectoryDoesNotPreventConstructorParameterResolution() { executeTestsForClass(TempDirectoryDoesNotPreventConstructorParameterResolutionTestCase.class).testEvents()// .assertStatistics(stats -> stats.started(1).succeeded(1)); } @Test @DisplayName("does not prevent user from deleting the temp dir within a test") void tempDirectoryDoesNotPreventUserFromDeletingTempDir() { executeTestsForClass(UserTempDirectoryDeletionDoesNotCauseFailureTestCase.class).testEvents()// .assertStatistics(stats -> stats.started(1).succeeded(1)); } @Test @DisplayName("is capable of removing a read-only file") void nonWritableFileDoesNotCauseFailure() { executeTestsForClass(NonWritableFileDoesNotCauseFailureTestCase.class).testEvents()// .assertStatistics(stats -> stats.started(1).succeeded(1)); } @Test @DisplayName("is capable of removing non-executable, non-writable, or non-readable directories and folders") void nonMintPermissionsContentDoesNotCauseFailure() { executeTestsForClass(NonMintPermissionContentInTempDirectoryDoesNotCauseFailureTestCase.class).testEvents()// .assertStatistics(stats -> stats.started(13).succeeded(13)); } @Test @DisplayName("is capable of removing a directory when its permissions have been changed") void nonMintPermissionsDoNotCauseFailure() { executeTestsForClass(NonMintTempDirectoryPermissionsDoNotCauseFailureTestCase.class).testEvents()// .assertStatistics(stats -> stats.started(42).succeeded(42)); } @Test @DisabledOnOs(OS.WINDOWS) @DisplayName("is capable of removing a read-only file in a read-only dir") void readOnlyFileInReadOnlyDirDoesNotCauseFailure() { executeTestsForClass(ReadOnlyFileInReadOnlyDirDoesNotCauseFailureTestCase.class).testEvents()// .assertStatistics(stats -> stats.started(1).succeeded(1)); } @Test @DisabledOnOs(OS.WINDOWS) @DisplayName("is capable of removing a read-only file in a dir in a read-only dir") void readOnlyFileInNestedReadOnlyDirDoesNotCauseFailure() { executeTestsForClass(ReadOnlyFileInDirInReadOnlyDirDoesNotCauseFailureTestCase.class).testEvents()// .assertStatistics(stats -> stats.started(1).succeeded(1)); } @Test @DisplayName("can be used via instance field inside nested test classes") void canBeUsedViaInstanceFieldInsideNestedTestClasses() { executeTestsForClass(TempDirUsageInsideNestedClassesTestCase.class).testEvents()// .assertStatistics(stats -> stats.started(3).succeeded(3)); } @Test @DisplayName("can be used via static field inside nested test classes") void canBeUsedViaStaticFieldInsideNestedTestClasses() { executeTestsForClass(StaticTempDirUsageInsideNestedClassTestCase.class).testEvents()// .assertStatistics(stats -> stats.started(2).succeeded(2)); } @ParameterizedTest(name = "{0}") @ValueSource(classes = { UndeletableDirectoryTestCase.class, UndeletableFileTestCase.class }) @DisplayName("only attempts to delete undeletable paths once") void onlyAttemptsToDeleteUndeletablePathsOnce(Class testClass) { var results = executeTestsForClass(testClass); var tempDir = determineTempDirFromReportEntries(results, UndeletableTestCase.TEMP_DIR); assertFailedDueToDeletionException(results, tempDir); } @Test @DisplayName("applies globally configured deletion strategy") void appliesGloballyConfiguredDeletionStrategy() { var results = executeTests(builder -> builder // .selectors(selectClass(UndeletableWithDefaultDeletionStrategyTestCase.class)) // .configurationParameter(DEFAULT_TEMP_DIR_DELETION_STRATEGY_PROPERTY_NAME, FailingTempDirDeletionStrategy.class.getName())); var tempDir = determineTempDirFromReportEntries(results, UndeletableWithDefaultDeletionStrategyTestCase.TEMP_DIR); assertFailedDueToDeletionException(results, tempDir); } private static void assertFailedDueToDeletionException(EngineExecutionResults results, Path tempDir) { assertSingleFailedTest(results, // cause( // instanceOf(DeletionException.class), // message("Failed to delete temp directory " + tempDir.toAbsolutePath() + ". " + // "The following paths could not be deleted (see suppressed exceptions for details): , undeletable"), // suppressed(0, instanceOf(DirectoryNotEmptyException.class)), // suppressed(1, instanceOf(IOException.class), message("Simulated failure")) // ) // ); } private static Path determineTempDirFromReportEntries(EngineExecutionResults results, String key) { return results.testEvents().reportingEntryPublished().stream() // .map(it -> it.getPayload(ReportEntry.class).orElseThrow()) // .map(it -> Path.of(it.getKeyValuePairs().get(key))) // .findAny() // .orElseThrow(); } @Test void usingTheRemovedScopeConfigurationParameterProducesWarning() { var results = discoverTests(request() // .selectors(selectClass(AllPossibleDeclarationLocationsTestCase.class)) // .configurationParameter("junit.jupiter.tempdir.scope", "per_context")); assertThat(results.getDiscoveryIssues()) // .contains(DiscoveryIssue.create(Severity.WARNING, """ The 'junit.jupiter.tempdir.scope' configuration parameter is no longer supported. \ Please remove it from your configuration.""")); } @Nested @DisplayName("reports failure") @TestMethodOrder(OrderAnnotation.class) class Failures { @Test @DisplayName("when @TempDir is used on static field of an unsupported type") @Order(20) void onlySupportsStaticFieldsOfTypePathAndFile() { var results = executeTestsForClass(AnnotationOnStaticFieldWithUnsupportedTypeTestCase.class); assertSingleFailedContainer(results, ExtensionConfigurationException.class, "Can only resolve @TempDir field of type java.nio.file.Path or java.io.File"); } @Test @DisplayName("when @TempDir is used on instance field of an unsupported type") @Order(21) void onlySupportsInstanceFieldsOfTypePathAndFile() { var results = executeTestsForClass(AnnotationOnInstanceFieldWithUnsupportedTypeTestCase.class); assertSingleFailedTest(results, ExtensionConfigurationException.class, "Can only resolve @TempDir field of type java.nio.file.Path or java.io.File"); } @Test @DisplayName("when @TempDir is used on parameter of an unsupported type") @Order(22) void onlySupportsParametersOfTypePathAndFile() { var results = executeTestsForClass(InvalidTestCase.class); // @formatter:off assertSingleFailedTest(results, instanceOf(ParameterResolutionException.class), message(m -> m.matches("Failed to resolve parameter \\[java.lang.String .+] in method \\[.+]: .+")), cause( instanceOf(ExtensionConfigurationException.class), message("Can only resolve @TempDir parameter of type java.nio.file.Path or java.io.File but was: java.lang.String"))); // @formatter:on } @Test @DisplayName("when @TempDir factory does not return directory") @Order(32) void doesNotSupportTempDirFactoryNotReturningDirectory() { var results = executeTestsForClass(FactoryNotReturningDirectoryTestCase.class); // @formatter:off assertSingleFailedTest(results, instanceOf(ParameterResolutionException.class), message(m -> m.matches("Failed to resolve parameter \\[.+] in method \\[.+]: .+")), cause( instanceOf(ExtensionConfigurationException.class), message("Failed to create default temp directory"), cause( instanceOf(PreconditionViolationException.class), message("temp directory must be a directory") ) )); // @formatter:on } @Test @DisplayName("when default @TempDir factory does not return directory") @Order(33) void doesNotSupportCustomDefaultTempDirFactoryNotReturningDirectory() { var results = executeTestsForClassWithDefaultFactory( CustomDefaultFactoryNotReturningDirectoryTestCase.class, FactoryNotReturningDirectory.class); // @formatter:off assertSingleFailedTest(results, instanceOf(ParameterResolutionException.class), message(m -> m.matches("Failed to resolve parameter \\[.+] in method \\[.+]: .+")), cause( instanceOf(ExtensionConfigurationException.class), message("Failed to create default temp directory"), cause( instanceOf(PreconditionViolationException.class), message("temp directory must be a directory") ) )); // @formatter:on } @NullMarked private static class FactoryNotReturningDirectory implements TempDirFactory { @SuppressWarnings("DataFlowIssue") @Override public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) { return null; } } @Test @DisplayName("when @TempDir factory returns a non-default file system path for a File annotated element") @Order(34) void doesNotSupportNonDefaultFileSystemTempDirFactoryOnFileAnnotatedElement() { var results = executeTestsForClass( FactoryReturningNonDefaultFileSystemPathForFileAnnotatedElementTestCase.class); // @formatter:off assertSingleFailedTest(results, instanceOf(ParameterResolutionException.class), message(m -> m.matches("Failed to resolve parameter \\[.+] in method \\[.+]: .+")), cause( instanceOf(ExtensionConfigurationException.class), message("Failed to create default temp directory"), cause( instanceOf(PreconditionViolationException.class), message("temp directory with non-default file system cannot be injected into " + File.class.getName() + " target") ) )); // @formatter:on } } @Nested @DisplayName("supports @TempDir") @TestMethodOrder(OrderAnnotation.class) class PrivateFields { @Test @DisplayName("on private static field") @Order(10) void supportsPrivateInstanceFields() { executeTestsForClass(AnnotationOnPrivateStaticFieldTestCase.class).testEvents()// .assertStatistics(stats -> stats.started(1).succeeded(1)); } @Test @DisplayName("on private instance field") @Order(11) void supportsPrivateStaticFields() { executeTestsForClass(AnnotationOnPrivateInstanceFieldTestCase.class).testEvents()// .assertStatistics(stats -> stats.started(1).succeeded(1)); } } @Nested @DisplayName("supports custom factory") class Factory { @Test @DisplayName("that uses test method name as temp dir name prefix") void supportsFactoryWithTestMethodNameAsPrefix() { executeTestsForClass(FactoryWithTestMethodNameAsPrefixTestCase.class).testEvents()// .assertStatistics(stats -> stats.started(1).succeeded(1)); } @Test @DisplayName("that uses custom temp dir parent directory") void supportsFactoryWithCustomParentDirectory() { executeTestsForClass(FactoryWithCustomParentDirectoryTestCase.class).testEvents()// .assertStatistics(stats -> stats.started(1).succeeded(1)); } @Test @DisplayName("that uses com.github.marschall:memoryfilesystem") void supportsFactoryWithMemoryFileSystem() { executeTestsForClass(FactoryWithMemoryFileSystemTestCase.class).testEvents()// .assertStatistics(stats -> stats.started(1).succeeded(1)); } @Test @DisplayName("that uses com.google.jimfs:jimfs") void supportsFactoryWithJimfs() { executeTestsForClass(FactoryWithJimfsTestCase.class).testEvents()// .assertStatistics(stats -> stats.started(1).succeeded(1)); } @Test @DisplayName("that uses annotated element name as temp dir name prefix") void supportsFactoryWithAnnotatedElementNameAsPrefix() { executeTestsForClass(FactoryWithAnnotatedElementNameAsPrefixTestCase.class).testEvents()// .assertStatistics(stats -> stats.started(1).succeeded(1)); } @Test @DisplayName("that uses custom meta-annotation") void supportsFactoryWithCustomMetaAnnotation() { executeTestsForClass(FactoryWithCustomMetaAnnotationTestCase.class).testEvents()// .assertStatistics(stats -> stats.started(1).succeeded(1)); } } @Nested @DisplayName("supports default factory") @TestMethodOrder(OrderAnnotation.class) class DefaultFactory { @Test @DisplayName("set to Jupiter's default") void supportsStandardDefaultFactory() { executeTestsForClassWithDefaultFactory(StandardDefaultFactoryTestCase.class, Standard.class) // .testEvents()// .assertStatistics(stats -> stats.started(1).succeeded(1)); } @Test @DisplayName("set to custom factory") void supportsCustomDefaultFactory() { executeTestsForClassWithDefaultFactory(CustomDefaultFactoryTestCase.class, Factory.class) // .testEvents()// .assertStatistics(stats -> stats.started(1).succeeded(1)); } @Test @DisplayName("set to custom factory together with declaration of Jupiter's default") void supportsCustomDefaultFactoryWithStandardFactoryOnDeclaration() { executeTestsForClassWithDefaultFactory( // CustomDefaultFactoryWithStandardDeclarationTestCase.class, Factory.class) // .testEvents()// .assertStatistics(stats -> stats.started(1).succeeded(1)); } @NullMarked private static class Factory implements TempDirFactory { private boolean closed; @Override public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) throws Exception { return Files.createTempDirectory("custom"); } @Override public void close() { if (closed) { throw new IllegalStateException("already closed"); } closed = true; } } } @SuppressWarnings("SameParameterValue") private static void assertSingleFailedContainer(EngineExecutionResults results, Class clazz, String message) { assertSingleFailedContainer(results, instanceOf(clazz), message(actual -> actual.contains(message))); } @SafeVarargs @SuppressWarnings("varargs") private static void assertSingleFailedContainer(EngineExecutionResults results, Condition... conditions) { results.containerEvents()// .assertStatistics(stats -> stats.started(2).failed(1).succeeded(1))// .assertThatEvents().haveExactly(1, finishedWithFailure(conditions)); } @SuppressWarnings("SameParameterValue") private static void assertSingleFailedTest(EngineExecutionResults results, Class clazz, String message) { assertSingleFailedTest(results, instanceOf(clazz), message(actual -> actual.contains(message))); } @SafeVarargs @SuppressWarnings("varargs") private static void assertSingleFailedTest(EngineExecutionResults results, Condition... conditions) { results.testEvents().assertStatistics(stats -> stats.started(1).failed(1).succeeded(0)); var failures = results.testEvents().stream().filter(finishedWithFailure()::matches) // .map(e -> e.getPayload(TestExecutionResult.class).flatMap(TestExecutionResult::getThrowable).orElse( null)) // .filter(Objects::nonNull).toList(); assertThat(failures).hasSize(1); assertThat(failures.getFirst()).has(allOf(conditions)); } // ------------------------------------------------------------------------- static class AnnotationOnPrivateInstanceFieldTestCase { @SuppressWarnings("unused") @TempDir private Path tempDir; @Test void test() { assertTrue(Files.exists(tempDir)); } } static class AnnotationOnPrivateStaticFieldTestCase { @SuppressWarnings("unused") @TempDir private static Path tempDir; @Test void test() { assertTrue(Files.exists(tempDir)); } } static class AnnotationOnStaticFieldWithUnsupportedTypeTestCase { @SuppressWarnings("unused") @TempDir static String tempDir; @Test void test1() { } } static class AnnotationOnInstanceFieldWithUnsupportedTypeTestCase { @SuppressWarnings("unused") @TempDir String tempDir; @Test void test1() { } } static class InvalidTestCase { @Test void wrongParameterType(@SuppressWarnings("unused") @TempDir String ignored) { fail("this should never be called"); } } @Nested @DisplayName("resolves java.io.File injection type") class FileAndPathInjection { @TempDir File fileTempDir; @TempDir Path pathTempDir; @Test @DisplayName("and injected File and Path do not reference the same temp directory") void checkFile(@TempDir File tempDir, @TempDir Path ref) { assertFileAndPathAreNotEqual(tempDir, ref); assertFileAndPathAreNotEqual(this.fileTempDir, this.pathTempDir); } private static void assertFileAndPathAreNotEqual(File tempDir, Path ref) { Path path = tempDir.toPath(); assertNotEquals(ref.toAbsolutePath(), path.toAbsolutePath()); assertTrue(Files.exists(path)); } } // https://github.com/junit-team/junit-framework/issues/1748 static class TempDirectoryDoesNotPreventConstructorParameterResolutionTestCase { @TempDir Path tempDir; TempDirectoryDoesNotPreventConstructorParameterResolutionTestCase(TestInfo testInfo) { assertNotNull(testInfo); } @Test void test() { assertNotNull(tempDir); } } // https://github.com/junit-team/junit-framework/issues/1801 static class UserTempDirectoryDeletionDoesNotCauseFailureTestCase { @Test void deleteTempDir(@TempDir Path tempDir) throws IOException { Files.delete(tempDir); assertThat(tempDir).doesNotExist(); } } // https://github.com/junit-team/junit-framework/issues/2046 static class NonWritableFileDoesNotCauseFailureTestCase { @Test void createReadonlyFile(@TempDir Path tempDir) throws IOException { // Removal of setWritable(false) files might fail (e.g. for Windows) // The test verifies that @TempDir is capable of removing of such files var path = Files.write(tempDir.resolve("test.txt"), new byte[0]); assumeTrue(path.toFile().setWritable(false), () -> "Unable to set file " + path + " readonly via .toFile().setWritable(false)"); } } // https://github.com/junit-team/junit-framework/issues/2171 static class ReadOnlyFileInReadOnlyDirDoesNotCauseFailureTestCase { @Test void createReadOnlyFileInReadOnlyDir(@TempDir File tempDir) throws IOException { File file = tempDir.toPath().resolve("file").toFile(); assumeTrue(file.createNewFile()); assumeTrue(tempDir.setReadOnly()); assumeTrue(file.setReadOnly()); } } // https://github.com/junit-team/junit-framework/issues/2171 static class ReadOnlyFileInDirInReadOnlyDirDoesNotCauseFailureTestCase { @Test void createReadOnlyFileInReadOnlyDir(@TempDir File tempDir) throws IOException { File file = tempDir.toPath().resolve("dir").resolve("file").toFile(); assumeTrue(file.getParentFile().mkdirs()); assumeTrue(file.createNewFile()); assumeTrue(tempDir.setReadOnly()); assumeTrue(file.getParentFile().setReadOnly()); assumeTrue(file.setReadOnly()); } } // https://github.com/junit-team/junit-framework/issues/2609 @SuppressWarnings("ResultOfMethodCallIgnored") static class NonMintPermissionContentInTempDirectoryDoesNotCauseFailureTestCase { @Test void createFile(@TempDir Path tempDir) throws IOException { Files.createFile(tempDir.resolve("test-file.txt")).toFile(); } @Test void createFolder(@TempDir Path tempDir) throws IOException { Files.createFile(tempDir.resolve("test-file.txt")).toFile(); } @Test void createNonWritableFile(@TempDir Path tempDir) throws IOException { Files.createFile(tempDir.resolve("test-file.txt")).toFile().setWritable(false); } @Test void createNonReadableFile(@TempDir Path tempDir) throws IOException { Files.createFile(tempDir.resolve("test-file.txt")).toFile().setReadable(false); } @Test void createNonWritableDirectory(@TempDir Path tempDir) throws IOException { Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setWritable(false); } @Test void createNonReadableDirectory(@TempDir Path tempDir) throws IOException { Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setReadable(false); } @Test void createNonExecutableDirectory(@TempDir Path tempDir) throws IOException { Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setExecutable(false); } @Test void createNonEmptyNonWritableDirectory(@TempDir Path tempDir) throws IOException { Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); subDir.toFile().setWritable(false); } @Test void createNonEmptyNonReadableDirectory(@TempDir Path tempDir) throws IOException { Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); subDir.toFile().setReadable(false); } @Test void createNonEmptyNonExecutableDirectory(@TempDir Path tempDir) throws IOException { Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); subDir.toFile().setExecutable(false); } @Test void createNonEmptyDirectory(@TempDir Path tempDir) throws IOException { Files.createDirectory(tempDir.resolve("test-sub-dir")); Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); } @Test void createNonEmptyDirectoryWithNonWritableFile(@TempDir Path tempDir) throws IOException { Files.createDirectory(tempDir.resolve("test-sub-dir")); Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setWritable(false); } @Test void createNonEmptyDirectoryWithNonReadableFile(@TempDir Path tempDir) throws IOException { Files.createDirectory(tempDir.resolve("test-sub-dir")); Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setReadable(false); } } // https://github.com/junit-team/junit-framework/issues/2609 @SuppressWarnings("ResultOfMethodCallIgnored") static class NonMintTempDirectoryPermissionsDoNotCauseFailureTestCase { @Nested class NonWritable { @Test void makeEmptyTempDirectoryNonWritable(@TempDir Path tempDir) { tempDir.toFile().setWritable(false); } @Test void makeTempDirectoryWithFileNonWritable(@TempDir Path tempDir) throws IOException { Files.createFile(tempDir.resolve("test-file.txt")); tempDir.toFile().setWritable(false); } @Test void makeTempDirectoryWithEmptyFolderNonWritable(@TempDir Path tempDir) throws IOException { Files.createDirectory(tempDir.resolve("test-sub-dir")); tempDir.toFile().setWritable(false); } @Test void makeTempDirectoryWithNonWritableFileNonWritable(@TempDir Path tempDir) throws IOException { Files.createFile(tempDir.resolve("test-file.txt")).toFile().setWritable(false); tempDir.toFile().setWritable(false); } @Test void makeTempDirectoryWithNonReadableFileNonWritable(@TempDir Path tempDir) throws IOException { Files.createFile(tempDir.resolve("test-file.txt")).toFile().setReadable(false); tempDir.toFile().setWritable(false); } @Test void makeTempDirectoryWithNonWritableFolderNonWritable(@TempDir Path tempDir) throws IOException { Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setWritable(false); tempDir.toFile().setWritable(false); } @Test void makeTempDirectoryWithNonReadableFolderNonWritable(@TempDir Path tempDir) throws IOException { Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setReadable(false); tempDir.toFile().setWritable(false); } @Test void makeTempDirectoryWithNonExecutableFolderNonWritable(@TempDir Path tempDir) throws IOException { Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setExecutable(false); tempDir.toFile().setWritable(false); } @Test void makeTempDirectoryWithNonEmptyNonReadableFolderNonWritable(@TempDir Path tempDir) throws IOException { Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); subDir.toFile().setReadable(false); tempDir.toFile().setWritable(false); } @Test void makeTempDirectoryWithNonEmptyNonWritableFolderNonWritable(@TempDir Path tempDir) throws IOException { Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); subDir.toFile().setWritable(false); tempDir.toFile().setWritable(false); } @Test void makeTempDirectoryWithNonEmptyNonExecutableFolderNonWritable(@TempDir Path tempDir) throws IOException { Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); subDir.toFile().setExecutable(false); tempDir.toFile().setWritable(false); } @Test void makeTempDirectoryWithNonEmptyFolderNonWritable(@TempDir Path tempDir) throws IOException { Files.createDirectory(tempDir.resolve("test-sub-dir")); Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); tempDir.toFile().setWritable(false); } @Test void makeTempDirectoryWithNonEmptyFolderContainingNonWritableFileNonWritable(@TempDir Path tempDir) throws IOException { Files.createDirectory(tempDir.resolve("test-sub-dir")); Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setWritable(false); tempDir.toFile().setWritable(false); } @Test void makeTempDirectoryWithNonEmptyFolderContainingNonReadableFileNonWritable(@TempDir Path tempDir) throws IOException { Files.createDirectory(tempDir.resolve("test-sub-dir")); Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setReadable(false); tempDir.toFile().setWritable(false); } } @Nested class NonReadable { @Test void makeEmptyTempDirectoryNonReadable(@TempDir Path tempDir) { tempDir.toFile().setReadable(false); } @Test void makeTempDirectoryWithFileNonReadable(@TempDir Path tempDir) throws IOException { Files.createFile(tempDir.resolve("test-file.txt")); tempDir.toFile().setReadable(false); } @Test void makeTempDirectoryWithEmptyFolderNonReadable(@TempDir Path tempDir) throws IOException { Files.createDirectory(tempDir.resolve("test-sub-dir")); tempDir.toFile().setReadable(false); } @Test void makeTempDirectoryWithNonWritableFileNonReadable(@TempDir Path tempDir) throws IOException { Files.createFile(tempDir.resolve("test-file.txt")).toFile().setWritable(false); tempDir.toFile().setReadable(false); } @Test void makeTempDirectoryWithNonReadableFileNonReadable(@TempDir Path tempDir) throws IOException { Files.createFile(tempDir.resolve("test-file.txt")).toFile().setReadable(false); tempDir.toFile().setReadable(false); } @Test void makeTempDirectoryWithNonWritableFolderNonReadable(@TempDir Path tempDir) throws IOException { Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setWritable(false); tempDir.toFile().setReadable(false); } @Test void makeTempDirectoryWithNonReadableFolderNonReadable(@TempDir Path tempDir) throws IOException { Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setReadable(false); tempDir.toFile().setReadable(false); } @Test void makeTempDirectoryWithNonExecutableFolderNonReadable(@TempDir Path tempDir) throws IOException { Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setExecutable(false); tempDir.toFile().setReadable(false); } @Test void makeTempDirectoryWithNonEmptyNonWritableFolderNonReadable(@TempDir Path tempDir) throws IOException { Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); subDir.toFile().setWritable(false); tempDir.toFile().setReadable(false); } @Test void makeTempDirectoryWithNonEmptyNonReadableFolderNonReadable(@TempDir Path tempDir) throws IOException { Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); subDir.toFile().setReadable(false); tempDir.toFile().setReadable(false); } @Test void makeTempDirectoryWithNonEmptyNonExecutableFolderNonReadable(@TempDir Path tempDir) throws IOException { Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); subDir.toFile().setExecutable(false); tempDir.toFile().setReadable(false); } @Test void makeTempDirectoryWithNonEmptyFolderNonReadable(@TempDir Path tempDir) throws IOException { Files.createDirectory(tempDir.resolve("test-sub-dir")); Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); tempDir.toFile().setReadable(false); } @Test void makeTempDirectoryWithNonEmptyFolderContainingNonWritableFileNonReadable(@TempDir Path tempDir) throws IOException { Files.createDirectory(tempDir.resolve("test-sub-dir")); Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setWritable(false); tempDir.toFile().setReadable(false); } @Test void makeTempDirectoryWithNonEmptyFolderContainingNonReadableFileNonReadable(@TempDir Path tempDir) throws IOException { Files.createDirectory(tempDir.resolve("test-sub-dir")); Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setReadable(false); tempDir.toFile().setReadable(false); } } @Nested class NonExecutable { @Test void makeEmptyTempDirectoryNonExecutable(@TempDir Path tempDir) { tempDir.toFile().setExecutable(false); } @Test void makeTempDirectoryWithFileNonExecutable(@TempDir Path tempDir) throws IOException { Files.createFile(tempDir.resolve("test-file.txt")); tempDir.toFile().setExecutable(false); } @Test void makeTempDirectoryWithEmptyFolderNonExecutable(@TempDir Path tempDir) throws IOException { Files.createDirectory(tempDir.resolve("test-sub-dir")); tempDir.toFile().setExecutable(false); } @Test void makeTempDirectoryWithNonWritableFileNonExecutable(@TempDir Path tempDir) throws IOException { Files.createFile(tempDir.resolve("test-file.txt")).toFile().setWritable(false); tempDir.toFile().setExecutable(false); } @Test void makeTempDirectoryWithNonReadableFileNonExecutable(@TempDir Path tempDir) throws IOException { Files.createFile(tempDir.resolve("test-file.txt")).toFile().setReadable(false); tempDir.toFile().setExecutable(false); } @Test void makeTempDirectoryWithNonWritableFolderNonExecutable(@TempDir Path tempDir) throws IOException { Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setWritable(false); tempDir.toFile().setExecutable(false); } @Test void makeTempDirectoryWithNonReadableFolderNonExecutable(@TempDir Path tempDir) throws IOException { Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setReadable(false); tempDir.toFile().setExecutable(false); } @Test void makeTempDirectoryWithNonExecutableFolderNonExecutable(@TempDir Path tempDir) throws IOException { Files.createDirectory(tempDir.resolve("test-sub-dir")).toFile().setExecutable(false); tempDir.toFile().setExecutable(false); } @Test void makeTempDirectoryWithNonEmptyNonWritableFolderNonExecutable(@TempDir Path tempDir) throws IOException { Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); subDir.toFile().setWritable(false); tempDir.toFile().setExecutable(false); } @Test void makeTempDirectoryWithNonEmptyNonReadableFolderNonExecutable(@TempDir Path tempDir) throws IOException { Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); subDir.toFile().setReadable(false); tempDir.toFile().setExecutable(false); } @Test void makeTempDirectoryWithNonEmptyNonExecutableFolderNonExecutable(@TempDir Path tempDir) throws IOException { Path subDir = Files.createDirectory(tempDir.resolve("test-sub-dir")); Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); subDir.toFile().setExecutable(false); tempDir.toFile().setExecutable(false); } @Test void makeTempDirectoryWithNonEmptyFolderNonExecutable(@TempDir Path tempDir) throws IOException { Files.createDirectory(tempDir.resolve("test-sub-dir")); Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")); tempDir.toFile().setExecutable(false); } @Test void makeTempDirectoryWithNonEmptyFolderContainingNonWritableFileNonExecutable(@TempDir Path tempDir) throws IOException { Files.createDirectory(tempDir.resolve("test-sub-dir")); Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setWritable(false); tempDir.toFile().setExecutable(false); } @Test void makeTempDirectoryWithNonEmptyFolderContainingNonReadableFileNonExecutable(@TempDir Path tempDir) throws IOException { Files.createDirectory(tempDir.resolve("test-sub-dir")); Files.createFile(tempDir.resolve("test-sub-dir/test-file.txt")).toFile().setReadable(false); tempDir.toFile().setExecutable(false); } } } // https://github.com/junit-team/junit-framework/issues/2079 static class TempDirUsageInsideNestedClassesTestCase { @TempDir File tempDir; @Test void topLevel() { assertNotNull(tempDir); assertTrue(tempDir.exists()); } @Nested class NestedTestClass { @Test void nested() { assertNotNull(tempDir); assertTrue(tempDir.exists()); } @Nested class EvenDeeperNestedTestClass { @Test void deeplyNested() { assertNotNull(tempDir); assertTrue(tempDir.exists()); } } } } @NullUnmarked static class StaticTempDirUsageInsideNestedClassTestCase { @TempDir static File tempDir; static File initialTempDir; @Test void topLevel() { assertNotNull(tempDir); assertTrue(tempDir.exists()); initialTempDir = tempDir; } @Nested class NestedTestClass { @Test void nested() { assertNotNull(tempDir); assertTrue(tempDir.exists()); assertSame(initialTempDir, tempDir); } } } @DisplayName("class") static class AllPossibleDeclarationLocationsTestCase { static final Map> tempDirs = new HashMap<>(); @TempDir static Path staticField1; @TempDir static Path staticField2; final Path instanceFieldSetViaConstructorInjection; @TempDir Path instanceField1; @TempDir Path instanceField2; @BeforeAll static void beforeAll(@TempDir Path param1, @TempDir Path param2, TestInfo testInfo) { getTempDirs(testInfo).putAll(Map.of( // "staticField1", staticField1, // "staticField2", staticField2, // "beforeAll1", param1, // "beforeAll2", param2 // )); assertAllTempDirsExist(testInfo); } AllPossibleDeclarationLocationsTestCase(@TempDir Path tempDir) { this.instanceFieldSetViaConstructorInjection = tempDir; } @BeforeEach void beforeEach(@TempDir Path param1, @TempDir Path param2, TestInfo testInfo) { getTempDirs(testInfo).putAll(Map.of( // "staticField1", staticField1, // "staticField2", staticField2, // "instanceFieldSetViaConstructorInjection", instanceFieldSetViaConstructorInjection, // "instanceField1", instanceField1, // "instanceField2", instanceField2, // "beforeEach1", param1, // "beforeEach2", param2 // )); assertAllTempDirsExist(testInfo); } @Test @DisplayName("testA") void testA(@TempDir Path param1, @TempDir Path param2, TestInfo testInfo) { getTempDirs(testInfo).putAll(Map.of( // "test1", param1, // "test2", param2 // )); assertAllTempDirsExist(testInfo); } @Test @DisplayName("testB") void testB(@TempDir Path param1, @TempDir Path param2, TestInfo testInfo) { getTempDirs(testInfo).putAll(Map.of( // "test1", param1, // "test2", param2 // )); assertAllTempDirsExist(testInfo); } @AfterEach void afterEach(@TempDir Path param1, @TempDir Path param2, TestInfo testInfo) { getTempDirs(testInfo).putAll(Map.of( // "afterEach1", param1, // "afterEach2", param2 // )); assertAllTempDirsExist(testInfo); } @AfterAll static void afterAll(@TempDir Path param1, @TempDir Path param2, TestInfo testInfo) { getTempDirs(testInfo).putAll(Map.of( // "afterAll1", param1, // "afterAll2", param2 // )); assertAllTempDirsExist(testInfo); } private static Map getTempDirs(TestInfo testInfo) { return tempDirs.computeIfAbsent(testInfo.getDisplayName(), _ -> new LinkedHashMap<>()); } private static void assertAllTempDirsExist(TestInfo testInfo) { assertAll(getTempDirs(testInfo).values().stream().map(tempDir -> () -> assertTrue(Files.exists(tempDir)))); } } static class UndeletableTestCase { static final String TEMP_DIR = "TEMP_DIR"; @TempDir(deletionStrategy = FailingTempDirDeletionStrategy.class) Path tempDir; @BeforeEach void reportTempDir(TestReporter reporter) { reporter.publishEntry(TEMP_DIR, tempDir.toString()); } } static class UndeletableDirectoryTestCase extends UndeletableTestCase { @Test void test() throws Exception { Files.createDirectory(tempDir.resolve(UNDELETABLE_PATH)); } } static class UndeletableFileTestCase extends UndeletableTestCase { @Test void test() throws Exception { Files.createFile(tempDir.resolve(UNDELETABLE_PATH)); } } static class UndeletableWithDefaultDeletionStrategyTestCase extends UndeletableTestCase { static final String TEMP_DIR = "TEMP_DIR"; @TempDir Path tempDir; @BeforeEach void reportTempDir(TestReporter reporter) { reporter.publishEntry(TEMP_DIR, tempDir.toString()); } @Test void test() throws Exception { Files.createFile(tempDir.resolve(UNDELETABLE_PATH)); } } static class FactoryWithTestMethodNameAsPrefixTestCase { @Test void test(@TempDir(factory = Factory.class) Path tempDir) { assertTrue(Files.exists(tempDir)); assertThat(tempDir.getFileName()).asString().startsWith("test"); } @NullMarked private static class Factory implements TempDirFactory { @Override public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) throws Exception { return Files.createTempDirectory(extensionContext.getRequiredTestMethod().getName()); } } } // https://github.com/junit-team/junit-framework/issues/2088 static class FactoryWithCustomParentDirectoryTestCase { @Test void test(@TempDir(factory = Factory.class) Path tempDir) { assertThat(tempDir).exists().hasParent(Factory.parent); assertThat(tempDir.getFileName()).asString().startsWith("prefix"); } @NullMarked private static class Factory implements TempDirFactory { @Nullable private static Path parent; private Factory() throws IOException { parent = Files.createTempDirectory("parent"); } @Override public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) throws Exception { return Files.createTempDirectory(requireNonNull(parent), "prefix"); } } } static class FactoryWithMemoryFileSystemTestCase { @Test void test(@TempDir(factory = Factory.class) Path tempDir) { assertThat(tempDir).exists().hasFileSystem(Factory.fileSystem); assertThat(tempDir.getFileName()).asString().startsWith("prefix"); } @NullMarked private static class Factory implements TempDirFactory { @Nullable private static FileSystem fileSystem; @Override public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) throws Exception { fileSystem = MemoryFileSystemBuilder.newEmpty().build(); return Files.createTempDirectory(fileSystem.getPath("/"), "prefix"); } @Override public void close() throws IOException { requireNonNull(fileSystem).close(); fileSystem = null; } } } static class FactoryWithJimfsTestCase { @Test void test(@TempDir(factory = Factory.class) Path tempDir) { assertThat(tempDir).exists().hasFileSystem(Factory.fileSystem); assertThat(tempDir.getFileName()).asString().startsWith("prefix"); } @NullMarked private static class Factory implements TempDirFactory { @Nullable private static FileSystem fileSystem; @Override public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) throws Exception { fileSystem = Jimfs.newFileSystem(Configuration.unix()); return Files.createTempDirectory(fileSystem.getPath("/"), "prefix"); } @Override public void close() throws IOException { requireNonNull(fileSystem).close(); fileSystem = null; } } } static class FactoryWithAnnotatedElementNameAsPrefixTestCase { @TempDir(factory = Factory.class) private Path tempDir1; @Test void test(@TempDir(factory = Factory.class) Path tempDir2) { assertThat(tempDir1.getFileName()).asString().startsWith("tempDir1"); assertThat(tempDir2.getFileName()).asString().startsWith("tempDir2"); } @NullMarked private static class Factory implements TempDirFactory { @Override public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) throws Exception { return Files.createTempDirectory(getName(elementContext.getAnnotatedElement())); } private static String getName(AnnotatedElement element) { return element instanceof Field field ? field.getName() : ((Parameter) element).getName(); } } } @NullUnmarked static class FactoryWithCustomMetaAnnotationTestCase { @TempDirForField private Path tempDir1; @Test void test(@TempDirForParameter Path tempDir2) { assertThat(tempDir1.getFileName()).asString().startsWith("field"); assertThat(tempDir2.getFileName()).asString().startsWith("parameter"); } @Target(ANNOTATION_TYPE) @Retention(RUNTIME) @TempDir(factory = FactoryWithCustomMetaAnnotationTestCase.Factory.class) private @interface TempDirWithPrefix { String value(); } @Target(FIELD) @Retention(RUNTIME) @TempDirWithPrefix("field") private @interface TempDirForField { } @Target(PARAMETER) @Retention(RUNTIME) @TempDirWithPrefix("parameter") private @interface TempDirForParameter { } @NullMarked private static class Factory implements TempDirFactory { @Override public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) throws Exception { String prefix = elementContext.findAnnotation(TempDirWithPrefix.class) // .map(TempDirWithPrefix::value).orElseThrow(); return Files.createTempDirectory(prefix); } } } static class FactoryNotReturningDirectoryTestCase { @Test void test(@SuppressWarnings("unused") @TempDir(factory = Factory.class) Path tempDir) { // never called } @NullMarked @SuppressWarnings("DataFlowIssue") private static class Factory implements TempDirFactory { @Override public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) { return null; } } } static class FactoryReturningNonDefaultFileSystemPathForFileAnnotatedElementTestCase { @Test void test(@SuppressWarnings("unused") @TempDir(factory = Factory.class) File tempDir) { // never called } @NullMarked private static class Factory implements TempDirFactory { private final FileSystem fileSystem = Jimfs.newFileSystem(Configuration.unix()); @Override public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) throws Exception { return Files.createTempDirectory(fileSystem.getPath("/"), "prefix"); } @Override public void close() throws IOException { fileSystem.close(); } } } static class StandardDefaultFactoryTestCase { @Test void test(@TempDir Path tempDir1, @TempDir Path tempDir2) { assertNotSame(tempDir1, tempDir2); assertThat(tempDir1.getFileName()).asString().startsWith("junit"); assertThat(tempDir2.getFileName()).asString().startsWith("junit"); } } static class CustomDefaultFactoryTestCase { @Test void test(@TempDir Path tempDir1, @TempDir Path tempDir2) { assertNotSame(tempDir1, tempDir2); assertThat(tempDir1.getFileName()).asString().startsWith("custom"); assertThat(tempDir2.getFileName()).asString().startsWith("custom"); } } static class CustomDefaultFactoryWithStandardDeclarationTestCase { @Test void test(@TempDir Path tempDir1, @TempDir(factory = Standard.class) Path tempDir2) { assertNotSame(tempDir1, tempDir2); assertThat(tempDir1.getFileName()).asString().startsWith("custom"); assertThat(tempDir2.getFileName()).asString().startsWith("junit"); } } static class CustomDefaultFactoryNotReturningDirectoryTestCase { @Test void test(@SuppressWarnings("unused") @TempDir Path tempDir) { // never called } } record TempDirRecordTestCase(@TempDir Path tempDir) { @Test void shouldExists() { assertTrue(Files.exists(tempDir)); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestExecutionExceptionHandlerTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.junit.platform.testkit.engine.EventConditions.container; import static org.junit.platform.testkit.engine.EventConditions.engine; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.EventConditions.started; import static org.junit.platform.testkit.engine.EventConditions.test; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.testkit.engine.EngineExecutionResults; /** * Integration tests that verify support for {@link TestExecutionExceptionHandler}. * * @since 5.0 */ class TestExecutionExceptionHandlerTests extends AbstractJupiterTestEngineTests { static List handlerCalls = new ArrayList<>(); @BeforeEach void resetStatics() { handlerCalls.clear(); RethrowException.handleExceptionCalled = false; ConvertException.handleExceptionCalled = false; SwallowException.handleExceptionCalled = false; ShouldNotBeCalled.handleExceptionCalled = false; } @Test void exceptionHandlerRethrowsException() { LauncherDiscoveryRequest request = request().selectors(selectMethod(ATestCase.class, "testRethrow")).build(); EngineExecutionResults executionResults = executeTests(request); assertTrue(RethrowException.handleExceptionCalled, "TestExecutionExceptionHandler should have been called"); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(ATestCase.class), started()), // event(test("testRethrow"), started()), // event(test("testRethrow"), finishedWithFailure(instanceOf(IOException.class), message("checked"))), // event(container(ATestCase.class), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void exceptionHandlerSwallowsException() { LauncherDiscoveryRequest request = request().selectors(selectMethod(ATestCase.class, "testSwallow")).build(); EngineExecutionResults executionResults = executeTests(request); assertTrue(SwallowException.handleExceptionCalled, "TestExecutionExceptionHandler should have been called"); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(ATestCase.class), started()), // event(test("testSwallow"), started()), // event(test("testSwallow"), finishedSuccessfully()), // event(container(ATestCase.class), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void exceptionHandlerConvertsException() { LauncherDiscoveryRequest request = request().selectors(selectMethod(ATestCase.class, "testConvert")).build(); EngineExecutionResults executionResults = executeTests(request); assertTrue(ConvertException.handleExceptionCalled, "TestExecutionExceptionHandler should have been called"); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(ATestCase.class), started()), // event(test("testConvert"), started()), // event(test("testConvert"), finishedWithFailure(instanceOf(IOException.class), message("checked"))), // event(container(ATestCase.class), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void severalHandlersAreCalledInOrder() { LauncherDiscoveryRequest request = request().selectors(selectMethod(ATestCase.class, "testSeveral")).build(); EngineExecutionResults executionResults = executeTests(request); assertTrue(ConvertException.handleExceptionCalled, "ConvertException should have been called"); assertTrue(RethrowException.handleExceptionCalled, "RethrowException should have been called"); assertTrue(SwallowException.handleExceptionCalled, "SwallowException should have been called"); assertFalse(ShouldNotBeCalled.handleExceptionCalled, "ShouldNotBeCalled should not have been called"); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(ATestCase.class), started()), // event(test("testSeveral"), started()), // event(test("testSeveral"), finishedSuccessfully()), // event(container(ATestCase.class), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); assertEquals(Arrays.asList("convert", "rethrow", "swallow"), handlerCalls); } // ------------------------------------------------------------------- static class ATestCase { @Test @ExtendWith(RethrowException.class) void testRethrow() throws IOException { throw new IOException("checked"); } @Test @ExtendWith(SwallowException.class) void testSwallow() throws IOException { throw new IOException("checked"); } @Test @ExtendWith(ConvertException.class) void testConvert() { throw new RuntimeException("unchecked"); } @Test @ExtendWith(ShouldNotBeCalled.class) @ExtendWith(SwallowException.class) @ExtendWith(RethrowException.class) @ExtendWith(ConvertException.class) void testSeveral() { throw new RuntimeException("unchecked"); } } static class RethrowException implements TestExecutionExceptionHandler { static boolean handleExceptionCalled = false; @Override public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { assertInstanceOf(IOException.class, throwable); handleExceptionCalled = true; handlerCalls.add("rethrow"); throw throwable; } } static class SwallowException implements TestExecutionExceptionHandler { static boolean handleExceptionCalled = false; @Override public void handleTestExecutionException(ExtensionContext context, Throwable throwable) { assertInstanceOf(IOException.class, throwable); handleExceptionCalled = true; handlerCalls.add("swallow"); //swallow exception by not rethrowing it } } static class ConvertException implements TestExecutionExceptionHandler { static boolean handleExceptionCalled = false; @Override public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { assertInstanceOf(RuntimeException.class, throwable); handleExceptionCalled = true; handlerCalls.add("convert"); throw new IOException("checked"); } } static class ShouldNotBeCalled implements TestExecutionExceptionHandler { static boolean handleExceptionCalled = false; @Override public void handleTestExecutionException(ExtensionContext context, Throwable throwable) { handleExceptionCalled = true; } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInfoParameterResolverTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.launcher.LauncherConstants.CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME; import java.util.Arrays; import java.util.List; import java.util.Set; 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.DisplayName; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.platform.engine.DiscoveryIssue.Severity; /** * Integration tests for {@link TestInfoParameterResolver}. * * @since 5.0 */ @Tag("class-tag") class TestInfoParameterResolverTests extends AbstractJupiterTestEngineTests { private static final List allDisplayNames = Arrays.asList("defaultDisplayName(TestInfo)", "custom display name", "getTags(TestInfo)", "customDisplayNameThatIsEmpty()"); TestInfoParameterResolverTests(TestInfo testInfo) { assertThat(testInfo.getTestClass()).contains(TestInfoParameterResolverTests.class); assertThat(testInfo.getTestMethod()).isPresent(); } @Test void defaultDisplayName(TestInfo testInfo) { assertEquals("defaultDisplayName(TestInfo)", testInfo.getDisplayName()); } @Test @DisplayName("custom display name") void providedDisplayName(TestInfo testInfo) { assertEquals("custom display name", testInfo.getDisplayName()); } @Test void customDisplayNameThatIsEmpty() { executeTests(request -> request // .selectors(selectClass(BlankDisplayNameTestCase.class)) // .configurationParameter(CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME, Severity.ERROR.name())) // .testEvents() // .assertStatistics(stats -> stats.started(1).succeeded(1)); } @Test @Tag("method-tag") void getTags(TestInfo testInfo) { assertEquals(2, testInfo.getTags().size()); assertTrue(testInfo.getTags().contains("method-tag")); assertTrue(testInfo.getTags().contains("class-tag")); } @BeforeEach @AfterEach void beforeAndAfter(TestInfo testInfo) { assertThat(allDisplayNames).contains(testInfo.getDisplayName()); } @BeforeAll static void beforeAll(TestInfo testInfo) { Set tags = testInfo.getTags(); assertEquals(1, tags.size()); assertTrue(tags.contains("class-tag")); } @BeforeAll @AfterAll static void beforeAndAfterAll(TestInfo testInfo) { assertEquals(TestInfoParameterResolverTests.class.getSimpleName(), testInfo.getDisplayName()); } static class BlankDisplayNameTestCase { @Test @DisplayName("") void test(TestInfo testInfo) { assertEquals("test(TestInfo)", testInfo.getDisplayName()); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInstanceFactoryTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.TEST_METHOD; import static org.junit.platform.commons.util.ClassUtils.nullSafeToString; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.junit.platform.testkit.engine.EventConditions.container; import static org.junit.platform.testkit.engine.EventConditions.engine; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.EventConditions.nestedContainer; import static org.junit.platform.testkit.engine.EventConditions.started; import static org.junit.platform.testkit.engine.EventConditions.test; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Constants; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionConfigurationException; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.TestInstanceFactory; import org.junit.jupiter.api.extension.TestInstanceFactoryContext; import org.junit.jupiter.api.extension.TestInstantiationException; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.test.TestClassLoader; import org.junit.platform.testkit.engine.EngineExecutionResults; /** * Integration tests that verify support for {@link TestInstanceFactory}. * * @since 5.3 */ class TestInstanceFactoryTests extends AbstractJupiterTestEngineTests { private static final List callSequence = new ArrayList<>(); @BeforeEach void resetCallSequence() { callSequence.clear(); } @Test void multipleFactoriesRegisteredOnSingleTestClass() { Class testClass = MultipleFactoriesRegisteredOnSingleTestCase.class; EngineExecutionResults executionResults = executeTestsForClass(testClass); assertEquals(0, executionResults.testEvents().started().count(), "# tests started"); assertEquals(0, executionResults.testEvents().failed().count(), "# tests aborted"); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(container(testClass), finishedWithFailure(instanceOf(ExtensionConfigurationException.class), message("The following TestInstanceFactory extensions were registered for test class [" + testClass.getName() + "], but only one is permitted: " + nullSafeToString(FooInstanceFactory.class, BarInstanceFactory.class)))), // event(engine(), finishedSuccessfully())); } @Test void multipleFactoriesRegisteredWithinTestClassHierarchy() { Class testClass = MultipleFactoriesRegisteredWithinClassHierarchyTestCase.class; EngineExecutionResults executionResults = executeTestsForClass(testClass); assertEquals(0, executionResults.testEvents().started().count(), "# tests started"); assertEquals(0, executionResults.testEvents().failed().count(), "# tests aborted"); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(container(testClass), finishedWithFailure(instanceOf(ExtensionConfigurationException.class), message("The following TestInstanceFactory extensions were registered for test class [" + testClass.getName() + "], but only one is permitted: " + nullSafeToString(FooInstanceFactory.class, BarInstanceFactory.class)))), // event(engine(), finishedSuccessfully())); } @Test void multipleFactoriesRegisteredWithinNestedClassStructure() { Class outerClass = MultipleFactoriesRegisteredWithinNestedClassStructureTestCase.class; Class nestedClass = MultipleFactoriesRegisteredWithinNestedClassStructureTestCase.InnerTestCase.class; EngineExecutionResults executionResults = executeTestsForClass(outerClass); assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(outerClass), started()), // event(test("outerTest()"), started()), // event(test("outerTest()"), finishedSuccessfully()), // event(nestedContainer(nestedClass), started()), // event(nestedContainer(nestedClass), finishedWithFailure(instanceOf(ExtensionConfigurationException.class), message("The following TestInstanceFactory extensions were registered for test class [" + nestedClass.getName() + "], but only one is permitted: " + nullSafeToString(FooInstanceFactory.class, BarInstanceFactory.class)))), // event(container(outerClass), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void nullTestInstanceFactoryWithPerMethodLifecycle() { Class testClass = NullTestInstanceFactoryTestCase.class; EngineExecutionResults executionResults = executeTestsForClass(testClass); assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); assertEquals(1, executionResults.testEvents().failed().count(), "# tests aborted"); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(test("testShouldNotBeCalled"), started()), // event(test("testShouldNotBeCalled"), finishedWithFailure(instanceOf(TestInstantiationException.class), message(m -> m.equals("TestInstanceFactory [" + NullTestInstanceFactory.class.getName() + "] failed to return an instance of [" + testClass.getName() + "] and instead returned an instance of [null].")))), // event(container(testClass), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void nullTestInstanceFactoryWithPerClassLifecycle() { Class testClass = PerClassLifecycleNullTestInstanceFactoryTestCase.class; EngineExecutionResults executionResults = executeTestsForClass(testClass); assertEquals(0, executionResults.testEvents().started().count(), "# tests started"); assertEquals(0, executionResults.testEvents().failed().count(), "# tests aborted"); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(container(testClass), finishedWithFailure(instanceOf(TestInstantiationException.class), message(m -> m.equals("TestInstanceFactory [" + NullTestInstanceFactory.class.getName() + "] failed to return an instance of [" + testClass.getName() + "] and instead returned an instance of [null].")))), // event(engine(), finishedSuccessfully())); } @Test void bogusTestInstanceFactoryWithPerMethodLifecycle() { Class testClass = BogusTestInstanceFactoryTestCase.class; EngineExecutionResults executionResults = executeTestsForClass(testClass); assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); assertEquals(1, executionResults.testEvents().failed().count(), "# tests aborted"); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(test("testShouldNotBeCalled"), started()), // event(test("testShouldNotBeCalled"), finishedWithFailure(instanceOf(TestInstantiationException.class), message(m -> m.equals("TestInstanceFactory [" + BogusTestInstanceFactory.class.getName() + "] failed to return an instance of [" + testClass.getName() + "] and instead returned an instance of [java.lang.String].")))), // event(container(testClass), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void bogusTestInstanceFactoryWithPerClassLifecycle() { Class testClass = PerClassLifecycleBogusTestInstanceFactoryTestCase.class; EngineExecutionResults executionResults = executeTestsForClass(testClass); assertEquals(0, executionResults.testEvents().started().count(), "# tests started"); assertEquals(0, executionResults.testEvents().failed().count(), "# tests aborted"); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(container(testClass), finishedWithFailure(instanceOf(TestInstantiationException.class), message(m -> m.equals("TestInstanceFactory [" + BogusTestInstanceFactory.class.getName() + "] failed to return an instance of [" + testClass.getName() + "] and instead returned an instance of [java.lang.String].")))), // event(engine(), finishedSuccessfully())); } @Test void explosiveTestInstanceFactoryWithPerMethodLifecycle() { Class testClass = ExplosiveTestInstanceFactoryTestCase.class; EngineExecutionResults executionResults = executeTestsForClass(testClass); assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); assertEquals(1, executionResults.testEvents().failed().count(), "# tests aborted"); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(test("testShouldNotBeCalled"), started()), // event(test("testShouldNotBeCalled"), finishedWithFailure(instanceOf(TestInstantiationException.class), message("TestInstanceFactory [" + ExplosiveTestInstanceFactory.class.getName() + "] failed to instantiate test class [" + testClass.getName() + "]: boom!"))), // event(container(testClass), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @Test void explosiveTestInstanceFactoryWithPerClassLifecycle() { Class testClass = PerClassLifecycleExplosiveTestInstanceFactoryTestCase.class; EngineExecutionResults executionResults = executeTestsForClass(testClass); assertEquals(0, executionResults.testEvents().started().count(), "# tests started"); assertEquals(0, executionResults.testEvents().failed().count(), "# tests aborted"); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(container(testClass), // finishedWithFailure(instanceOf(TestInstantiationException.class), message("TestInstanceFactory [" + ExplosiveTestInstanceFactory.class.getName() + "] failed to instantiate test class [" + testClass.getName() + "]: boom!"))), // event(engine(), finishedSuccessfully())); } @Test void proxyTestInstanceFactoryFailsDueToUseOfDifferentClassLoader() { Class testClass = ProxiedTestCase.class; EngineExecutionResults executionResults = executeTestsForClass(testClass); assertEquals(0, executionResults.testEvents().started().count(), "# tests started"); assertEquals(0, executionResults.testEvents().failed().count(), "# tests aborted"); executionResults.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(testClass), started()), // event(container(testClass), // // NOTE: the test class names are the same even though the objects are // instantiated using different ClassLoaders. Thus, we check for the // appended "@" but ignore the actual hash code for the test class // loaded by the different ClassLoader. finishedWithFailure(instanceOf(TestInstantiationException.class), message(m -> m.startsWith("TestInstanceFactory [" + ProxyTestInstanceFactory.class.getName() + "]") && m.contains("failed to return an instance of [" + testClass.getName() + "@" + Integer.toHexString(System.identityHashCode(testClass))) && m.contains("and instead returned an instance of [" + testClass.getName() + "@")// ))), // event(engine(), finishedSuccessfully())); } @Test void instanceFactoryOnTopLevelTestClass() { EngineExecutionResults executionResults = executeTestsForClass(ParentTestCase.class); assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); // @formatter:off assertThat(callSequence).containsExactly( "FooInstanceFactory instantiated: ParentTestCase", "parentTest", "close ParentTestCase" ); // @formatter:on } @Test void instanceFactorySupportedWhenTestClassDeclaresMultipleConstructors() { executeTestsForClass(MultipleConstructorsTestCase.class).testEvents()// .assertStatistics(stats -> stats.started(1).succeeded(1)); // @formatter:off assertThat(callSequence).containsExactly( "MultipleConstructorsTestInstanceFactory instantiated: MultipleConstructorsTestCase", "test: 42" ); // @formatter:on } @Test void inheritedFactoryInTestClassHierarchy() { EngineExecutionResults executionResults = executeTestsForClass(InheritedFactoryTestCase.class); assertEquals(2, executionResults.testEvents().started().count(), "# tests started"); assertEquals(2, executionResults.testEvents().succeeded().count(), "# tests succeeded"); // @formatter:off assertThat(callSequence).containsExactly( "FooInstanceFactory instantiated: InheritedFactoryTestCase", "parentTest", "close InheritedFactoryTestCase", "FooInstanceFactory instantiated: InheritedFactoryTestCase", "childTest", "close InheritedFactoryTestCase" ); // @formatter:on } @Test void instanceFactoriesInNestedClassStructureAreInherited() { EngineExecutionResults executionResults = executeTestsForClass(OuterTestCase.class); assertEquals(3, executionResults.testEvents().started().count(), "# tests started"); assertEquals(3, executionResults.testEvents().succeeded().count(), "# tests succeeded"); // @formatter:off assertThat(callSequence).containsExactly( // OuterTestCase "FooInstanceFactory instantiated: OuterTestCase", "outerTest", "close OuterTestCase", // InnerTestCase "FooInstanceFactory instantiated: OuterTestCase", "FooInstanceFactory instantiated: InnerTestCase", "innerTest1", "close InnerTestCase", "close OuterTestCase", // InnerInnerTestCase "FooInstanceFactory instantiated: OuterTestCase", "FooInstanceFactory instantiated: InnerTestCase", "FooInstanceFactory instantiated: InnerInnerTestCase", "innerTest2", "close InnerInnerTestCase", "close InnerTestCase", "close OuterTestCase" ); // @formatter:on } @Test void instanceFactoryRegisteredViaTestInterface() { EngineExecutionResults executionResults = executeTestsForClass(FactoryFromInterfaceTestCase.class); assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); // @formatter:off assertThat(callSequence).containsExactly( "FooInstanceFactory instantiated: FactoryFromInterfaceTestCase", "test", "close FactoryFromInterfaceTestCase" ); // @formatter:on } @Test void instanceFactoryRegisteredAsLambdaExpression() { EngineExecutionResults executionResults = executeTestsForClass(LambdaFactoryTestCase.class); assertEquals(1, executionResults.testEvents().started().count(), "# tests started"); assertEquals(1, executionResults.testEvents().succeeded().count(), "# tests succeeded"); // @formatter:off assertThat(callSequence).containsExactly( "beforeEach: lambda", "test: lambda" ); // @formatter:on } @Test void instanceFactoryWithPerClassLifecycle() { EngineExecutionResults executionResults = executeTestsForClass(PerClassLifecycleTestCase.class); assertEquals(1, PerClassLifecycleTestCase.counter.get()); assertEquals(2, executionResults.testEvents().started().count(), "# tests started"); assertEquals(2, executionResults.testEvents().succeeded().count(), "# tests succeeded"); // @formatter:off assertThat(callSequence).containsExactly( "FooInstanceFactory instantiated: PerClassLifecycleTestCase", "@BeforeAll", "@BeforeEach", "test1", "@BeforeEach", "test2", "@AfterAll", "close PerClassLifecycleTestCase" ); // @formatter:on } @Test void instanceFactoryWithLegacyContext() { EngineExecutionResults executionResults = executeTestsForClass(LegacyContextTestCase.class); assertEquals(3, executionResults.testEvents().started().count(), "# tests started"); assertEquals(3, executionResults.testEvents().succeeded().count(), "# tests succeeded"); // @formatter:off assertThat(callSequence).containsExactly( "LegacyInstanceFactory instantiated: LegacyContextTestCase", "outerTest", "LegacyInstanceFactory instantiated: LegacyContextTestCase", "LegacyInstanceFactory instantiated: InnerTestCase", "innerTest1", "LegacyInstanceFactory instantiated: LegacyContextTestCase", "LegacyInstanceFactory instantiated: InnerTestCase", "innerTest2", "close InnerTestCase", "close InnerTestCase", "close LegacyContextTestCase", "close LegacyContextTestCase", "close LegacyContextTestCase" ); // @formatter:on } @Test void instanceFactoryWithLegacyContextAndChangedDefaultScope() { var executionResults = executeTests(request() // .selectors(selectClass(LegacyContextTestCase.class)) // .configurationParameter( Constants.DEFAULT_TEST_CLASS_INSTANCE_CONSTRUCTION_EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME, TEST_METHOD.name())); assertEquals(3, executionResults.testEvents().started().count(), "# tests started"); assertEquals(3, executionResults.testEvents().succeeded().count(), "# tests succeeded"); // @formatter:off assertThat(callSequence).containsExactly( "LegacyInstanceFactory instantiated: LegacyContextTestCase", "outerTest", "close LegacyContextTestCase", "LegacyInstanceFactory instantiated: LegacyContextTestCase", "LegacyInstanceFactory instantiated: InnerTestCase", "innerTest1", "close InnerTestCase", "close LegacyContextTestCase", "LegacyInstanceFactory instantiated: LegacyContextTestCase", "LegacyInstanceFactory instantiated: InnerTestCase", "innerTest2", "close InnerTestCase", "close LegacyContextTestCase" ); // @formatter:on } // ------------------------------------------------------------------------- @ExtendWith({ FooInstanceFactory.class, BarInstanceFactory.class }) static class MultipleFactoriesRegisteredOnSingleTestCase { @Test void testShouldNotBeCalled() { callSequence.add("testShouldNotBeCalled"); } } @ExtendWith(NullTestInstanceFactory.class) static class NullTestInstanceFactoryTestCase { @Test void testShouldNotBeCalled() { callSequence.add("testShouldNotBeCalled"); } } @TestInstance(PER_CLASS) static class PerClassLifecycleNullTestInstanceFactoryTestCase extends NullTestInstanceFactoryTestCase { } @ExtendWith(BogusTestInstanceFactory.class) static class BogusTestInstanceFactoryTestCase { @Test void testShouldNotBeCalled() { callSequence.add("testShouldNotBeCalled"); } } @TestInstance(PER_CLASS) static class PerClassLifecycleBogusTestInstanceFactoryTestCase extends BogusTestInstanceFactoryTestCase { } @ExtendWith(ExplosiveTestInstanceFactory.class) static class ExplosiveTestInstanceFactoryTestCase { @Test void testShouldNotBeCalled() { callSequence.add("testShouldNotBeCalled"); } } @TestInstance(PER_CLASS) static class PerClassLifecycleExplosiveTestInstanceFactoryTestCase extends ExplosiveTestInstanceFactoryTestCase { } private static class MultipleConstructorsTestInstanceFactory implements TestInstanceFactory { @Override public Object createTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext) { instantiated(getClass(), factoryContext.getTestClass()); return new MultipleConstructorsTestCase(42); } } @ExtendWith(MultipleConstructorsTestInstanceFactory.class) static class MultipleConstructorsTestCase { private final int number; MultipleConstructorsTestCase(String text) { this.number = -1; } MultipleConstructorsTestCase(int number) { this.number = number; } @Test void test() { callSequence.add("test: " + this.number); } } @ExtendWith(FooInstanceFactory.class) static class ParentTestCase { @Test void parentTest() { callSequence.add("parentTest"); } } static class InheritedFactoryTestCase extends ParentTestCase { @Test void childTest() { callSequence.add("childTest"); } } @ExtendWith(BarInstanceFactory.class) static class MultipleFactoriesRegisteredWithinClassHierarchyTestCase extends ParentTestCase { @Test void childTest() { callSequence.add("childTest"); } } @ExtendWith(FooInstanceFactory.class) static class OuterTestCase { @Test void outerTest() { callSequence.add("outerTest"); } @Nested class InnerTestCase { @Test void innerTest1() { callSequence.add("innerTest1"); } @Nested class InnerInnerTestCase { @Test void innerTest2() { callSequence.add("innerTest2"); } } } } @ExtendWith(FooInstanceFactory.class) static class MultipleFactoriesRegisteredWithinNestedClassStructureTestCase { @Test void outerTest() { } @Nested @ExtendWith(BarInstanceFactory.class) class InnerTestCase { @Test void innerTest() { } } } @ExtendWith(FooInstanceFactory.class) interface TestInterface { } static class FactoryFromInterfaceTestCase implements TestInterface { @Test void test() { callSequence.add("test"); } } static class LambdaFactoryTestCase { private final String text; @RegisterExtension static final TestInstanceFactory factory = (__, ___) -> new LambdaFactoryTestCase("lambda"); LambdaFactoryTestCase(String text) { this.text = text; } @BeforeEach void beforeEach() { callSequence.add("beforeEach: " + this.text); } @Test void test() { callSequence.add("test: " + this.text); } } @ExtendWith(FooInstanceFactory.class) @TestInstance(PER_CLASS) static class PerClassLifecycleTestCase { static final AtomicInteger counter = new AtomicInteger(); PerClassLifecycleTestCase() { counter.incrementAndGet(); } @BeforeAll void beforeAll() { callSequence.add("@BeforeAll"); } @BeforeEach void beforeEach() { callSequence.add("@BeforeEach"); } @Test void test1() { callSequence.add("test1"); } @Test void test2() { callSequence.add("test2"); } @AfterAll void afterAll() { callSequence.add("@AfterAll"); } } @ExtendWith(LegacyInstanceFactory.class) static class LegacyContextTestCase { @Test void outerTest() { callSequence.add("outerTest"); } @Nested class InnerTestCase { @Test void innerTest1() { callSequence.add("innerTest1"); } @Test void innerTest2() { callSequence.add("innerTest2"); } } } @ExtendWith(ProxyTestInstanceFactory.class) @TestInstance(PER_CLASS) static class ProxiedTestCase { @Test void test1() { callSequence.add("test1"); } @Test void test2() { callSequence.add("test2"); } } // ------------------------------------------------------------------------- private static abstract class AbstractTestInstanceFactory implements TestInstanceFactory { @Override public Object createTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext) { Class testClass = factoryContext.getTestClass(); instantiated(getClass(), testClass); extensionContext.getStore(ExtensionContext.Namespace.create(this)).put(new Object(), (AutoCloseable) () -> callSequence.add("close " + testClass.getSimpleName())); if (factoryContext.getOuterInstance().isPresent()) { return ReflectionSupport.newInstance(testClass, factoryContext.getOuterInstance().get()); } // else return ReflectionSupport.newInstance(testClass); } } private static class FooInstanceFactory extends AbstractTestInstanceFactory { @Override public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { return TEST_METHOD; } } private static class BarInstanceFactory extends AbstractTestInstanceFactory { @Override public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { return TEST_METHOD; } } private static class LegacyInstanceFactory extends AbstractTestInstanceFactory { } /** * {@link TestInstanceFactory} that returns null. */ private static class NullTestInstanceFactory implements TestInstanceFactory { @SuppressWarnings("DataFlowIssue") @Override public Object createTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext) { return null; } } /** * {@link TestInstanceFactory} that returns an object of a type that does * not match the supplied test class. */ private static class BogusTestInstanceFactory implements TestInstanceFactory { @Override public Object createTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext) { return "bogus"; } } /** * {@link TestInstanceFactory} that always throws an exception. */ private static class ExplosiveTestInstanceFactory implements TestInstanceFactory { @Override public Object createTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext) { throw new RuntimeException("boom!"); } } /** * This does not actually create a proxy. Rather, it simulates what * a proxy-based implementation might do, by loading the class from a * different {@link ClassLoader}. */ private static class ProxyTestInstanceFactory implements TestInstanceFactory { @Override public Object createTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext) { Class testClass = factoryContext.getTestClass(); String className = testClass.getName(); instantiated(getClass(), testClass); try (var testClassLoader = TestClassLoader.forClasses(testClass)) { // Load test class from different class loader Class clazz = testClassLoader.loadClass(className); return ReflectionSupport.newInstance(clazz); } catch (Exception ex) { throw new RuntimeException("Failed to load class [" + className + "]", ex); } } } private static void instantiated(Class factoryClass, Class testClass) { callSequence.add(factoryClass.getSimpleName() + " instantiated: " + testClass.getSimpleName()); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInstancePostProcessorAndPreDestroyCallbackTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static java.util.Arrays.asList; import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestInstancePostProcessor; import org.junit.jupiter.api.extension.TestInstancePreDestroyCallback; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.jupiter.engine.JupiterTestEngine; /** * Integration tests that verify support for {@link TestInstancePostProcessor} * and {@link TestInstancePreDestroyCallback} in the {@link JupiterTestEngine}. * * @since 5.6 */ class TestInstancePostProcessorAndPreDestroyCallbackTests extends AbstractJupiterTestEngineTests { private static final List callSequence = new ArrayList<>(); @Test void postProcessorAndPreDestroyCallbacks() { // @formatter:off assertPostProcessorAndPreDestroyCallbacks(TopLevelTestCase.class, "fooPostProcessTestInstance", "barPostProcessTestInstance", "test-1", "barPreDestroyTestInstance", "fooPreDestroyTestInstance" ); // @formatter:on } @Test void postProcessorAndPreDestroyCallbacksInSubclass() { // @formatter:off assertPostProcessorAndPreDestroyCallbacks(SecondLevelTestCase.class, "fooPostProcessTestInstance", "barPostProcessTestInstance", "bazPostProcessTestInstance", "test-2", "bazPreDestroyTestInstance", "barPreDestroyTestInstance", "fooPreDestroyTestInstance" ); // @formatter:on } @Test void postProcessorAndPreDestroyCallbacksInSubSubclass() { // @formatter:off assertPostProcessorAndPreDestroyCallbacks(ThirdLevelTestCase.class, "fooPostProcessTestInstance", "barPostProcessTestInstance", "bazPostProcessTestInstance", "quuxPostProcessTestInstance", "test-3", "quuxPreDestroyTestInstance", "bazPreDestroyTestInstance", "barPreDestroyTestInstance", "fooPreDestroyTestInstance" ); // @formatter:on } @Test void preDestroyTestInstanceMethodThrowsAnException() { // @formatter:off assertPostProcessorAndPreDestroyCallbacks(ExceptionInTestInstancePreDestroyCallbackTestCase.class, 0, "fooPostProcessTestInstance", "test", "exceptionThrowingTestInstancePreDestroyCallback" ); // @formatter:on } @Test void postProcessTestInstanceMethodThrowsAnException() { assertPostProcessorAndPreDestroyCallbacks(ExceptionInTestInstancePostProcessorTestCase.class, 0, "exceptionThrowingTestInstancePostProcessor", "exceptionPreDestroyTestInstance"); } @Test void testClassConstructorThrowsAnException() { assertPostProcessorAndPreDestroyCallbacks(ExceptionInTestClassConstructorTestCase.class, 0, "exceptionThrowingConstructor"); } private void assertPostProcessorAndPreDestroyCallbacks(Class testClass, String... expectedCalls) { assertPostProcessorAndPreDestroyCallbacks(testClass, 1, expectedCalls); } private void assertPostProcessorAndPreDestroyCallbacks(Class testClass, int testsSuccessful, String... expectedCalls) { callSequence.clear(); executeTestsForClass(testClass).testEvents()// .assertStatistics(stats -> stats.started(1).succeeded(testsSuccessful)); assertEquals(asList(expectedCalls), callSequence, () -> "wrong call sequence for " + testClass.getName()); } // ------------------------------------------------------------------------- // Must NOT be private; otherwise, the @Test method gets discovered but never executed. @ExtendWith({ FooTestInstanceCallbacks.class, BarTestInstanceCallbacks.class }) static class TopLevelTestCase { @Test void test() { callSequence.add("test-1"); } } // Must NOT be private; otherwise, the @Test method gets discovered but never executed. @ExtendWith(BazTestInstanceCallbacks.class) static class SecondLevelTestCase extends TopLevelTestCase { @Test @Override void test() { callSequence.add("test-2"); } } @ExtendWith(QuuxTestInstanceCallbacks.class) static class ThirdLevelTestCase extends SecondLevelTestCase { @Test @Override void test() { callSequence.add("test-3"); } } @ExtendWith(ExceptionThrowingTestInstancePreDestroyCallback.class) static class ExceptionInTestInstancePreDestroyCallbackTestCase { @Test void test() { callSequence.add("test"); } } @ExtendWith(ExceptionThrowingTestInstancePostProcessor.class) static class ExceptionInTestInstancePostProcessorTestCase { @Test void test() { callSequence.add("test"); } } @ExtendWith(FooTestInstanceCallbacks.class) static class ExceptionInTestClassConstructorTestCase { ExceptionInTestClassConstructorTestCase() { callSequence.add("exceptionThrowingConstructor"); throw new RuntimeException("in constructor"); } @Test void test() { callSequence.add("test"); } } // ------------------------------------------------------------------------- static class FooTestInstanceCallbacks extends AbstractTestInstanceCallbacks { protected FooTestInstanceCallbacks() { super("foo"); } } static class BarTestInstanceCallbacks extends AbstractTestInstanceCallbacks { protected BarTestInstanceCallbacks() { super("bar"); } } static class BazTestInstanceCallbacks extends AbstractTestInstanceCallbacks { protected BazTestInstanceCallbacks() { super("baz"); } } static class QuuxTestInstanceCallbacks extends AbstractTestInstanceCallbacks { protected QuuxTestInstanceCallbacks() { super("quux"); } } static class ExceptionThrowingTestInstancePreDestroyCallback extends AbstractTestInstanceCallbacks { protected ExceptionThrowingTestInstancePreDestroyCallback() { super("foo"); } @Override public void preDestroyTestInstance(ExtensionContext context) { callSequence.add("exceptionThrowingTestInstancePreDestroyCallback"); throw new EnigmaException("preDestroyTestInstance"); } } static class ExceptionThrowingTestInstancePostProcessor extends AbstractTestInstanceCallbacks { protected ExceptionThrowingTestInstancePostProcessor() { super("exception"); } @Override public void postProcessTestInstance(Object testInstance, ExtensionContext context) { callSequence.add("exceptionThrowingTestInstancePostProcessor"); throw new EnigmaException("postProcessTestInstance"); } } private static abstract class AbstractTestInstanceCallbacks implements TestInstancePostProcessor, TestInstancePreDestroyCallback { private final String name; AbstractTestInstanceCallbacks(String name) { this.name = name; } @Override public void postProcessTestInstance(Object testInstance, ExtensionContext context) { callSequence.add(name + "PostProcessTestInstance"); } @Override public void preDestroyTestInstance(ExtensionContext context) { callSequence.add(name + "PreDestroyTestInstance"); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInstancePostProcessorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.TEST_METHOD; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestInstancePostProcessor; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; /** * Integration tests that verify support for {@link TestInstancePostProcessor}. * * @since 5.0 */ class TestInstancePostProcessorTests extends AbstractJupiterTestEngineTests { private static final List callSequence = new ArrayList<>(); @BeforeEach void resetCallSequence() { callSequence.clear(); } @Test void instancePostProcessorsInNestedClasses() { executeTestsForClass(OuterTestCase.class).testEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); // @formatter:off assertThat(callSequence).containsExactly( // OuterTestCase "foo:OuterTestCase", "legacy:OuterTestCase", "beforeOuterMethod", "testOuter", "close:foo:OuterTestCase", // InnerTestCase "foo:OuterTestCase", "legacy:OuterTestCase", "foo:InnerTestCase", "legacy:InnerTestCase", "bar:InnerTestCase", "beforeOuterMethod", "beforeInnerMethod", "testInner", "close:bar:InnerTestCase", "close:foo:InnerTestCase", "close:foo:OuterTestCase", "close:legacy:InnerTestCase", "close:legacy:OuterTestCase", "close:legacy:OuterTestCase" ); // @formatter:on } @Test void testSpecificTestInstancePostProcessorIsCalled() { executeTestsForClass(TestCaseWithTestSpecificTestInstancePostProcessor.class).testEvents()// .assertStatistics(stats -> stats.started(2).succeeded(2)); // @formatter:off assertThat(callSequence).containsExactly( "foo:TestCaseWithTestSpecificTestInstancePostProcessor", "legacy:TestCaseWithTestSpecificTestInstancePostProcessor", "beforeEachMethod", "test1", "close:foo:TestCaseWithTestSpecificTestInstancePostProcessor", "beforeEachMethod", "test2", "close:legacy:TestCaseWithTestSpecificTestInstancePostProcessor" ); // @formatter:on } // ------------------------------------------------------------------- @ExtendWith(FooInstancePostProcessor.class) @ExtendWith(LegacyInstancePostProcessor.class) static class OuterTestCase implements Named { private final Map outerNames = new HashMap<>(); @Override public void setName(String source, String name) { outerNames.put(source, name); } @BeforeEach void beforeOuterMethod() { callSequence.add("beforeOuterMethod"); } @Test void testOuter() { assertEquals( Map.of("foo", OuterTestCase.class.getSimpleName(), "legacy", OuterTestCase.class.getSimpleName()), outerNames); callSequence.add("testOuter"); } @Nested @ExtendWith(BarInstancePostProcessor.class) class InnerTestCase implements Named { private final Map innerNames = new HashMap<>(); @Override public void setName(String source, String name) { innerNames.put(source, name); } @BeforeEach void beforeInnerMethod() { callSequence.add("beforeInnerMethod"); } @Test void testInner() { assertEquals( Map.of("foo", InnerTestCase.class.getSimpleName(), "legacy", OuterTestCase.class.getSimpleName()), outerNames); assertEquals(Map.of("foo", InnerTestCase.class.getSimpleName(), "bar", InnerTestCase.class.getSimpleName(), "legacy", InnerTestCase.class.getSimpleName()), innerNames); callSequence.add("testInner"); } } } static class TestCaseWithTestSpecificTestInstancePostProcessor implements Named { private final Map names = new HashMap<>(); @Override public void setName(String source, String name) { names.put(source, name); } @BeforeEach void beforeEachMethod() { callSequence.add("beforeEachMethod"); } @ExtendWith(FooInstancePostProcessor.class) @ExtendWith(LegacyInstancePostProcessor.class) @Test void test1() { callSequence.add("test1"); assertEquals(Map.of("foo", getClass().getSimpleName(), "legacy", getClass().getSimpleName()), names); } @Test void test2() { callSequence.add("test2"); assertEquals(Map.of(), names); } } static abstract class AbstractInstancePostProcessor implements TestInstancePostProcessor { private final String name; AbstractInstancePostProcessor(String name) { this.name = name; } @Override public void postProcessTestInstance(Object testInstance, ExtensionContext context) { if (testInstance instanceof Named named) { named.setName(name, context.getRequiredTestClass().getSimpleName()); } String instanceType = testInstance.getClass().getSimpleName(); callSequence.add(name + ":" + instanceType); context.getStore(ExtensionContext.Namespace.create(this)).put(new Object(), (AutoCloseable) () -> callSequence.add("close:" + name + ":" + instanceType)); } } static class FooInstancePostProcessor extends AbstractInstancePostProcessor { FooInstancePostProcessor() { super("foo"); } @Override public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { return TEST_METHOD; } } static class BarInstancePostProcessor extends AbstractInstancePostProcessor { BarInstancePostProcessor() { super("bar"); } @Override public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { return TEST_METHOD; } } static class LegacyInstancePostProcessor extends AbstractInstancePostProcessor { LegacyInstancePostProcessor() { super("legacy"); } } private interface Named { void setName(String source, String name); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreConstructCallbackTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.TEST_METHOD; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; 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.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.TestInstanceFactory; import org.junit.jupiter.api.extension.TestInstanceFactoryContext; import org.junit.jupiter.api.extension.TestInstancePreConstructCallback; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; /** * Integration tests that verify support for {@link TestInstancePreConstructCallback}. * * @since 5.9 */ class TestInstancePreConstructCallbackTests extends AbstractJupiterTestEngineTests { private static final List callSequence = new ArrayList<>(); @BeforeEach void resetCallSequence() { callSequence.clear(); } @Test void instancePreConstruct() { executeTestsForClass(InstancePreConstructTestCase.class).testEvents()// .assertStatistics(stats -> stats.started(2).succeeded(2)); // @formatter:off assertThat(callSequence).containsExactly( "beforeAll", "PreConstructCallback: name=foo, testClass=InstancePreConstructTestCase, outerInstance: null", "constructor", "beforeEach", "test1", "afterEach", "close: name=foo, testClass=InstancePreConstructTestCase", "PreConstructCallback: name=foo, testClass=InstancePreConstructTestCase, outerInstance: null", "constructor", "beforeEach", "test2", "afterEach", "close: name=foo, testClass=InstancePreConstructTestCase", "afterAll" ); // @formatter:on } @Test void factoryPreConstruct() { executeTestsForClass(FactoryPreConstructTestCase.class).testEvents()// .assertStatistics(stats -> stats.started(2).succeeded(2)); // @formatter:off assertThat(callSequence).containsExactly( "beforeAll", "PreConstructCallback: name=foo, testClass=FactoryPreConstructTestCase, outerInstance: null", "testInstanceFactory", "constructor", "beforeEach", "test1", "afterEach", "close: name=foo, testClass=FactoryPreConstructTestCase", "PreConstructCallback: name=foo, testClass=FactoryPreConstructTestCase, outerInstance: null", "testInstanceFactory", "constructor", "beforeEach", "test2", "afterEach", "close: name=foo, testClass=FactoryPreConstructTestCase", "afterAll" ); // @formatter:on } @Test void preConstructInNested() { executeTestsForClass(PreConstructInNestedTestCase.class).testEvents()// .assertStatistics(stats -> stats.started(3).succeeded(3)); // @formatter:off assertThat(callSequence).containsExactly( "beforeAll", "PreConstructCallback: name=foo, testClass=PreConstructInNestedTestCase, outerInstance: null", "constructor", "beforeEach", "outerTest1", "afterEach", "close: name=foo, testClass=PreConstructInNestedTestCase", "PreConstructCallback: name=foo, testClass=PreConstructInNestedTestCase, outerInstance: null", "constructor", "beforeEach", "outerTest2", "afterEach", "close: name=foo, testClass=PreConstructInNestedTestCase", "PreConstructCallback: name=foo, testClass=PreConstructInNestedTestCase, outerInstance: null", "constructor", "PreConstructCallback: name=foo, testClass=InnerTestCase, outerInstance: #3", "PreConstructCallback: name=bar, testClass=InnerTestCase, outerInstance: #3", "PreConstructCallback: name=baz, testClass=InnerTestCase, outerInstance: #3", "constructorInner", "beforeEach", "beforeEachInner", "innerTest1", "afterEachInner", "afterEach", "close: name=baz, testClass=InnerTestCase", "close: name=bar, testClass=InnerTestCase", "close: name=foo, testClass=InnerTestCase", "close: name=foo, testClass=PreConstructInNestedTestCase", "afterAll" ); // @formatter:on } @Test void preConstructOnMethod() { executeTestsForClass(PreConstructOnMethod.class).testEvents()// .assertStatistics(stats -> stats.started(2).succeeded(2)); // @formatter:off assertThat(callSequence).containsExactly( "PreConstructCallback: name=foo, testClass=PreConstructOnMethod, outerInstance: null", "constructor", "beforeEach", "test1", "afterEach", "close: name=foo, testClass=PreConstructOnMethod", "constructor", "beforeEach", "test2", "afterEach" ); // @formatter:on } @Test void preConstructWithClassLifecycle() { executeTestsForClass(PreConstructWithClassLifecycle.class).testEvents()// .assertStatistics(stats -> stats.started(2).succeeded(2)); // @formatter:off assertThat(callSequence).containsExactly( "PreConstructCallback: name=foo, testClass=PreConstructWithClassLifecycle, outerInstance: null", "PreConstructCallback: name=bar, testClass=PreConstructWithClassLifecycle, outerInstance: null", "constructor", "beforeEach", "test1", "beforeEach", "test2", "close: name=bar, testClass=PreConstructWithClassLifecycle", "close: name=foo, testClass=PreConstructWithClassLifecycle" ); // @formatter:on } @Test void legacyPreConstruct() { executeTestsForClass(LegacyPreConstructTestCase.class).testEvents()// .assertStatistics(stats -> stats.started(3).succeeded(3)); // @formatter:off assertThat(callSequence).containsExactly( "beforeAll", "PreConstructCallback: name=foo, testClass=LegacyPreConstructTestCase, outerInstance: null", "PreConstructCallback: name=legacy, testClass=LegacyPreConstructTestCase, outerInstance: null", "constructor", "beforeEach", "outerTest1", "afterEach", "close: name=foo, testClass=LegacyPreConstructTestCase", "PreConstructCallback: name=foo, testClass=LegacyPreConstructTestCase, outerInstance: null", "PreConstructCallback: name=legacy, testClass=LegacyPreConstructTestCase, outerInstance: null", "constructor", "beforeEach", "outerTest2", "afterEach", "close: name=foo, testClass=LegacyPreConstructTestCase", "PreConstructCallback: name=foo, testClass=LegacyPreConstructTestCase, outerInstance: null", "PreConstructCallback: name=legacy, testClass=LegacyPreConstructTestCase, outerInstance: null", "constructor", "PreConstructCallback: name=foo, testClass=InnerTestCase, outerInstance: LegacyPreConstructTestCase", "PreConstructCallback: name=legacy, testClass=InnerTestCase, outerInstance: LegacyPreConstructTestCase", "constructorInner", "beforeEach", "beforeEachInner", "innerTest1", "afterEachInner", "afterEach", "close: name=foo, testClass=InnerTestCase", "close: name=foo, testClass=LegacyPreConstructTestCase", "close: name=legacy, testClass=InnerTestCase", "afterAll", "close: name=legacy, testClass=LegacyPreConstructTestCase", "close: name=legacy, testClass=LegacyPreConstructTestCase", "close: name=legacy, testClass=LegacyPreConstructTestCase" ); // @formatter:on } private abstract static class CallSequenceRecordingTestCase { protected static void record(String event) { callSequence.add(event); } } @ExtendWith(InstancePreConstructCallbackRecordingFoo.class) static class InstancePreConstructTestCase extends CallSequenceRecordingTestCase { InstancePreConstructTestCase() { record("constructor"); } @BeforeAll static void beforeAll() { record("beforeAll"); } @BeforeEach void beforeEach() { record("beforeEach"); } @Test void test1() { record("test1"); } @Test void test2() { record("test2"); } @AfterEach void afterEach() { record("afterEach"); } @AfterAll static void afterAll() { record("afterAll"); } } @ExtendWith(InstancePreConstructCallbackRecordingFoo.class) static class FactoryPreConstructTestCase extends CallSequenceRecordingTestCase { @RegisterExtension static final TestInstanceFactory factory = (factoryContext, extensionContext) -> { record("testInstanceFactory"); return new FactoryPreConstructTestCase(); }; FactoryPreConstructTestCase() { record("constructor"); } @BeforeAll static void beforeAll() { record("beforeAll"); } @BeforeEach void beforeEach() { record("beforeEach"); } @Test void test1() { record("test1"); } @Test void test2() { record("test2"); } @AfterEach void afterEach() { record("afterEach"); } @AfterAll static void afterAll() { record("afterAll"); } } @ExtendWith(InstancePreConstructCallbackRecordingFoo.class) static class PreConstructInNestedTestCase extends CallSequenceRecordingTestCase { static AtomicInteger instanceCounter = new AtomicInteger(); private final String instanceId; PreConstructInNestedTestCase() { record("constructor"); instanceId = "#" + instanceCounter.incrementAndGet(); } @BeforeAll static void beforeAll() { instanceCounter.set(0); record("beforeAll"); } @BeforeEach void beforeEach() { record("beforeEach"); } @Test void outerTest1() { record("outerTest1"); } @Test void outerTest2() { record("outerTest2"); } @AfterEach void afterEach() { record("afterEach"); } @AfterAll static void afterAll() { record("afterAll"); } @Override public String toString() { return instanceId; } @ExtendWith(InstancePreConstructCallbackRecordingBar.class) abstract class InnerParent extends CallSequenceRecordingTestCase { } @Nested @ExtendWith(InstancePreConstructCallbackRecordingBaz.class) class InnerTestCase extends InnerParent { InnerTestCase() { record("constructorInner"); } @BeforeEach void beforeEachInner() { record("beforeEachInner"); } @Test void innerTest1() { record("innerTest1"); } @AfterEach void afterEachInner() { record("afterEachInner"); } } } @SuppressWarnings("NewClassNamingConvention") static class PreConstructOnMethod extends CallSequenceRecordingTestCase { PreConstructOnMethod() { record("constructor"); } @BeforeEach void beforeEach() { record("beforeEach"); } @ExtendWith(InstancePreConstructCallbackRecordingFoo.class) @Test void test1() { record("test1"); } @Test void test2() { record("test2"); } @AfterEach void afterEach() { record("afterEach"); } } @SuppressWarnings("NewClassNamingConvention") @TestInstance(TestInstance.Lifecycle.PER_CLASS) @ExtendWith(InstancePreConstructCallbackRecordingFoo.class) @ExtendWith(InstancePreConstructCallbackRecordingBar.class) static class PreConstructWithClassLifecycle extends CallSequenceRecordingTestCase { PreConstructWithClassLifecycle() { record("constructor"); } @BeforeEach void beforeEach() { record("beforeEach"); } @Test void test1() { callSequence.add("test1"); } @Test void test2() { callSequence.add("test2"); } } @ExtendWith(InstancePreConstructCallbackRecordingFoo.class) @ExtendWith(InstancePreConstructCallbackRecordingLegacy.class) static class LegacyPreConstructTestCase extends CallSequenceRecordingTestCase { LegacyPreConstructTestCase() { record("constructor"); } @BeforeAll static void beforeAll() { record("beforeAll"); } @BeforeEach void beforeEach() { record("beforeEach"); } @Test void outerTest1() { record("outerTest1"); } @Test void outerTest2() { record("outerTest2"); } @AfterEach void afterEach() { record("afterEach"); } @AfterAll static void afterAll() { record("afterAll"); } @Override public String toString() { return "LegacyPreConstructTestCase"; } @Nested class InnerTestCase extends CallSequenceRecordingTestCase { InnerTestCase() { record("constructorInner"); } @BeforeEach void beforeEachInner() { record("beforeEachInner"); } @Test void innerTest1() { record("innerTest1"); } @AfterEach void afterEachInner() { record("afterEachInner"); } } } static abstract class AbstractTestInstancePreConstructCallback implements TestInstancePreConstructCallback { private final String name; AbstractTestInstancePreConstructCallback(String name) { this.name = name; } @Override public void preConstructTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext context) { assertThat(context.getTestInstance()).isNotPresent(); assertThat(context.getTestClass()).isPresent(); if (name.equals("legacy")) { assertThat(factoryContext.getTestClass()).isSameAs(context.getTestClass().get()); } else if (context.getTestInstanceLifecycle().orElse(null) != TestInstance.Lifecycle.PER_CLASS) { assertThat(context.getTestMethod()).isPresent(); } else { assertThat(context.getTestMethod()).isEmpty(); } String testClass = factoryContext.getTestClass().getSimpleName(); callSequence.add("PreConstructCallback: name=" + name + ", testClass=" + testClass + ", outerInstance: " + factoryContext.getOuterInstance().orElse(null)); context.getStore(ExtensionContext.Namespace.create(this)).put(new Object(), (AutoCloseable) () -> callSequence.add("close: name=" + name + ", testClass=" + testClass)); } } static class InstancePreConstructCallbackRecordingFoo extends AbstractTestInstancePreConstructCallback { InstancePreConstructCallbackRecordingFoo() { super("foo"); } @Override public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { return TEST_METHOD; } } static class InstancePreConstructCallbackRecordingBar extends AbstractTestInstancePreConstructCallback { InstancePreConstructCallbackRecordingBar() { super("bar"); } @Override public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { return TEST_METHOD; } } static class InstancePreConstructCallbackRecordingBaz extends AbstractTestInstancePreConstructCallback { InstancePreConstructCallbackRecordingBaz() { super("baz"); } @Override public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { return TEST_METHOD; } } static class InstancePreConstructCallbackRecordingLegacy extends AbstractTestInstancePreConstructCallback { InstancePreConstructCallbackRecordingLegacy() { super("legacy"); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreDestroyCallbackTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestInstancePreDestroyCallback; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; /** * Integration tests that verify support for {@link TestInstancePreDestroyCallback}. * * @since 5.6 */ class TestInstancePreDestroyCallbackTests extends AbstractJupiterTestEngineTests { private static final List callSequence = new ArrayList<>(); @BeforeEach void resetCallSequence() { callSequence.clear(); } @Test void instancePreDestroyCallbacksInNestedClasses() { executeTestsForClass(OuterTestCase.class).testEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); // @formatter:off assertThat(callSequence).containsExactly( // OuterTestCase "beforeOuterMethod", "testOuter", "fooPreDestroyCallbackTestInstance:OuterTestCase", // InnerTestCase "beforeOuterMethod", "beforeInnerMethod", "testInner", "bazPreDestroyCallbackTestInstance:InnerTestCase", "barPreDestroyCallbackTestInstance:InnerTestCase", "fooPreDestroyCallbackTestInstance:InnerTestCase" ); // @formatter:on } @Test void testSpecificTestInstancePreDestroyCallbackIsCalled() { executeTestsForClass(TestCaseWithTestSpecificTestInstancePreDestroyCallback.class).testEvents()// .assertStatistics(stats -> stats.started(1).succeeded(1)); // @formatter:off assertThat(callSequence).containsExactly( "beforeEachMethod", "test", "fooPreDestroyCallbackTestInstance:TestCaseWithTestSpecificTestInstancePreDestroyCallback" ); // @formatter:on } @Test void classLifecyclePreDestroyCallbacks() { executeTestsForClass(PerClassLifecyclePreDestroyCallbacksWithTwoTestMethods.class).testEvents()// .assertStatistics(stats -> stats.started(2).succeeded(2)); // @formatter:off assertThat(callSequence).containsExactly( "beforeEachMethod", "test1", "beforeEachMethod", "test2", "barPreDestroyCallbackTestInstance:PerClassLifecyclePreDestroyCallbacksWithTwoTestMethods", "fooPreDestroyCallbackTestInstance:PerClassLifecyclePreDestroyCallbacksWithTwoTestMethods" ); // @formatter:on } // ------------------------------------------------------------------- private abstract static class Destroyable { boolean destroyed; void setDestroyed() { this.destroyed = true; } } @ExtendWith(FooInstancePreDestroyCallback.class) static class OuterTestCase extends Destroyable { @BeforeEach void beforeOuterMethod() { callSequence.add("beforeOuterMethod"); } @Test void testOuter() { assertFalse(destroyed); callSequence.add("testOuter"); } @Nested @ExtendWith(BarInstancePreDestroyCallback.class) @ExtendWith(BazInstancePreDestroyCallback.class) class InnerTestCase extends Destroyable { @BeforeEach void beforeInnerMethod() { assertFalse(destroyed); callSequence.add("beforeInnerMethod"); } @Test void testInner() { callSequence.add("testInner"); } } } static class TestCaseWithTestSpecificTestInstancePreDestroyCallback extends Destroyable { @BeforeEach void beforeEachMethod() { assertFalse(destroyed); callSequence.add("beforeEachMethod"); } @ExtendWith(FooInstancePreDestroyCallback.class) @Test void test() { callSequence.add("test"); } } @SuppressWarnings("NewClassNamingConvention") @TestInstance(PER_CLASS) @ExtendWith(FooInstancePreDestroyCallback.class) @ExtendWith(BarInstancePreDestroyCallback.class) static class PerClassLifecyclePreDestroyCallbacksWithTwoTestMethods extends Destroyable { @BeforeEach void beforeEachMethod() { callSequence.add("beforeEachMethod"); } @Test void test1() { callSequence.add("test1"); } @Test void test2() { callSequence.add("test2"); } } static abstract class AbstractTestInstancePreDestroyCallback implements TestInstancePreDestroyCallback { private final String name; AbstractTestInstancePreDestroyCallback(String name) { this.name = name; } @Override public void preDestroyTestInstance(ExtensionContext context) { assertThat(context.getTestInstance()).isPresent(); Object testInstance = context.getTestInstance().get(); if (testInstance instanceof Destroyable destroyable) { destroyable.setDestroyed(); } callSequence.add(name + "PreDestroyCallbackTestInstance:" + testInstance.getClass().getSimpleName()); } } static class FooInstancePreDestroyCallback extends AbstractTestInstancePreDestroyCallback { FooInstancePreDestroyCallback() { super("foo"); } } static class BarInstancePreDestroyCallback extends AbstractTestInstancePreDestroyCallback { BarInstancePreDestroyCallback() { super("bar"); } } static class BazInstancePreDestroyCallback extends AbstractTestInstancePreDestroyCallback { BazInstancePreDestroyCallback() { super("baz"); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreDestroyCallbackUtilityMethodTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.reportEntry; import static org.junit.platform.testkit.engine.EventConditions.started; import static org.junit.platform.testkit.engine.EventConditions.test; import java.util.Map; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestInstancePostProcessor; import org.junit.jupiter.api.extension.TestInstancePreDestroyCallback; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; public class TestInstancePreDestroyCallbackUtilityMethodTests extends AbstractJupiterTestEngineTests { @ParameterizedTest(name = "{0}") @ValueSource(classes = { PerMethodLifecycleOnAllLevels.class, PerMethodWithinPerClassLifecycle.class, PerClassWithinPerMethodLifecycle.class, PerClassLifecycleOnAllLevels.class }) void destroysWhatWasPostProcessed(Class testClass) { executeTestsForClass(testClass).allEvents() // .assertStatistics(stats -> stats.reportingEntryPublished(4)) // .assertEventsMatchLooselyInOrder( // reportEntry(Map.of("post-process", testClass.getSimpleName())), reportEntry(Map.of("post-process", "Inner")), // event(test(), started()), // reportEntry(Map.of("pre-destroy", "Inner")), // reportEntry(Map.of("pre-destroy", testClass.getSimpleName())) // ); } @ExtendWith(TestInstanceLifecycleExtension.class) static class PerMethodLifecycleOnAllLevels { @Nested class Inner { @Test void test() { } } } @ExtendWith(TestInstanceLifecycleExtension.class) @TestInstance(PER_CLASS) static class PerMethodWithinPerClassLifecycle { @Nested class Inner { @Test void test() { } } } @ExtendWith(TestInstanceLifecycleExtension.class) static class PerClassWithinPerMethodLifecycle { @Nested @TestInstance(PER_CLASS) class Inner { @Test void test() { } } } @ExtendWith(TestInstanceLifecycleExtension.class) @TestInstance(PER_CLASS) static class PerClassLifecycleOnAllLevels { @Nested @TestInstance(PER_CLASS) class Inner { @Test void test() { } } } private static class TestInstanceLifecycleExtension implements TestInstancePostProcessor, TestInstancePreDestroyCallback { @Override public void postProcessTestInstance(Object testInstance, ExtensionContext context) { context.publishReportEntry("post-process", testInstance.getClass().getSimpleName()); } @Override public void preDestroyTestInstance(ExtensionContext context) { TestInstancePreDestroyCallback.preDestroyTestInstances(context, testInstance -> context.publishReportEntry("pre-destroy", testInstance.getClass().getSimpleName())); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestReporterParameterResolverTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestReporter; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.platform.commons.support.ReflectionSupport; /** * @since 5.0 */ class TestReporterParameterResolverTests { TestReporterParameterResolver resolver = new TestReporterParameterResolver(); @SuppressWarnings("DataFlowIssue") @Test void supports() { Parameter parameter1 = findParameterOfMethod("methodWithTestReporterParameter", TestReporter.class); assertTrue(this.resolver.supportsParameter(parameterContext(parameter1), null)); Parameter parameter2 = findParameterOfMethod("methodWithoutTestReporterParameter", String.class); assertFalse(this.resolver.supportsParameter(parameterContext(parameter2), null)); } @Test void resolve() { Parameter parameter = findParameterOfMethod("methodWithTestReporterParameter", TestReporter.class); TestReporter testReporter = this.resolver.resolveParameter(parameterContext(parameter), mock()); assertNotNull(testReporter); } private Parameter findParameterOfMethod(String methodName, Class... parameterTypes) { Method method = ReflectionSupport.findMethod(Sample.class, methodName, parameterTypes).orElseThrow(); return method.getParameters()[0]; } private static ParameterContext parameterContext(Parameter parameter) { ParameterContext parameterContext = mock(); when(parameterContext.getParameter()).thenReturn(parameter); return parameterContext; } static class Sample { void methodWithTestReporterParameter(TestReporter reporter) { } void methodWithoutTestReporterParameter(String nothing) { } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestWatcherTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static java.util.function.Predicate.not; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assumptions.abort; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.stream.Stream; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.RepeatedTest; 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; import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Namespace; import org.junit.jupiter.api.extension.ExtensionContext.Store; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.TestWatcher; import org.junit.jupiter.api.fixtures.TrackLogRecords; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.testkit.engine.EngineExecutionResults; /** * Integration tests for the {@link TestWatcher} extension API. * * @since 5.4 */ class TestWatcherTests extends AbstractJupiterTestEngineTests { private static final List testWatcherMethodNames = Arrays.stream(TestWatcher.class.getDeclaredMethods()) // .filter(not(Method::isSynthetic)) // .map(Method::getName) // .toList(); @BeforeEach void clearResults() { TrackingTestWatcher.results.clear(); DataRetrievingTestWatcher.results.clear(); } @Test void testWatcherIsInvokedForTestMethodsInTopLevelAndNestedTestClasses() { assertCommonStatistics(executeTestsForClass(TrackingTestWatcherTestMethodsTestCase.class)); assertThat(TrackingTestWatcher.results.keySet()).containsAll(testWatcherMethodNames); TrackingTestWatcher.results.values().forEach(testMethodNames -> assertEquals(2, testMethodNames.size())); } @Test void testWatcherIsInvokedForRepeatedTestMethods() { EngineExecutionResults results = executeTestsForClass(TrackingTestWatcherRepeatedTestMethodsTestCase.class); results.containerEvents().assertStatistics( stats -> stats.skipped(1).started(5).succeeded(5).aborted(0).failed(0)); results.testEvents().assertStatistics( stats -> stats.dynamicallyRegistered(6).skipped(0).started(6).succeeded(2).aborted(2).failed(2)); // Since the @RepeatedTest container is disabled, the individual invocations never occur. assertThat(TrackingTestWatcher.results.keySet()).containsAll(testWatcherMethodNames); // 2 => number of iterations declared in @RepeatedTest(2). TrackingTestWatcher.results.forEach((testWatcherMethod, testMethodNames) -> assertEquals( "testDisabled".equals(testWatcherMethod) ? 1 : 2, testMethodNames.size())); } @Test void testWatcherIsNotInvokedForTestFactoryMethods() { EngineExecutionResults results = executeTestsForClass(TrackingTestWatcherTestFactoryMethodsTestCase.class); results.containerEvents().assertStatistics( stats -> stats.skipped(1).started(5).succeeded(5).aborted(0).failed(0)); results.testEvents().assertStatistics( stats -> stats.dynamicallyRegistered(6).skipped(0).started(6).succeeded(2).aborted(2).failed(2)); // There should be zero results, since the TestWatcher API is not supported for @TestFactory containers. assertThat(TrackingTestWatcher.results).isEmpty(); } @Test void testWatcherExceptionsAreLoggedAndSwallowed(@TrackLogRecords LogRecordListener logRecordListener) { assertCommonStatistics(executeTestsForClass(ExceptionThrowingTestWatcherTestCase.class)); // @formatter:off long exceptionCount = logRecordListener.stream(MethodBasedTestDescriptor.class, Level.WARNING) .map(LogRecord::getThrown) .filter(JUnitException.class::isInstance) .map(throwable -> throwable.getStackTrace()[0].getMethodName()) .filter(testWatcherMethodNames::contains) .count(); // @formatter:on assertEquals(8, exceptionCount, "Thrown exceptions were not logged properly."); } @Test void testWatcherIsInvokedForTestMethodsInTestCaseWithProblematicConstructor() { EngineExecutionResults results = executeTestsForClass(ProblematicConstructorTestCase.class); results.testEvents().assertStatistics(stats -> stats.skipped(0).started(8).succeeded(0).aborted(0).failed(8)); assertThat(TrackingTestWatcher.results.keySet()).containsExactly("testFailed"); assertThat(TrackingTestWatcher.results.get("testFailed")).hasSize(8); } @Test void testWatcherSemanticsWhenRegisteredAtClassLevel() { Class testClass = ClassLevelTestWatcherTestCase.class; assertStatsForAbstractDisabledMethodsTestCase(testClass); // We get "testDisabled" events for the @Test method and the @RepeatedTest container. assertThat(TrackingTestWatcher.results.get("testDisabled")).containsExactly("test", "repeatedTest"); } @Test void testWatcherSemanticsWhenRegisteredAtInstanceLevelWithTestInstanceLifecyclePerClass() { Class testClass = TestInstancePerClassInstanceLevelTestWatcherTestCase.class; assertStatsForAbstractDisabledMethodsTestCase(testClass); // We get "testDisabled" events for the @Test method and the @RepeatedTest container. assertThat(TrackingTestWatcher.results.get("testDisabled")).containsExactly("test", "repeatedTest"); } @Test void testWatcherSemanticsWhenRegisteredAtInstanceLevelWithTestInstanceLifecyclePerMethod() { Class testClass = TestInstancePerMethodInstanceLevelTestWatcherTestCase.class; assertStatsForAbstractDisabledMethodsTestCase(testClass); // Since the TestWatcher is registered at the instance level with test instance // lifecycle per-method semantics, we get a "testDisabled" event only for the @Test // method and NOT for the @RepeatedTest container. assertThat(TrackingTestWatcher.results.get("testDisabled")).containsExactly("test"); } @Test void testWatcherSemanticsWhenRegisteredAtMethodLevel() { Class testClass = MethodLevelTestWatcherTestCase.class; assertStatsForAbstractDisabledMethodsTestCase(testClass); // We get "testDisabled" events for the @Test method and the @RepeatedTest container. assertThat(TrackingTestWatcher.results.get("testDisabled")).containsExactly("test", "repeatedTest"); } @Test void testWatcherCanRetrieveDataFromTheExtensionContextStore() { Class testClass = DataRetrievingTestWatcherTestCase.class; EngineExecutionResults results = executeTestsForClass(testClass); results.testEvents().assertStatistics(stats -> stats.started(1).succeeded(1).failed(0)); assertThat(DataRetrievingTestWatcher.results).containsExactly(entry("key", "enigma")); } private void assertCommonStatistics(EngineExecutionResults results) { results.containerEvents().assertStatistics(stats -> stats.started(3).succeeded(3).failed(0)); results.testEvents().assertStatistics(stats -> stats.skipped(2).started(6).succeeded(2).aborted(2).failed(2)); } private void assertStatsForAbstractDisabledMethodsTestCase(Class testClass) { EngineExecutionResults results = executeTestsForClass(testClass); results.containerEvents().assertStatistics(// stats -> stats.skipped(1).started(2).succeeded(2).aborted(0).failed(0)); results.testEvents().assertStatistics(// stats -> stats.skipped(1).started(0).succeeded(0).aborted(0).failed(0)); assertThat(TrackingTestWatcher.results.keySet()).containsExactly("testDisabled"); } // ------------------------------------------------------------------------- private static abstract class AbstractTestCase { @Test public void successfulTest() { //no-op } @Test public void failedTest() { fail("Must fail"); } @Test public void abortedTest() { abort(); } @Test @Disabled public void skippedTest() { //no-op } @Nested class SecondLevel { @Test public void successfulTest() { //no-op } @Test public void failedTest() { fail("Must fail"); } @Test public void abortedTest() { abort(); } @Test @Disabled public void skippedTest() { //no-op } } } @ExtendWith(TrackingTestWatcher.class) static class TrackingTestWatcherTestMethodsTestCase extends AbstractTestCase { } @ExtendWith(TrackingTestWatcher.class) static class TrackingTestWatcherRepeatedTestMethodsTestCase { @RepeatedTest(2) void successfulTest() { //no-op } @RepeatedTest(2) void failedTest() { fail("Must fail"); } @RepeatedTest(2) void abortedTest() { abort(); } @RepeatedTest(2) @Disabled void skippedTest() { //no-op } } @NullMarked @ExtendWith(TrackingTestWatcher.class) static class TrackingTestWatcherTestFactoryMethodsTestCase { @TestFactory Stream successfulTest() { return Stream.of("A", "B").map(text -> dynamicTest(text, () -> assertTrue(true))); } @TestFactory Stream failedTest() { return Stream.of("A", "B").map(text -> dynamicTest(text, () -> fail("Must fail"))); } @TestFactory Stream abortedTest() { return Stream.of("A", "B").map(text -> dynamicTest(text, Assumptions::abort)); } @TestFactory @Disabled Stream skippedTest() { return Stream.of("A", "B").map(text -> dynamicTest(text, Assertions::fail)); } } @ExtendWith(ExceptionThrowingTestWatcher.class) static class ExceptionThrowingTestWatcherTestCase extends AbstractTestCase { } @ExtendWith(TrackingTestWatcher.class) static class ProblematicConstructorTestCase extends AbstractTestCase { ProblematicConstructorTestCase(Object ignore) { } } @TestMethodOrder(OrderAnnotation.class) private static abstract class AbstractDisabledMethodsTestCase { @Disabled @Test @Order(1) void test() { } @Disabled @RepeatedTest(2) @Order(2) void repeatedTest() { } } static class ClassLevelTestWatcherTestCase extends AbstractDisabledMethodsTestCase { @RegisterExtension static TestWatcher watcher = new TrackingTestWatcher(); } @TestInstance(Lifecycle.PER_CLASS) static class TestInstancePerClassInstanceLevelTestWatcherTestCase extends AbstractDisabledMethodsTestCase { @RegisterExtension TestWatcher watcher = new TrackingTestWatcher(); } @TestInstance(Lifecycle.PER_METHOD) static class TestInstancePerMethodInstanceLevelTestWatcherTestCase extends AbstractDisabledMethodsTestCase { @RegisterExtension TestWatcher watcher = new TrackingTestWatcher(); } static class MethodLevelTestWatcherTestCase extends AbstractDisabledMethodsTestCase { @Override @Disabled @Test @Order(1) @ExtendWith(TrackingTestWatcher.class) void test() { } @Override @Disabled @RepeatedTest(1) @Order(2) @ExtendWith(TrackingTestWatcher.class) void repeatedTest() { } } @NullMarked private static class TrackingTestWatcher implements TestWatcher { private static final Map> results = new HashMap<>(); @Override public void testSuccessful(ExtensionContext context) { trackResult("testSuccessful", context); } @Override public void testAborted(ExtensionContext context, @Nullable Throwable cause) { trackResult("testAborted", context); } @Override public void testFailed(ExtensionContext context, @Nullable Throwable cause) { trackResult("testFailed", context); } @Override public void testDisabled(ExtensionContext context, Optional reason) { trackResult("testDisabled", context); } protected void trackResult(String testWatcherMethod, ExtensionContext context) { String testMethod = context.getRequiredTestMethod().getName(); results.computeIfAbsent(testWatcherMethod, k -> new ArrayList<>()).add(testMethod); } } @NullMarked private static class ExceptionThrowingTestWatcher implements TestWatcher { @Override public void testSuccessful(ExtensionContext context) { throw new JUnitException("Exception in testSuccessful()"); } @Override public void testDisabled(ExtensionContext context, Optional reason) { throw new JUnitException("Exception in testDisabled()"); } @Override public void testAborted(ExtensionContext context, @Nullable Throwable cause) { throw new JUnitException("Exception in testAborted()"); } @Override public void testFailed(ExtensionContext context, @Nullable Throwable cause) { throw new JUnitException("Exception in testFailed()"); } } /** * {@link TestWatcher} that retrieves data from the {@link ExtensionContext.Store}. * @see #3944 */ @NullMarked static class DataRetrievingTestWatcher implements BeforeTestExecutionCallback, TestWatcher { private static final Namespace NAMESPACE = Namespace.create(DataRetrievingTestWatcher.class); private static final String KEY = "key"; private static final Map results = new HashMap<>(); @Override public void beforeTestExecution(ExtensionContext context) throws Exception { getStore(context).put(KEY, "enigma"); } @Override public void testSuccessful(ExtensionContext context) { results.put(KEY, getStore(context).get(KEY, String.class)); } private static Store getStore(ExtensionContext context) { return context.getStore(NAMESPACE); } } @ExtendWith(DataRetrievingTestWatcher.class) static class DataRetrievingTestWatcherTestCase { @Test public void test() { //no-op } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutConfigurationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static java.util.concurrent.TimeUnit.DAYS; import static java.util.concurrent.TimeUnit.HOURS; import static java.util.concurrent.TimeUnit.MICROSECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Constants.DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_TIMEOUT_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME; import static org.junit.jupiter.api.Timeout.TIMEOUT_MODE_PROPERTY_NAME; import static org.junit.jupiter.api.Timeout.ThreadMode.SEPARATE_THREAD; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.Optional; import java.util.function.Function; import java.util.logging.Level; import java.util.logging.LogRecord; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.fixtures.TrackLogRecords; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.commons.util.RuntimeUtils; import org.mockito.stubbing.Answer; /** * @since 5.5 */ class TimeoutConfigurationTests { ExtensionContext extensionContext = mock(); TimeoutConfiguration config = new TimeoutConfiguration(extensionContext); @Test void notDisabledByDefault() { assertThat(config.isTimeoutDisabled()).isFalse(); } @ParameterizedTest @CsvSource(textBlock = """ Enabled, false Disabled, true Disabled_ON_debug, false """) void disabledBasedOnTimeoutMode(String timeoutMode, boolean disabled) { when(extensionContext.getConfigurationParameter(eq(TIMEOUT_MODE_PROPERTY_NAME), any())) // .thenAnswer(callConverter(timeoutMode)); config = new TimeoutConfiguration(extensionContext); assertThat(config.isTimeoutDisabled()) // .isEqualTo("disabled_on_debug".equalsIgnoreCase(timeoutMode) ? RuntimeUtils.isDebugMode() : disabled); } @Test void noTimeoutIfNoPropertiesAreSet() { assertThat(config.getDefaultBeforeAllMethodTimeout()).isEmpty(); assertThat(config.getDefaultBeforeEachMethodTimeout()).isEmpty(); assertThat(config.getDefaultTestMethodTimeout()).isEmpty(); assertThat(config.getDefaultTestTemplateMethodTimeout()).isEmpty(); assertThat(config.getDefaultTestFactoryMethodTimeout()).isEmpty(); assertThat(config.getDefaultAfterEachMethodTimeout()).isEmpty(); assertThat(config.getDefaultAfterAllMethodTimeout()).isEmpty(); assertThat(config.getDefaultTimeoutThreadMode()).isEmpty(); } @Test void defaultTimeoutIsUsedUnlessAMoreSpecificOneIsSet() { when(extensionContext.getConfigurationParameter(DEFAULT_TIMEOUT_PROPERTY_NAME)).thenReturn(Optional.of("42")); assertThat(config.getDefaultBeforeAllMethodTimeout()).contains(new TimeoutDuration(42, SECONDS)); assertThat(config.getDefaultBeforeEachMethodTimeout()).contains(new TimeoutDuration(42, SECONDS)); assertThat(config.getDefaultTestMethodTimeout()).contains(new TimeoutDuration(42, SECONDS)); assertThat(config.getDefaultTestTemplateMethodTimeout()).contains(new TimeoutDuration(42, SECONDS)); assertThat(config.getDefaultTestFactoryMethodTimeout()).contains(new TimeoutDuration(42, SECONDS)); assertThat(config.getDefaultAfterEachMethodTimeout()).contains(new TimeoutDuration(42, SECONDS)); assertThat(config.getDefaultAfterAllMethodTimeout()).contains(new TimeoutDuration(42, SECONDS)); } @Test void defaultCategoryTimeoutIsUsedUnlessAMoreSpecificOneIsSet() { when(extensionContext.getConfigurationParameter(DEFAULT_TIMEOUT_PROPERTY_NAME)).thenReturn(Optional.of("2")); when(extensionContext.getConfigurationParameter(DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( Optional.of("3")); when(extensionContext.getConfigurationParameter(DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( Optional.of("5")); assertThat(config.getDefaultBeforeAllMethodTimeout()).contains(new TimeoutDuration(3, SECONDS)); assertThat(config.getDefaultBeforeEachMethodTimeout()).contains(new TimeoutDuration(3, SECONDS)); assertThat(config.getDefaultTestMethodTimeout()).contains(new TimeoutDuration(5, SECONDS)); assertThat(config.getDefaultTestTemplateMethodTimeout()).contains(new TimeoutDuration(5, SECONDS)); assertThat(config.getDefaultTestFactoryMethodTimeout()).contains(new TimeoutDuration(5, SECONDS)); assertThat(config.getDefaultAfterEachMethodTimeout()).contains(new TimeoutDuration(3, SECONDS)); assertThat(config.getDefaultAfterAllMethodTimeout()).contains(new TimeoutDuration(3, SECONDS)); } @Test void specificTimeoutsAreUsedIfSet() { when(extensionContext.getConfigurationParameter(DEFAULT_TIMEOUT_PROPERTY_NAME)).thenReturn(Optional.of("2")); when(extensionContext.getConfigurationParameter(DEFAULT_LIFECYCLE_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( Optional.of("3")); when(extensionContext.getConfigurationParameter(DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( Optional.of("5")); when(extensionContext.getConfigurationParameter(DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( Optional.of("7ns")); when(extensionContext.getConfigurationParameter(DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( Optional.of("11μs")); when(extensionContext.getConfigurationParameter(DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( Optional.of("13ms")); when(extensionContext.getConfigurationParameter(DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( Optional.of("17s")); when(extensionContext.getConfigurationParameter(DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( Optional.of("19m")); when(extensionContext.getConfigurationParameter(DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( Optional.of("23h")); when(extensionContext.getConfigurationParameter(DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( Optional.of("29d")); assertThat(config.getDefaultBeforeAllMethodTimeout()).contains(new TimeoutDuration(7, NANOSECONDS)); assertThat(config.getDefaultBeforeEachMethodTimeout()).contains(new TimeoutDuration(11, MICROSECONDS)); assertThat(config.getDefaultTestMethodTimeout()).contains(new TimeoutDuration(13, MILLISECONDS)); assertThat(config.getDefaultTestTemplateMethodTimeout()).contains(new TimeoutDuration(17, SECONDS)); assertThat(config.getDefaultTestFactoryMethodTimeout()).contains(new TimeoutDuration(19, MINUTES)); assertThat(config.getDefaultAfterEachMethodTimeout()).contains(new TimeoutDuration(23, HOURS)); assertThat(config.getDefaultAfterAllMethodTimeout()).contains(new TimeoutDuration(29, DAYS)); } @Test void logsInvalidValues(@TrackLogRecords LogRecordListener logRecordListener) { when(extensionContext.getConfigurationParameter(DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( Optional.of("invalid")); assertThat(config.getDefaultTestMethodTimeout()).isEmpty(); assertThat(logRecordListener.stream(Level.WARNING).map(LogRecord::getMessage)) // .containsExactly( "Ignored invalid timeout 'invalid' set via the 'junit.jupiter.execution.timeout.test.method.default' configuration parameter."); } @Test void specificThreadModeIsUsed() { when(extensionContext.getConfigurationParameter(eq(DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME), any())) // .thenAnswer(callConverter("SEPARATE_THREAD")); assertThat(config.getDefaultTimeoutThreadMode()).contains(SEPARATE_THREAD); } @Test void logsInvalidThreadModeValueAndReturnEmpty() { when(extensionContext.getConfigurationParameter(eq(DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME), any())) // .thenAnswer(callConverter("invalid")); assertThatThrownBy(() -> config.getDefaultTimeoutThreadMode()) // .hasMessage( "Invalid timeout thread mode 'INVALID' set via the 'junit.jupiter.execution.timeout.thread.mode.default' configuration parameter."); } @SuppressWarnings("unchecked") private static Answer callConverter(String value) { return invocation -> Optional.ofNullable(invocation.getArgument(1, Function.class).apply(value)); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutDurationParserTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static java.util.concurrent.TimeUnit.DAYS; import static java.util.concurrent.TimeUnit.HOURS; import static java.util.concurrent.TimeUnit.MICROSECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import java.time.format.DateTimeParseException; import java.util.Map; import java.util.stream.Stream; import org.junit.jupiter.api.DynamicNode; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; /** * @since 5.5 */ class TimeoutDurationParserTests { private final TimeoutDurationParser parser = new TimeoutDurationParser(); @Test void parsesNumberWithoutUnitIntoSecondsDurations() { assertEquals(new TimeoutDuration(42, SECONDS), parser.parse("42")); } @TestFactory Stream parsesNumbersWithUnits() { var unitsWithRepresentations = Map.of( // NANOSECONDS, "ns", // MICROSECONDS, "μs", // MILLISECONDS, "ms", // SECONDS, "s", // MINUTES, "m", // HOURS, "h", // DAYS, "d"); return unitsWithRepresentations.entrySet().stream() // .map(entry -> { var unit = entry.getKey(); var plainRepresentation = entry.getValue(); var representations = Stream.of( // plainRepresentation, // " " + plainRepresentation, // plainRepresentation.toUpperCase(), // " " + plainRepresentation.toUpperCase()); return dynamicContainer(unit.name().toLowerCase(), representations.map(representation -> dynamicTest("\"" + representation + "\"", () -> { var expected = new TimeoutDuration(42, unit); var actual = parser.parse("42" + representation); assertEquals(expected, actual); }))); }); } @Test void rejectsNumbersStartingWithZero() { assertThrows(DateTimeParseException.class, () -> parser.parse("01")); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutDurationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; import org.junit.jupiter.api.Test; /** * @since 5.5 */ class TimeoutDurationTests { @Test void formatsDurationNicely() { assertThat(new TimeoutDuration(1, SECONDS)).hasToString("1 second"); assertThat(new TimeoutDuration(2, SECONDS)).hasToString("2 seconds"); } @Test void fulfillsEqualsAndHashCodeContract() { var oneSecond = new TimeoutDuration(1, SECONDS); assertThat(oneSecond) // .isEqualTo(oneSecond) // .isEqualTo(new TimeoutDuration(1, SECONDS)) // .hasSameHashCodeAs(new TimeoutDuration(1, SECONDS)) // .isNotEqualTo("foo") // .isNotEqualTo(new TimeoutDuration(2, SECONDS)) // .isNotEqualTo(new TimeoutDuration(1, MINUTES)); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactoryTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.engine.extension.TimeoutExceptionFactory.create; import java.util.concurrent.TimeoutException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; /** * @since 5.9 */ @DisplayName("TimeoutExceptionFactory") class TimeoutExceptionFactoryTests { private static final TimeoutDuration tenMillisDuration = new TimeoutDuration(10, MILLISECONDS); private static final Exception suppressedException = new Exception("Winke!"); private static final String methodSignature = "test()"; @Test @DisplayName("creates exception with method signature and timeout") void createExceptionWithMethodSignatureTimeout() { TimeoutException exception = create(methodSignature, tenMillisDuration); assertThat(exception) // .hasMessage("test() timed out after 10 milliseconds") // .hasNoSuppressedExceptions(); } @Test @DisplayName("creates exception with method signature, timeout and throwable") void createExceptionWithMethodSignatureTimeoutAndThrowable() { TimeoutException exception = create(methodSignature, tenMillisDuration, suppressedException); assertThat(exception) // .hasMessage("test() timed out after 10 milliseconds") // .hasSuppressedException(suppressedException); } @SuppressWarnings({ "DataFlowIssue", "ThrowableNotThrown" }) @Nested @DisplayName("throws exception when") class ThrowException { @Test @DisplayName("method signature is null") void methodSignatureIsNull() { assertThatThrownBy(() -> create(null, tenMillisDuration, suppressedException)) // .hasMessage("method signature must not be null"); } @Test @DisplayName("method timeout duration is null") void timeoutDurationIsnull() { assertThatThrownBy(() -> create(methodSignature, null, suppressedException)) // .hasMessage("timeout duration must not be null"); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Constants.DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.TIMEOUT_MODE_PROPERTY_NAME; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.junit.jupiter.api.Timeout.ThreadMode.SAME_THREAD; import static org.junit.jupiter.api.Timeout.ThreadMode.SEPARATE_THREAD; import static org.junit.platform.engine.TestExecutionResult.Status.FAILED; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import java.time.Duration; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeoutException; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; 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.DisplayName; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.RuntimeUtils; import org.junit.platform.engine.TestExecutionResult.Status; import org.junit.platform.testkit.engine.EngineExecutionResults; import org.junit.platform.testkit.engine.Events; import org.junit.platform.testkit.engine.Execution; import org.opentest4j.AssertionFailedError; /** * @since 5.5 */ @DisplayName("@Timeout") class TimeoutExtensionTests extends AbstractJupiterTestEngineTests { @Test @DisplayName("is applied on annotated @Test methods") void appliesTimeoutOnAnnotatedTestMethods() { EngineExecutionResults results = executeTests(request() // .selectors(selectMethod(TimeoutAnnotatedTestMethodTestCase.class, "testMethod")) // .configurationParameter(DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // .build()); Execution execution = findExecution(results.testEvents(), "testMethod()"); assertThat(execution.getDuration()) // .isGreaterThanOrEqualTo(Duration.ofMillis(10)) // .isLessThan(Duration.ofSeconds(1)); assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // .isInstanceOf(TimeoutException.class) // .hasMessage("testMethod() timed out after 10 milliseconds"); } @Test @DisplayName("is not applied on annotated @Test methods using timeout mode: disabled") void doesNotApplyTimeoutOnAnnotatedTestMethodsUsingDisabledTimeoutMode() { EngineExecutionResults results = executeTests(request() // .selectors(selectMethod(TimeoutAnnotatedTestMethodTestCase.class, "testMethod")) // .configurationParameter(DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // .configurationParameter(TIMEOUT_MODE_PROPERTY_NAME, "disabled").build()); Execution execution = findExecution(results.testEvents(), "testMethod()"); assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable()) // .isEmpty(); } @Test @DisplayName("is not applied on annotated @Test methods using timeout mode: disabled") void applyTimeoutOnAnnotatedTestMethodsUsingDisabledOnDebugTimeoutMode() { EngineExecutionResults results = executeTests(request() // .selectors(selectMethod(TimeoutAnnotatedTestMethodTestCase.class, "testMethod")) // .configurationParameter(DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // .configurationParameter(TIMEOUT_MODE_PROPERTY_NAME, "disabled_on_debug").build()); Execution execution = findExecution(results.testEvents(), "testMethod()"); assertThat(execution.getDuration()) // .isGreaterThanOrEqualTo(Duration.ofMillis(10)) // // The check to see if debugging is pushing the timer just above 1 second .isLessThan(Duration.ofSeconds(2)); // Should we test if we're debugging? This test will fail if we are debugging. if (RuntimeUtils.isDebugMode()) { assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable()) // .isEmpty(); } else { assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // .isInstanceOf(TimeoutException.class) // .hasMessage("testMethod() timed out after 10 milliseconds"); } } @Test @DisplayName("is applied on annotated @TestTemplate methods") void appliesTimeoutOnAnnotatedTestTemplateMethods() { EngineExecutionResults results = executeTests(request() // .selectors(selectMethod(TimeoutAnnotatedTestMethodTestCase.class, "testTemplateMethod")) // .configurationParameter(DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // .build()); Stream.of("repetition 1", "repetition 2").forEach(displayName -> { Execution execution = findExecution(results.testEvents(), displayName); assertThat(execution.getDuration()) // .isGreaterThanOrEqualTo(Duration.ofMillis(10)) // .isLessThan(Duration.ofSeconds(1)); assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // .isInstanceOf(TimeoutException.class) // .hasMessage("testTemplateMethod() timed out after 10 milliseconds"); }); } @Test @DisplayName("is applied on annotated @TestFactory methods") void appliesTimeoutOnAnnotatedTestFactoryMethods() { EngineExecutionResults results = executeTests(request() // .selectors(selectMethod(TimeoutAnnotatedTestMethodTestCase.class, "testFactoryMethod")) // .configurationParameter(DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // .build()); Execution execution = findExecution(results.containerEvents(), "testFactoryMethod()"); assertThat(execution.getDuration()) // .isGreaterThanOrEqualTo(Duration.ofMillis(10)) // .isLessThan(Duration.ofSeconds(1)); assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // .isInstanceOf(TimeoutException.class) // .hasMessage("testFactoryMethod() timed out after 10 milliseconds"); } @ParameterizedTest(name = "{0}") @ValueSource(classes = { TimeoutAnnotatedClassTestCase.class, InheritedTimeoutAnnotatedClassTestCase.class }) @DisplayName("is applied on testable methods in annotated classes") void appliesTimeoutOnTestableMethodsInAnnotatedClasses(Class testClass) { EngineExecutionResults results = executeTests(request() // .selectors(selectClass(testClass)) // .configurationParameter(DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // .configurationParameter(DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // .configurationParameter(DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // .build()); assertAll(Stream.of("testMethod()", "repetition 1", "repetition 2", "testFactoryMethod()") // .map(displayName -> () -> { Execution execution = findExecution(results.allEvents(), displayName); assertThat(execution.getDuration()) // .isGreaterThanOrEqualTo(Duration.ofMillis(10)) // .isLessThan(Duration.ofSeconds(1)); assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // .isInstanceOf(TimeoutException.class) // .hasMessageEndingWith("timed out after 10000000 nanoseconds"); })); } @Test @DisplayName("fails methods that do not throw InterruptedException") void failsMethodsWithoutInterruptedException() { EngineExecutionResults results = executeTestsForClass(MethodWithoutInterruptedExceptionTestCase.class); Execution execution = findExecution(results.testEvents(), "methodThatDoesNotThrowInterruptedException()"); assertThat(execution.getDuration()) // .isGreaterThanOrEqualTo(Duration.ofMillis(1)) // .isLessThan(Duration.ofSeconds(1)); assertThat(execution.getTerminationInfo().getExecutionResult().getStatus()).isEqualTo(FAILED); assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // .isInstanceOf(TimeoutException.class) // .hasMessage("methodThatDoesNotThrowInterruptedException() timed out after 1 millisecond"); } @Test @DisplayName("is applied on annotated @BeforeAll methods") void appliesTimeoutOnAnnotatedBeforeAllMethods() { EngineExecutionResults results = executeTests(request() // .selectors(selectClass(TimeoutAnnotatedBeforeAllMethodTestCase.class)) // .configurationParameter(DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // .build()); Execution execution = findExecution(results.containerEvents(), TimeoutAnnotatedBeforeAllMethodTestCase.class.getSimpleName()); assertThat(execution.getDuration()) // .isGreaterThanOrEqualTo(Duration.ofMillis(10)) // .isLessThan(Duration.ofSeconds(1)); assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // .isInstanceOf(TimeoutException.class) // .hasMessage("setUp() timed out after 10 milliseconds"); } @Test @DisplayName("is applied on annotated @BeforeEach methods") void appliesTimeoutOnAnnotatedBeforeEachMethods() { EngineExecutionResults results = executeTests(request() // .selectors(selectClass(TimeoutAnnotatedBeforeEachMethodTestCase.class)) // .configurationParameter(DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // .build()); Execution execution = findExecution(results.testEvents(), "testMethod()"); assertThat(execution.getDuration()) // .isGreaterThanOrEqualTo(Duration.ofMillis(10)) // .isLessThan(Duration.ofSeconds(1)); assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // .isInstanceOf(TimeoutException.class) // .hasMessage("setUp() timed out after 10 milliseconds"); } @Test @DisplayName("is applied on annotated @AfterEach methods") void appliesTimeoutOnAnnotatedAfterEachMethods() { EngineExecutionResults results = executeTests(request() // .selectors(selectClass(TimeoutAnnotatedAfterEachMethodTestCase.class)) // .configurationParameter(DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // .build()); Execution execution = findExecution(results.testEvents(), "testMethod()"); assertThat(execution.getDuration()) // .isGreaterThanOrEqualTo(Duration.ofMillis(10)) // .isLessThan(Duration.ofSeconds(1)); assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // .isInstanceOf(TimeoutException.class) // .hasMessage("tearDown() timed out after 10 milliseconds"); } @Test @DisplayName("is applied on annotated @AfterAll methods") void appliesTimeoutOnAnnotatedAfterAllMethods() { EngineExecutionResults results = executeTests(request() // .selectors(selectClass(TimeoutAnnotatedAfterAllMethodTestCase.class)) // .configurationParameter(DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // .build()); Execution execution = findExecution(results.containerEvents(), TimeoutAnnotatedAfterAllMethodTestCase.class.getSimpleName()); assertThat(execution.getDuration()) // .isGreaterThanOrEqualTo(Duration.ofMillis(10)) // .isLessThan(Duration.ofSeconds(1)); assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // .isInstanceOf(TimeoutException.class) // .hasMessage("tearDown() timed out after 10 milliseconds"); } @ParameterizedTest(name = "{0}") @MethodSource @DisplayName("is applied from configuration parameters by default") void appliesDefaultTimeoutsFromConfigurationParameters(String propertyName, String slowMethod) { PlainTestCase.slowMethod = slowMethod; EngineExecutionResults results = executeTests(request() // .selectors(selectClass(PlainTestCase.class)) // .configurationParameter(propertyName, "1ns") // .build()); var failure = results.allEvents().executions().failed() // .map(execution -> execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // .findFirst(); assertThat(failure).containsInstanceOf(TimeoutException.class); assertThat(failure.orElseThrow()).hasMessage(slowMethod + " timed out after 1 nanosecond"); } static Stream appliesDefaultTimeoutsFromConfigurationParameters() { return Stream.of( // Arguments.of(DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME, "beforeAll()"), // Arguments.of(DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME, "beforeEach()"), // Arguments.of(DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME, "test()"), // Arguments.of(DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME, "testTemplate()"), // Arguments.of(DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME, "testFactory()"), // Arguments.of(DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME, "afterEach()"), // Arguments.of(DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME, "afterAll()") // ); } @Test @DisplayName("does not swallow unrecoverable exceptions") void doesNotSwallowUnrecoverableExceptions() { assertThrows(OutOfMemoryError.class, () -> executeTestsForClass(UnrecoverableExceptionTestCase.class)); } @Test @DisplayName("does not affect tests that don't exceed the timeout") void doesNotAffectTestsThatDoNotExceedTimeoutDuration() { executeTestsForClass(NonTimeoutExceedingTestCase.class).allEvents().assertStatistics(stats -> stats.failed(0)); } @Test @DisplayName("includes fully qualified class name if method is not in the test class") void includesClassNameIfMethodIsNotInTestClass() { EngineExecutionResults results = executeTestsForClass(NestedClassWithOuterSetupMethodTestCase.class); Execution execution = findExecution(results.testEvents(), "testMethod()"); assertThat(execution.getDuration()) // .isGreaterThanOrEqualTo(Duration.ofMillis(10)) // .isLessThan(Duration.ofSeconds(1)); assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // .isInstanceOf(TimeoutException.class) // .hasMessageEndingWith( "$NestedClassWithOuterSetupMethodTestCase#setUp() timed out after 10 milliseconds"); } @Test @DisplayName("reports illegal timeout durations") void reportsIllegalTimeoutDurations() { EngineExecutionResults results = executeTestsForClass(IllegalTimeoutDurationTestCase.class); Execution execution = findExecution(results.testEvents(), "testMethod()"); assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // .isInstanceOf(PreconditionViolationException.class) // .hasMessage("timeout duration must be a positive number: 0"); } private static Execution findExecution(Events events, String displayName) { return events.executions()// .filter(execution -> execution.getTestDescriptor().getDisplayName().contains(displayName))// .findFirst() // .orElseThrow(); } @Nested @DisplayName("separate thread") class SeparateThread { @Test @DisplayName("timeout exceeded") void timeoutExceededInSeparateThread() { EngineExecutionResults results = executeTestsForClass(TimeoutExceedingSeparateThreadTestCase.class); Execution execution = findExecution(results.testEvents(), "testMethod()"); Throwable failure = execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow(); assertThat(failure) // .isInstanceOf(TimeoutException.class) // .hasMessage("testMethod() timed out after 100 milliseconds"); assertThat(failure.getCause()) // .hasMessageStartingWith("Execution timed out in ") // .hasStackTraceContaining(TimeoutExceedingSeparateThreadTestCase.class.getName() + ".testMethod"); } @Test @DisplayName("non timeout exceeded") void nonTimeoutExceededInSeparateThread() { executeTestsForClass(NonTimeoutExceedingSeparateThreadTestCase.class).allEvents() // .assertStatistics(stats -> stats.failed(0)); } @Test @DisplayName("does not swallow unrecoverable exceptions") void separateThreadDoesNotSwallowUnrecoverableExceptions() { assertThrows(OutOfMemoryError.class, () -> executeTestsForClass(UnrecoverableExceptionInSeparateThreadTestCase.class)); } @Test @DisplayName("handles invocation exceptions") void separateThreadHandlesInvocationExceptions() { EngineExecutionResults results = executeTests(request() // .selectors(selectMethod(ExceptionInSeparateThreadTestCase.class, "test")) // .build()); Execution execution = findExecution(results.testEvents(), "test()"); assertThat(execution.getDuration()) // .isLessThan(Duration.ofSeconds(5)); assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // .isInstanceOf(RuntimeException.class) // .hasMessage("Oppps!"); } @Test @DisplayName("propagates assertion exceptions") void separateThreadHandlesOpenTestFailedAssertion() { EngineExecutionResults results = executeTestsForClass(FailedAssertionInSeparateThreadTestCase.class); Execution openTestFailure = findExecution(results.testEvents(), "testOpenTestAssertion()"); assertThat(openTestFailure.getDuration()) // .isLessThan(Duration.ofSeconds(5)); assertThat(openTestFailure.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // .isInstanceOf(AssertionFailedError.class); Execution javaLangFailure = findExecution(results.testEvents(), "testJavaLangAssertion()"); assertThat(javaLangFailure.getDuration()) // .isLessThan(Duration.ofSeconds(5)); assertThat(javaLangFailure.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // .isInstanceOf(AssertionError.class); } @Test @DisplayName("when one test is stuck \"forever\" the next tests should not get stuck") void oneThreadStuckForever() { EngineExecutionResults results = executeTestsForClass(OneTestStuckForeverAndTheOthersNotTestCase.class); Execution stuckExecution = findExecution(results.testEvents(), "stuck()"); assertThat(stuckExecution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // .isInstanceOf(TimeoutException.class) // .hasMessage("stuck() timed out after 10 milliseconds"); Execution testZeroExecution = findExecution(results.testEvents(), "testZero()"); assertThat(testZeroExecution.getTerminationInfo().getExecutionResult().getStatus()) // .isEqualTo(Status.SUCCESSFUL); Execution testOneExecution = findExecution(results.testEvents(), "testOne()"); assertThat(testOneExecution.getTerminationInfo().getExecutionResult().getStatus()) // .isEqualTo(Status.SUCCESSFUL); } @Test @DisplayName("mixed same thread and separate thread tests") void mixedSameThreadAndSeparateThreadTests() { EngineExecutionResults results = executeTestsForClass(MixedSameThreadAndSeparateThreadTestCase.class); Execution stuck = findExecution(results.testEvents(), "testZero()"); assertThat(stuck.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // .isInstanceOf(TimeoutException.class) // .hasMessage("testZero() timed out after 10 milliseconds"); Execution testZeroExecution = findExecution(results.testEvents(), "testOne()"); assertThat(testZeroExecution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // .isInstanceOf(TimeoutException.class) // .hasMessage("testOne() timed out after 10 milliseconds"); Execution testOneExecution = findExecution(results.testEvents(), "testTwo()"); assertThat(testOneExecution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // .isInstanceOf(TimeoutException.class) // .hasMessage("testTwo() timed out after 10 milliseconds"); } @Test @DisplayName("one test is stuck \"forever\" in separate thread and other tests in same thread not") void oneThreadStuckForeverAndOtherTestsInSameThread() { EngineExecutionResults results = executeTestsForClass( OneTestStuckForeverAndTheOthersInSameThreadNotTestCase.class); Execution stuckExecution = findExecution(results.testEvents(), "stuck()"); assertThat(stuckExecution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // .isInstanceOf(TimeoutException.class) // .hasMessage("stuck() timed out after 10 milliseconds"); Execution testZeroExecution = findExecution(results.testEvents(), "testZero()"); assertThat(testZeroExecution.getTerminationInfo().getExecutionResult().getStatus()) // .isEqualTo(Status.SUCCESSFUL); Execution testOneExecution = findExecution(results.testEvents(), "testOne()"); assertThat(testOneExecution.getTerminationInfo().getExecutionResult().getStatus()) // .isEqualTo(Status.SUCCESSFUL); } @Test @DisplayName("is not applied on annotated @Test methods using timeout mode: disabled") void doesNotApplyTimeoutOnAnnotatedTestMethodsUsingDisabledTimeoutMode() { EngineExecutionResults results = executeTests(request() // .selectors(selectMethod(TimeoutExceedingSeparateThreadTestCase.class, "testMethod")) // .configurationParameter(TIMEOUT_MODE_PROPERTY_NAME, "disabled").build()); Execution execution = findExecution(results.testEvents(), "testMethod()"); assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable()) // .isEmpty(); } @Nested @DisplayName("on class level") class OnClassLevel { @Test @DisplayName("timeout exceeded") void timeoutExceededInSeparateThreadOnClassLevel() { EngineExecutionResults results = executeTestsForClass(TimeoutExceededOnClassLevelTestCase.class); Execution execution = findExecution(results.testEvents(), "exceptionThrown()"); assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // .isInstanceOf(TimeoutException.class) // .hasMessage("exceptionThrown() timed out after 100 milliseconds"); } @Test @DisplayName("non timeout exceeded") void nonTimeoutExceededInSeparateThreadOnClassLevel() { executeTestsForClass(NonTimeoutExceededOnClassLevelTestCase.class).allEvents() // .assertStatistics(stats -> stats.failed(0)); } } } static class TimeoutAnnotatedTestMethodTestCase { @Test @Timeout(value = 10, unit = MILLISECONDS) void testMethod() throws Exception { Thread.sleep(1000); } @RepeatedTest(2) @Timeout(value = 10, unit = MILLISECONDS) void testTemplateMethod() throws Exception { Thread.sleep(1000); } @TestFactory @Timeout(value = 10, unit = MILLISECONDS) Stream testFactoryMethod() throws Exception { Thread.sleep(1000); return Stream.empty(); } } static class TimeoutAnnotatedBeforeAllMethodTestCase { @BeforeAll @Timeout(value = 10, unit = MILLISECONDS) static void setUp() throws Exception { Thread.sleep(1000); } @Test void testMethod() { // never called } } static class TimeoutAnnotatedBeforeEachMethodTestCase { @BeforeEach @Timeout(value = 10, unit = MILLISECONDS) void setUp() throws Exception { Thread.sleep(1000); } @Test void testMethod() { // never called } } static class TimeoutAnnotatedAfterEachMethodTestCase { @Test void testMethod() { // do nothing } @AfterEach @Timeout(value = 10, unit = MILLISECONDS) void tearDown() throws Exception { Thread.sleep(1000); } } static class TimeoutAnnotatedAfterAllMethodTestCase { @Test void testMethod() { // do nothing } @AfterAll @Timeout(value = 10, unit = MILLISECONDS) static void tearDown() throws Exception { Thread.sleep(1000); } } @Timeout(value = 10_000_000, unit = NANOSECONDS) static class TimeoutAnnotatedClassTestCase { @Nested class NestedClass { @Test void testMethod() throws Exception { Thread.sleep(1000); } @RepeatedTest(2) void testTemplateMethod() throws Exception { Thread.sleep(1000); } @TestFactory Stream testFactoryMethod() throws Exception { Thread.sleep(1000); return Stream.empty(); } } } static class InheritedTimeoutAnnotatedClassTestCase extends TimeoutAnnotatedClassTestCase { } static class MethodWithoutInterruptedExceptionTestCase { @Test @Timeout(value = 1, unit = MILLISECONDS) void methodThatDoesNotThrowInterruptedException() { new EventuallyInterruptibleInvocation().proceed(); } } static class PlainTestCase { @Nullable public static String slowMethod; @BeforeAll static void beforeAll() throws Exception { waitForInterrupt("beforeAll()"); } @BeforeEach void beforeEach() throws Exception { waitForInterrupt("beforeEach()"); } @Test void test() throws Exception { waitForInterrupt("test()"); } @RepeatedTest(2) void testTemplate() throws Exception { waitForInterrupt("testTemplate()"); } @TestFactory Stream testFactory() throws Exception { waitForInterrupt("testFactory()"); return Stream.empty(); } @AfterEach void afterEach() throws Exception { waitForInterrupt("afterEach()"); } @AfterAll static void afterAll() throws Exception { waitForInterrupt("afterAll()"); } private static void waitForInterrupt(String methodName) throws InterruptedException { if (methodName.equals(slowMethod)) { blockUntilInterrupted(); } } } static class UnrecoverableExceptionTestCase { @Test @Timeout(value = 1, unit = NANOSECONDS) void test() { new EventuallyInterruptibleInvocation().proceed(); throw new OutOfMemoryError(); } } @Timeout(10) static class NonTimeoutExceedingTestCase { @Test void testMethod() { } @RepeatedTest(1) void testTemplateMethod() { } @TestFactory Stream testFactoryMethod() { return Stream.of(dynamicTest("dynamicTest", () -> { })); } } static class NestedClassWithOuterSetupMethodTestCase { @Timeout(value = 10, unit = MILLISECONDS) @BeforeEach void setUp() throws Exception { Thread.sleep(1000); } @Nested class NestedClass { @BeforeEach void setUp() { } @Test void testMethod() { } } } static class IllegalTimeoutDurationTestCase { @Test @Timeout(0) void testMethod() { } } static class TimeoutExceedingWithInferredThreadModeTestCase { @Test @Timeout(value = 10, unit = MILLISECONDS) void testMethod() throws InterruptedException { Thread.sleep(1000); } } static class TimeoutExceedingSeparateThreadTestCase { @Test @Timeout(value = 100, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) void testMethod() throws InterruptedException { Thread.sleep(1000); } } static class NonTimeoutExceedingSeparateThreadTestCase { @Test @Timeout(value = 100, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) void testMethod() { } } static class UnrecoverableExceptionInSeparateThreadTestCase { @Test @Timeout(value = 100, unit = SECONDS, threadMode = SEPARATE_THREAD) void test() { throw new OutOfMemoryError(); } } static class ExceptionInSeparateThreadTestCase { @Test @Timeout(value = 5, unit = SECONDS, threadMode = SEPARATE_THREAD) void test() { throw new RuntimeException("Oppps!"); } } static class FailedAssertionInSeparateThreadTestCase { @Test @Timeout(value = 5, unit = SECONDS, threadMode = SEPARATE_THREAD) void testOpenTestAssertion() { throw new AssertionFailedError(); } @Test @Timeout(value = 5, unit = SECONDS, threadMode = SEPARATE_THREAD) void testJavaLangAssertion() { throw new AssertionError(); } } @Timeout(value = 100, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) static class TimeoutExceededOnClassLevelTestCase { @Test void exceptionThrown() throws InterruptedException { Thread.sleep(1000); } } @Timeout(value = 100, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) static class NonTimeoutExceededOnClassLevelTestCase { @Test void test() { } } @TestMethodOrder(OrderAnnotation.class) static class OneTestStuckForeverAndTheOthersNotTestCase { @Test @Order(0) @Timeout(value = 10, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) void stuck() throws InterruptedException { blockUntilInterrupted(); } @Test @Order(1) @Timeout(value = 100, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) void testZero() { } @Test @Order(2) @Timeout(value = 100, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) void testOne() { } } static class MixedSameThreadAndSeparateThreadTestCase { @Test @Timeout(value = 10, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) void testZero() throws InterruptedException { Thread.sleep(1000); } @Test @Timeout(value = 10, unit = MILLISECONDS, threadMode = SAME_THREAD) void testOne() throws InterruptedException { Thread.sleep(1000); } @Test @Timeout(value = 10, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) void testTwo() throws InterruptedException { Thread.sleep(1000); } } @TestMethodOrder(OrderAnnotation.class) static class OneTestStuckForeverAndTheOthersInSameThreadNotTestCase { @Test @Order(0) @Timeout(value = 10, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) void stuck() throws InterruptedException { blockUntilInterrupted(); } @Test @Order(1) @Timeout(value = 10, unit = MILLISECONDS, threadMode = SAME_THREAD) void testZero() { } @Test @Order(2) @Timeout(value = 10, unit = MILLISECONDS, threadMode = SAME_THREAD) void testOne() { } } private static void blockUntilInterrupted() throws InterruptedException { new CountDownLatch(1).await(); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactoryTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.verify; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout.ThreadMode; import org.junit.jupiter.api.extension.DisabledInEclipse; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext.Store; import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; import org.junit.jupiter.engine.execution.NamespaceAwareStore; import org.junit.jupiter.engine.extension.TimeoutInvocationFactory.SingleThreadExecutorResource; import org.junit.jupiter.engine.extension.TimeoutInvocationFactory.TimeoutInvocationParameters; import org.junit.platform.engine.support.store.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; // org.mockito.exceptions.base.MockitoException: Unable to initialize @Spy annotated field 'store'. // Mockito cannot mock this class: class org.junit.jupiter.engine.execution.NamespaceAwareStore. // You are seeing this disclaimer because Mockito is configured to create inlined mocks. // Byte Buddy could not instrument all classes within the mock's type hierarchy. @DisabledInEclipse("Mockito cannot create a spy for NamespaceAwareStore using the inline MockMaker in Eclipse IDE") @DisplayName("TimeoutInvocationFactory") @ExtendWith(MockitoExtension.class) class TimeoutInvocationFactoryTests { @Spy private final Store store = new NamespaceAwareStore(new NamespacedHierarchicalStore<>(null), Namespace.create(TimeoutInvocationFactoryTests.class)); @Mock private Invocation invocation; @Mock private TimeoutDuration timeoutDuration; private TimeoutInvocationFactory timeoutInvocationFactory; private TimeoutInvocationParameters parameters; @BeforeEach void setUp() { parameters = new TimeoutInvocationParameters<>(invocation, timeoutDuration, () -> "description", PreInterruptCallbackInvocation.NOOP); timeoutInvocationFactory = new TimeoutInvocationFactory(store); } @SuppressWarnings("DataFlowIssue") @Test @DisplayName("throws exception when null store is provided on create") void shouldThrowExceptionWhenInstantiatingWithNullStore() { assertThatThrownBy(() -> new TimeoutInvocationFactory(null)) // .hasMessage("store must not be null"); } @SuppressWarnings("DataFlowIssue") @Test @DisplayName("throws exception when null timeout thread mode is provided on create") void shouldThrowExceptionWhenNullTimeoutThreadModeIsProvidedWhenCreate() { assertThatThrownBy(() -> timeoutInvocationFactory.create(null, parameters)) // .hasMessage("thread mode must not be null"); } @SuppressWarnings("DataFlowIssue") @Test @DisplayName("throws exception when null timeout invocation parameters is provided on create") void shouldThrowExceptionWhenNullTimeoutInvocationParametersIsProvidedWhenCreate() { assertThatThrownBy(() -> timeoutInvocationFactory.create(ThreadMode.SAME_THREAD, null)) // .hasMessage("timeout invocation parameters must not be null"); } @SuppressWarnings("resource") @Test @DisplayName("creates timeout invocation for SAME_THREAD thread mode") void shouldCreateTimeoutInvocationForSameThreadTimeoutThreadMode() { var invocation = timeoutInvocationFactory.create(ThreadMode.SAME_THREAD, parameters); assertThat(invocation).isInstanceOf(SameThreadTimeoutInvocation.class); verify(store).computeIfAbsent(SingleThreadExecutorResource.class); } @Test @DisplayName("creates timeout invocation for SEPARATE_THREAD thread mode") void shouldCreateTimeoutInvocationForSeparateThreadTimeoutThreadMode() { var invocation = timeoutInvocationFactory.create(ThreadMode.SEPARATE_THREAD, parameters); assertThat(invocation).isInstanceOf(SeparateThreadTimeoutInvocation.class); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/sub/AlwaysDisabledCondition.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension.sub; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.ExtensionContext; /** * Intentionally in a subpackage in order to properly test deactivation * of conditions based on patterns. In other words, we do not want this * condition declared in the same package as the * {@link org.junit.jupiter.engine.extension.DisabledCondition} * * ExecutionCondition always returns disabled, since we want to test the * deactivation of the condition itself. * * @since 5.7 */ public class AlwaysDisabledCondition implements ExecutionCondition { @Override public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { return ConditionEvaluationResult.disabled("Always Disabled"); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/sub/AnotherAlwaysDisabledCondition.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension.sub; /** * Intentionally in a subpackage in order to properly test deactivation * of conditions based on patterns. In other words, we do not want this * condition declared in the same package as the * {@link org.junit.jupiter.engine.extension.DisabledCondition} * * ExecutionCondition always returns disabled, since we want to test the * deactivation of the condition itself. * * @since 5.7 */ public class AnotherAlwaysDisabledCondition extends AlwaysDisabledCondition { } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/sub/SystemPropertyCondition.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.extension.sub; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.Objects; import java.util.Optional; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; /** * Intentionally in a subpackage in order to properly test deactivation * of conditions based on patterns. In other words, we do not want this * condition declared in the same package as the * {@link org.junit.jupiter.engine.extension.DisabledCondition} * * @since 5.0 */ public class SystemPropertyCondition implements ExecutionCondition { @Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @ExtendWith(SystemPropertyCondition.class) public @interface SystemProperty { String key(); String value(); } @Override public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { Optional optional = findAnnotation(context.getElement(), SystemProperty.class); if (optional.isPresent()) { SystemProperty systemProperty = optional.get(); String key = systemProperty.key(); String expected = systemProperty.value(); String actual = System.getProperty(key); if (!Objects.equals(expected, actual)) { return ConditionEvaluationResult.disabled( "System property [%s] has a value of [%s] instead of [%s]".formatted(key, actual, expected)); } } return ConditionEvaluationResult.enabled("@SystemProperty is not present"); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/subpackage/SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.subpackage; import static org.assertj.core.api.Assertions.assertThat; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestReporter; /** * @since 5.9 */ public class SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase { protected boolean beforeEachInvoked = false; @BeforeEach void beforeEach() { this.beforeEachInvoked = true; } @Test void test(TestInfo testInfo, TestReporter reporter) { reporter.publishEntry("invokedSuper", testInfo.getTestMethod().orElseThrow().toGenericString()); assertThat(this.beforeEachInvoked).isTrue(); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollectorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.support; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import java.net.URL; import java.net.URLClassLoader; import java.util.logging.Level; import java.util.logging.LogRecord; import org.junit.internal.AssumptionViolatedException; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.fixtures.TrackLogRecords; import org.junit.jupiter.api.function.Executable; import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.ReflectionUtils; /** * Unit tests for {@link OpenTest4JAndJUnit4AwareThrowableCollector}. * * @since 5.5.2 */ @TrackLogRecords class OpenTest4JAndJUnit4AwareThrowableCollectorTests { @Test void simulateJUnit4NotInTheClasspath(LogRecordListener listener) throws Throwable { TestClassLoader classLoader = new TestClassLoader(true, false); doWithCustomClassLoader(classLoader, () -> { // Ensure that our custom ClassLoader actually throws a ClassNotFoundException // when attempting to load the AssumptionViolatedException class. assertThrows(ClassNotFoundException.class, () -> ReflectionSupport.tryToLoadClass(AssumptionViolatedException.class.getName()).get()); Class clazz = classLoader.loadClass(OpenTest4JAndJUnit4AwareThrowableCollector.class.getName()); assertNotNull(ReflectionSupport.newInstance(clazz)); // @formatter:off assertThat(listener.stream(Level.FINE).map(LogRecord::getMessage).findFirst().orElse("")) .isEqualTo( "Failed to load class org.junit.internal.AssumptionViolatedException: " + "only supporting org.opentest4j.TestAbortedException for aborted execution."); // @formatter:on }); } @Test void simulateHamcrestNotInTheClasspath(LogRecordListener listener) throws Throwable { TestClassLoader classLoader = new TestClassLoader(false, true); doWithCustomClassLoader(classLoader, () -> { // Ensure that our custom ClassLoader actually throws a NoClassDefFoundError // when attempting to load the AssumptionViolatedException class. assertThrows(NoClassDefFoundError.class, () -> ReflectionUtils.tryToLoadClass(AssumptionViolatedException.class.getName()).get()); Class clazz = classLoader.loadClass(OpenTest4JAndJUnit4AwareThrowableCollector.class.getName()); assertNotNull(ReflectionUtils.newInstance(clazz)); // @formatter:off assertThat(listener.stream(Level.FINE).map(LogRecord::getMessage).findFirst().orElse("")) .isEqualTo( "Failed to load class org.junit.internal.AssumptionViolatedException: " + "only supporting org.opentest4j.TestAbortedException for aborted execution. " + "Note that org.junit.internal.AssumptionViolatedException requires that Hamcrest is on the classpath."); // @formatter:on }); } private void doWithCustomClassLoader(ClassLoader classLoader, Executable executable) throws Throwable { ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); try { // We have to set our custom ClassLoader as the TCCL so that // ReflectionUtils uses it (indirectly via ClassLoaderUtils). Thread.currentThread().setContextClassLoader(classLoader); executable.execute(); } finally { Thread.currentThread().setContextClassLoader(originalClassLoader); } } private static class TestClassLoader extends URLClassLoader { private static final URL[] CLASSPATH_URLS = new URL[] { OpenTest4JAndJUnit4AwareThrowableCollector.class.getProtectionDomain().getCodeSource().getLocation() }; private final boolean simulateJUnit4Missing; private final boolean simulateHamcrestMissing; TestClassLoader(boolean simulateJUnit4Missing, boolean simulateHamcrestMissing) { super(CLASSPATH_URLS, getSystemClassLoader()); this.simulateJUnit4Missing = simulateJUnit4Missing; this.simulateHamcrestMissing = simulateHamcrestMissing; } @Override public Class loadClass(String name) throws ClassNotFoundException { // Load a new instance of the OpenTest4JAndJUnit4AwareThrowableCollector class if (name.equals(OpenTest4JAndJUnit4AwareThrowableCollector.class.getName())) { return findClass(name); } // Simulate that JUnit 4 is not in the classpath when loading AssumptionViolatedException if (this.simulateJUnit4Missing && name.equals(AssumptionViolatedException.class.getName())) { throw new ClassNotFoundException(AssumptionViolatedException.class.getName()); } // Simulate that Hamcrest is not in the classpath when loading AssumptionViolatedException if (this.simulateHamcrestMissing && name.equals(AssumptionViolatedException.class.getName())) { throw new NoClassDefFoundError("org/hamcrest/SelfDescribing"); } // Else return super.loadClass(name); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/JupiterMigrationSupportTestSuite.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.migrationsupport; import org.junit.platform.suite.api.IncludeClassNamePatterns; import org.junit.platform.suite.api.IncludeEngines; import org.junit.platform.suite.api.SelectPackages; import org.junit.platform.suite.api.Suite; /** * Test suite for JUnit Jupiter migration support. * *

Logging Configuration

* *

In order for our log4j2 configuration to be used in an IDE, you must * set the following system property before running any tests — for * example, in Run Configurations in Eclipse. * *

 * -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager
 * 
* * @since 5.0 */ @Suite @SelectPackages("org.junit.jupiter.migrationsupport") @IncludeClassNamePatterns(".*Tests?") @IncludeEngines("junit-jupiter") class JupiterMigrationSupportTestSuite { } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/conditions/IgnoreAnnotationIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.migrationsupport.conditions; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import java.util.ArrayList; import java.util.List; import org.junit.Ignore; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.migrationsupport.EnableJUnit4MigrationSupport; /** * Empirical integration tests for JUnit 4's {@link Ignore @Ignore} support in * JUnit Jupiter, covering the {@link IgnoreCondition} and * {@link EnableJUnit4MigrationSupport @EnableJUnit4MigrationSupport}. * * @since 5.4 * @see IgnoreConditionTests */ @SuppressWarnings("removal") class IgnoreAnnotationIntegrationTests { @Nested @ExtendWith(IgnoreCondition.class) class ExplicitIgnoreConditionRegistration extends BaseNestedTestCase { } @Nested @EnableJUnit4MigrationSupport class ImplicitIgnoreConditionRegistration extends BaseNestedTestCase { } @TestInstance(PER_CLASS) private static abstract class BaseNestedTestCase { private static List tests = new ArrayList<>(); @BeforeAll void clearTracking() { tests.clear(); } @AfterAll void verifyTracking() { assertThat(tests).containsExactly("notIgnored"); } @BeforeEach void track(TestInfo testInfo) { tests.add(testInfo.getTestMethod().get().getName()); } @Test @Ignore void ignored() { fail("This method should have been disabled via @Ignore"); } @Test // @Ignore void notIgnored() { /* no-op */ } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/conditions/IgnoreConditionTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.migrationsupport.conditions; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.junit.platform.testkit.engine.EventConditions.container; import static org.junit.platform.testkit.engine.EventConditions.engine; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; import static org.junit.platform.testkit.engine.EventConditions.skippedWithReason; import static org.junit.platform.testkit.engine.EventConditions.started; import static org.junit.platform.testkit.engine.EventConditions.test; import org.junit.Ignore; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.platform.testkit.engine.EngineExecutionResults; import org.junit.platform.testkit.engine.EngineTestKit; import org.junit.platform.testkit.engine.Events; /** * Integration tests for JUnit 4's {@link Ignore @Ignore} support in JUnit * Jupiter provided by the {@link IgnoreCondition}. * * @since 5.4 * @see IgnoreAnnotationIntegrationTests */ @SuppressWarnings("removal") class IgnoreConditionTests { @Test void ignoredTestClassWithDefaultMessage() { Class testClass = IgnoredClassWithDefaultMessageTestCase.class; // @formatter:off executeTestsForClass(testClass).allEvents().assertEventsMatchExactly( event(engine(), started()), event(container(testClass), skippedWithReason(testClass + " is disabled via @org.junit.Ignore")), event(engine(), finishedSuccessfully()) ); // @formatter:on } @Test void ignoredTestClassWithCustomMessage() { Class testClass = IgnoredClassWithCustomMessageTestCase.class; // @formatter:off executeTestsForClass(testClass).allEvents().assertEventsMatchExactly( event(engine(), started()), event(container(testClass), skippedWithReason("Ignored Class")), event(engine(), finishedSuccessfully()) ); // @formatter:on } @Test void ignoredAndNotIgnoredTestMethods() { EngineExecutionResults executionResults = executeTestsForClass(IgnoredMethodsTestCase.class); Events containers = executionResults.containerEvents(); Events tests = executionResults.testEvents(); // executionResults.allEvents().debug(); // executionResults.allEvents().debug(System.err); // containers.debug(); // tests.debug(System.err); // tests.debug(); // tests.skipped().debug(); // tests.started().debug(); // tests.succeeded().debug(); // executionResults.allEvents().executions().debug(); // containers.executions().debug(); // tests.executions().debug(); executionResults.allEvents().executions().assertThatExecutions().hasSize(5); containers.executions().assertThatExecutions().hasSize(2); tests.executions().assertThatExecutions().hasSize(3); // @formatter:off // tests.debug().assertEventsMatchExactly( tests.assertEventsMatchExactly( event(test("ignoredWithCustomMessage"), skippedWithReason("Ignored Method")), event(test("notIgnored"), started()), event(test("notIgnored"), finishedSuccessfully()), event(test("ignoredWithDefaultMessage"), skippedWithReason( reason -> reason.endsWith("ignoredWithDefaultMessage() is disabled via @org.junit.Ignore"))) ); // @formatter:on } private EngineExecutionResults executeTestsForClass(Class testClass) { return EngineTestKit.execute("junit-jupiter", request().selectors(selectClass(testClass)).build()); } // ------------------------------------------------------------------------- @ExtendWith(IgnoreCondition.class) @Ignore static class IgnoredClassWithDefaultMessageTestCase { @Test void ignoredBecauseClassIsIgnored() { /* no-op */ } } @ExtendWith(IgnoreCondition.class) @Ignore("Ignored Class") static class IgnoredClassWithCustomMessageTestCase { @Test void ignoredBecauseClassIsIgnored() { /* no-op */ } } @SuppressWarnings("JUnitMixedFramework") @ExtendWith(IgnoreCondition.class) static class IgnoredMethodsTestCase { @Test void notIgnored() { /* no-op */ } @Test @Ignore void ignoredWithDefaultMessage() { fail("This method should have been disabled via @Ignore"); } @Test @Ignore("Ignored Method") void ignoredWithCustomMessage() { fail("This method should have been disabled via @Ignore"); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/AbstractTestRuleAdapterTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.migrationsupport.rules; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import org.junit.jupiter.api.Test; import org.junit.jupiter.migrationsupport.rules.adapter.AbstractTestRuleAdapter; import org.junit.jupiter.migrationsupport.rules.member.TestRuleAnnotatedMember; import org.junit.platform.commons.JUnitException; import org.junit.rules.ErrorCollector; import org.junit.rules.TemporaryFolder; import org.junit.rules.TestRule; import org.junit.rules.Verifier; /** * @since 5.0 */ @SuppressWarnings("removal") public class AbstractTestRuleAdapterTests { @Test void constructionWithAssignableArgumentsIsSuccessful() { new TestableTestRuleAdapter(new SimpleRuleAnnotatedMember(new ErrorCollector()), Verifier.class); } @Test void constructionWithUnassignableArgumentsFails() { assertPreconditionViolationFor(() -> new TestableTestRuleAdapter( new SimpleRuleAnnotatedMember(new TemporaryFolder()), Verifier.class)).withMessage( "class org.junit.rules.Verifier is not assignable from class org.junit.rules.TemporaryFolder"); } @Test void exceptionsDuringMethodLookupAreWrappedAndThrown() { AbstractTestRuleAdapter adapter = new AbstractTestRuleAdapter( new SimpleRuleAnnotatedMember(new ErrorCollector()), Verifier.class) { @Override public void before() { super.executeMethod("foo"); } }; JUnitException exception = assertThrows(JUnitException.class, adapter::before); assertEquals(exception.getMessage(), "Failed to find method foo() in class org.junit.rules.ErrorCollector"); } private static class TestableTestRuleAdapter extends AbstractTestRuleAdapter { TestableTestRuleAdapter(TestRuleAnnotatedMember annotatedMember, Class adapteeClass) { super(annotatedMember, adapteeClass); } } private record SimpleRuleAnnotatedMember(TestRule testRule) implements TestRuleAnnotatedMember { @Override public TestRule getTestRule() { return this.testRule; } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupportWithBothRuleTypesTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.migrationsupport.rules; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.migrationsupport.rules.FailAfterAllHelper.fail; import org.jspecify.annotations.Nullable; import org.junit.Rule; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.rules.ExternalResource; import org.junit.rules.Verifier; @SuppressWarnings("removal") @EnableRuleMigrationSupport public class EnableRuleMigrationSupportWithBothRuleTypesTests { private static boolean afterOfRule1WasExecuted = false; private static boolean beforeOfRule2WasExecuted = false; private static boolean afterOfRule2WasExecuted = false; private static int numberOfRule1InstancesCreated = 0; private static int numberOfRule2InstancesCreated = 0; @Rule public Verifier verifier1 = new Verifier() { { numberOfRule1InstancesCreated++; } @Override protected void verify() { afterOfRule1WasExecuted = true; } }; @Rule public ExternalResource getResource2() { return new ExternalResource() { { numberOfRule2InstancesCreated++; } @Nullable private Object instance; @Override protected void before() { instance = this; beforeOfRule2WasExecuted = true; } @Override protected void after() { assertNotNull(instance); assertSame(this, instance); afterOfRule2WasExecuted = true; } }; } @Test void beforeMethodOfBothRule2WasExecuted() { assertTrue(beforeOfRule2WasExecuted); } @AfterAll static void afterMethodsOfBothRulesWereExecuted() { assertEquals(1, numberOfRule1InstancesCreated); assertEquals(1, numberOfRule2InstancesCreated); if (!afterOfRule1WasExecuted) { fail(); } if (!afterOfRule2WasExecuted) { fail(); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/ExpectedExceptionSupportTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.migrationsupport.rules; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.EventConditions.test; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import java.io.IOException; import org.junit.Rule; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.platform.testkit.engine.EngineTestKit; import org.junit.platform.testkit.engine.Events; import org.junit.rules.ExpectedException; /** * Integration tests for {@link ExpectedExceptionSupport}. * * @since 5.0 */ @SuppressWarnings("removal") class ExpectedExceptionSupportTests { @Test void expectedExceptionIsProcessedCorrectly() { Events tests = executeTestsForClass(ExpectedExceptionTestCase.class); tests.assertStatistics(stats -> stats.started(4).succeeded(1).aborted(0).failed(3)); tests.succeeded().assertThatEvents().have( event(test("correctExceptionExpectedThrown"), finishedSuccessfully())); tests.failed().assertThatEvents()// .haveExactly(1, // event(test("noExceptionExpectedButThrown"), // finishedWithFailure(message("no exception expected")))) // .haveExactly(1, // event(test("exceptionExpectedButNotThrown"), // finishedWithFailure(instanceOf(AssertionError.class), // message("Expected test to throw an instance of java.lang.RuntimeException")))) // .haveExactly(1, // event(test("wrongExceptionExpected"), // finishedWithFailure(instanceOf(AssertionError.class), // message(value -> value.contains("Expected: an instance of java.io.IOException"))))); } @Test void expectedExceptionSupportWithoutExpectedExceptionRule() { Class testClass = ExpectedExceptionSupportWithoutExpectedExceptionRuleTestCase.class; Events tests = executeTestsForClass(testClass); tests.assertStatistics(stats -> stats.started(2).succeeded(1).aborted(0).failed(1)); tests.succeeded().assertThatEvents().have(event(test("success"), finishedSuccessfully())); tests.failed().assertThatEvents()// .haveExactly(1, event(test("failure"), finishedWithFailure(message("must fail")))); } private Events executeTestsForClass(Class testClass) { return EngineTestKit.execute("junit-jupiter", request().selectors(selectClass(testClass)).build()).testEvents(); } @ExtendWith(ExpectedExceptionSupport.class) static class ExpectedExceptionTestCase { @SuppressWarnings("deprecation") @Rule public ExpectedException thrown = ExpectedException.none(); @Test void noExceptionExpectedButThrown() { throw new RuntimeException("no exception expected"); } @Test void exceptionExpectedButNotThrown() { thrown.expect(RuntimeException.class); } @Test void wrongExceptionExpected() { thrown.expect(IOException.class); throw new RuntimeException("wrong exception"); } @Test void correctExceptionExpectedThrown() { thrown.expect(RuntimeException.class); throw new RuntimeException("right exception"); } } @ExtendWith(ExpectedExceptionSupport.class) static class ExpectedExceptionSupportWithoutExpectedExceptionRuleTestCase { @Test void success() { /* no-op */ } @Test void failure() { fail("must fail"); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForDifferentDeclaredReturnTypesRulesTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.migrationsupport.rules; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.migrationsupport.rules.FailAfterAllHelper.fail; import org.junit.Rule; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.rules.ExternalResource; import org.junit.rules.TestRule; @SuppressWarnings("removal") @ExtendWith(ExternalResourceSupport.class) class ExternalResourceSupportForDifferentDeclaredReturnTypesRulesTests { private static boolean beforeOfRule1WasExecuted = false; private static boolean beforeOfRule2WasExecuted = false; private static boolean afterOfRule1WasExecuted = false; private static boolean afterOfRule2WasExecuted = false; @Rule public MyExternalResource1 getResource1() { return new MyExternalResource1(); } @Rule public TestRule getResource2() { return new ExternalResource() { @Override protected void before() { beforeOfRule2WasExecuted = true; } @Override protected void after() { afterOfRule2WasExecuted = true; } }; } @Test void beforeMethodsOfBothRulesWereExecuted() { assertTrue(beforeOfRule1WasExecuted); assertTrue(beforeOfRule2WasExecuted); } @AfterAll static void afterMethodsOfBothRulesWereExecuted() { if (!afterOfRule1WasExecuted) { fail(); } if (!afterOfRule2WasExecuted) { fail(); } } private static class MyExternalResource1 extends ExternalResource { @Override protected void before() { beforeOfRule1WasExecuted = true; } @Override protected void after() { afterOfRule1WasExecuted = true; } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMixedMethodAndFieldRulesTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.migrationsupport.rules; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.migrationsupport.rules.FailAfterAllHelper.fail; import java.util.ArrayList; import java.util.List; import org.junit.Rule; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.rules.ExternalResource; @SuppressWarnings("removal") @ExtendWith(ExternalResourceSupport.class) public class ExternalResourceSupportForMixedMethodAndFieldRulesTests { private static List initEvents = new ArrayList<>(); private static List beforeEvents = new ArrayList<>(); private static List afterEvents = new ArrayList<>(); @BeforeAll static void clear() { initEvents.clear(); beforeEvents.clear(); afterEvents.clear(); } @Rule public ExternalResource fieldRule1 = new MyExternalResource("fieldRule1"); @Rule public ExternalResource fieldRule2 = new MyExternalResource("fieldRule2"); @SuppressWarnings("JUnitMalformedDeclaration") @Rule ExternalResource methodRule1() { return new MyExternalResource("methodRule1"); } @SuppressWarnings("JUnitMalformedDeclaration") @Rule ExternalResource methodRule2() { return new MyExternalResource("methodRule2"); } @Test void constructorsAndBeforeEachMethodsOfAllRulesWereExecuted() { assertThat(initEvents).hasSize(4); // the order of fields and methods is not stable, but fields are initialized before methods are called assertThat(initEvents.subList(0, 2)).allMatch(item -> item.startsWith("fieldRule")); assertThat(initEvents.subList(2, 4)).allMatch(item -> item.startsWith("methodRule")); // beforeEach methods of rules from fields are run before those from methods but in reverse order of instantiation assertEquals(asList(initEvents.get(1), initEvents.get(0), initEvents.get(3), initEvents.get(2)), beforeEvents); } @AfterAll static void afterMethodsOfAllRulesWereExecuted() { // beforeEach methods of rules from methods are run before those from fields but in reverse order if (!asList(initEvents.get(2), initEvents.get(3), initEvents.get(0), initEvents.get(1)).equals(afterEvents)) { fail(); } } static class MyExternalResource extends ExternalResource { private final String name; MyExternalResource(String name) { this.name = name; initEvents.add(name); } @Override protected void before() { beforeEvents.add(name); } @Override protected void after() { afterEvents.add(name); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMultipleFieldRulesTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.migrationsupport.rules; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.migrationsupport.rules.FailAfterAllHelper.fail; import org.junit.Rule; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.rules.ExternalResource; @SuppressWarnings("removal") @ExtendWith(ExternalResourceSupport.class) public class ExternalResourceSupportForMultipleFieldRulesTests { private static boolean beforeOfRule1WasExecuted = false; private static boolean beforeOfRule2WasExecuted = false; private static boolean afterOfRule1WasExecuted = false; private static boolean afterOfRule2WasExecuted = false; @Rule public ExternalResource resource1 = new ExternalResource() { @Override protected void before() { beforeOfRule1WasExecuted = true; } @Override protected void after() { afterOfRule1WasExecuted = true; } }; @Rule public ExternalResource resource2 = new ExternalResource() { @Override protected void before() { beforeOfRule2WasExecuted = true; } @Override protected void after() { afterOfRule2WasExecuted = true; } }; @Test void beforeMethodsOfBothRulesWereExecuted() { assertTrue(beforeOfRule1WasExecuted); assertTrue(beforeOfRule2WasExecuted); } @AfterAll static void afterMethodsOfBothRulesWereExecuted() { if (!afterOfRule1WasExecuted) { fail(); } if (!afterOfRule2WasExecuted) { fail(); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMultipleMethodRulesTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.migrationsupport.rules; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.migrationsupport.rules.FailAfterAllHelper.fail; import org.junit.Rule; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.rules.ExternalResource; @SuppressWarnings("removal") @ExtendWith(ExternalResourceSupport.class) public class ExternalResourceSupportForMultipleMethodRulesTests { private static boolean beforeOfRule1WasExecuted = false; private static boolean beforeOfRule2WasExecuted = false; private static boolean afterOfRule1WasExecuted = false; private static boolean afterOfRule2WasExecuted = false; @Rule public ExternalResource getResource1() { return new ExternalResource() { @Override protected void before() { beforeOfRule1WasExecuted = true; } @Override protected void after() { afterOfRule1WasExecuted = true; } }; } @Rule public ExternalResource getResource2() { return new ExternalResource() { @Override protected void before() { beforeOfRule2WasExecuted = true; } @Override protected void after() { afterOfRule2WasExecuted = true; } }; } @Test void beforeMethodsOfBothRulesWereExecuted() { assertTrue(beforeOfRule1WasExecuted); assertTrue(beforeOfRule2WasExecuted); } @AfterAll static void afterMethodsOfBothRulesWereExecuted() { if (!afterOfRule1WasExecuted) { fail(); } if (!afterOfRule2WasExecuted) { fail(); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForTemporaryFolderFieldTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.migrationsupport.rules; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; import java.io.IOException; import org.junit.Rule; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.rules.TemporaryFolder; @SuppressWarnings("removal") @ExtendWith(ExternalResourceSupport.class) public class ExternalResourceSupportForTemporaryFolderFieldTests { private File file; @Rule public TemporaryFolder folder = new TemporaryFolder(); @BeforeEach void setup() throws IOException { this.file = folder.newFile("temp.txt"); } @Test void checkTemporaryFolder() { assertTrue(file.canRead()); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportWithInheritanceTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.migrationsupport.rules; public class ExternalResourceSupportWithInheritanceTests extends ExternalResourceSupportForMixedMethodAndFieldRulesTests { } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceWithoutAdapterTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.migrationsupport.rules; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.Rule; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.rules.TemporaryFolder; public class ExternalResourceWithoutAdapterTests { @Rule public TemporaryFolder folder = new TemporaryFolder(); @BeforeEach void setup() { try { folder.newFile("temp.txt"); } catch (Exception exception) { assertEquals("the temporary folder has not yet been created", exception.getMessage()); } } @Test void checkTemporaryFolder() { // only needed to invoke testing at all } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/FailAfterAllHelper.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.migrationsupport.rules; /** * @since 5.0 */ class FailAfterAllHelper { static void fail() { // hack: use this unrecoverable exception to fail the build, since all others would be swallowed... throw new OutOfMemoryError("a postcondition was violated"); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/LauncherBasedEnableRuleMigrationSupportTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.migrationsupport.rules; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import org.junit.Rule; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.platform.testkit.engine.EngineTestKit; import org.junit.platform.testkit.engine.Events; import org.junit.rules.ErrorCollector; import org.junit.rules.ExternalResource; import org.junit.rules.Verifier; @SuppressWarnings("removal") class LauncherBasedEnableRuleMigrationSupportTests { @Test void enableRuleMigrationSupportAnnotationWorksForBothRuleTypes() { Events tests = executeTestsForClass(EnableRuleMigrationSupportWithBothRuleTypesTestCase.class); tests.assertStatistics(stats -> stats.started(1).succeeded(1).aborted(0).failed(0)); assertTrue(EnableRuleMigrationSupportWithBothRuleTypesTestCase.afterOfRule1WasExecuted, "after of rule 1 executed?"); assertTrue(EnableRuleMigrationSupportWithBothRuleTypesTestCase.beforeOfRule2WasExecuted, "before of rule 2 executed?"); assertTrue(EnableRuleMigrationSupportWithBothRuleTypesTestCase.afterOfRule2WasExecuted, "before of rule 2 executed?"); } @Test void verifierSupportForErrorCollectorFieldFailsTheTest() { Events tests = executeTestsForClass(VerifierSupportForErrorCollectorTestCase.class); tests.assertStatistics(stats -> stats.started(1).succeeded(0).aborted(0).failed(1)); assertTrue(VerifierSupportForErrorCollectorTestCase.survivedBothErrors, "after of rule 1 executed?"); } private Events executeTestsForClass(Class testClass) { return EngineTestKit.execute("junit-jupiter", request().selectors(selectClass(testClass)).build()).testEvents(); } @EnableRuleMigrationSupport static class EnableRuleMigrationSupportWithBothRuleTypesTestCase { static boolean afterOfRule1WasExecuted = false; static boolean beforeOfRule2WasExecuted = false; static boolean afterOfRule2WasExecuted = false; @Rule public Verifier verifier1 = new Verifier() { @Override protected void verify() { afterOfRule1WasExecuted = true; } }; private final ExternalResource resource2 = new ExternalResource() { @Override protected void before() { beforeOfRule2WasExecuted = true; } @Override protected void after() { afterOfRule2WasExecuted = true; } }; @Rule public ExternalResource getResource2() { return resource2; } @Test void beforeMethodOfBothRule2WasExecuted() { assertTrue(beforeOfRule2WasExecuted); } } @ExtendWith(VerifierSupport.class) static class VerifierSupportForErrorCollectorTestCase { static boolean survivedBothErrors = false; @Rule public ErrorCollector collector = new ErrorCollector(); @Test void addingTwoThrowablesToErrorCollectorFailsLate() { collector.addError(new Throwable("first thing went wrong")); collector.addError(new Throwable("second thing went wrong")); survivedBothErrors = true; } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/VerifierSupportForMixedMethodAndFieldRulesTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.migrationsupport.rules; import static org.junit.jupiter.migrationsupport.rules.FailAfterAllHelper.fail; import org.junit.Rule; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.rules.Verifier; @SuppressWarnings("removal") @ExtendWith(VerifierSupport.class) public class VerifierSupportForMixedMethodAndFieldRulesTests { private static boolean afterOfRule1WasExecuted = false; private static boolean afterOfRule2WasExecuted = false; @Rule public Verifier verifier1 = new Verifier() { @Override protected void verify() { afterOfRule1WasExecuted = true; } }; private Verifier verifier2 = new Verifier() { @Override protected void verify() { afterOfRule2WasExecuted = true; } }; @Rule public Verifier getVerifier2() { return verifier2; } @Test void testNothing() { //needed to start the test process at all } @AfterAll static void afterMethodsOfBothRulesWereExecuted() { if (!afterOfRule1WasExecuted) { fail(); } if (!afterOfRule2WasExecuted) { fail(); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/WrongExtendWithForVerifierFieldTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.migrationsupport.rules; import static org.junit.jupiter.migrationsupport.rules.FailAfterAllHelper.fail; import org.junit.Rule; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.rules.Verifier; @SuppressWarnings("removal") @ExtendWith(ExternalResourceSupport.class) public class WrongExtendWithForVerifierFieldTests { private static boolean afterOfRule1WasExecuted = false; @Rule public Verifier verifier1 = new Verifier() { @Override protected void verify() { afterOfRule1WasExecuted = true; } }; @Test void testNothing() { //needed to start the test process at all } @AfterAll static void afterMethodOfRuleWasNotExecuted() { if (afterOfRule1WasExecuted) { fail(); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/WrongExtendWithForVerifierMethodTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.migrationsupport.rules; import static org.junit.jupiter.migrationsupport.rules.FailAfterAllHelper.fail; import org.junit.Rule; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.rules.Verifier; @SuppressWarnings("removal") @ExtendWith(ExternalResourceSupport.class) public class WrongExtendWithForVerifierMethodTests { private static boolean afterOfRule1WasExecuted = false; private Verifier verifier1 = new Verifier() { @Override protected void verify() { afterOfRule1WasExecuted = true; } }; @Rule public Verifier getVerifier1() { return verifier1; } @Test void testNothing() { //needed to start the test process at all } @AfterAll static void afterMethodsOfBothRulesWereExecuted() { if (afterOfRule1WasExecuted) { fail(); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterInfoIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import org.jspecify.annotations.NullMarked; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.BeforeClassTemplateInvocationCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.jupiter.params.provider.ValueSource; /** * @since 1.14 */ class ParameterInfoIntegrationTests extends AbstractJupiterTestEngineTests { @Test void storesParameterInfoInExtensionContextStoreOnDifferentLevels() { var results = executeTestsForClass(TestCase.class); results.allEvents().debug().assertStatistics(stats -> stats.started(7).succeeded(7)); } @ParameterizedClass @ValueSource(ints = 1) @ExtendWith(ParameterInfoConsumingExtension.class) record TestCase(int i) { @Nested @ParameterizedClass @ValueSource(ints = 2) class Inner { @Parameter int j; @ParameterizedTest @ValueSource(ints = 3) void test(int k) { assertEquals(1, i); assertEquals(2, j); assertEquals(3, k); } } } @NullMarked private static class ParameterInfoConsumingExtension implements BeforeClassTemplateInvocationCallback, BeforeEachCallback { @Override public void beforeClassTemplateInvocation(ExtensionContext parameterizedClassInvocationContext) { if (TestCase.Inner.class.equals(parameterizedClassInvocationContext.getRequiredTestClass())) { assertParameterInfo(parameterizedClassInvocationContext, "j", 2); var nestedParameterizedClassContext = parameterizedClassInvocationContext.getParent().orElseThrow(); assertParameterInfo(nestedParameterizedClassContext, "i", 1); parameterizedClassInvocationContext = nestedParameterizedClassContext.getParent().orElseThrow(); } assertParameterInfo(parameterizedClassInvocationContext, "i", 1); var outerParameterizedClassContext = parameterizedClassInvocationContext.getParent().orElseThrow(); assertNull(ParameterInfo.get(outerParameterizedClassContext)); } @Override public void beforeEach(ExtensionContext parameterizedTestInvocationContext) { assertParameterInfo(parameterizedTestInvocationContext, "k", 3); var parameterizedTestContext = parameterizedTestInvocationContext.getParent().orElseThrow(); assertParameterInfo(parameterizedTestContext, "j", 2); var nestedParameterizedClassInvocationContext = parameterizedTestContext.getParent().orElseThrow(); assertParameterInfo(nestedParameterizedClassInvocationContext, "j", 2); var nestedParameterizedClassContext = nestedParameterizedClassInvocationContext.getParent().orElseThrow(); assertParameterInfo(nestedParameterizedClassContext, "i", 1); var outerParameterizedClassInvocationContext = nestedParameterizedClassContext.getParent().orElseThrow(); assertParameterInfo(outerParameterizedClassInvocationContext, "i", 1); var outerParameterizedClassContext = outerParameterizedClassInvocationContext.getParent().orElseThrow(); assertNull(ParameterInfo.get(outerParameterizedClassContext)); } private static void assertParameterInfo(ExtensionContext context, String parameterName, int argumentValue) { var parameterInfo = ParameterInfo.get(context); assertNotNull(parameterInfo); var declaration = parameterInfo.getDeclarations().get(0).orElseThrow(); assertEquals(parameterName, declaration.getParameterName().orElseThrow()); assertEquals(int.class, declaration.getParameterType()); assertEquals(argumentValue, parameterInfo.getArguments().getInteger(0)); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedClassIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import static java.util.Comparator.comparing; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; 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.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.params.ArgumentCountValidationMode.NONE; import static org.junit.jupiter.params.ArgumentCountValidationMode.STRICT; import static org.junit.jupiter.params.ParameterizedInvocationConstants.ARGUMENTS_PLACEHOLDER; import static org.junit.jupiter.params.ParameterizedInvocationConstants.ARGUMENT_SET_NAME_OR_ARGUMENTS_WITH_NAMES_PLACEHOLDER; import static org.junit.jupiter.params.ParameterizedInvocationConstants.DISPLAY_NAME_PLACEHOLDER; import static org.junit.jupiter.params.ParameterizedInvocationConstants.INDEX_PLACEHOLDER; import static org.junit.jupiter.params.provider.Arguments.argumentSet; import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.testkit.engine.EventConditions.container; import static org.junit.platform.testkit.engine.EventConditions.displayName; import static org.junit.platform.testkit.engine.EventConditions.dynamicTestRegistered; import static org.junit.platform.testkit.engine.EventConditions.engine; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.EventConditions.legacyReportingName; import static org.junit.platform.testkit.engine.EventConditions.started; import static org.junit.platform.testkit.engine.EventConditions.test; import static org.junit.platform.testkit.engine.EventConditions.uniqueId; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.suppressed; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; import java.util.stream.Stream; import org.assertj.core.api.Condition; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; 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.Constants; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Named; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestReporter; import org.junit.jupiter.api.extension.AnnotatedElementContext; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.TemplateInvocationValidationException; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.jupiter.engine.descriptor.ClassTemplateInvocationTestDescriptor; import org.junit.jupiter.params.aggregator.AggregateWith; import org.junit.jupiter.params.aggregator.ArgumentsAccessor; import org.junit.jupiter.params.aggregator.ArgumentsAggregationException; import org.junit.jupiter.params.aggregator.SimpleArgumentsAggregator; import org.junit.jupiter.params.converter.ArgumentConversionException; import org.junit.jupiter.params.converter.ArgumentConverter; import org.junit.jupiter.params.converter.ConvertWith; import org.junit.jupiter.params.converter.SimpleArgumentConverter; import org.junit.jupiter.params.converter.TypedArgumentConverter; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.ArgumentsProvider; import org.junit.jupiter.params.provider.ArgumentsSource; import org.junit.jupiter.params.provider.CsvFileSource; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.EmptySource; import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.FieldSource; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.NullAndEmptySource; import org.junit.jupiter.params.provider.NullSource; import org.junit.jupiter.params.provider.ValueSource; import org.junit.jupiter.params.support.FieldContext; import org.junit.jupiter.params.support.ParameterDeclarations; import org.junit.platform.commons.util.StringUtils; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.testkit.engine.EngineExecutionResults; import org.junit.platform.testkit.engine.Event; import org.junit.platform.testkit.engine.EventType; import org.junit.platform.testkit.engine.Events; public class ParameterizedClassIntegrationTests extends AbstractJupiterTestEngineTests { @ParameterizedTest @ValueSource(classes = { ConstructorInjectionTestCase.class, RecordTestCase.class, RecordWithParameterAnnotationOnComponentTestCase.class, FieldInjectionTestCase.class, RecordWithBuiltInConverterTestCase.class, RecordWithRegisteredConversionTestCase.class, FieldInjectionWithRegisteredConversionTestCase.class, RecordWithBuiltInAggregatorTestCase.class, FieldInjectionWithBuiltInAggregatorTestCase.class, RecordWithCustomAggregatorTestCase.class, FieldInjectionWithCustomAggregatorTestCase.class }) void injectsParametersIntoClass(Class classTemplateClass) { var results = executeTestsForClass(classTemplateClass); String parameterNamePrefix = classTemplateClass.getSimpleName().contains("Aggregator") ? "" : "value = "; results.allEvents().assertEventsMatchExactly( // event(engine(), started()), // event(container(classTemplateClass), started()), // event(dynamicTestRegistered("#1"), displayName("[1] %s-1".formatted(parameterNamePrefix))), // event(container("#1"), started()), // event(dynamicTestRegistered("test1")), // event(dynamicTestRegistered("test2")), // event(test("test1"), legacyReportingName("test1()[1]"), started()), // event(test("test1"), finishedSuccessfully()), // event(test("test2"), legacyReportingName("test2()[1]"), started()), // event(test("test2"), finishedSuccessfully()), // event(container("#1"), finishedSuccessfully()), // event(dynamicTestRegistered("#2"), displayName("[2] %s1".formatted(parameterNamePrefix))), // event(container("#2"), started()), // event(dynamicTestRegistered("test1")), // event(dynamicTestRegistered("test2")), // event(test("test1"), legacyReportingName("test1()[2]"), started()), // event(test("test1"), finishedWithFailure(message(it -> it.contains("negative")))), // event(test("test2"), legacyReportingName("test2()[2]"), started()), // event(test("test2"), finishedWithFailure(message(it -> it.contains("negative")))), // event(container("#2"), finishedSuccessfully()), // event(container(classTemplateClass), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); } @ParameterizedTest @ValueSource(classes = { ArgumentConversionPerInvocationConstructorInjectionTestCase.class, ArgumentConversionPerInvocationFieldInjectionTestCase.class }) void argumentConverterIsOnlyCalledOncePerInvocation(Class classTemplateClass) { var results = executeTestsForClass(classTemplateClass); results.allEvents().assertStatistics(stats -> stats.started(5).succeeded(5)); } @Nested class Sources { @ParameterizedTest @ValueSource(classes = { NullAndEmptySourceConstructorInjectionTestCase.class, NullAndEmptySourceConstructorFieldInjectionTestCase.class }) void supportsNullAndEmptySource(Class classTemplateClass) { var results = executeTestsForClass(classTemplateClass); results.allEvents().assertStatistics(stats -> stats.started(6).succeeded(6)); assertThat(invocationDisplayNames(results)) // .containsExactly("[1] value = null", "[2] value = \"\""); } @ParameterizedTest @ValueSource(classes = { CsvFileSourceConstructorInjectionTestCase.class, CsvFileSourceFieldInjectionTestCase.class }) void supportsCsvFileSource(Class classTemplateClass) { var results = executeTestsForClass(classTemplateClass); results.allEvents().assertStatistics(stats -> stats.started(10).succeeded(10)); assertThat(invocationDisplayNames(results)) // .containsExactly("[1] name = \"foo\", value = \"1\"", "[2] name = \"bar\", value = \"2\"", "[3] name = \"baz\", value = \"3\"", "[4] name = \"qux\", value = \"4\""); } @ParameterizedTest @ValueSource(classes = { SingleEnumSourceConstructorInjectionTestCase.class, SingleEnumSourceFieldInjectionTestCase.class }) void supportsSingleEnumSource(Class classTemplateClass) { var results = executeTestsForClass(classTemplateClass); results.allEvents().assertStatistics(stats -> stats.started(4).succeeded(4)); assertThat(invocationDisplayNames(results)) // .containsExactly("[1] value = FOO"); } @ParameterizedTest @ValueSource(classes = { RepeatedEnumSourceConstructorInjectionTestCase.class, RepeatedEnumSourceFieldInjectionTestCase.class }) void supportsRepeatedEnumSource(Class classTemplateClass) { var results = executeTestsForClass(classTemplateClass); results.allEvents().assertStatistics(stats -> stats.started(6).succeeded(6)); assertThat(invocationDisplayNames(results)) // .containsExactly("[1] value = FOO", "[2] value = BAR"); } @ParameterizedTest @ValueSource(classes = { MethodSourceConstructorInjectionTestCase.class, MethodSourceFieldInjectionTestCase.class }) void supportsMethodSource(Class classTemplateClass) { var results = executeTestsForClass(classTemplateClass); results.allEvents().assertStatistics(stats -> stats.started(6).succeeded(6)); assertThat(invocationDisplayNames(results)) // .containsExactly("[1] value = \"foo\"", "[2] value = \"bar\""); } @Test void doesNotSupportDerivingMethodName() { var results = executeTestsForClass(MethodSourceWithoutMethodNameTestCase.class); results.allEvents().failed() // .assertEventsMatchExactly(finishedWithFailure( message("You must specify a method name when using @MethodSource with @ParameterizedClass"))); } @ParameterizedTest @ValueSource(classes = { FieldSourceConstructorInjectionTestCase.class, FieldSourceFieldInjectionTestCase.class }) void supportsFieldSource(Class classTemplateClass) { var results = executeTestsForClass(classTemplateClass); results.allEvents().assertStatistics(stats -> stats.started(6).succeeded(6)); assertThat(invocationDisplayNames(results)) // .containsExactly("[1] value = \"foo\"", "[2] value = \"bar\""); } @Test void doesNotSupportDerivingFieldName() { var results = executeTestsForClass(FieldSourceWithoutFieldNameTestCase.class); results.allEvents().failed() // .assertEventsMatchExactly(finishedWithFailure( message("You must specify a field name when using @FieldSource with @ParameterizedClass"))); } @ParameterizedTest @ValueSource(classes = { ArgumentsSourceConstructorInjectionTestCase.class, ArgumentsSourceFieldInjectionTestCase.class }) void supportsArgumentsSource(Class classTemplateClass) { var results = executeTestsForClass(classTemplateClass); results.allEvents().assertStatistics(stats -> stats.started(6).succeeded(6)); assertThat(invocationDisplayNames(results)) // .containsExactly("[1] value = \"foo\"", "[2] value = \"bar\""); } @Test void failsWhenNoArgumentsSourceIsDeclared() { var results = executeTestsForClass(NoArgumentSourceTestCase.class); results.containerEvents().assertThatEvents() // .haveExactly(1, event(finishedWithFailure(message( "Configuration error: You must configure at least one arguments source for this @ParameterizedClass")))); } @Test void annotationsAreInherited() { var results = executeTestsForClass(ConcreteInheritanceTestCase.class); int numArgumentSets = 13; var numContainers = numArgumentSets * 3; // once for outer invocation, once for nested class, once for inner invocation var numTests = numArgumentSets * 2; // once for outer test, once for inner test results.containerEvents() // .assertStatistics(stats -> stats.started(numContainers + 2).succeeded(numContainers + 2)); results.testEvents() // .assertStatistics(stats -> stats.started(numTests).succeeded(numTests)); } } @Nested class AnnotationAttributes { @Test void supportsCustomNamePatterns() { var results = executeTestsForClass(CustomNamePatternTestCase.class); results.allEvents().assertStatistics(stats -> stats.started(6).succeeded(6)); assertThat(invocationDisplayNames(results)) // .containsExactly("1 | TesT | 1, \"foo\" | set", "2 | TesT | 2, \"bar\" | number = 2, name = \"bar\""); } @Test void closesAutoCloseableArguments() { AutoCloseableArgument.closeCounter = 0; var results = executeTestsForClass(AutoCloseableArgumentTestCase.class); results.allEvents().assertStatistics(stats -> stats.started(4).succeeded(4)); assertThat(AutoCloseableArgument.closeCounter).isEqualTo(2); } @Test void doesNotCloseAutoCloseableArgumentsWhenDisabled() { AutoCloseableArgument.closeCounter = 0; var results = executeTestsForClass(AutoCloseableArgumentWithDisabledCleanupTestCase.class); results.allEvents().assertStatistics(stats -> stats.started(4).succeeded(4)); assertThat(AutoCloseableArgument.closeCounter).isEqualTo(0); } @Test void failsOnStrictArgumentCountValidationMode() { var results = executeTestsForClass(StrictArgumentCountValidationModeTestCase.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(finishedWithFailure(message( "Configuration error: @ParameterizedClass consumes 1 parameter but there were 2 arguments provided.%nNote: the provided arguments were [foo, unused]".formatted())))); } @ParameterizedTest @ValueSource(classes = { NoneArgumentCountValidationModeTestCase.class, DefaultArgumentCountValidationModeTestCase.class }) void doesNotFailOnNoneOrDefaultArgumentCountValidationMode(Class classTemplateClass) { var results = executeTestsForClass(classTemplateClass); results.allEvents().assertStatistics(stats -> stats.started(4).succeeded(4)); } @Test void failsOnStrictArgumentCountValidationModeSetViaConfigurationParameter() { var results = executeTests(request -> request // .selectors(selectClass(DefaultArgumentCountValidationModeTestCase.class)).configurationParameter( ArgumentCountValidator.ARGUMENT_COUNT_VALIDATION_KEY, STRICT.name())); results.allEvents().assertThatEvents() // .haveExactly(1, event(finishedWithFailure(message( "Configuration error: @ParameterizedClass consumes 1 parameter but there were 2 arguments provided.%nNote: the provided arguments were [foo, unused]".formatted())))); } @Test void failsForSkippedParameters() { var results = executeTestsForClass(InvalidUnusedParameterIndexesTestCase.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(finishedWithFailure(message( "2 configuration errors:%n- no field annotated with @Parameter(0) declared%n- no field annotated with @Parameter(2) declared".formatted())))); } @Test void failsWhenInvocationIsRequiredButNoArgumentSetsAreProvided() { var results = executeTestsForClass(ForbiddenZeroInvocationsTestCase.class); results.containerEvents().assertThatEvents() // .haveExactly(1, event(finishedWithFailure(instanceOf(TemplateInvocationValidationException.class), message( "Configuration error: You must configure at least one set of arguments for this @ParameterizedClass")))); } @Test void doesNotFailWhenInvocationIsNotRequiredAndNoArgumentSetsAreProvided() { var results = executeTestsForClass(AllowedZeroInvocationsTestCase.class); results.allEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); } } @Nested class Nesting { @ParameterizedTest @ValueSource(classes = { NestedFieldInjectionTestCase.class, NestedConstructorInjectionTestCase.class }) void supportsNestedParameterizedClass(Class classTemplateClass) { var results = executeTestsForClass(classTemplateClass); results.containerEvents().assertStatistics(stats -> stats.started(14).succeeded(14)); results.testEvents().assertStatistics(stats -> stats.started(8).succeeded(8)); assertThat(invocationDisplayNames(results)) // .containsExactly( // "[1] number = 1", "[1] text = \"foo\"", "[2] text = \"bar\"", // "[2] number = 2", "[1] text = \"foo\"", "[2] text = \"bar\"" // ); assertThat(allReportEntries(results)).map(it -> it.get("value")).containsExactly( // @formatter:off "beforeAll: %s".formatted(classTemplateClass.getSimpleName()), "beforeParameterizedClassInvocation: %s".formatted(classTemplateClass.getSimpleName()), "beforeAll: InnerTestCase", "beforeParameterizedClassInvocation: InnerTestCase", "beforeEach: [1] flag = true [%s]".formatted(classTemplateClass.getSimpleName()), "beforeEach: [1] flag = true [InnerTestCase]", "test(1, foo, true)", "afterEach: [1] flag = true [InnerTestCase]", "afterEach: [1] flag = true [%s]".formatted(classTemplateClass.getSimpleName()), "beforeEach: [2] flag = false [%s]".formatted(classTemplateClass.getSimpleName()), "beforeEach: [2] flag = false [InnerTestCase]", "test(1, foo, false)", "afterEach: [2] flag = false [InnerTestCase]", "afterEach: [2] flag = false [%s]".formatted(classTemplateClass.getSimpleName()), "afterParameterizedClassInvocation: InnerTestCase", "beforeParameterizedClassInvocation: InnerTestCase", "beforeEach: [1] flag = true [%s]".formatted(classTemplateClass.getSimpleName()), "beforeEach: [1] flag = true [InnerTestCase]", "test(1, bar, true)", "afterEach: [1] flag = true [InnerTestCase]", "afterEach: [1] flag = true [%s]".formatted(classTemplateClass.getSimpleName()), "beforeEach: [2] flag = false [%s]".formatted(classTemplateClass.getSimpleName()), "beforeEach: [2] flag = false [InnerTestCase]", "test(1, bar, false)", "afterEach: [2] flag = false [InnerTestCase]", "afterEach: [2] flag = false [%s]".formatted(classTemplateClass.getSimpleName()), "afterParameterizedClassInvocation: InnerTestCase", "afterAll: InnerTestCase", "afterParameterizedClassInvocation: %s".formatted(classTemplateClass.getSimpleName()), "beforeParameterizedClassInvocation: %s".formatted(classTemplateClass.getSimpleName()), "beforeAll: InnerTestCase", "beforeParameterizedClassInvocation: InnerTestCase", "beforeEach: [1] flag = true [%s]".formatted(classTemplateClass.getSimpleName()), "beforeEach: [1] flag = true [InnerTestCase]", "test(2, foo, true)", "afterEach: [1] flag = true [InnerTestCase]", "afterEach: [1] flag = true [%s]".formatted(classTemplateClass.getSimpleName()), "beforeEach: [2] flag = false [%s]".formatted(classTemplateClass.getSimpleName()), "beforeEach: [2] flag = false [InnerTestCase]", "test(2, foo, false)", "afterEach: [2] flag = false [InnerTestCase]", "afterEach: [2] flag = false [%s]".formatted(classTemplateClass.getSimpleName()), "afterParameterizedClassInvocation: InnerTestCase", "beforeParameterizedClassInvocation: InnerTestCase", "beforeEach: [1] flag = true [%s]".formatted(classTemplateClass.getSimpleName()), "beforeEach: [1] flag = true [InnerTestCase]", "test(2, bar, true)", "afterEach: [1] flag = true [InnerTestCase]", "afterEach: [1] flag = true [%s]".formatted(classTemplateClass.getSimpleName()), "beforeEach: [2] flag = false [%s]".formatted(classTemplateClass.getSimpleName()), "beforeEach: [2] flag = false [InnerTestCase]", "test(2, bar, false)", "afterEach: [2] flag = false [InnerTestCase]", "afterEach: [2] flag = false [%s]".formatted(classTemplateClass.getSimpleName()), "afterParameterizedClassInvocation: InnerTestCase", "afterAll: InnerTestCase", "afterParameterizedClassInvocation: %s".formatted(classTemplateClass.getSimpleName()), "afterAll: %s".formatted(classTemplateClass.getSimpleName()) // @formatter:on ); var legacyReportingNames = results.testEvents() // .filter(it -> it.getType() == EventType.STARTED) // .map(e -> e.getTestDescriptor().getLegacyReportingName()); assertThat(legacyReportingNames).containsExactly( // "test(boolean, TestReporter)[1][1][1]", // "test(boolean, TestReporter)[1][1][2]", // "test(boolean, TestReporter)[1][2][1]", // "test(boolean, TestReporter)[1][2][2]", // "test(boolean, TestReporter)[2][1][1]", // "test(boolean, TestReporter)[2][1][2]", // "test(boolean, TestReporter)[2][2][1]", // "test(boolean, TestReporter)[2][2][2]"); } @ParameterizedTest @ValueSource(classes = { ConstructorInjectionWithRegularNestedTestCase.class, FieldInjectionWithRegularNestedTestCase.class }) void supportsRegularNestedTestClassesInsideParameterizedClass(Class classTemplateClass) { var results = executeTestsForClass(classTemplateClass); results.containerEvents().assertStatistics(stats -> stats.started(6).succeeded(6)); results.testEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); } } @Nested class FieldInjection { @Test void supportsMultipleAggregatorFields() { var results = executeTestsForClass(MultiAggregatorFieldInjectionTestCase.class); results.allEvents().assertStatistics(stats -> stats.started(6).succeeded(6)); } @Test void supportsInjectionOfInheritedFields() { var results = executeTestsForClass(InheritedHiddenParameterFieldTestCase.class); results.allEvents().assertStatistics(stats -> stats.started(6).succeeded(6)); assertThat(allReportEntries(results)) // .extracting(it -> tuple(it.get("super.value"), it.get("this.value"))) // .containsExactly(tuple("foo", "1"), tuple("bar", "2")); } @Test void doesNotSupportInjectionForFinalFields() { var classTemplateClass = InvalidFinalFieldTestCase.class; var results = executeTestsForClass(classTemplateClass); results.allEvents().assertThatEvents() // .haveExactly(1, finishedWithFailure(message( "Configuration error: @Parameter field [final int %s.i] must not be declared as final.".formatted( classTemplateClass.getName())))); } @Test void aggregatorFieldsMustNotDeclareIndex() { var classTemplateClass = InvalidAggregatorFieldWithIndexTestCase.class; var results = executeTestsForClass(classTemplateClass); results.allEvents().assertThatEvents() // .haveExactly(1, finishedWithFailure(message( "Configuration error: no index may be declared in @Parameter(0) annotation on aggregator field [%s %s.accessor].".formatted( ArgumentsAccessor.class.getName(), classTemplateClass.getName())))); } @Test void declaredIndexMustNotBeNegative() { var classTemplateClass = InvalidParameterIndexTestCase.class; var results = executeTestsForClass(classTemplateClass); results.allEvents().assertThatEvents() // .haveExactly(1, finishedWithFailure(message( "Configuration error: index must be greater than or equal to zero in @Parameter(-42) annotation on field [int %s.i].".formatted( classTemplateClass.getName())))); } @Test void declaredIndexMustBeUnique() { var classTemplateClass = InvalidDuplicateParameterDeclarationTestCase.class; var results = executeTestsForClass(classTemplateClass); results.allEvents().assertThatEvents() // .haveExactly(1, finishedWithFailure(message( "Configuration error: duplicate index declared in @Parameter(0) annotation on fields [int %s.i, long %s.l].".formatted( classTemplateClass.getName(), classTemplateClass.getName())))); } @Test void failsWithMeaningfulErrorWhenTooFewArgumentsProvidedForFieldInjection() { var results = executeTestsForClass(NotEnoughArgumentsForFieldsTestCase.class); results.containerEvents().assertThatEvents() // .haveExactly(1, finishedWithFailure(message(withPlatformSpecificLineSeparator( """ Configuration error: @ParameterizedClass has 2 required parameters (due to field injection) but there was 1 argument provided. Note: the provided arguments were [1]""")))); } } @Nested class PerClassLifecycle { @Test void supportsFieldInjectionForTestInstanceLifecyclePerClass() { var results = executeTestsForClass(FieldInjectionWithPerClassTestInstanceLifecycleTestCase.class); results.allEvents().assertStatistics(stats -> stats.started(8).succeeded(8)); Supplier>> valueTrackingReportEntries = () -> allReportEntries(results) // .filter(it -> it.containsKey("instanceHashCode")); Supplier>> lifecycleReportEntries = () -> allReportEntries(results) // .filter(it -> !it.containsKey("instanceHashCode")); assertThat(valueTrackingReportEntries.get().map(it -> it.get("value"))) // .containsExactly("foo", "foo", "bar", "bar"); assertThat(valueTrackingReportEntries.get().map(it -> it.get("instanceHashCode")).distinct()) // .hasSize(1); assertThat(lifecycleReportEntries.get().map(it -> it.get("value"))) // .containsExactly( //@formatter:off "beforeParameterizedClassInvocation1", "beforeParameterizedClassInvocation2", "test1", "test2", "afterParameterizedClassInvocation1", "afterParameterizedClassInvocation2", "beforeParameterizedClassInvocation1", "beforeParameterizedClassInvocation2", "test1", "test2", "afterParameterizedClassInvocation1", "afterParameterizedClassInvocation2" //@formatter:on ); } @Test void doesNotSupportConstructorInjectionForTestInstanceLifecyclePerClass() { var results = executeTests(request -> request // .selectors(selectClass(ConstructorInjectionTestCase.class)) // .configurationParameter(Constants.DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME, PER_CLASS.name())); results.allEvents().assertThatEvents() // .haveExactly(1, finishedWithFailure(message(it -> it.contains( "Constructor injection is not supported for @ParameterizedClass classes with @TestInstance(Lifecycle.PER_CLASS)")))); } } @Nested class LifecycleMethods { @ParameterizedTest @CsvSource(textBlock = """ NonStaticBeforeLifecycleMethodTestCase, @BeforeParameterizedClassInvocation, beforeParameterizedClassInvocation NonStaticAfterLifecycleMethodTestCase, @AfterParameterizedClassInvocation, afterParameterizedClassInvocation """) void lifecycleMethodsNeedToBeStaticByDefault(String simpleClassName, String annotationName, String lifecycleMethodName) throws Exception { var className = ParameterizedClassIntegrationTests.class.getName() + "$" + simpleClassName; var results = discoverTestsForClass(Class.forName(className)); var issue = getOnlyElement(results.getDiscoveryIssues()); assertThat(issue.severity()) // .isEqualTo(Severity.ERROR); assertThat(issue.message()) // .isEqualTo( "%s method 'void %s.%s()' must be static unless the test class is annotated with @TestInstance(Lifecycle.PER_CLASS).", annotationName, className, lifecycleMethodName); assertThat(issue.source()) // .containsInstanceOf(org.junit.platform.engine.support.descriptor.MethodSource.class); } @Test void lifecycleMethodsMustNotBePrivate() { var results = discoverTestsForClass(PrivateLifecycleMethodTestCase.class); var issue = getOnlyElement(results.getDiscoveryIssues()); assertThat(issue.severity()) // .isEqualTo(Severity.ERROR); assertThat(issue.message()) // .isEqualTo( "@BeforeParameterizedClassInvocation method 'private static void %s.beforeParameterizedClassInvocation()' must not be private.", PrivateLifecycleMethodTestCase.class.getName()); assertThat(issue.source()) // .containsInstanceOf(org.junit.platform.engine.support.descriptor.MethodSource.class); } @Test void lifecycleMethodsMustNotDeclareReturnType() { var results = discoverTestsForClass(NonVoidLifecycleMethodTestCase.class); var issue = getOnlyElement(results.getDiscoveryIssues()); assertThat(issue.severity()) // .isEqualTo(Severity.ERROR); assertThat(issue.message()) // .isEqualTo( "@BeforeParameterizedClassInvocation method 'static int %s.beforeParameterizedClassInvocation()' must not return a value.", NonVoidLifecycleMethodTestCase.class.getName()); assertThat(issue.source()) // .containsInstanceOf(org.junit.platform.engine.support.descriptor.MethodSource.class); } @Test void lifecycleMethodsFromSuperclassAreWrappedAroundLifecycleMethodsFromTestClass() { var results = executeTestsForClass(LifecycleMethodsFromSuperclassTestCase.class); results.allEvents().assertStatistics(stats -> stats.started(4).succeeded(4)); assertThat(allReportEntries(results).map(it -> it.get("value"))) // .containsExactly("zzz_before", "aaa_before", "test", "aaa_after", "zzz_after"); } @Test void exceptionsInLifecycleMethodsArePropagated() { var results = executeTestsForClass(LifecycleMethodsErrorHandlingTestCase.class); results.allEvents().assertStatistics(stats -> stats.started(3).failed(1).succeeded(2)); results.containerEvents().assertThatEvents() // .haveExactly(1, finishedWithFailure( // message("zzz_before"), // suppressed(0, message("aaa_after")), // suppressed(1, message("zzz_after")))); assertThat(allReportEntries(results).map(it -> it.get("value"))) // .containsExactly("zzz_before", "aaa_after", "zzz_after"); } @ParameterizedTest @ValueSource(classes = { LifecycleMethodArgumentInjectionWithConstructorInjectionTestCase.class, LifecycleMethodArgumentInjectionWithFieldInjectionTestCase.class }) void supportsInjectingArgumentsIntoLifecycleMethods(Class classTemplateClass) { var results = executeTestsForClass(classTemplateClass); results.allEvents().assertStatistics(stats -> stats.started(5).succeeded(5)); } @ParameterizedTest @ValueSource(classes = { CustomConverterAnnotationsWithLifecycleMethodsAndConstructorInjectionTestCase.class, CustomConverterAnnotationsWithLifecycleMethodsAndFieldInjectionTestCase.class }) void convertersHaveAccessToTheirAnnotations(Class classTemplateClass) { var results = executeTestsForClass(classTemplateClass); results.allEvents().assertStatistics(stats -> stats.started(4).succeeded(4)); } @ParameterizedTest @ValueSource(classes = { ValidLifecycleMethodInjectionWithConstructorInjectionTestCase.class, ValidLifecycleMethodInjectionWithFieldInjectionTestCase.class }) void supportsMixedInjectionsForLifecycleMethods(Class classTemplateClass) { var results = executeTestsForClass(classTemplateClass); results.allEvents().assertStatistics(stats -> stats.started(4).succeeded(4)); } @Test void failsForLifecycleMethodWithInvalidParameters() { var results = executeTestsForClass(LifecycleMethodWithInvalidParametersTestCase.class); var expectedMessage = withPlatformSpecificLineSeparator( """ 2 configuration errors: - parameter 'value' with index 0 is incompatible with the parameter declared on the parameterized class: \ expected type 'int' but found 'long' - parameter 'anotherValue' with index 1 must not be annotated with @ConvertWith"""); var failedResult = getFirstTestExecutionResult(results.containerEvents().failed()); assertThat(failedResult.getThrowable().orElseThrow()) // .hasMessage( "Invalid @BeforeParameterizedClassInvocation lifecycle method declaration: static void %s.before(long,int)".formatted( LifecycleMethodWithInvalidParametersTestCase.class.getName())) // .cause().hasMessage(expectedMessage); } @Test void failsForLifecycleMethodWithInvalidParameterOrder() { var results = executeTestsForClass(LifecycleMethodWithInvalidParameterOrderTestCase.class); results.containerEvents().assertThatEvents() // .haveExactly(1, finishedWithFailure(message( ("@BeforeParameterizedClassInvocation method [static void %s.before(%s,int,%s)] declares formal parameters in an invalid order: " + "argument aggregators must be declared after any indexed arguments and before any arguments resolved by another ParameterResolver.").formatted( LifecycleMethodWithInvalidParameterOrderTestCase.class.getName(), ArgumentsAccessor.class.getName(), ArgumentsAccessor.class.getName())))); } @Test void failsForLifecycleMethodWithParameterAfterAggregator() { var results = executeTestsForClass(LifecycleMethodWithParameterAfterAggregatorTestCase.class); results.containerEvents().assertThatEvents() // .haveExactly(1, finishedWithFailure( message(it -> it.contains("No ParameterResolver registered for parameter [int value]")))); } @Test void lifecycleMethodsMustNotBeDeclaredInRegularTestClasses() { var testClassName = RegularClassWithLifecycleMethodsTestCase.class.getName(); var results = discoverTestsForClass(RegularClassWithLifecycleMethodsTestCase.class); assertThat(results.getDiscoveryIssues()).hasSize(2); var issues = results.getDiscoveryIssues().stream() // .sorted(comparing(DiscoveryIssue::message)) // .toList(); assertThat(issues) // .extracting(DiscoveryIssue::severity) // .containsOnly(Severity.ERROR); assertThat(issues) // .extracting(DiscoveryIssue::source) // .extracting(Optional::orElseThrow) // .allMatch(org.junit.platform.engine.support.descriptor.MethodSource.class::isInstance); assertThat(issues.getFirst().message()) // .isEqualTo( "@AfterParameterizedClassInvocation method 'static void %s.after()' must not be declared in test class '%s' because it is not annotated with @ParameterizedClass.", testClassName, testClassName); assertThat(issues.getLast().message()) // .isEqualTo( "@BeforeParameterizedClassInvocation method 'static void %s.before()' must not be declared in test class '%s' because it is not annotated with @ParameterizedClass.", testClassName, testClassName); } } private static String withPlatformSpecificLineSeparator(String textBlock) { return textBlock.replace("\n", System.lineSeparator()); } // ------------------------------------------------------------------- private static Stream invocationDisplayNames(EngineExecutionResults results) { return results.containerEvents() // .started() // .filter(uniqueId(lastSegmentType(ClassTemplateInvocationTestDescriptor.SEGMENT_TYPE))::matches) // .map(Event::getTestDescriptor) // .map(TestDescriptor::getDisplayName); } private static Stream> allReportEntries(EngineExecutionResults results) { return results.allEvents().reportingEntryPublished() // .map(e -> e.getRequiredPayload(ReportEntry.class)) // .map(ReportEntry::getKeyValuePairs); } private static Condition lastSegmentType(@SuppressWarnings("SameParameterValue") String segmentType) { return new Condition<>(it -> segmentType.equals(it.getLastSegment().getType()), "last segment type is '%s'", segmentType); } private static TestExecutionResult getFirstTestExecutionResult(Events events) { return events.stream() // .findFirst() // .flatMap(Event::getPayload) // .map(TestExecutionResult.class::cast) // .orElseThrow(); } @ParameterizedClassWithNegativeAndPositiveValue static class ConstructorInjectionTestCase { private int value; private final TestInfo testInfo; ConstructorInjectionTestCase(int value, TestInfo testInfo) { this.value = value; this.testInfo = testInfo; } @Test void test1() { assertEquals("test1()", testInfo.getDisplayName()); assertTrue(value < 0, "negative"); value *= -1; } @Test void test2() { assertEquals("test2()", testInfo.getDisplayName()); assertTrue(value < 0, "negative"); value *= -1; } } @ParameterizedClassWithNegativeAndPositiveValue record RecordTestCase(int value, TestInfo testInfo) { @Test void test1() { assertEquals("test1()", testInfo.getDisplayName()); assertTrue(value < 0, "negative"); } @Test void test2() { assertEquals("test2()", testInfo.getDisplayName()); assertTrue(value < 0, "negative"); } } @ParameterizedClass @ValueSource(ints = { -1, 1 }) record RecordWithParameterAnnotationOnComponentTestCase(@Parameter int value) { @Test void test1() { assertTrue(value < 0, "negative"); } @Test void test2() { assertTrue(value < 0, "negative"); } } @ParameterizedClass @ValueSource(ints = { -1, 1 }) static class FieldInjectionTestCase { @Parameter private int value; @Test void test1() { assertTrue(value < 0, "negative"); value *= -1; } @Test void test2() { assertTrue(value < 0, "negative"); value *= -1; } } @ParameterizedClass(quoteTextArguments = false) @CsvSource({ "-1", "1" }) record RecordWithBuiltInConverterTestCase(int value) { @Test void test1() { assertTrue(value < 0, "negative"); } @Test void test2() { assertTrue(value < 0, "negative"); } } @ParameterizedClass @ValueSource(ints = { -1, 1 }) record RecordWithRegisteredConversionTestCase(@ConvertWith(CustomIntegerToStringConverter.class) String value) { @Test void test1() { assertTrue(value.startsWith("minus"), "negative"); } @Test void test2() { assertTrue(value.startsWith("minus"), "negative"); } } @ParameterizedClass @ValueSource(ints = { -1, 1 }) static class FieldInjectionWithRegisteredConversionTestCase { @Parameter @ConvertWith(CustomIntegerToStringConverter.class) private String value; @Test void test1() { assertNotNull(value); assertTrue(value.startsWith("minus"), "negative"); } @Test void test2() { assertNotNull(value); assertTrue(value.startsWith("minus"), "negative"); } } @NullMarked private static class CustomIntegerToStringConverter extends TypedArgumentConverter { CustomIntegerToStringConverter() { super(Integer.class, String.class); } @Override protected String convert(@Nullable Integer source) throws ArgumentConversionException { return switch (requireNonNull(source)) { case -1 -> "minus one"; case +1 -> "plus one"; default -> throw new IllegalArgumentException("Unsupported value: " + source); }; } } @SuppressWarnings("JUnitMalformedDeclaration") @ParameterizedClass @ValueSource(ints = { -1, 1 }) record RecordWithBuiltInAggregatorTestCase(ArgumentsAccessor accessor) { @Test void test1() { assertTrue(requireNonNull(accessor.getInteger(0)) < 0, "negative"); } @Test void test2() { assertTrue(requireNonNull(accessor.getInteger(0)) < 0, "negative"); } } @SuppressWarnings("JUnitMalformedDeclaration") @ParameterizedClass @ValueSource(ints = { -1, 1 }) static class FieldInjectionWithBuiltInAggregatorTestCase { @Parameter private ArgumentsAccessor accessor; @Test void test1() { assertTrue(requireNonNull(accessor.getInteger(0)) < 0, "negative"); } @Test void test2() { assertTrue(requireNonNull(accessor.getInteger(0)) < 0, "negative"); } } @ParameterizedClass @ValueSource(ints = { -1, 1 }) record RecordWithCustomAggregatorTestCase(@AggregateWith(TimesTwoAggregator.class) int value) { @Test void test1() { assertTrue(value <= -2, "negative"); } @Test void test2() { assertTrue(value <= -2, "negative"); } } @ParameterizedClass @ValueSource(ints = { -1, 1 }) static class FieldInjectionWithCustomAggregatorTestCase { @TimesTwo private int value; @Test void test1() { assertTrue(value <= -2, "negative"); } @Test void test2() { assertTrue(value <= -2, "negative"); } } @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @ParameterizedClass(quoteTextArguments = false) @ValueSource(ints = { -1, 1 }) @interface ParameterizedClassWithNegativeAndPositiveValue { } @NullMarked private static class TimesTwoAggregator extends SimpleArgumentsAggregator { @Override protected Object aggregateArguments(ArgumentsAccessor accessor, Class targetType, AnnotatedElementContext context, int parameterIndex) throws ArgumentsAggregationException { assertThat(targetType).isEqualTo(int.class); return requireNonNull(accessor.getInteger(0)) * 2; } } @ParameterizedClass @NullAndEmptySource record NullAndEmptySourceConstructorInjectionTestCase(String value) { @Test void test() { assertTrue(StringUtils.isBlank(value)); } } @ParameterizedClass @NullAndEmptySource static class NullAndEmptySourceConstructorFieldInjectionTestCase { @Parameter String value; @Test void test() { assertTrue(StringUtils.isBlank(value)); } } @ParameterizedClass @CsvFileSource(resources = "two-column.csv") record CsvFileSourceConstructorInjectionTestCase(String name, int value) { @Test void test() { assertNotNull(name); assertTrue(value > 0 && value < 5); } } @ParameterizedClass @CsvFileSource(resources = "two-column.csv") static class CsvFileSourceFieldInjectionTestCase { @Parameter(0) String name; @Parameter(1) int value; @Test void test() { assertNotNull(name); assertTrue(value > 0 && value < 5); } } @ParameterizedClass @EnumSource record SingleEnumSourceConstructorInjectionTestCase(EnumOne value) { @Test void test() { assertEquals(EnumOne.FOO, value); } } @ParameterizedClass @EnumSource static class SingleEnumSourceFieldInjectionTestCase { @Parameter EnumOne value; @Test void test() { assertEquals(EnumOne.FOO, value); } } @ParameterizedClass @EnumSource(EnumOne.class) @EnumSource(EnumTwo.class) record RepeatedEnumSourceConstructorInjectionTestCase(Object value) { @Test void test() { assertTrue(value == EnumOne.FOO || value == EnumTwo.BAR); } } @ParameterizedClass @EnumSource(EnumOne.class) @EnumSource(EnumTwo.class) static class RepeatedEnumSourceFieldInjectionTestCase { @Parameter Object value; @Test void test() { assertTrue(value == EnumOne.FOO || value == EnumTwo.BAR); } } private enum EnumOne { FOO } private enum EnumTwo { BAR } @ParameterizedClass @MethodSource("parameters") record MethodSourceConstructorInjectionTestCase(String value) { static Stream parameters() { return Stream.of("foo", "bar"); } @Test void test() { assertTrue(value.equals("foo") || value.equals("bar")); } } @ParameterizedClass @MethodSource("parameters") static class MethodSourceFieldInjectionTestCase { static Stream parameters() { return Stream.of("foo", "bar"); } @Parameter String value; @Test void test() { assertTrue(value.equals("foo") || value.equals("bar")); } } @SuppressWarnings("JUnitMalformedDeclaration") @ParameterizedClass @MethodSource record MethodSourceWithoutMethodNameTestCase(String value) { @Test void test() { fail("should not be executed"); } } @ParameterizedClass @FieldSource("parameters") record FieldSourceConstructorInjectionTestCase(String value) { static final List parameters = List.of("foo", "bar"); @Test void test() { assertTrue(value.equals("foo") || value.equals("bar")); } } @ParameterizedClass @FieldSource("parameters") static class FieldSourceFieldInjectionTestCase { static final List parameters = List.of("foo", "bar"); @Parameter String value; @Test void test() { assertTrue(value.equals("foo") || value.equals("bar")); } } @ParameterizedClass @FieldSource record FieldSourceWithoutFieldNameTestCase(String value) { @Test void test() { fail("should not be executed"); } } @ParameterizedClass @ArgumentsSource(CustomArgumentsProvider.class) record ArgumentsSourceConstructorInjectionTestCase(String value) { @Test void test() { assertTrue(value.equals("foo") || value.equals("bar")); } } @ParameterizedClass @ArgumentsSource(CustomArgumentsProvider.class) static class ArgumentsSourceFieldInjectionTestCase { @Parameter String value; @Test void test() { assertTrue(value.equals("foo") || value.equals("bar")); } } @NullMarked static class CustomArgumentsProvider implements ArgumentsProvider { @Override public Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context) throws Exception { return Stream.of("foo", "bar").map(Arguments::of); } } @ParameterizedClass(name = INDEX_PLACEHOLDER + " | " // + DISPLAY_NAME_PLACEHOLDER + " | " // + ARGUMENTS_PLACEHOLDER + " | " // + ARGUMENT_SET_NAME_OR_ARGUMENTS_WITH_NAMES_PLACEHOLDER) @MethodSource("arguments") @DisplayName("TesT") record CustomNamePatternTestCase(int number, String name) { static Stream arguments() { return Stream.of(argumentSet("set", 1, "foo"), Arguments.of(2, "bar")); } @Test void test() { assertTrue(number > 0); assertFalse(name.isBlank()); } } @ParameterizedClass @ArgumentsSource(AutoCloseableArgumentProvider.class) record AutoCloseableArgumentTestCase(AutoCloseableArgument argument) { @Test void test() { assertNotNull(argument); assertEquals(0, AutoCloseableArgument.closeCounter); } } @ParameterizedClass(autoCloseArguments = false) @ArgumentsSource(AutoCloseableArgumentProvider.class) record AutoCloseableArgumentWithDisabledCleanupTestCase(AutoCloseableArgument argument) { @Test void test() { assertNotNull(argument); assertEquals(0, AutoCloseableArgument.closeCounter); } } @NullMarked private static class AutoCloseableArgumentProvider implements ArgumentsProvider { @Override public Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context) { return Stream.of(arguments(new AutoCloseableArgument(), Named.of("unused", new AutoCloseableArgument()))); } } static class AutoCloseableArgument implements AutoCloseable { static int closeCounter = 0; @Override public void close() { closeCounter++; } } @ParameterizedClass(argumentCountValidation = STRICT) @CsvSource("foo, unused") record StrictArgumentCountValidationModeTestCase(String value) { @Test void test() { fail("should not be called"); } } @ParameterizedClass(argumentCountValidation = NONE) @CsvSource("foo, unused") record NoneArgumentCountValidationModeTestCase(String value) { @Test void test() { assertEquals("foo", value); } } @ParameterizedClass @CsvSource("foo, unused") record DefaultArgumentCountValidationModeTestCase(String value) { @Test void test() { assertEquals("foo", value); } } @ParameterizedClass @MethodSource("org.junit.jupiter.params.ParameterizedClassIntegrationTests#zeroArguments") record ForbiddenZeroInvocationsTestCase(String value) { @Test void test() { fail("should not be called"); } } @ParameterizedClass(allowZeroInvocations = true) @MethodSource("org.junit.jupiter.params.ParameterizedClassIntegrationTests#zeroArguments") record AllowedZeroInvocationsTestCase(String value) { @Test void test() { fail("should not be called"); } } static Stream zeroArguments() { return Stream.empty(); } @SuppressWarnings("JUnitMalformedDeclaration") @ParameterizedClass record NoArgumentSourceTestCase(String value) { @Test void test() { fail("should not be called"); } } @ParameterizedClass @ValueSource(ints = { 1, 2 }) static class NestedFieldInjectionTestCase extends LifecycleCallbacks { @Parameter int number; @Nested @ParameterizedClass @ValueSource(strings = { "foo", "bar" }) class InnerTestCase extends LifecycleCallbacks { @Parameter String text; @ParameterizedTest @ValueSource(booleans = { true, false }) void test(boolean flag, TestReporter reporter) { reporter.publishEntry("test(" + number + ", " + text + ", " + flag + ")"); assertTrue(number > 0); assertTrue(List.of("foo", "bar").contains(text)); } } } @ParameterizedClass @ValueSource(ints = { 1, 2 }) static class NestedConstructorInjectionTestCase extends LifecycleCallbacks { final int number; NestedConstructorInjectionTestCase(int number) { this.number = number; } @Nested @ParameterizedClass @ValueSource(strings = { "foo", "bar" }) class InnerTestCase extends LifecycleCallbacks { final String text; InnerTestCase(String text) { this.text = text; } @ParameterizedTest @ValueSource(booleans = { true, false }) void test(boolean flag, TestReporter reporter) { reporter.publishEntry("test(" + number + ", " + text + ", " + flag + ")"); assertTrue(number > 0); assertTrue(List.of("foo", "bar").contains(text)); } } } @SuppressWarnings("JUnitMalformedDeclaration") static class LifecycleCallbacks { @BeforeAll static void beforeAll(TestReporter reporter, TestInfo testInfo) { reporter.publishEntry("beforeAll: " + testInfo.getTestClass().orElseThrow().getSimpleName()); } @BeforeParameterizedClassInvocation(injectArguments = false) static void beforeParameterizedClassInvocation(TestReporter reporter, TestInfo testInfo) { reporter.publishEntry( "beforeParameterizedClassInvocation: " + testInfo.getTestClass().orElseThrow().getSimpleName()); } @BeforeEach void beforeEach(TestReporter reporter, TestInfo testInfo) { reporter.publishEntry( "beforeEach: " + testInfo.getDisplayName() + " [" + this.getClass().getSimpleName() + "]"); } @AfterEach void afterEach(TestReporter reporter, TestInfo testInfo) { reporter.publishEntry( "afterEach: " + testInfo.getDisplayName() + " [" + this.getClass().getSimpleName() + "]"); } @AfterParameterizedClassInvocation(injectArguments = false) static void afterParameterizedClassInvocation(TestReporter reporter, TestInfo testInfo) { reporter.publishEntry( "afterParameterizedClassInvocation: " + testInfo.getTestClass().orElseThrow().getSimpleName()); } @AfterAll static void afterAll(TestReporter reporter, TestInfo testInfo) { reporter.publishEntry("afterAll: " + testInfo.getTestClass().orElseThrow().getSimpleName()); } } @ParameterizedClass @ValueSource(ints = { 1, 2 }) record ConstructorInjectionWithRegularNestedTestCase(int number) { @Nested @TestInstance(PER_CLASS) class InnerTestCase { InnerTestCase(TestInfo testInfo) { assertThat(testInfo.getTestClass()).contains(InnerTestCase.class); assertThat(testInfo.getTestMethod()).isEmpty(); } @Test void test() { assertTrue(number >= 0); } } } @ParameterizedClass @ValueSource(ints = { 1, 2 }) static class FieldInjectionWithRegularNestedTestCase { @Parameter int number; @Nested @TestInstance(PER_CLASS) class InnerTestCase { InnerTestCase(TestInfo testInfo) { assertThat(testInfo.getTestClass()).contains(InnerTestCase.class); assertThat(testInfo.getTestMethod()).isEmpty(); } @Test void test() { assertTrue(number >= 0); } } } @ParameterizedClass @CsvSource({ "1, foo", "2, bar" }) static class MultiAggregatorFieldInjectionTestCase { @Parameter ArgumentsAccessor accessor; @TimesTwo int numberTimesTwo; @Parameter(0) int number; @Parameter(1) String text; @Test void test() { assertEquals(2, accessor.size()); assertEquals(number, accessor.getInteger(0)); assertEquals(number * 2, numberTimesTwo); assertEquals(text, accessor.getString(1)); } } @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD, ElementType.PARAMETER }) @Parameter @AggregateWith(TimesTwoAggregator.class) @interface TimesTwo { } @ParameterizedClass @MethodSource("methodSource") @FieldSource("fieldSource") @TestInstance(PER_CLASS) static class FieldInjectionWithPerClassTestInstanceLifecycleTestCase { List methodSource() { return List.of("foo"); } final List fieldSource = List.of("bar"); @BeforeParameterizedClassInvocation(injectArguments = false) void beforeParameterizedClassInvocation1(TestReporter reporter) { reporter.publishEntry("beforeParameterizedClassInvocation1"); } @BeforeParameterizedClassInvocation(injectArguments = false) void beforeParameterizedClassInvocation2(TestReporter reporter) { reporter.publishEntry("beforeParameterizedClassInvocation2"); } @AfterParameterizedClassInvocation(injectArguments = false) void afterParameterizedClassInvocation1(TestReporter reporter) { reporter.publishEntry("afterParameterizedClassInvocation1"); } @AfterParameterizedClassInvocation(injectArguments = false) void afterParameterizedClassInvocation2(TestReporter reporter) { reporter.publishEntry("afterParameterizedClassInvocation2"); } @Parameter private String value; @Test void test1(TestReporter reporter, TestInfo testInfo) { publishReportEntry(reporter, testInfo); } @Test void test2(TestReporter reporter, TestInfo testInfo) { publishReportEntry(reporter, testInfo); } private void publishReportEntry(TestReporter reporter, TestInfo testInfo) { assertNotNull(value); reporter.publishEntry(testInfo.getTestMethod().orElseThrow().getName()); reporter.publishEntry(Map.of( // "instanceHashCode", Integer.toHexString(hashCode()), // "value", value // )); } } abstract static class BaseTestCase { @Parameter(0) String value; } @ParameterizedClass @CsvSource({ "foo, 1", "bar, 2" }) static class InheritedHiddenParameterFieldTestCase extends BaseTestCase { @Parameter(1) String value; @Test void test(TestReporter reporter) { reporter.publishEntry(Map.of( // "super.value", super.value, // "this.value", this.value // )); } } @ParameterizedClass @ValueSource(ints = 1) static class InvalidFinalFieldTestCase { @SuppressWarnings("unused") @Parameter final int i = -1; @Test void test() { fail("should not be called"); } } @SuppressWarnings("JUnitMalformedDeclaration") @ParameterizedClass @ValueSource(ints = 1) static class InvalidAggregatorFieldWithIndexTestCase { @SuppressWarnings("unused") @Parameter(0) ArgumentsAccessor accessor; @Test void test() { fail("should not be called"); } } @ParameterizedClass @ValueSource(ints = 1) static class InvalidParameterIndexTestCase { @SuppressWarnings("unused") @Parameter(-42) int i; @Test void test() { fail("should not be called"); } } @ParameterizedClass @ValueSource(ints = 1) static class InvalidDuplicateParameterDeclarationTestCase { @SuppressWarnings("unused") @Parameter(0) int i; @SuppressWarnings("unused") @Parameter(0) long l; @Test void test() { fail("should not be called"); } } @ParameterizedClass @ValueSource(ints = 1) static class NotEnoughArgumentsForFieldsTestCase { @SuppressWarnings("unused") @Parameter(0) int i; @SuppressWarnings("unused") @Parameter(1) String s; @org.junit.jupiter.api.Test void test() { fail("should not be called"); } } @ParameterizedClass @CsvSource({ "unused1, foo, unused2, bar", "unused4, baz, unused5, qux" }) static class InvalidUnusedParameterIndexesTestCase { @Parameter(1) String second; @Parameter(3) String fourth; @Test void test(TestReporter reporter) { reporter.publishEntry(Map.of( // "second", second, // "fourth", fourth // )); } } @ParameterizedClass @ValueSource(ints = 1) record ArgumentConversionPerInvocationConstructorInjectionTestCase( @ConvertWith(Wrapper.Converter.class) Wrapper wrapper) { @Nullable static Wrapper instance; @BeforeAll @AfterAll static void clearWrapper() { instance = null; } @Test void test1() { setOrCheckWrapper(); } @Test void test2() { setOrCheckWrapper(); } private void setOrCheckWrapper() { if (instance == null) { instance = wrapper; } else { assertSame(instance, wrapper); } } } @ParameterizedClass @ValueSource(ints = 1) static class ArgumentConversionPerInvocationFieldInjectionTestCase { @Nullable static Wrapper instance; @BeforeAll @AfterAll static void clearWrapper() { instance = null; } @Parameter @ConvertWith(Wrapper.Converter.class) Wrapper wrapper; @Test void test1() { setOrCheckWrapper(); } @Test void test2() { setOrCheckWrapper(); } private void setOrCheckWrapper() { if (instance == null) { instance = wrapper; } else { assertSame(instance, wrapper); } } } record Wrapper(int value) { @NullMarked static class Converter extends SimpleArgumentConverter { @Override protected Object convert(@Nullable Object source, Class targetType) { return new Wrapper((Integer) requireNonNull(source)); } } } @ParameterizedClass @ValueSource(ints = 1) record NonStaticBeforeLifecycleMethodTestCase() { @BeforeParameterizedClassInvocation void beforeParameterizedClassInvocation() { fail("should not be called"); } @Test void test() { fail("should not be called"); } } @ParameterizedClass @ValueSource(ints = 1) record NonStaticAfterLifecycleMethodTestCase() { @AfterParameterizedClassInvocation void afterParameterizedClassInvocation() { fail("should not be called"); } @Test void test() { fail("should not be called"); } } @ParameterizedClass @ValueSource(ints = 1) record PrivateLifecycleMethodTestCase() { @BeforeParameterizedClassInvocation private static void beforeParameterizedClassInvocation() { fail("should not be called"); } @Test void test() { fail("should not be called"); } } @ParameterizedClass @ValueSource(ints = 1) record NonVoidLifecycleMethodTestCase() { @BeforeParameterizedClassInvocation static int beforeParameterizedClassInvocation() { return fail("should not be called"); } @Test void test() { fail("should not be called"); } } static abstract class AbstractBaseLifecycleTestCase { @BeforeParameterizedClassInvocation static void zzz_before(TestReporter reporter) { reporter.publishEntry("zzz_before"); } @AfterParameterizedClassInvocation static void zzz_after(TestReporter reporter) { reporter.publishEntry("zzz_after"); } } @ParameterizedClass @ValueSource(ints = 1) static class LifecycleMethodsFromSuperclassTestCase extends AbstractBaseLifecycleTestCase { @BeforeParameterizedClassInvocation static void aaa_before(TestReporter reporter) { reporter.publishEntry("aaa_before"); } @AfterParameterizedClassInvocation static void aaa_after(TestReporter reporter) { reporter.publishEntry("aaa_after"); } @Test void test(TestReporter reporter) { reporter.publishEntry("test"); } } static abstract class AbstractBaseLifecycleWithErrorsTestCase { @BeforeParameterizedClassInvocation static void zzz_before(TestReporter reporter) { reporter.publishEntry("zzz_before"); fail("zzz_before"); } @AfterParameterizedClassInvocation static void zzz_after(TestReporter reporter) { reporter.publishEntry("zzz_after"); fail("zzz_after"); } } @ParameterizedClass @ValueSource(ints = 1) static class LifecycleMethodsErrorHandlingTestCase extends AbstractBaseLifecycleWithErrorsTestCase { @BeforeParameterizedClassInvocation static void aaa_before(TestReporter reporter) { fail("should not be called"); } @AfterParameterizedClassInvocation static void aaa_after(TestReporter reporter) { reporter.publishEntry("aaa_after"); fail("aaa_after"); } @Test void test(TestReporter reporter) { reporter.publishEntry("test"); } } @ParameterizedClass @ValueSource(ints = 1) record LifecycleMethodArgumentInjectionWithConstructorInjectionTestCase( @ConvertWith(AtomicIntegerConverter.class) AtomicInteger counter) { @BeforeParameterizedClassInvocation static void before(AtomicInteger counter) { assertEquals(2, counter.incrementAndGet()); } @AfterParameterizedClassInvocation static void after(AtomicInteger counter) { assertEquals(4, counter.get()); } @Test void test1() { this.counter.incrementAndGet(); } @Test void test2() { this.counter.incrementAndGet(); } } @ParameterizedClass @ValueSource(ints = 1) static class LifecycleMethodArgumentInjectionWithFieldInjectionTestCase { @Parameter @ConvertWith(AtomicIntegerConverter.class) AtomicInteger counter; @BeforeParameterizedClassInvocation static void before(AtomicInteger counter) { assertEquals(2, counter.incrementAndGet()); } @AfterParameterizedClassInvocation static void after(AtomicInteger counter) { assertEquals(4, counter.get()); } @Test void test1() { this.counter.incrementAndGet(); } @Test void test2() { this.counter.incrementAndGet(); } } @NullMarked static class AtomicIntegerConverter extends SimpleArgumentConverter { @Override protected Object convert(@Nullable Object source, Class targetType) { return new AtomicInteger((Integer) requireNonNull(source)); } } @ParameterizedClass @ValueSource(strings = "foo") record CustomConverterAnnotationsWithLifecycleMethodsAndConstructorInjectionTestCase( @CustomConversion String value) { @BeforeParameterizedClassInvocation static void before(String value) { assertEquals("foo", value); } @Test void test() { assertEquals("foo", this.value); } } @ParameterizedClass @ValueSource(strings = "foo") static class CustomConverterAnnotationsWithLifecycleMethodsAndFieldInjectionTestCase { @Parameter @CustomConversion String value; @BeforeParameterizedClassInvocation static void before(String value) { assertEquals("foo", value); } @Test void test() { assertEquals("foo", this.value); } } @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.PARAMETER, ElementType.FIELD }) @ConvertWith(CustomConversion.Converter.class) @interface CustomConversion { class Converter implements ArgumentConverter { @Override public @Nullable Object convert(@Nullable Object source, ParameterContext context) throws ArgumentConversionException { assertNotNull(context.getParameter().getAnnotation(CustomConversion.class)); return source; } @Override public @Nullable Object convert(@Nullable Object source, FieldContext context) throws ArgumentConversionException { assertNotNull(context.getField().getAnnotation(CustomConversion.class)); return source; } } } @ParameterizedClass @ValueSource(ints = 1) static class ValidLifecycleMethodInjectionWithConstructorInjectionTestCase extends AbstractValidLifecycleMethodInjectionTestCase { private final AtomicInteger value; ValidLifecycleMethodInjectionWithConstructorInjectionTestCase( @ConvertWith(AtomicIntegerConverter.class) AtomicInteger value) { this.value = value; } @Test void test() { assertEquals(5, this.value.getAndIncrement()); } } @ParameterizedClass @ValueSource(ints = 1) static class ValidLifecycleMethodInjectionWithFieldInjectionTestCase extends AbstractValidLifecycleMethodInjectionTestCase { @Parameter @ConvertWith(AtomicIntegerConverter.class) AtomicInteger value; @Test void test() { assertEquals(5, this.value.getAndIncrement()); } } abstract static class AbstractValidLifecycleMethodInjectionTestCase { @BeforeParameterizedClassInvocation static void before0() { } @BeforeParameterizedClassInvocation static void before1(AtomicInteger value) { value.incrementAndGet(); } @BeforeParameterizedClassInvocation static void before2(ArgumentsAccessor accessor) { assertEquals(1, accessor.getInteger(0)); } @BeforeParameterizedClassInvocation static void before3(AtomicInteger value, TestInfo testInfo) { assertEquals("[1] value = 1", testInfo.getDisplayName()); value.incrementAndGet(); } @BeforeParameterizedClassInvocation static void before4(ArgumentsAccessor accessor, TestInfo testInfo) { assertEquals(1, accessor.getInteger(0)); assertEquals("[1] value = 1", testInfo.getDisplayName()); } @BeforeParameterizedClassInvocation static void before4(AtomicInteger value, ArgumentsAccessor accessor) { assertEquals(1, accessor.getInteger(0)); value.incrementAndGet(); } @BeforeParameterizedClassInvocation static void before5(AtomicInteger value, ArgumentsAccessor accessor, TestInfo testInfo) { assertEquals(1, accessor.getInteger(0)); assertEquals("[1] value = 1", testInfo.getDisplayName()); value.incrementAndGet(); } @BeforeParameterizedClassInvocation static void before6(@TimesTwo int valueTimesTwo) { assertEquals(2, valueTimesTwo); } @AfterParameterizedClassInvocation static void after(AtomicInteger value, ArgumentsAccessor accessor, TestInfo testInfo) { assertEquals(6, value.get()); assertEquals(1, accessor.getInteger(0)); assertEquals("[1] value = 1", testInfo.getDisplayName()); } } @ParameterizedClass @CsvSource("1, 2") record LifecycleMethodWithInvalidParametersTestCase(int value, int anotherValue) { @BeforeParameterizedClassInvocation static void before(long value, @ConvertWith(CustomIntegerToStringConverter.class) int anotherValue) { fail("should not be called"); } @Test void test() { fail("should not be called"); } } @ParameterizedClass @ValueSource(ints = 1) record LifecycleMethodWithInvalidParameterOrderTestCase(int value) { @BeforeParameterizedClassInvocation static void before(ArgumentsAccessor accessor1, int value, ArgumentsAccessor accessor2) { fail("should not be called"); } @Test void test() { fail("should not be called"); } } @ParameterizedClass @ValueSource(ints = 1) record LifecycleMethodWithParameterAfterAggregatorTestCase(int value) { @BeforeParameterizedClassInvocation static void before(@TimesTwo int valueTimesTwo, int value) { fail("should not be called"); } @Test void test() { fail("should not be called"); } } @ParameterizedClass // argument sets: 13 = 2 + 4 + 1 + 1 + 1 + 1 + 1 + 1 + 1 @ArgumentsSource(CustomArgumentsProvider.class) // 2 @CsvFileSource(resources = "two-column.csv") // 4 @CsvSource("csv") // 1 @EmptySource // 1 @EnumSource(EnumOne.class) // 1 @FieldSource("field") // 1 @MethodSource("method") // 1 @NullSource // 1 @ValueSource(strings = "value") // 1 abstract static class BaseInheritanceTestCase { static final List field = List.of("field"); static List method() { return List.of("method"); } @SuppressWarnings("unused") @Parameter @ConvertWith(ToStringConverter.class) // For @EnumSource String value; @Test void test() { } @Nested @ParameterizedClass @ValueSource(ints = 1) class Inner { @Test void test() { } } @NullMarked static class ToStringConverter extends SimpleArgumentConverter { @Override protected @Nullable Object convert(@Nullable Object source, Class targetType) throws ArgumentConversionException { return source == null ? null : String.valueOf(source); } } } static class ConcreteInheritanceTestCase extends BaseInheritanceTestCase { } static class RegularClassWithLifecycleMethodsTestCase { @BeforeParameterizedClassInvocation static void before() { } @AfterParameterizedClassInvocation static void after() { } @Test void test() { } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedInvocationNameFormatterTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Named.named; import static org.junit.jupiter.params.ParameterizedInvocationConstants.ARGUMENTS_PLACEHOLDER; import static org.junit.jupiter.params.ParameterizedInvocationConstants.ARGUMENTS_WITH_NAMES_PLACEHOLDER; import static org.junit.jupiter.params.ParameterizedInvocationConstants.ARGUMENT_SET_NAME_OR_ARGUMENTS_WITH_NAMES_PLACEHOLDER; import static org.junit.jupiter.params.ParameterizedInvocationConstants.ARGUMENT_SET_NAME_PLACEHOLDER; import static org.junit.jupiter.params.ParameterizedInvocationConstants.DEFAULT_DISPLAY_NAME; import static org.junit.jupiter.params.ParameterizedInvocationConstants.DISPLAY_NAME_PLACEHOLDER; import static org.junit.jupiter.params.ParameterizedInvocationConstants.INDEX_PLACEHOLDER; import static org.junit.jupiter.params.provider.Arguments.argumentSet; import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.lang.reflect.Method; import java.math.BigDecimal; import java.sql.Date; import java.time.LocalDate; import java.time.LocalTime; import java.time.ZoneId; import java.util.Arrays; import java.util.Locale; import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.AnnotatedElementContext; import org.junit.jupiter.api.extension.ExtensionConfigurationException; import org.junit.jupiter.params.aggregator.AggregateWith; import org.junit.jupiter.params.aggregator.ArgumentsAccessor; import org.junit.jupiter.params.aggregator.SimpleArgumentsAggregator; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.support.ParameterNameAndArgument; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.support.ReflectionSupport; /** * Tests for {@link ParameterizedInvocationNameFormatter}. * * @since 5.0 */ @SuppressWarnings("ALL") class ParameterizedInvocationNameFormatterTests { private final Locale originalLocale = Locale.getDefault(); @AfterEach void restoreLocale() { Locale.setDefault(originalLocale); } @Test void formatsDisplayName() { var formatter = formatter(DISPLAY_NAME_PLACEHOLDER, "enigma"); assertEquals("enigma", format(formatter, 1, arguments())); assertEquals("enigma", format(formatter, 2, arguments())); } @Test void formatsDisplayNameContainingApostrophe() { String displayName = "display'Zero"; var formatter = formatter(DISPLAY_NAME_PLACEHOLDER, "display'Zero"); assertEquals(displayName, format(formatter, 1, arguments())); assertEquals(displayName, format(formatter, 2, arguments())); } @Test void formatsDisplayNameContainingFormatElements() { String displayName = "{enigma} {0} '{1}'"; var formatter = formatter(DISPLAY_NAME_PLACEHOLDER, displayName); assertEquals(displayName, format(formatter, 1, arguments())); assertEquals(displayName, format(formatter, 2, arguments())); } @Test void formatsInvocationIndex() { var formatter = formatter(INDEX_PLACEHOLDER, "enigma"); assertEquals("1", format(formatter, 1, arguments())); assertEquals("2", format(formatter, 2, arguments())); } @Test void defaultDisplayName() { var formatter = formatter(DEFAULT_DISPLAY_NAME, "IGNORED"); var formattedName = format(formatter, 1, arguments("apple", "banana")); assertThat(formattedName).isEqualTo("[1] \"apple\", \"banana\""); } @Test void formatsIndividualArguments() { var formatter = formatter("{0} -> {1}", "enigma"); assertEquals("\"foo\" -> 42", format(formatter, 1, arguments("foo", 42))); } @Test void formatsCompleteArgumentsList() { var formatter = formatter(ARGUMENTS_PLACEHOLDER, "enigma"); // @formatter:off Arguments args = arguments( 42, '$', "enigma", null, new int[] { 1, 2, 3 }, new String[] { "foo", "bar" }, new Integer[][] { { 2, 4 }, { 3, 9 } } ); // @formatter:on assertEquals("42, '$', \"enigma\", null, [1, 2, 3], [foo, bar], [[2, 4], [3, 9]]", format(formatter, 1, args)); } @Test void formatsCompleteArgumentsListWithNames() { var testMethod = ParameterizedTestCases.getMethod("parameterizedTest", int.class, String.class, Object[].class); var formatter = formatter(ARGUMENTS_WITH_NAMES_PLACEHOLDER, "enigma", testMethod); var formattedName = format(formatter, 1, arguments(42, "enigma", new Object[] { "foo", 1 })); assertEquals("someNumber = 42, someString = \"enigma\", someArray = [foo, 1]", formattedName); } @Test void formatsCompleteArgumentsListWithoutNamesForAggregators() { var testMethod = ParameterizedTestCases.getMethod("parameterizedTestWithAggregator", int.class, String.class); var formatter = formatter(ARGUMENTS_WITH_NAMES_PLACEHOLDER, "enigma", testMethod); var formattedName = format(formatter, 1, arguments(42, "foo", "bar")); assertEquals("someNumber = 42, \"foo\", \"bar\"", formattedName); } @Test void formatsCompleteArgumentsListWithArrays() { var formatter = formatter(ARGUMENTS_PLACEHOLDER, "enigma"); // Explicit test for https://github.com/junit-team/junit-framework/issues/814 assertEquals("[foo, bar]", format(formatter, 1, arguments((Object) new String[] { "foo", "bar" }))); assertEquals("[foo, bar], 42, true", format(formatter, 1, arguments(new String[] { "foo", "bar" }, 42, true))); } @Test void formatsEverythingUsingCustomPattern() { var pattern = DISPLAY_NAME_PLACEHOLDER + " " + INDEX_PLACEHOLDER + " :: " + ARGUMENTS_PLACEHOLDER + " :: {1}"; var formatter = formatter(pattern, "enigma"); assertEquals("enigma 1 :: \"foo\", \"bar\" :: \"bar\"", format(formatter, 1, arguments("foo", "bar"))); assertEquals("enigma 2 :: \"foo\", 42 :: 42", format(formatter, 2, arguments("foo", 42))); } @Test void formatDoesNotAlterArgumentsArray() { Object[] actual = { 1, "two", Byte.valueOf("-128"), new Integer[][] { { 2, 4 }, { 3, 9 } } }; var formatter = formatter(ARGUMENTS_PLACEHOLDER, "enigma"); var expected = Arrays.copyOf(actual, actual.length); assertEquals("1, \"two\", -128, [[2, 4], [3, 9]]", format(formatter, 1, arguments(actual))); assertArrayEquals(expected, actual); } @Test void formatDoesNotRaiseAnArrayStoreException() { var formatter = formatter("{0} -> {1}", "enigma"); Object[] arguments = new Number[] { 1, 2 }; assertEquals("1 -> 2", format(formatter, 1, arguments(arguments))); } @Test void throwsReadableExceptionForInvalidPattern() { var exception = assertThrows(JUnitException.class, () -> formatter("{index", "enigma")); assertNotNull(exception.getCause()); assertEquals(IllegalArgumentException.class, exception.getCause().getClass()); } @Test void formattingDoesNotFailIfArgumentToStringImplementationReturnsNull() { var formatter = formatter(ARGUMENTS_PLACEHOLDER, "enigma"); var formattedName = format(formatter, 1, arguments(new ToStringReturnsNull(), "foo")); assertThat(formattedName).isEqualTo("null, \"foo\""); } @Test void formattingDoesNotFailIfArgumentToStringImplementationThrowsAnException() { var formatter = formatter(ARGUMENTS_PLACEHOLDER, "enigma"); var formattedName = format(formatter, 1, arguments(new ToStringThrowsException(), "foo")); assertThat(formattedName).startsWith(ToStringThrowsException.class.getName() + "@"); assertThat(formattedName).endsWith("\"foo\""); } @ParameterizedTest(name = "{0}") @CsvSource(delimiter = '|', textBlock = """ US | 42.23 is positive on 2019 Jan 13 at 12:34:56 DE | 42,23 is positive on 13.01.2019 at 12:34:56 """) void customFormattingExpressionsAreSupported(Locale locale, String expectedValue) { var pattern = "[{index}] {1,number,#.##} is {1,choice,0 format(formatter, 1, arguments())) .havingCause() .isExactlyInstanceOf(ExtensionConfigurationException.class) .withMessage("When the display name pattern for a @ParameterizedTest contains %s, " + "the arguments must be supplied as an ArgumentSet.", ARGUMENT_SET_NAME_PLACEHOLDER); // @formatter:on } @Test void defaultDisplayName() { var formatter = formatter(DEFAULT_DISPLAY_NAME, "IGNORED"); var formattedName = format(formatter, 42, argumentSet("Fruits", "apple", "banana")); assertThat(formattedName).isEqualTo("[42] Fruits"); } @Test void argumentSetNameAndArgumentsPlaceholders() { var pattern = ARGUMENT_SET_NAME_PLACEHOLDER + " :: " + ARGUMENTS_PLACEHOLDER; var formatter = formatter(pattern, "IGNORED"); var formattedName = format(formatter, -1, argumentSet("Fruits", "apple", "banana")); assertThat(formattedName).isEqualTo("Fruits :: \"apple\", \"banana\""); } @Test void mixedTypesOfArgumentsImplementationsAndCustomDisplayNamePattern() { var pattern = "[%s] %s :: %s".formatted(INDEX_PLACEHOLDER, DISPLAY_NAME_PLACEHOLDER, ARGUMENT_SET_NAME_OR_ARGUMENTS_WITH_NAMES_PLACEHOLDER); var testMethod = ParameterizedTestCases.getMethod("processFruits", String.class, String.class); var formatter = formatter(pattern, "Mixed Arguments Types", testMethod); var name1 = format(formatter, 1, argumentSet("Fruits", "apple", "banana")); var name2 = format(formatter, 2, arguments("apple", "banana")); assertThat(name1).isEqualTo("[1] Mixed Arguments Types :: Fruits"); assertThat(name2).isEqualTo("[2] Mixed Arguments Types :: fruit1 = \"apple\", fruit2 = \"banana\""); } } @Nested class QuotedTextTests { @ParameterizedTest @CsvSource(delimiterString = "->", textBlock = """ 'Jane Smith' -> 'Jane Smith' \\ -> \\\\ " -> \\" # The following represents a single ' enclosed in ''. '''' -> '''' '\n' -> \\n '\r\n' -> \\r\\n ' \t ' -> ' \\t ' '\b' -> \\b '\f' -> \\f '\u0007' -> '\u0007' """) void quotedStrings(String argument, String expected) { var formatter = formatter(DEFAULT_DISPLAY_NAME, "IGNORED"); var formattedName = format(formatter, 1, arguments(argument)); assertThat(formattedName).isEqualTo("[1] " + '"' + expected + '"'); } @ParameterizedTest @CsvSource(quoteCharacter = '"', delimiterString = "->", textBlock = """ X -> X \\ -> \\\\ ' -> \\' # The following represents a single " enclosed in "". The escaping is # necessary, because three " characters in a row close the text block. \"""\" -> \"""\" "\n" -> \\n "\r" -> \\r "\t" -> \\t "\b" -> \\b "\f" -> \\f "\u0007" -> "\u0007" """) void quotedCharacters(char argument, String expected) { var formatter = formatter(DEFAULT_DISPLAY_NAME, "IGNORED"); var formattedName = format(formatter, 1, arguments(argument)); assertThat(formattedName).isEqualTo("[1] " + "'" + expected + "'"); } @Test void quotedStringsForArgumentsWithNames() { var testMethod = ParameterizedTestCases.getMethod("processFruit", String.class, int.class); var formatter = formatter(DEFAULT_DISPLAY_NAME, "IGNORED", testMethod); var name1 = format(formatter, 1, arguments("apple", 42)); var name2 = format(formatter, 2, arguments("banana", 99)); assertThat(name1).isEqualTo("[1] fruit = \"apple\", ranking = 42"); assertThat(name2).isEqualTo("[2] fruit = \"banana\", ranking = 99"); } @Test void quotedStringsForArgumentsWithNamesAndNamedArguments() { var testMethod = ParameterizedTestCases.getMethod("processFruit", String.class, int.class); var formatter = formatter(DEFAULT_DISPLAY_NAME, "IGNORED", testMethod); var name1 = format(formatter, 1, arguments(named("Apple", "apple"), 42)); var name2 = format(formatter, 2, arguments(named("Banana", "banana"), 99)); assertThat(name1).isEqualTo("[1] fruit = Apple, ranking = 42"); assertThat(name2).isEqualTo("[2] fruit = Banana, ranking = 99"); } @Test void quotedStringsForArgumentsWithNamesAndParameterNameAndArgument() { var testMethod = ParameterizedTestCases.getMethod("processFruit", String.class, int.class); var formatter = formatter(DEFAULT_DISPLAY_NAME, "IGNORED", testMethod); var name1 = format(formatter, 1, arguments(new ParameterNameAndArgument("FRUIT", "apple"), 42)); var name2 = format(formatter, 2, arguments(new ParameterNameAndArgument("FRUCHT", "Banane"), 99)); assertThat(name1).isEqualTo("[1] FRUIT = \"apple\", ranking = 42"); assertThat(name2).isEqualTo("[2] FRUCHT = \"Banane\", ranking = 99"); } } // ------------------------------------------------------------------------- private static ParameterizedInvocationNameFormatter formatter(String pattern, String displayName) { return formatter(pattern, displayName, 512); } private static ParameterizedInvocationNameFormatter formatter(String pattern, String displayName, int argumentMaxLength) { ParameterizedDeclarationContext context = mock(); when(context.getResolverFacade()).thenReturn(mock()); when(context.getAnnotationName()).thenReturn(ParameterizedTest.class.getSimpleName()); return new ParameterizedInvocationNameFormatter(pattern, displayName, context, argumentMaxLength); } private static ParameterizedInvocationNameFormatter formatter(String pattern, String displayName, Method method) { var context = new ParameterizedTestContext(method.getDeclaringClass(), method, method.getAnnotation(ParameterizedTest.class)); return new ParameterizedInvocationNameFormatter(pattern, displayName, context, 512); } private static String format(ParameterizedInvocationNameFormatter formatter, int invocationIndex, Arguments arguments) { return formatter.format(invocationIndex, EvaluatedArgumentSet.allOf(arguments), true); } @NullUnmarked private static class ToStringReturnsNull { @Override public String toString() { return null; } } private static class ToStringThrowsException { @Override public String toString() { throw new RuntimeException("Boom!"); } } private static class ParameterizedTestCases { static Method getMethod(String methodName, Class... parameterTypes) { return ReflectionSupport.findMethod(ParameterizedTestCases.class, methodName, parameterTypes).orElseThrow(); } @SuppressWarnings("unused") @ParameterizedTest void parameterizedTest(int someNumber, String someString, Object[] someArray) { } @SuppressWarnings("unused") @ParameterizedTest void parameterizedTestWithAggregator(int someNumber, @AggregateWith(CustomAggregator.class) String someAggregatedString) { } @SuppressWarnings("unused") @ParameterizedTest void processFruit(String fruit, int ranking) { } @SuppressWarnings("unused") @ParameterizedTest void processFruits(String fruit1, String fruit2) { } private static class CustomAggregator extends SimpleArgumentsAggregator { @Override protected @Nullable Object aggregateArguments(ArgumentsAccessor accessor, Class targetType, AnnotatedElementContext context, int parameterIndex) { return accessor.get(0); } } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestContextTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.params.aggregator.AggregatorIntegrationTests.CsvToPerson; import org.junit.jupiter.params.aggregator.AggregatorIntegrationTests.Person; import org.junit.jupiter.params.aggregator.ArgumentsAccessor; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.commons.util.ReflectionUtils; /** * Unit tests for {@link ParameterizedTestContext}. * * @since 5.2 */ class ParameterizedTestContextTests { @ParameterizedTest @ValueSource(strings = { "onePrimitive", "twoPrimitives", "twoAggregators", "twoAggregatorsWithTestInfoAtTheEnd", "mixedMode" }) void validSignatures(String methodName) { assertDoesNotThrow(() -> createMethodContext(ValidTestCase.class, methodName)); } @ParameterizedTest @ValueSource(strings = { "twoAggregatorsWithPrimitiveInTheMiddle", "twoAggregatorsWithTestInfoInTheMiddle" }) void invalidSignatures(String methodName) { assertPreconditionViolationFor(() -> createMethodContext(InvalidTestCase.class, methodName)); } private ParameterizedTestContext createMethodContext(Class testClass, String methodName) { var method = ReflectionUtils.findMethods(testClass, m -> m.getName().equals(methodName)).getFirst(); return new ParameterizedTestContext(testClass, method, method.getAnnotation(ParameterizedTest.class)); } @SuppressWarnings("JUnitMalformedDeclaration") static class ValidTestCase { @ParameterizedTest void onePrimitive(int num) { } @ParameterizedTest void twoPrimitives(int num1, int num2) { } @ParameterizedTest void twoAggregators(@CsvToPerson Person person, ArgumentsAccessor arguments) { } @ParameterizedTest void twoAggregatorsWithTestInfoAtTheEnd(@CsvToPerson Person person1, @CsvToPerson Person person2, TestInfo testInfo) { } @ParameterizedTest void mixedMode(int num1, int num2, ArgumentsAccessor arguments1, ArgumentsAccessor arguments2, @CsvToPerson Person person1, @CsvToPerson Person person2, TestInfo testInfo1, TestInfo testInfo2) { } } @SuppressWarnings("JUnitMalformedDeclaration") static class InvalidTestCase { @ParameterizedTest void twoAggregatorsWithPrimitiveInTheMiddle(@CsvToPerson Person person1, int num, @CsvToPerson Person person2) { } @ParameterizedTest void twoAggregatorsWithTestInfoInTheMiddle(@CsvToPerson Person person1, TestInfo testInfo, @CsvToPerson Person person2) { } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.params.ParameterizedInvocationContextProvider.arguments; import static org.junit.jupiter.params.ParameterizedTestExtension.DECLARATION_CONTEXT_KEY; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.mockito.Mockito.mock; import java.io.FileNotFoundException; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.nio.file.Path; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.stream.Stream; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.MediaType; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.extension.ExecutableInvoker; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TemplateInvocationValidationException; import org.junit.jupiter.api.extension.TestInstances; import org.junit.jupiter.api.function.ThrowingConsumer; import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.jupiter.engine.execution.NamespaceAwareStore; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.ArgumentsProvider; import org.junit.jupiter.params.provider.ArgumentsSource; import org.junit.jupiter.params.support.ParameterDeclarations; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.support.AnnotationSupport; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; /** * Unit tests for {@link ParameterizedTestExtension}. * * @since 5.0 */ class ParameterizedTestExtensionTests { private final ParameterizedTestExtension parameterizedTestExtension = new ParameterizedTestExtension(); static boolean streamWasClosed = false; @Test void supportsReturnsFalseForMissingTestMethod() { var extensionContextWithoutTestMethod = getExtensionContextReturningSingleMethod(new TestCaseWithoutMethod()); assertFalse(this.parameterizedTestExtension.supportsTestTemplate(extensionContextWithoutTestMethod)); } @Test void supportsReturnsFalseForTestMethodWithoutParameterizedTestAnnotation() { var extensionContextWithUnAnnotatedTestMethod = getExtensionContextReturningSingleMethod( new TestCaseWithMethod()); assertFalse(this.parameterizedTestExtension.supportsTestTemplate(extensionContextWithUnAnnotatedTestMethod)); } @Test void supportsReturnsTrueForTestMethodWithParameterizedTestAnnotation() { var extensionContextWithAnnotatedTestMethod = getExtensionContextReturningSingleMethod( new TestCaseWithAnnotatedMethod()); assertTrue(this.parameterizedTestExtension.supportsTestTemplate(extensionContextWithAnnotatedTestMethod)); } @Test void streamsReturnedByProvidersAreClosedWhenCallingProvide() { var extensionContext = getExtensionContextReturningSingleMethod( new ArgumentsProviderWithCloseHandlerTestCase()); // we need to call supportsTestTemplate() first, because it creates and // puts the ParameterizedTestMethodContext into the Store this.parameterizedTestExtension.supportsTestTemplate(extensionContext); var stream = this.parameterizedTestExtension.provideTestTemplateInvocationContexts(extensionContext); assertFalse(streamWasClosed); // cause the stream to be evaluated stream.count(); assertTrue(streamWasClosed); } @Test void emptyDisplayNameIsIllegal() { var extensionContext = getExtensionContextReturningSingleMethod(new EmptyDisplayNameProviderTestCase()); assertPreconditionViolationFor( () -> this.parameterizedTestExtension.provideTestTemplateInvocationContexts(extensionContext)); } @Test void defaultDisplayNameWithEmptyStringInConfigurationIsIllegal() { AtomicInteger invocations = new AtomicInteger(); Function> configurationSupplier = key -> { if (key.equals(ParameterizedInvocationNameFormatter.DISPLAY_NAME_PATTERN_KEY)) { invocations.incrementAndGet(); return Optional.of(""); } else { return Optional.empty(); } }; var extensionContext = getExtensionContextReturningSingleMethod(new DefaultDisplayNameProviderTestCase(), configurationSupplier); assertPreconditionViolationFor( () -> this.parameterizedTestExtension.provideTestTemplateInvocationContexts(extensionContext)); assertEquals(1, invocations.get()); } @Test void argumentsRethrowsOriginalExceptionFromProviderAsUncheckedException() { ArgumentsProvider failingProvider = new ArgumentsProvider() { @Override public Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context) throws Exception { throw new FileNotFoundException("a message"); } }; var exception = assertThrows(FileNotFoundException.class, () -> arguments(failingProvider, mock(), mock())); assertEquals("a message", exception.getMessage()); } @Test void throwsExceptionWhenParameterizedTestIsNotInvokedAtLeastOnce() { var extensionContextWithAnnotatedTestMethod = getExtensionContextReturningSingleMethod( new TestCaseWithAnnotatedMethod()); var stream = this.parameterizedTestExtension.provideTestTemplateInvocationContexts( extensionContextWithAnnotatedTestMethod); // cause the stream to be evaluated stream.toArray(); var exception = assertThrows(TemplateInvocationValidationException.class, stream::close); assertThat(exception).hasMessage( "Configuration error: You must configure at least one set of arguments for this @ParameterizedTest"); } @Test void doesNotThrowExceptionWhenParametrizedTestDoesNotRequireArguments() { var extensionContext = getExtensionContextReturningSingleMethod(new TestCaseAllowNoArgumentsMethod()); var stream = this.parameterizedTestExtension.provideTestTemplateInvocationContexts(extensionContext); // cause the stream to be evaluated stream.toArray(); stream.close(); } @Test void throwsExceptionWhenParameterizedTestHasNoArgumentsSource() { var extensionContext = getExtensionContextReturningSingleMethod(new TestCaseWithNoArgumentsSource()); assertPreconditionViolationFor( () -> this.parameterizedTestExtension.provideTestTemplateInvocationContexts(extensionContext))// .withMessage( "Configuration error: You must configure at least one arguments source for this @ParameterizedTest"); } @Test void throwsExceptionWhenArgumentsProviderIsNotStatic() { var extensionContextWithAnnotatedTestMethod = getExtensionContextReturningSingleMethod( new NonStaticArgumentsProviderTestCase()); var stream = this.parameterizedTestExtension.provideTestTemplateInvocationContexts( extensionContextWithAnnotatedTestMethod); var exception = assertThrows(JUnitException.class, stream::toArray); assertThat(exception) // .hasMessage( "The ArgumentsProvider [%s] must be either a top-level class or a static nested class".formatted( NonStaticArgumentsProvider.class.getName())); } @Test void throwsExceptionWhenArgumentsProviderDoesNotContainUnambiguousConstructor() { var extensionContextWithAnnotatedTestMethod = getExtensionContextReturningSingleMethod( new MissingNoArgumentsConstructorArgumentsProviderTestCase()); var stream = this.parameterizedTestExtension.provideTestTemplateInvocationContexts( extensionContextWithAnnotatedTestMethod); var exception = assertThrows(JUnitException.class, stream::toArray); String className = AmbiguousConstructorArgumentsProvider.class.getName(); assertThat(exception) // .hasMessage(""" Failed to find constructor for ArgumentsProvider [%s]. \ Please ensure that a no-argument or a single constructor exists.""", className); } private ExtensionContext getExtensionContextReturningSingleMethod(Object testCase) { return getExtensionContextReturningSingleMethod(testCase, ignored -> Optional.empty()); } @NullMarked private ExtensionContext getExtensionContextReturningSingleMethod(Object testCase, Function> configurationSupplier) { Class testClass = testCase.getClass(); var method = ReflectionUtils.findMethods(testClass, it -> "method".equals(it.getName())).stream().findFirst(); return new ExtensionContext() { private final NamespacedHierarchicalStore store = new NamespacedHierarchicalStore<>( null); @Override public Optional getTestMethod() { return method; } @Override public Optional getParent() { return Optional.empty(); } @Override public ExtensionContext getRoot() { return this; } @Override public String getUniqueId() { throw new UnsupportedOperationException(); } @Override public String getDisplayName() { return "display-name"; } @Override public Set getTags() { throw new UnsupportedOperationException(); } @Override public Optional getElement() { return Optional.empty(); } @Override public Optional> getTestClass() { return Optional.of(testClass); } @Override public List> getEnclosingTestClasses() { return List.of(); } @Override public Optional getTestInstanceLifecycle() { return Optional.empty(); } @Override public Optional getTestInstance() { return Optional.empty(); } @Override public Optional getTestInstances() { return Optional.empty(); } @Override public Optional getExecutionException() { return Optional.empty(); } @Override public Optional getConfigurationParameter(String key) { return configurationSupplier.apply(key); } @Override public Optional getConfigurationParameter(String key, Function transformer) { return configurationSupplier.apply(key).map(transformer); } @Override public void publishReportEntry(Map map) { } @Override public void publishFile(String fileName, MediaType mediaType, ThrowingConsumer action) { } @Override public void publishDirectory(String name, ThrowingConsumer action) { } @Override public Store getStore(Namespace namespace) { var store = new NamespaceAwareStore(this.store, org.junit.platform.engine.support.store.Namespace.create(namespace.getParts())); method // .map(it -> new ParameterizedTestContext(testClass, it, AnnotationSupport.findAnnotation(it, ParameterizedTest.class).orElseThrow())) // .ifPresent(ctx -> store.put(DECLARATION_CONTEXT_KEY, ctx)); return store; } @Override public Store getStore(StoreScope scope, Namespace namespace) { return getStore(namespace); } @Override public ExecutionMode getExecutionMode() { return ExecutionMode.SAME_THREAD; } @Override public ExecutableInvoker getExecutableInvoker() { return new ExecutableInvoker() { @Override public Object invoke(Method method, @Nullable Object target) { throw new UnsupportedOperationException(); } @Override public T invoke(Constructor constructor, @Nullable Object outerInstance) { return ReflectionUtils.newInstance(constructor); } }; } }; } static class TestCaseWithoutMethod { } static class TestCaseWithMethod { void method() { } } static class TestCaseWithAnnotatedMethod { @ParameterizedTest @ArgumentsSource(ZeroArgumentsProvider.class) void method() { } } static class TestCaseAllowNoArgumentsMethod { @ParameterizedTest(allowZeroInvocations = true) @ArgumentsSource(ZeroArgumentsProvider.class) void method() { } } static class TestCaseWithNoArgumentsSource { @ParameterizedTest(allowZeroInvocations = true) @SuppressWarnings("JUnitMalformedDeclaration") void method() { } } @NullMarked static class ZeroArgumentsProvider implements ArgumentsProvider { @Override public Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context) { return Stream.empty(); } } static class ArgumentsProviderWithCloseHandlerTestCase { @ParameterizedTest @ArgumentsSource(ArgumentsProviderWithCloseHandler.class) void method(String parameter) { } } @NullMarked static class ArgumentsProviderWithCloseHandler implements ArgumentsProvider { @Override public Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context) { var argumentsStream = Stream.of("foo", "bar").map(Arguments::of); return argumentsStream.onClose(() -> streamWasClosed = true); } } static class NonStaticArgumentsProviderTestCase { @ParameterizedTest @ArgumentsSource(NonStaticArgumentsProvider.class) void method() { } } @SuppressWarnings("InnerClassMayBeStatic") @NullMarked class NonStaticArgumentsProvider implements ArgumentsProvider { @Override public Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context) { throw new UnsupportedOperationException(); } } static class MissingNoArgumentsConstructorArgumentsProviderTestCase { @ParameterizedTest @ArgumentsSource(AmbiguousConstructorArgumentsProvider.class) void method() { } } static class EmptyDisplayNameProviderTestCase { @ParameterizedTest(name = "") @ArgumentsSource(AmbiguousConstructorArgumentsProvider.class) void method() { } } static class DefaultDisplayNameProviderTestCase { @ParameterizedTest @ArgumentsSource(AmbiguousConstructorArgumentsProvider.class) void method() { } } @NullMarked static class AmbiguousConstructorArgumentsProvider implements ArgumentsProvider { @SuppressWarnings("unused") AmbiguousConstructorArgumentsProvider(String parameter) { } @SuppressWarnings("unused") AmbiguousConstructorArgumentsProvider(int parameter) { } @Override public Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context) { throw new UnsupportedOperationException(); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import static java.lang.annotation.RetentionPolicy.RUNTIME; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.within; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.junit.jupiter.api.Named.named; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.appendTestTemplateInvocationSegment; import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.uniqueIdForTestTemplateMethod; import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectIteration; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.testkit.engine.EventConditions.abortedWithReason; import static org.junit.platform.testkit.engine.EventConditions.container; import static org.junit.platform.testkit.engine.EventConditions.displayName; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.EventConditions.started; import static org.junit.platform.testkit.engine.EventConditions.test; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Locale; import java.util.Map; import java.util.NavigableMap; import java.util.NavigableSet; import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; import java.util.function.Supplier; import java.util.stream.Stream; import org.assertj.core.api.InstanceOfAssertFactories; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; import org.junit.jupiter.api.Named; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.TestReporter; import org.junit.jupiter.api.extension.AnnotatedElementContext; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.TemplateInvocationValidationException; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.jupiter.engine.JupiterTestEngine; import org.junit.jupiter.params.ParameterizedTestIntegrationTests.RepeatableSourcesTestCase.Action; import org.junit.jupiter.params.aggregator.AggregateWith; import org.junit.jupiter.params.aggregator.ArgumentsAccessor; import org.junit.jupiter.params.aggregator.ArgumentsAggregationException; import org.junit.jupiter.params.aggregator.SimpleArgumentsAggregator; import org.junit.jupiter.params.converter.ArgumentConversionException; import org.junit.jupiter.params.converter.ArgumentConverter; import org.junit.jupiter.params.converter.ConvertWith; import org.junit.jupiter.params.converter.TypedArgumentConverter; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.ArgumentsProvider; import org.junit.jupiter.params.provider.ArgumentsSource; import org.junit.jupiter.params.provider.CsvFileSource; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.EmptySource; import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.FieldSource; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.NullAndEmptySource; import org.junit.jupiter.params.provider.NullSource; import org.junit.jupiter.params.provider.ValueSource; import org.junit.jupiter.params.support.ParameterDeclarations; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.ClassUtils; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.testkit.engine.EngineExecutionResults; import org.junit.platform.testkit.engine.EngineTestKit; import org.junit.platform.testkit.engine.Event; import org.junit.platform.testkit.engine.EventConditions; import org.opentest4j.TestAbortedException; /** * @since 5.0 */ class ParameterizedTestIntegrationTests extends AbstractJupiterTestEngineTests { private final Locale originalLocale = Locale.getDefault(Locale.Category.FORMAT); @AfterEach void reset() { Locale.setDefault(Locale.Category.FORMAT, originalLocale); AutoCloseableArgument.closeCounter = 0; } @ParameterizedTest @CsvSource(textBlock = """ apple, True banana, true lemon, false kumquat, FALSE """) void sweetFruit(String fruit, Boolean sweet) { switch (fruit) { case "apple" -> assertThat(sweet).isTrue(); case "banana" -> assertThat(sweet).isTrue(); case "lemon" -> assertThat(sweet).isFalse(); case "kumquat" -> assertThat(sweet).isFalse(); default -> fail("Unexpected fruit : " + fruit); } } @ParameterizedTest @CsvSource(nullValues = "null", textBlock = """ apple, True banana, true lemon, false kumquat, null """) void sweetFruitWithNullableBoolean(String fruit, Boolean sweet) { switch (fruit) { case "apple" -> assertThat(sweet).isTrue(); case "banana" -> assertThat(sweet).isTrue(); case "lemon" -> assertThat(sweet).isFalse(); case "kumquat" -> assertThat(sweet).isNull(); // null --> null default -> fail("Unexpected fruit : " + fruit); } } @ParameterizedTest @CsvSource(quoteCharacter = '"', textBlock = """ # This is a comment preceded by multiple opening blank lines. apple, 1 banana, 2 # This is a comment pointing out that the next line contains multiple explicit newlines in quoted text. "lemon \s \s lime", 0xF1 # The next line is a blank line in the middle of the CSV rows. strawberry, 700_000 # This is a comment followed by 2 closing blank line. """) void executesLinesFromTextBlock(String fruit, int rank) { switch (fruit) { case "apple" -> assertThat(rank).isEqualTo(1); case "banana" -> assertThat(rank).isEqualTo(2); case "lemon \n\n\n lime" -> assertThat(rank).isEqualTo(241); case "strawberry" -> assertThat(rank).isEqualTo(700_000); default -> fail("Unexpected fruit : " + fruit); } } @ParameterizedTest(name = "[{index}] {arguments}") @CsvSource(delimiter = '|', useHeadersInDisplayName = true, nullValues = "NIL", textBlock = """ #--------------------------------- FRUIT | RANK #--------------------------------- apple | 1 #--------------------------------- banana | 2 #--------------------------------- cherry | 3.14159265358979323846 #--------------------------------- | 0 #--------------------------------- NIL | 0 #--------------------------------- """) void executesLinesFromTextBlockUsingTableFormatAndHeadersAndNullValues(String fruit, double rank, TestInfo testInfo) { assertFruitTable(fruit, rank, testInfo); } @ParameterizedTest(name = "[{index}] {arguments}") @CsvFileSource(resources = "two-column-with-headers.csv", delimiter = '|', useHeadersInDisplayName = true, nullValues = "NIL") void executesLinesFromClasspathResourceUsingTableFormatAndHeadersAndNullValues(String fruit, double rank, TestInfo testInfo) { assertFruitTable(fruit, rank, testInfo); } private void assertFruitTable(@Nullable String fruit, double rank, TestInfo testInfo) { String displayName = testInfo.getDisplayName(); if (fruit == null) { assertThat(rank).isEqualTo(0); assertThat(displayName).matches("\\[(4|5)\\] FRUIT = null, RANK = \"0\""); return; } switch (fruit) { case "apple" -> { assertThat(rank).isEqualTo(1); assertThat(displayName).isEqualTo("[1] FRUIT = \"apple\", RANK = \"1\""); } case "banana" -> { assertThat(rank).isEqualTo(2); assertThat(displayName).isEqualTo("[2] FRUIT = \"banana\", RANK = \"2\""); } case "cherry" -> { assertThat(rank).isCloseTo(Math.PI, within(0.0)); assertThat(displayName).isEqualTo("[3] FRUIT = \"cherry\", RANK = \"3.14159265358979323846\""); } default -> fail("Unexpected fruit : " + fruit); } } @Test void executesWithSingleArgumentsProviderWithMultipleInvocations() { var results = execute("testWithTwoSingleStringArgumentsProvider", String.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), displayName("[1] argument = foo"), finishedWithFailure(message("foo")))) // .haveExactly(1, event(test(), displayName("[2] argument = bar"), finishedWithFailure(message("bar")))); } @Test void executesWithCsvSource() { var results = execute("testWithCsvSource", String.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), displayName("[1] argument = foo"), finishedWithFailure(message("foo")))) // .haveExactly(1, event(test(), displayName("[2] argument = bar"), finishedWithFailure(message("bar")))); } /** * @since 6.0 */ @Test void executesWithCsvSourceAndSpecialCharacters() { // @formatter:off execute("testWithCsvSourceAndSpecialCharacters", String.class) .testEvents() .started() .assertEventsMatchExactly( displayName(quoted("üñåé")), displayName(quoted("\\n")), displayName(quoted("\\r")), displayName(quoted("\uFFFD")), displayName(quoted("😱")), displayName(quoted("Zero\u200BWidth\u200BSpaces")) ); // @formatter:on } private static String quoted(String text) { return '"' + text + '"'; } @Test void executesWithCustomName() { var results = execute("testWithCustomName", String.class, int.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), displayName("foo and 23"), finishedWithFailure(message("foo, 23")))) // .haveExactly(1, event(test(), displayName("bar and 42"), finishedWithFailure(message("bar, 42")))); } @Test void executesWithMessageFormat() { Locale.setDefault(Locale.Category.FORMAT, Locale.ROOT); var results = execute("testWithMessageFormat", double.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), displayName("3.1416"), finishedWithFailure(message(String.valueOf(Math.PI))))); } /** * @since 5.2 */ @Test void executesWithPrimitiveWideningConversion() { var results = execute("testWithPrimitiveWideningConversion", double.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), displayName("[1] num = 1"), finishedWithFailure(message("num: 1.0")))) // .haveExactly(1, event(test(), displayName("[2] num = 2"), finishedWithFailure(message("num: 2.0")))); } /** * @since 5.1 */ @Test void executesWithImplicitGenericConverter() { var results = execute("testWithImplicitGenericConverter", Book.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), displayName("[1] book = book 1"), finishedWithFailure(message("book 1")))) // .haveExactly(1, event(test(), displayName("[2] book = book 2"), finishedWithFailure(message("book 2")))); } /** * @since 6.0 */ @Test void executesWithImplicitGenericConverterWithCharSequenceConstructor() { var results = execute("testWithImplicitGenericConverterWithCharSequenceConstructor", Record.class); results.testEvents().assertThatEvents() // .haveExactly(1, event(displayName("\"record 1\""), finishedWithFailure(message("record 1")))) // .haveExactly(1, event(displayName("\"record 2\""), finishedWithFailure(message("record 2")))); } @Test void legacyReportingNames() { var results = execute("testWithCustomName", String.class, int.class); // @formatter:off var legacyReportingNames = results.testEvents().dynamicallyRegistered() .map(Event::getTestDescriptor) .map(TestDescriptor::getLegacyReportingName); // @formatter:on assertThat(legacyReportingNames).containsExactly("testWithCustomName(String, int)[1]", "testWithCustomName(String, int)[2]"); } @Test void executesWithExplicitConverter() { var results = execute("testWithExplicitConverter", int.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), displayName("[1] length = O"), finishedWithFailure(message("length: 1")))) // .haveExactly(1, event(test(), displayName("[2] length = XXX"), finishedWithFailure(message("length: 3")))); } @Test void executesWithAggregator() { var results = execute("testWithAggregator", String.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), displayName("[1] ab, cd"), finishedWithFailure(message("concatenation: abcd")))) // .haveExactly(1, event(test(), displayName("[2] ef, gh"), finishedWithFailure(message("concatenation: efgh")))); } @Test void executesWithIgnoreLeadingAndTrailingSetToFalseForCsvSource() { var results = execute("testWithIgnoreLeadingAndTrailingWhitespaceSetToFalseForCsvSource", String.class, String.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), finishedWithFailure(message("arguments: ' ab ', ' cd'")))) // .haveExactly(1, event(test(), finishedWithFailure(message("arguments: 'ef ', 'gh'")))); } @Test void executesWithIgnoreLeadingAndTrailingSetToTrueForCsvSource() { var results = execute("testWithIgnoreLeadingAndTrailingWhitespaceSetToTrueForCsvSource", String.class, String.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), finishedWithFailure(message("arguments: 'ab', 'cd'")))) // .haveExactly(1, event(test(), finishedWithFailure(message("arguments: 'ef', 'gh'")))); } @Test void executesWithIgnoreLeadingAndTrailingSetToFalseForCsvFileSource() { var results = execute("testWithIgnoreLeadingAndTrailingWhitespaceSetToFalseForCsvFileSource", String.class, String.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), finishedWithFailure(message("arguments: ' ab ', ' cd'")))) // .haveExactly(1, event(test(), finishedWithFailure(message("arguments: 'ef ', 'gh'")))); } @Test void executesWithIgnoreLeadingAndTrailingSetToTrueForCsvFileSource() { var results = execute("testWithIgnoreLeadingAndTrailingWhitespaceSetToTrueForCsvFileSource", String.class, String.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), finishedWithFailure(message("arguments: 'ab', 'cd'")))) // .haveExactly(1, event(test(), finishedWithFailure(message("arguments: 'ef', 'gh'")))); } @Test void failsContainerOnEmptyName() { var results = execute("testWithEmptyName", String.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(container(), displayName("testWithEmptyName(String)"), // finishedWithFailure(message(msg -> msg.contains("must be declared with a non-empty name"))))); } @Test void reportsExceptionForErroneousConverter() { var results = execute("testWithErroneousConverter", Object.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), finishedWithFailure(instanceOf(ParameterResolutionException.class), // message("Error converting parameter at index 0: something went horribly wrong")))); } @Test void executesLifecycleMethods() { // reset static collections LifecycleTestCase.lifecycleEvents.clear(); LifecycleTestCase.testMethods.clear(); var results = executeTestsForClass(LifecycleTestCase.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test("test1"), displayName("[1] argument = foo"), finishedWithFailure(message("foo")))) // .haveExactly(1, event(test("test1"), displayName("[2] argument = bar"), finishedWithFailure(message("bar")))); List testMethods = new ArrayList<>(LifecycleTestCase.testMethods); // @formatter:off assertThat(LifecycleTestCase.lifecycleEvents).containsExactly( "beforeAll:ParameterizedTestIntegrationTests$LifecycleTestCase", "providerMethod", "constructor:[1] argument = foo", "beforeEach:[1] argument = foo", testMethods.get(0) + ":[1] argument = foo", "afterEach:[1] argument = foo", "constructor:[2] argument = bar", "beforeEach:[2] argument = bar", testMethods.get(0) + ":[2] argument = bar", "afterEach:[2] argument = bar", "providerMethod", "constructor:[1] argument = foo", "beforeEach:[1] argument = foo", testMethods.get(1) + ":[1] argument = foo", "afterEach:[1] argument = foo", "constructor:[2] argument = bar", "beforeEach:[2] argument = bar", testMethods.get(1) + ":[2] argument = bar", "afterEach:[2] argument = bar", "afterAll:ParameterizedTestIntegrationTests$LifecycleTestCase"); // @formatter:on } @Test void truncatesArgumentsThatExceedMaxLength() { var results = EngineTestKit.engine(new JupiterTestEngine()) // .configurationParameter(ParameterizedInvocationNameFormatter.ARGUMENT_MAX_LENGTH_KEY, "2") // .selectors(selectMethod(TestCase.class, "testWithCsvSource", String.class.getName())) // .execute(); results.testEvents().assertThatEvents() // .haveExactly(1, event(displayName("[1] argument = f…"), started())) // .haveExactly(1, event(displayName("[2] argument = b…"), started())); } @Test void displayNamePatternFromConfiguration() { var results = EngineTestKit.engine(new JupiterTestEngine()) // .configurationParameter(ParameterizedInvocationNameFormatter.DISPLAY_NAME_PATTERN_KEY, "{index}") // .selectors(selectMethod(TestCase.class, "testWithCsvSource", String.class.getName())) // .execute(); results.testEvents().assertThatEvents() // .haveExactly(1, event(displayName("1"), started())) // .haveExactly(1, event(displayName("2"), started())); } @Test void failsWhenInvocationIsRequiredButNoArgumentSetsAreProvided() { var results = execute(ZeroInvocationsTestCase.class, "testThatRequiresInvocations", String.class); results.containerEvents().assertThatEvents() // .haveExactly(1, event(finishedWithFailure(instanceOf(TemplateInvocationValidationException.class), message( "Configuration error: You must configure at least one set of arguments for this @ParameterizedTest")))); } @Test void doesNotFailWhenInvocationIsNotRequiredAndNoArgumentSetsAreProvided() { var results = execute(ZeroInvocationsTestCase.class, "testThatDoesNotRequireInvocations", String.class); results.allEvents().assertStatistics(stats -> stats.started(3).succeeded(3)); } @Test void failsWhenNoArgumentsSourceIsDeclared() { var results = execute(ZeroInvocationsTestCase.class, "testThatHasNoArgumentsSource", String.class); results.containerEvents().assertThatEvents() // .haveExactly(1, // event(displayName("testThatHasNoArgumentsSource(String)"), finishedWithFailure(message( "Configuration error: You must configure at least one arguments source for this @ParameterizedTest")))); } @Test void executesWithDefaultLocaleConversionFormat() { var results = execute(LocaleConversionTestCase.class, "testWithBcp47", Locale.class); results.allEvents().assertStatistics(stats -> stats.started(4).succeeded(4)); } @Test void emitsWarningForNoLongerSupportedConfigurationParameter() { var results = discoverTests(request -> request // .configurationParameter("junit.jupiter.params.arguments.conversion.locale.format", "iso_639") // .selectors(selectMethod(LocaleConversionTestCase.class, "testWithBcp47", Locale.class))); assertThat(results.getDiscoveryIssues()) // .contains(DiscoveryIssue.create(Severity.WARNING, """ The 'junit.jupiter.params.arguments.conversion.locale.format' configuration parameter \ is no longer supported. Please remove it from your configuration.""")); } @Test void executesWithCustomLocalConverterUsingIso639Format() { var results = execute(LocaleConversionTestCase.class, "testWithIso639", Locale.class); results.allEvents().assertStatistics(stats -> stats.started(4).succeeded(4)); } @Test void reportsExceptionInStaticInitializersWithoutInvocationCountValidation() { var results = executeTestsForClass(ExceptionInStaticInitializerTestCase.class); var failure = results.containerEvents().stream() // .filter(finishedWithFailure()::matches) // .findAny() // .orElseThrow(); var throwable = failure.getRequiredPayload(TestExecutionResult.class).getThrowable().orElseThrow(); assertThat(throwable) // .isInstanceOf(ExceptionInInitializerError.class) // .hasNoSuppressedExceptions(); } private EngineExecutionResults execute(String methodName, Class... methodParameterTypes) { return execute(TestCase.class, methodName, methodParameterTypes); } private EngineExecutionResults execute(Class testClass, String methodName, Class... methodParameterTypes) { return executeTests(selectMethod(testClass, methodName, ClassUtils.nullSafeToString(methodParameterTypes))); } /** * @since 5.4 */ @Nested class NullSourceIntegrationTests { @Test void executesWithNullSourceForString() { var results = execute("testWithNullSourceForString", String.class); results.testEvents().failed().assertEventsMatchExactly( event(test(), displayName("[1] argument = null"), finishedWithFailure(message("null")))); } @Test void executesWithNullSourceForStringAndTestInfo() { var results = execute("testWithNullSourceForStringAndTestInfo", String.class, TestInfo.class); results.testEvents().failed().assertEventsMatchExactly( event(test(), displayName("[1] argument = null"), finishedWithFailure(message("null")))); } @Test void executesWithNullSourceForNumber() { var results = execute("testWithNullSourceForNumber", Number.class); results.testEvents().failed().assertEventsMatchExactly( event(test(), displayName("[1] argument = null"), finishedWithFailure(message("null")))); } @Test void failsWithNullSourceWithZeroFormalParameters() { var methodName = "testWithNullSourceWithZeroFormalParameters"; execute(methodName).containerEvents().failed().assertEventsMatchExactly(// event(container(methodName), // finishedWithFailure(// instanceOf(PreconditionViolationException.class), // message(msg -> msg.matches( "@NullSource cannot provide a null argument to method .+: no formal parameters declared."))))); } @Test void failsWithNullSourceForPrimitive() { var results = execute("testWithNullSourceForPrimitive", int.class); results.testEvents().failed().assertEventsMatchExactly(event(test(), displayName("[1] argument = null"), finishedWithFailure(instanceOf(ParameterResolutionException.class), message( "Error converting parameter at index 0: Cannot convert null to primitive value of type int")))); } private EngineExecutionResults execute(String methodName, Class... methodParameterTypes) { return ParameterizedTestIntegrationTests.this.execute(NullSourceTestCase.class, methodName, methodParameterTypes); } } /** * @since 5.4 */ @Nested class EmptySourceIntegrationTests { @Test void executesWithEmptySourceForString() { var results = execute("testWithEmptySourceForString", String.class); results.testEvents().succeeded().assertEventsMatchExactly( event(test(), displayName("[1] argument = \"\""))); } @Test void testWithEmptySourceForStringFromAnnotationAttribute() { var results = execute("testWithEmptySourceForStringFromAnnotationAttribute", Object.class); results.testEvents().succeeded().assertEventsMatchExactly( event(test(), displayName("[1] argument = \"\""))); } @Test void executesWithEmptySourceForStringAndTestInfo() { var results = execute("testWithEmptySourceForStringAndTestInfo", String.class, TestInfo.class); results.testEvents().succeeded().assertEventsMatchExactly( event(test(), displayName("[1] argument = \"\""))); } /** * @since 6.1 */ @Test void executesWithEmptySourceForIterable() { var results = execute("testWithEmptySourceForIterable", Iterable.class); results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument = []"))); } /** * @since 6.1 */ @Test void executesWithEmptySourceForIterator() { var results = execute("testWithEmptySourceForIterator", Iterator.class); results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument = []"))); } /** * @since 6.1 */ @Test void executesWithEmptySourceForListIterator() { var results = execute("testWithEmptySourceForListIterator", ListIterator.class); results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument = []"))); } /** * @since 5.10 */ @Test void executesWithEmptySourceForCollection() { var results = execute("testWithEmptySourceForCollection", Collection.class); results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument = []"))); } @Test void executesWithEmptySourceForList() { var results = execute("testWithEmptySourceForList", List.class); results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument = []"))); } /** * @since 5.10 */ @ParameterizedTest(name = "{1}") @CsvSource(textBlock = """ testWithEmptySourceForArrayList, java.util.ArrayList testWithEmptySourceForLinkedList, java.util.LinkedList """) void executesWithEmptySourceForListSubtype(String methodName, Class parameterType) { var results = execute(methodName, parameterType); results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument = []"))); } @Test void executesWithEmptySourceForSet() { var results = execute("testWithEmptySourceForSet", Set.class); results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument = []"))); } /** * @since 5.10 */ @ParameterizedTest(name = "{1}") @CsvSource(textBlock = """ testWithEmptySourceForSortedSet, java.util.SortedSet testWithEmptySourceForNavigableSet, java.util.NavigableSet testWithEmptySourceForHashSet, java.util.HashSet testWithEmptySourceForTreeSet, java.util.TreeSet testWithEmptySourceForLinkedHashSet, java.util.LinkedHashSet """) void executesWithEmptySourceForSetSubtype(String methodName, Class parameterType) { var results = execute(methodName, parameterType); results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument = []"))); } @Test void executesWithEmptySourceForMap() { var results = execute("testWithEmptySourceForMap", Map.class); results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument = {}"))); } /** * @since 5.10 */ @ParameterizedTest(name = "{1}") @CsvSource(textBlock = """ testWithEmptySourceForSortedMap, java.util.SortedMap testWithEmptySourceForNavigableMap, java.util.NavigableMap testWithEmptySourceForHashMap, java.util.HashMap testWithEmptySourceForTreeMap, java.util.TreeMap testWithEmptySourceForLinkedHashMap, java.util.LinkedHashMap """) void executesWithEmptySourceForMapSubtype(String methodName, Class parameterType) { var results = execute(methodName, parameterType); results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument = {}"))); } @Test void executesWithEmptySourceForOneDimensionalPrimitiveArray() { var results = execute("testWithEmptySourceForOneDimensionalPrimitiveArray", int[].class); results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument = []"))); } @Test void executesWithEmptySourceForOneDimensionalStringArray() { var results = execute("testWithEmptySourceForOneDimensionalStringArray", String[].class); results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument = []"))); } @Test void executesWithEmptySourceForTwoDimensionalPrimitiveArray() { var results = execute("testWithEmptySourceForTwoDimensionalPrimitiveArray", int[][].class); results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument = []"))); } @Test void executesWithEmptySourceForTwoDimensionalStringArray() { var results = execute("testWithEmptySourceForTwoDimensionalStringArray", String[][].class); results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument = []"))); } @Test void failsWithEmptySourceWithZeroFormalParameters() { var methodName = "testWithEmptySourceWithZeroFormalParameters"; execute(methodName).containerEvents().failed().assertEventsMatchExactly(// event(container(methodName), // finishedWithFailure(// instanceOf(PreconditionViolationException.class), // message(msg -> msg.matches( "@EmptySource cannot provide an empty argument to method .+: no formal parameters declared."))))); } @ParameterizedTest(name = "{1}") @CsvSource(textBlock = """ testWithEmptySourceForPrimitive, int testWithEmptySourceForUnsupportedReferenceType, java.lang.Integer """) void failsWithEmptySourceForUnsupportedType(String methodName, Class parameterType) { execute(methodName, parameterType).containerEvents().failed().assertEventsMatchExactly(// event(container(methodName), // finishedWithFailure(// instanceOf(PreconditionViolationException.class), // message(msg -> msg.matches("@EmptySource cannot provide an empty argument to method .+: \\[" + parameterType.getName() + "] is not a supported type."))// ))); } @Test void testWithEmptySourceForUnsupportedReferenceTypeFromAttribute() { var methodName = "testWithEmptySourceForUnsupportedReferenceTypeFromAttribute"; execute(methodName, String.class).containerEvents().failed().assertEventsMatchExactly(// event(container(methodName), // finishedWithFailure(// instanceOf(PreconditionViolationException.class), // message( "@EmptySource cannot provide an empty argument for 'type': [java.lang.Integer] is not a supported type.")// ))); } private EngineExecutionResults execute(String methodName, Class... methodParameterTypes) { return ParameterizedTestIntegrationTests.this.execute(EmptySourceTestCase.class, methodName, methodParameterTypes); } } /** * @since 5.4 */ @Nested class NullAndEmptySourceIntegrationTests { @Test void executesWithNullAndEmptySourceForString() { var results = execute("testWithNullAndEmptySourceForString", String.class); assertNullAndEmptyString(results); } @Test void executesWithNullAndEmptySourceForStringAndTestInfo() { var results = execute("testWithNullAndEmptySourceForStringAndTestInfo", String.class, TestInfo.class); assertNullAndEmptyString(results); } @Test void executesWithNullAndEmptySourceForList() { var results = execute("testWithNullAndEmptySourceForList", List.class); assertNullAndEmpty(results); } @Test void executesWithNullAndEmptySourceForArrayList() { var results = execute("testWithNullAndEmptySourceForArrayList", ArrayList.class); assertNullAndEmpty(results); } @Test void executesWithNullAndEmptySourceForOneDimensionalPrimitiveArray() { var results = execute("testWithNullAndEmptySourceForOneDimensionalPrimitiveArray", int[].class); assertNullAndEmpty(results); } @Test void executesWithNullAndEmptySourceForTwoDimensionalStringArray() { var results = execute("testWithNullAndEmptySourceForTwoDimensionalStringArray", String[][].class); assertNullAndEmpty(results); } private EngineExecutionResults execute(String methodName, Class... methodParameterTypes) { return ParameterizedTestIntegrationTests.this.execute(NullAndEmptySourceTestCase.class, methodName, methodParameterTypes); } private void assertNullAndEmptyString(EngineExecutionResults results) { results.testEvents().succeeded().assertEventsMatchExactly(// event(test(), displayName("[1] argument = null")), // event(test(), displayName("[2] argument = \"\""))// ); } private void assertNullAndEmpty(EngineExecutionResults results) { results.testEvents().succeeded().assertEventsMatchExactly(// event(test(), displayName("[1] argument = null")), // event(test(), displayName("[2] argument = []"))// ); } } @Nested class MethodSourceIntegrationTests { @Test void emptyMethodSource() { execute("emptyMethodSource", String.class).testEvents().assertThatEvents()// .haveExactly(1, event(test(), finishedWithFailure(message("empty method source")))); } /** * @since 5.3.2 */ @Test void oneDimensionalPrimitiveArray() { execute("oneDimensionalPrimitiveArray", int.class).testEvents().assertThatEvents()// .haveExactly(1, event(test(), finishedWithFailure(message("1"))))// .haveExactly(1, event(test(), finishedWithFailure(message("2")))); } /** * @since 5.3.2 */ @Test void twoDimensionalPrimitiveArray() { execute("twoDimensionalPrimitiveArray", int[].class).testEvents().assertThatEvents()// .haveExactly(1, event(test(), finishedWithFailure(message("[1, 2]"))))// .haveExactly(1, event(test(), finishedWithFailure(message("[3, 4]")))); } /** * @since 5.3.2 */ @Test void oneDimensionalObjectArray() { execute("oneDimensionalObjectArray", Object.class).testEvents().assertThatEvents()// .haveExactly(1, event(test(), finishedWithFailure(message("one"))))// .haveExactly(1, event(test(), finishedWithFailure(message("2"))))// .haveExactly(1, event(test(), finishedWithFailure(message("three")))); } /** * @since 5.3.2 */ @Test void oneDimensionalStringArray() { execute("oneDimensionalStringArray", String.class).testEvents().assertThatEvents()// .haveExactly(1, event(test(), finishedWithFailure(message("one"))))// .haveExactly(1, event(test(), finishedWithFailure(message("two")))); } @Test void twoDimensionalObjectArray() { execute("twoDimensionalObjectArray", String.class, int.class).testEvents().assertThatEvents()// .haveExactly(1, event(test(), finishedWithFailure(message("one:2"))))// .haveExactly(1, event(test(), finishedWithFailure(message("three:4")))); } /** * @since 5.3.2 */ @Test void twoDimensionalStringArray() { execute("twoDimensionalStringArray", String.class, String.class).testEvents().assertThatEvents()// .haveExactly(1, event(test(), finishedWithFailure(message("one:two"))))// .haveExactly(1, event(test(), finishedWithFailure(message("three:four")))); } /** * @since 5.3.2 */ @Test void streamOfOneDimensionalPrimitiveArrays() { execute("streamOfOneDimensionalPrimitiveArrays", int[].class).testEvents().assertThatEvents()// .haveExactly(1, event(test(), finishedWithFailure(message("[1, 2]"))))// .haveExactly(1, event(test(), finishedWithFailure(message("[3, 4]")))); } /** * @since 5.3.2 */ @Test void streamOfTwoDimensionalPrimitiveArrays() { assertStreamOfTwoDimensionalPrimitiveArrays("streamOfTwoDimensionalPrimitiveArrays"); } /** * @since 5.3.2 */ @Test void streamOfTwoDimensionalPrimitiveArraysWrappedInObjectArrays() { assertStreamOfTwoDimensionalPrimitiveArrays("streamOfTwoDimensionalPrimitiveArraysWrappedInObjectArrays"); } /** * @since 5.3.2 */ @Test void streamOfTwoDimensionalPrimitiveArraysWrappedInArguments() { assertStreamOfTwoDimensionalPrimitiveArrays("streamOfTwoDimensionalPrimitiveArraysWrappedInArguments"); } private void assertStreamOfTwoDimensionalPrimitiveArrays(String methodName) { execute(methodName, int[][].class).testEvents().assertThatEvents()// .haveExactly(1, event(test(), finishedWithFailure(message("[[1, 2], [3, 4]]"))))// .haveExactly(1, event(test(), finishedWithFailure(message("[[5, 6], [7, 8]]")))); } /** * @since 5.3.2 */ @Test void streamOfOneDimensionalObjectArrays() { execute("streamOfOneDimensionalObjectArrays", String.class, int.class).testEvents().assertThatEvents()// .haveExactly(1, event(test(), finishedWithFailure(message("one:2"))))// .haveExactly(1, event(test(), finishedWithFailure(message("three:4")))); } /** * @since 5.3.2 */ @Test void streamOfTwoDimensionalObjectArrays() { execute("streamOfTwoDimensionalObjectArrays", Object[][].class).testEvents().assertThatEvents()// .haveExactly(1, event(test(), finishedWithFailure(message("[[one, 2], [three, 4]]"))))// .haveExactly(1, event(test(), finishedWithFailure(message("[[five, 6], [seven, 8]]")))); } @Test void reportsContainerWithAssumptionFailureInMethodSourceAsAborted() { execute("assumptionFailureInMethodSourceFactoryMethod", String.class).allEvents().assertThatEvents() // .haveExactly(1, event(container("test-template:assumptionFailureInMethodSourceFactoryMethod"), // abortedWithReason(instanceOf(TestAbortedException.class), message("nothing to test")))); } @Test void namedParameters() { execute("namedParameters", String.class).allEvents().assertThatEvents() // .haveAtLeast(1, event(test(), displayName("cool name"), finishedWithFailure(message("parameter value")))) // .haveAtLeast(1, event(test(), displayName("default name"), finishedWithFailure(message("default name")))); } @Test void nameParametersAlias() { execute("namedParametersAlias", String.class).allEvents().assertThatEvents() // .haveAtLeast(1, event(test(), displayName("cool name"), finishedWithFailure(message("parameter value")))) // .haveAtLeast(1, event(test(), displayName("default name"), finishedWithFailure(message("default name")))); } /** * @since 5.9.1 * @see GitHub - Issue #3001 */ @Test void duplicateMethodNames() { // It is sufficient to assert that 8 tests started and finished, because // without the fix for #3001 the 4 parameterized tests would fail. In // other words, we're not really testing the support for @RepeatedTest // and @TestFactory, but their presence also contributes to the bug // reported in #3001. executeTestsForClass(DuplicateMethodNamesMethodSourceTestCase.class)// .testEvents()// .assertStatistics(stats -> stats.started(8).failed(0).finished(8)); } private EngineExecutionResults execute(String methodName, Class... methodParameterTypes) { return ParameterizedTestIntegrationTests.this.execute(MethodSourceTestCase.class, methodName, methodParameterTypes); } } /** * @since 5.11 */ @Nested class FieldSourceIntegrationTests { @Test void oneDimensionalPrimitiveArray() { execute("oneDimensionalPrimitiveArray", int.class).testEvents().assertThatEvents()// .haveExactly(1, event(test(), finishedWithFailure(message("1"))))// .haveExactly(1, event(test(), finishedWithFailure(message("2")))); } @Test void twoDimensionalPrimitiveArray() { execute("twoDimensionalPrimitiveArray", int[].class).testEvents().assertThatEvents()// .haveExactly(1, event(test(), finishedWithFailure(message("[1, 2]"))))// .haveExactly(1, event(test(), finishedWithFailure(message("[3, 4]")))); } @Test void oneDimensionalObjectArray() { execute("oneDimensionalObjectArray", Object.class).testEvents().assertThatEvents()// .haveExactly(1, event(test(), finishedWithFailure(message("one"))))// .haveExactly(1, event(test(), finishedWithFailure(message("2"))))// .haveExactly(1, event(test(), finishedWithFailure(message("three")))); } @Test void oneDimensionalStringArray() { execute("oneDimensionalStringArray", String.class).testEvents().assertThatEvents()// .haveExactly(1, event(test(), finishedWithFailure(message("one"))))// .haveExactly(1, event(test(), finishedWithFailure(message("two")))); } @Test void twoDimensionalObjectArray() { execute("twoDimensionalObjectArray", String.class, int.class).testEvents().assertThatEvents()// .haveExactly(1, event(test(), finishedWithFailure(message("one:2"))))// .haveExactly(1, event(test(), finishedWithFailure(message("three:4")))); } @Test void twoDimensionalStringArray() { execute("twoDimensionalStringArray", String.class, String.class).testEvents().assertThatEvents()// .haveExactly(1, event(test(), finishedWithFailure(message("one:two"))))// .haveExactly(1, event(test(), finishedWithFailure(message("three:four")))); } @Test void supplierOfStreamOfOneDimensionalPrimitiveArrays() { execute("supplierOfStreamOfOneDimensionalPrimitiveArrays", int[].class).testEvents().assertThatEvents()// .haveExactly(1, event(test(), finishedWithFailure(message("[1, 2]"))))// .haveExactly(1, event(test(), finishedWithFailure(message("[3, 4]")))); } @Test void supplierOfStreamOfTwoDimensionalPrimitiveArrays() { assertStreamOfTwoDimensionalPrimitiveArrays("supplierOfStreamOfTwoDimensionalPrimitiveArrays"); } @Test void supplierOfStreamOfTwoDimensionalPrimitiveArraysWrappedInObjectArrays() { assertStreamOfTwoDimensionalPrimitiveArrays( "supplierOfStreamOfTwoDimensionalPrimitiveArraysWrappedInObjectArrays"); } @Test void supplierOfStreamOfTwoDimensionalPrimitiveArraysWrappedInArguments() { assertStreamOfTwoDimensionalPrimitiveArrays( "supplierOfStreamOfTwoDimensionalPrimitiveArraysWrappedInArguments"); } private void assertStreamOfTwoDimensionalPrimitiveArrays(String methodName) { execute(methodName, int[][].class).testEvents().assertThatEvents()// .haveExactly(1, event(test(), finishedWithFailure(message("[[1, 2], [3, 4]]"))))// .haveExactly(1, event(test(), finishedWithFailure(message("[[5, 6], [7, 8]]")))); } @Test void supplierOfStreamOfOneDimensionalObjectArrays() { execute("supplierOfStreamOfOneDimensionalObjectArrays", String.class, int.class).testEvents()// .assertThatEvents()// .haveExactly(1, event(test(), finishedWithFailure(message("one:2"))))// .haveExactly(1, event(test(), finishedWithFailure(message("three:4")))); } @Test void supplierOfStreamOfTwoDimensionalObjectArrays() { execute("supplierOfStreamOfTwoDimensionalObjectArrays", Object[][].class).testEvents().assertThatEvents()// .haveExactly(1, event(test(), finishedWithFailure(message("[[one, 2], [three, 4]]"))))// .haveExactly(1, event(test(), finishedWithFailure(message("[[five, 6], [seven, 8]]")))); } @Test void listOfNamedParameters() { execute("listOfNamedParameters", String.class).allEvents().assertThatEvents() // .haveAtLeast(1, event(test(), displayName("cool name"), finishedWithFailure(message("parameter value")))) // .haveAtLeast(1, event(test(), displayName("default name"), finishedWithFailure(message("default name")))); } @Test void nonStaticFieldInTopLevelTestClass() { Class testClass = BaseLifecyclePerClassFieldSourceTestCase.class; execute(testClass, "test", String.class).testEvents().assertThatEvents()// .haveExactly(1, event(test(), finishedWithFailure(message("base-1"))))// .haveExactly(1, event(test(), finishedWithFailure(message("base-2")))); } @Test void nonStaticFieldInSubclassTakesPrecedenceOverFieldInSuperclass() { Class testClass = SubclassOfBaseLifecyclePerClassFieldSourceTestCase.class; execute(testClass, "test", String.class).testEvents().assertThatEvents()// .haveExactly(1, event(test(), finishedWithFailure(message("sub-1"))))// .haveExactly(1, event(test(), finishedWithFailure(message("sub-2")))); } @Test void nonStaticFieldInNestedTestClass() { Class testClass = EnclosingFieldSourceTestCase.NestedLifecyclePerClassFieldSourceTestCase.class; execute(testClass, "nonStaticFieldSource", String.class)// .testEvents().assertThatEvents()// .haveExactly(1, event(test(), finishedWithFailure(message("apple"))))// .haveExactly(1, event(test(), finishedWithFailure(message("banana")))); } private EngineExecutionResults execute(String methodName, Class... methodParameterTypes) { return execute(FieldSourceTestCase.class, methodName, methodParameterTypes); } private EngineExecutionResults execute(Class testClass, String methodName, Class... methodParameterTypes) { return ParameterizedTestIntegrationTests.this.execute(testClass, methodName, methodParameterTypes); } } @Nested class UnusedArgumentsIntegrationTests { @Test void executesWithArgumentsSourceProvidingUnusedArguments() { var results = execute("testWithTwoUnusedStringArgumentsProvider", String.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), displayName("[1] argument = foo"), finishedWithFailure(message("foo")))) // .haveExactly(1, event(test(), displayName("[2] argument = bar"), finishedWithFailure(message("bar")))); } @Test void executesWithCsvSourceContainingUnusedArguments() { var results = execute("testWithCsvSourceContainingUnusedArguments", String.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), displayName("[1] argument = foo"), finishedWithFailure(message("foo")))) // .haveExactly(1, event(test(), displayName("[2] argument = bar"), finishedWithFailure(message("bar")))); } @Test void executesWithCsvFileSourceContainingUnusedArguments() { var results = execute("testWithCsvFileSourceContainingUnusedArguments", String.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), displayName("[1] argument = foo"), finishedWithFailure(message("foo")))) // .haveExactly(1, event(test(), displayName("[2] argument = bar"), finishedWithFailure(message("bar")))); } @Test void executesWithMethodSourceProvidingUnusedArguments() { var results = execute("testWithMethodSourceProvidingUnusedArguments", String.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), displayName("[1] argument = foo"), finishedWithFailure(message("foo")))) // .haveExactly(1, event(test(), displayName("[2] argument = bar"), finishedWithFailure(message("bar")))); } @Test void executesWithFieldSourceProvidingUnusedArguments() { var results = execute("testWithFieldSourceProvidingUnusedArguments", String.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), displayName("[1] argument = foo"), finishedWithFailure(message("foo")))) // .haveExactly(1, event(test(), displayName("[2] argument = bar"), finishedWithFailure(message("bar")))); } private EngineExecutionResults execute(String methodName, Class... methodParameterTypes) { return ParameterizedTestIntegrationTests.this.execute(UnusedArgumentsTestCase.class, methodName, methodParameterTypes); } } @Nested class UnusedArgumentsWithStrictArgumentsCountIntegrationTests { @Test void failsWithArgumentsSourceProvidingUnusedArguments() { var results = execute(ArgumentCountValidationMode.STRICT, UnusedArgumentsTestCase.class, "testWithTwoUnusedStringArgumentsProvider", String.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(finishedWithFailure(message( "Configuration error: @ParameterizedTest consumes 1 parameter but there were 2 arguments provided.%nNote: the provided arguments were [foo, unused1]".formatted())))); } @Test void failsWithMethodSourceProvidingUnusedArguments() { var results = execute(ArgumentCountValidationMode.STRICT, UnusedArgumentsTestCase.class, "testWithMethodSourceProvidingUnusedArguments", String.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(finishedWithFailure(message( "Configuration error: @ParameterizedTest consumes 1 parameter but there were 2 arguments provided.%nNote: the provided arguments were [foo, unused1]".formatted())))); } @Test void failsWithCsvSourceUnusedArgumentsAndStrictArgumentCountValidationAnnotationAttribute() { var results = execute(ArgumentCountValidationMode.NONE, UnusedArgumentsTestCase.class, "testWithStrictArgumentCountValidation", String.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(finishedWithFailure(message( "Configuration error: @ParameterizedTest consumes 1 parameter but there were 2 arguments provided.%nNote: the provided arguments were [foo, unused1]".formatted())))); } @Test void failsWithCsvSourceUnusedArgumentsButExecutesRemainingArgumentsWhereThereIsNoUnusedArgument() { var results = execute(ArgumentCountValidationMode.STRICT, UnusedArgumentsTestCase.class, "testWithCsvSourceContainingDifferentNumbersOfArguments", String.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(finishedWithFailure(message( "Configuration error: @ParameterizedTest consumes 1 parameter but there were 2 arguments provided.%nNote: the provided arguments were [foo, unused1]".formatted())))) // .haveExactly(1, event(test(), displayName("[2] argument = bar"), finishedWithFailure(message("bar")))); } @Test void executesWithCsvSourceUnusedArgumentsAndArgumentCountValidationAnnotationAttribute() { var results = execute(ArgumentCountValidationMode.NONE, UnusedArgumentsTestCase.class, "testWithNoneArgumentCountValidation", String.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), displayName("[1] argument = foo"), finishedWithFailure(message("foo")))); } @Test void executesWithMethodSourceProvidingUnusedArguments() { var results = execute(ArgumentCountValidationMode.STRICT, RepeatableSourcesTestCase.class, "testWithRepeatableCsvSource", String.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), displayName("[1] argument = a"), finishedWithFailure(message("a")))) // .haveExactly(1, event(test(), displayName("[2] argument = b"), finishedWithFailure(message("b")))); } @Test void evaluatesArgumentsAtMostOnce() { var results = execute(ArgumentCountValidationMode.STRICT, UnusedArgumentsTestCase.class, "testWithEvaluationReportingArgumentsProvider", String.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(finishedWithFailure(message( "Configuration error: @ParameterizedTest consumes 1 parameter but there were 2 arguments provided.%nNote: the provided arguments were [foo, unused]".formatted())))); results.allEvents().reportingEntryPublished().assertThatEvents() // .haveExactly(1, event(EventConditions.reportEntry(Map.of("evaluated", "true")))); } private EngineExecutionResults execute(ArgumentCountValidationMode configurationValue, Class javaClass, String methodName, Class... methodParameterTypes) { return EngineTestKit.engine(new JupiterTestEngine()) // .selectors(selectMethod(javaClass, methodName, methodParameterTypes)) // .configurationParameter(ArgumentCountValidator.ARGUMENT_COUNT_VALIDATION_KEY, configurationValue.name().toLowerCase()) // .execute(); } } @Nested class RepeatableSourcesIntegrationTests { @ParameterizedTest @ValueSource(strings = { "testWithRepeatableCsvFileSource", "testWithRepeatableCsvFileSourceAsMetaAnnotation" }) void executesWithRepeatableCsvFileSource(String methodName) { var results = execute(methodName, String.class, String.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), displayName("[1] column1 = foo, column2 = 1"), finishedWithFailure(message("foo 1")))) // .haveExactly(1, event(test(), displayName("[5] FRUIT = apple, RANK = 1"), finishedWithFailure(message("apple 1")))); } @ParameterizedTest @ValueSource(strings = { "testWithRepeatableCsvSource", "testWithRepeatableCsvSourceAsMetaAnnotation" }) void executesWithRepeatableCsvSource(String methodName) { var results = execute(methodName, String.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), displayName("[1] argument = a"), finishedWithFailure(message("a")))) // .haveExactly(1, event(test(), displayName("[2] argument = b"), finishedWithFailure(message("b")))); } @ParameterizedTest @ValueSource(strings = { "testWithRepeatableMethodSource", "testWithRepeatableMethodSourceAsMetaAnnotation" }) void executesWithRepeatableMethodSource(String methodName) { var results = execute(methodName, String.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), displayName("[1] argument = some"), finishedWithFailure(message("some")))) // .haveExactly(1, event(test(), displayName("[2] argument = other"), finishedWithFailure(message("other")))); } @ParameterizedTest @ValueSource(strings = { "testWithRepeatableEnumSource", "testWithRepeatableEnumSourceAsMetaAnnotation" }) void executesWithRepeatableEnumSource(String methodName) { var results = execute(methodName, Action.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), displayName("[1] argument = FOO"), finishedWithFailure(message("FOO")))) // .haveExactly(1, event(test(), displayName("[2] argument = BAR"), finishedWithFailure(message("BAR")))); } @ParameterizedTest @ValueSource(strings = { "testWithRepeatableValueSource", "testWithRepeatableValueSourceAsMetaAnnotation" }) void executesWithRepeatableValueSource(String methodName) { var results = execute(methodName, String.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), displayName("[1] argument = foo"), finishedWithFailure(message("foo")))) // .haveExactly(1, event(test(), displayName("[2] argument = bar"), finishedWithFailure(message("bar")))); } @ParameterizedTest @ValueSource(strings = { "testWithRepeatableFieldSource", "testWithRepeatableFieldSourceAsMetaAnnotation" }) void executesWithRepeatableFieldSource(String methodName) { var results = execute(methodName, String.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), displayName("[1] argument = some"), finishedWithFailure(message("some")))) // .haveExactly(1, event(test(), displayName("[2] argument = other"), finishedWithFailure(message("other")))); } @ParameterizedTest @ValueSource(strings = { "testWithRepeatableArgumentsSource", "testWithRepeatableArgumentsSourceAsMetaAnnotation" }) void executesWithRepeatableArgumentsSource(String methodName) { var results = execute(methodName, String.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), displayName("[1] argument = foo"), finishedWithFailure(message("foo")))) // .haveExactly(1, event(test(), displayName("[2] argument = bar"), finishedWithFailure(message("bar")))) // .haveExactly(1, event(test(), displayName("[3] argument = foo"), finishedWithFailure(message("foo")))) // .haveExactly(1, event(test(), displayName("[4] argument = bar"), finishedWithFailure(message("bar")))); } @Test void executesWithSameRepeatableAnnotationMultipleTimes() { var results = execute("testWithSameRepeatableAnnotationMultipleTimes", String.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), started())) // .haveExactly(1, event(test(), finishedWithFailure(message("foo")))); } @Test void executesWithDifferentRepeatableAnnotations() { var results = execute("testWithDifferentRepeatableAnnotations", String.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), displayName("[1] argument = a"), finishedWithFailure(message("a")))) // .haveExactly(1, event(test(), displayName("[2] argument = b"), finishedWithFailure(message("b")))) // .haveExactly(1, event(test(), displayName("[3] argument = c"), finishedWithFailure(message("c")))) // .haveExactly(1, event(test(), displayName("[4] argument = d"), finishedWithFailure(message("d")))); } private EngineExecutionResults execute(String methodName, Class... methodParameterTypes) { return ParameterizedTestIntegrationTests.this.execute(RepeatableSourcesTestCase.class, methodName, methodParameterTypes); } } @Test void closeAutoCloseableArgumentsAfterTest() { var results = execute("testWithAutoCloseableArgument", AutoCloseableArgument.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), finishedSuccessfully())); assertEquals(2, AutoCloseableArgument.closeCounter); } @Test void doNotCloseAutoCloseableArgumentsAfterTestWhenDisabled() { var results = execute("testWithAutoCloseableArgumentButDisabledCleanup", AutoCloseableArgument.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), finishedSuccessfully())); assertEquals(0, AutoCloseableArgument.closeCounter); } @Test void closeAutoCloseableArgumentsAfterTestDespiteEarlyFailure() { var results = execute(FailureInBeforeEachTestCase.class, "test", AutoCloseableArgument.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), finishedWithFailure(message("beforeEach")))); assertEquals(2, AutoCloseableArgument.closeCounter); } @Test void executesTwoIterationsBasedOnIterationAndUniqueIdSelector() { var methodId = uniqueIdForTestTemplateMethod(TestCase.class, "testWithThreeIterations(int)"); var results = executeTests(selectUniqueId(appendTestTemplateInvocationSegment(methodId, 3)), selectIteration(selectMethod(TestCase.class, "testWithThreeIterations", "int"), 1)); results.allEvents().assertThatEvents() // .haveExactly(2, event(test(), finishedWithFailure())) // .haveExactly(1, event(test(), displayName("[2] argument = 3"), finishedWithFailure())) // .haveExactly(1, event(test(), displayName("[3] argument = 5"), finishedWithFailure())); } @Nested class SpiParameterInjectionIntegrationTests { @Test void injectsParametersIntoArgumentsProviderConstructor() { execute(SpiParameterInjectionTestCase.class, "argumentsProviderWithConstructorParameter", String.class) // .testEvents() // .assertStatistics(it -> it.succeeded(1)); } @Test void injectsParametersIntoArgumentConverterConstructor() { execute(SpiParameterInjectionTestCase.class, "argumentConverterWithConstructorParameter", String.class) // .testEvents() // .assertStatistics(it -> it.succeeded(1)); } @Test void injectsParametersIntoArgumentsAggregatorConstructor() { execute(SpiParameterInjectionTestCase.class, "argumentsAggregatorWithConstructorParameter", String.class) // .testEvents() // .assertStatistics(it -> it.succeeded(1)); } } // ------------------------------------------------------------------------- static class TestCase { @ParameterizedTest(quoteTextArguments = false) @ArgumentsSource(TwoSingleStringArgumentsProvider.class) void testWithTwoSingleStringArgumentsProvider(String argument) { fail(argument); } @ParameterizedTest(quoteTextArguments = false) @CsvSource({ "foo", "bar" }) void testWithCsvSource(String argument) { fail(argument); } @ParameterizedTest(name = "{0}") @CsvSource({ "'üñåé'", "'\n'", "'\r'", "'\u0007'", "😱", "'Zero\u200BWidth\u200BSpaces'" }) void testWithCsvSourceAndSpecialCharacters(String argument) { } @ParameterizedTest(quoteTextArguments = false, name = "{0} and {1}") @CsvSource({ "foo, 23", "bar, 42" }) void testWithCustomName(String argument, int i) { fail(argument + ", " + i); } @ParameterizedTest(quoteTextArguments = false) @ValueSource(shorts = { 1, 2 }) void testWithPrimitiveWideningConversion(double num) { fail("num: " + num); } @ParameterizedTest(quoteTextArguments = false) @ValueSource(strings = { "book 1", "book 2" }) void testWithImplicitGenericConverter(Book book) { fail(book.title); } @ParameterizedTest(name = "{0}") @ValueSource(strings = { "record 1", "record 2" }) void testWithImplicitGenericConverterWithCharSequenceConstructor(Record record) { fail(record.title.toString()); } @ParameterizedTest(quoteTextArguments = false) @ValueSource(strings = { "O", "XXX" }) void testWithExplicitConverter(@ConvertWith(StringLengthConverter.class) int length) { fail("length: " + length); } @ParameterizedTest(name = " \t ") @ValueSource(strings = "not important") void testWithEmptyName(String argument) { fail(argument); } @ParameterizedTest(quoteTextArguments = false) @ValueSource(ints = 42) void testWithErroneousConverter(@ConvertWith(ErroneousConverter.class) Object ignored) { fail("this should never be called"); } @ParameterizedTest(quoteTextArguments = false, name = "{0,number,#.####}") @ValueSource(doubles = Math.PI) void testWithMessageFormat(double argument) { fail(String.valueOf(argument)); } @ParameterizedTest(quoteTextArguments = false) @CsvSource({ "ab, cd", "ef, gh" }) void testWithAggregator(@AggregateWith(StringAggregator.class) String concatenation) { fail("concatenation: " + concatenation); } @ParameterizedTest(quoteTextArguments = false) @CsvSource(value = { " ab , cd", "ef ,gh" }, ignoreLeadingAndTrailingWhitespace = false) void testWithIgnoreLeadingAndTrailingWhitespaceSetToFalseForCsvSource(String argument1, String argument2) { fail("arguments: '" + argument1 + "', '" + argument2 + "'"); } @ParameterizedTest(quoteTextArguments = false) @CsvSource(value = { " ab , cd", "ef ,gh" }, ignoreLeadingAndTrailingWhitespace = true) void testWithIgnoreLeadingAndTrailingWhitespaceSetToTrueForCsvSource(String argument1, String argument2) { fail("arguments: '" + argument1 + "', '" + argument2 + "'"); } @ParameterizedTest(quoteTextArguments = false) @CsvFileSource(resources = "provider/leading-trailing-spaces.csv", ignoreLeadingAndTrailingWhitespace = false) void testWithIgnoreLeadingAndTrailingWhitespaceSetToFalseForCsvFileSource(String argument1, String argument2) { fail("arguments: '" + argument1 + "', '" + argument2 + "'"); } @ParameterizedTest(quoteTextArguments = false) @CsvFileSource(resources = "provider/leading-trailing-spaces.csv", ignoreLeadingAndTrailingWhitespace = true) void testWithIgnoreLeadingAndTrailingWhitespaceSetToTrueForCsvFileSource(String argument1, String argument2) { fail("arguments: '" + argument1 + "', '" + argument2 + "'"); } @ParameterizedTest(quoteTextArguments = false) @ArgumentsSource(AutoCloseableArgumentProvider.class) void testWithAutoCloseableArgument(AutoCloseableArgument autoCloseable) { assertEquals(0, AutoCloseableArgument.closeCounter); } @ParameterizedTest(quoteTextArguments = false, autoCloseArguments = false) @ArgumentsSource(AutoCloseableArgumentProvider.class) void testWithAutoCloseableArgumentButDisabledCleanup(AutoCloseableArgument autoCloseable) { assertEquals(0, AutoCloseableArgument.closeCounter); } @ParameterizedTest @ValueSource(ints = { 2, 3, 5 }) void testWithThreeIterations(int argument) { fail("argument: " + argument); } } @SuppressWarnings("JUnitMalformedDeclaration") static class NullSourceTestCase { @ParameterizedTest @NullSource void testWithNullSourceForString(String argument) { fail(String.valueOf(argument)); } @ParameterizedTest @NullSource void testWithNullSourceForStringAndTestInfo(String argument, TestInfo testInfo) { assertThat(testInfo).isNotNull(); fail(String.valueOf(argument)); } @ParameterizedTest @NullSource void testWithNullSourceForNumber(Number argument) { fail(String.valueOf(argument)); } @ParameterizedTest @NullSource void testWithNullSourceWithZeroFormalParameters() { fail("should not have been executed"); } @ParameterizedTest @NullSource void testWithNullSourceForPrimitive(int argument) { fail("should not have been executed"); } } @SuppressWarnings("JUnitMalformedDeclaration") static class EmptySourceTestCase { @ParameterizedTest @EmptySource void testWithEmptySourceForString(String argument) { assertThat(argument).isEmpty(); } @ParameterizedTest @EmptySource(type = String.class) void testWithEmptySourceForStringFromAnnotationAttribute(Object argument) { assertThat(argument).asInstanceOf(InstanceOfAssertFactories.STRING).isEmpty(); } @ParameterizedTest @EmptySource void testWithEmptySourceForStringAndTestInfo(String argument, TestInfo testInfo) { assertThat(argument).isEmpty(); assertThat(testInfo).isNotNull(); } @ParameterizedTest @EmptySource void testWithEmptySourceForIterable(Iterable argument) { assertThat(argument).isEmpty(); } @ParameterizedTest @EmptySource void testWithEmptySourceForIterator(Iterator argument) { assertThat(argument).isExhausted(); } @ParameterizedTest @EmptySource void testWithEmptySourceForListIterator(ListIterator argument) { assertThat(argument).isExhausted(); } @ParameterizedTest @EmptySource void testWithEmptySourceForCollection(Collection argument) { assertThat(argument).isEmpty(); } @ParameterizedTest @EmptySource void testWithEmptySourceForList(List argument) { assertThat(argument).isEmpty(); } @ParameterizedTest @EmptySource void testWithEmptySourceForArrayList(ArrayList argument) { assertThat(argument).isEmpty(); } @ParameterizedTest @EmptySource void testWithEmptySourceForLinkedList(LinkedList argument) { assertThat(argument).isEmpty(); } @ParameterizedTest @EmptySource void testWithEmptySourceForSet(Set argument) { assertThat(argument).isEmpty(); } @ParameterizedTest @EmptySource void testWithEmptySourceForSortedSet(SortedSet argument) { assertThat(argument).isEmpty(); } @ParameterizedTest @EmptySource void testWithEmptySourceForNavigableSet(NavigableSet argument) { assertThat(argument).isEmpty(); } @ParameterizedTest @EmptySource void testWithEmptySourceForHashSet(HashSet argument) { assertThat(argument).isEmpty(); } @ParameterizedTest @EmptySource void testWithEmptySourceForTreeSet(TreeSet argument) { assertThat(argument).isEmpty(); } @ParameterizedTest @EmptySource void testWithEmptySourceForLinkedHashSet(LinkedHashSet argument) { assertThat(argument).isEmpty(); } @ParameterizedTest @EmptySource void testWithEmptySourceForMap(Map argument) { assertThat(argument).isEmpty(); } @ParameterizedTest @EmptySource void testWithEmptySourceForSortedMap(SortedMap argument) { assertThat(argument).isEmpty(); } @ParameterizedTest @EmptySource void testWithEmptySourceForNavigableMap(NavigableMap argument) { assertThat(argument).isEmpty(); } @ParameterizedTest @EmptySource void testWithEmptySourceForHashMap(HashMap argument) { assertThat(argument).isEmpty(); } @ParameterizedTest @EmptySource void testWithEmptySourceForTreeMap(TreeMap argument) { assertThat(argument).isEmpty(); } @ParameterizedTest @EmptySource void testWithEmptySourceForLinkedHashMap(LinkedHashMap argument) { assertThat(argument).isEmpty(); } @ParameterizedTest @EmptySource void testWithEmptySourceForOneDimensionalPrimitiveArray(int[] argument) { assertThat(argument).isEmpty(); } @ParameterizedTest @EmptySource void testWithEmptySourceForOneDimensionalStringArray(String[] argument) { assertThat(argument).isEmpty(); } @ParameterizedTest @EmptySource void testWithEmptySourceForTwoDimensionalPrimitiveArray(int[][] argument) { assertThat(argument).isEmpty(); } @ParameterizedTest @EmptySource void testWithEmptySourceForTwoDimensionalStringArray(String[][] argument) { assertThat(argument).isEmpty(); } @ParameterizedTest @EmptySource void testWithEmptySourceWithZeroFormalParameters() { fail("should not have been executed"); } @ParameterizedTest @EmptySource void testWithEmptySourceForPrimitive(int argument) { fail("should not have been executed"); } @ParameterizedTest @EmptySource void testWithEmptySourceForUnsupportedReferenceType(Integer argument) { fail("should not have been executed"); } @ParameterizedTest @EmptySource(type = Integer.class) void testWithEmptySourceForUnsupportedReferenceTypeFromAttribute(String argument) { fail("should not have been executed"); } } @SuppressWarnings("JUnitMalformedDeclaration") static class NullAndEmptySourceTestCase { @ParameterizedTest @NullAndEmptySource void testWithNullAndEmptySourceForString(String argument) { assertTrue(argument == null || argument.isEmpty()); } @ParameterizedTest @NullAndEmptySource void testWithNullAndEmptySourceForStringAndTestInfo(String argument, TestInfo testInfo) { assertTrue(argument == null || argument.isEmpty()); assertThat(testInfo).isNotNull(); } @ParameterizedTest @NullAndEmptySource void testWithNullAndEmptySourceForList(List argument) { assertTrue(argument == null || argument.isEmpty()); } @ParameterizedTest @NullAndEmptySource void testWithNullAndEmptySourceForArrayList(ArrayList argument) { assertTrue(argument == null || argument.isEmpty()); } @ParameterizedTest @NullAndEmptySource void testWithNullAndEmptySourceForOneDimensionalPrimitiveArray(int[] argument) { assertTrue(argument == null || argument.length == 0); } @ParameterizedTest @NullAndEmptySource void testWithNullAndEmptySourceForTwoDimensionalStringArray(String[][] argument) { assertTrue(argument == null || argument.length == 0); } } @SuppressWarnings("JUnitMalformedDeclaration") @TestMethodOrder(OrderAnnotation.class) static class MethodSourceTestCase { @Target(ElementType.METHOD) @Retention(RUNTIME) @ParameterizedTest(quoteTextArguments = false, name = "{arguments}") @MethodSource @interface MethodSourceTest { } @MethodSourceTest @Order(0) void emptyMethodSource(String argument) { fail(argument); } @MethodSourceTest @Order(1) void oneDimensionalPrimitiveArray(int x) { fail("" + x); } @MethodSourceTest @Order(2) void twoDimensionalPrimitiveArray(int[] array) { fail(Arrays.toString(array)); } @MethodSourceTest @Order(3) void oneDimensionalObjectArray(Object o) { fail("" + o); } @MethodSourceTest @Order(4) void oneDimensionalStringArray(String s) { fail(s); } @MethodSourceTest @Order(5) void twoDimensionalObjectArray(String s, int x) { fail(s + ":" + x); } @MethodSourceTest @Order(6) void twoDimensionalStringArray(String s1, String s2) { fail(s1 + ":" + s2); } @MethodSourceTest @Order(7) void streamOfOneDimensionalPrimitiveArrays(int[] array) { fail(Arrays.toString(array)); } @MethodSourceTest @Order(8) void streamOfTwoDimensionalPrimitiveArrays(int[][] array) { fail(Arrays.deepToString(array)); } @MethodSourceTest @Order(9) void streamOfTwoDimensionalPrimitiveArraysWrappedInObjectArrays(int[][] array) { fail(Arrays.deepToString(array)); } @MethodSourceTest @Order(10) void streamOfTwoDimensionalPrimitiveArraysWrappedInArguments(int[][] array) { fail(Arrays.deepToString(array)); } @MethodSourceTest @Order(11) void streamOfOneDimensionalObjectArrays(String s, int x) { fail(s + ":" + x); } @MethodSourceTest @Order(12) void streamOfTwoDimensionalObjectArrays(Object[][] array) { fail(Arrays.deepToString(array)); } @MethodSourceTest @Order(13) void namedParameters(String string) { fail(string); } @MethodSourceTest @Order(14) void namedParametersAlias(String string) { fail(string); } // --------------------------------------------------------------------- static Stream emptyMethodSource() { return Stream.of(arguments("empty method source")); } static int[] oneDimensionalPrimitiveArray() { return new int[] { 1, 2 }; } static int[][] twoDimensionalPrimitiveArray() { return new int[][] { { 1, 2 }, { 3, 4 } }; } static Object[] oneDimensionalObjectArray() { return new Object[] { "one", 2, "three" }; } static Object[] oneDimensionalStringArray() { return new Object[] { "one", "two" }; } static Object[][] twoDimensionalObjectArray() { return new Object[][] { { "one", 2 }, { "three", 4 } }; } static String[][] twoDimensionalStringArray() { return new String[][] { { "one", "two" }, { "three", "four" } }; } static Stream streamOfOneDimensionalPrimitiveArrays() { return Stream.of(new int[] { 1, 2 }, new int[] { 3, 4 }); } static Stream streamOfTwoDimensionalPrimitiveArrays() { return Stream.of(new int[][] { { 1, 2 }, { 3, 4 } }, new int[][] { { 5, 6 }, { 7, 8 } }); } static Stream streamOfTwoDimensionalPrimitiveArraysWrappedInObjectArrays() { return Stream.of(new Object[] { new int[][] { { 1, 2 }, { 3, 4 } } }, new Object[] { new int[][] { { 5, 6 }, { 7, 8 } } }); } static Stream streamOfTwoDimensionalPrimitiveArraysWrappedInArguments() { return Stream.of(arguments((Object) new int[][] { { 1, 2 }, { 3, 4 } }), arguments((Object) new int[][] { { 5, 6 }, { 7, 8 } })); } static Stream streamOfOneDimensionalObjectArrays() { return Stream.of(new Object[] { "one", 2 }, new Object[] { "three", 4 }); } static Stream streamOfTwoDimensionalObjectArrays() { return Stream.of(new Object[][] { { "one", 2 }, { "three", 4 } }, new Object[][] { { "five", 6 }, { "seven", 8 } }); } static Stream namedParameters() { return Stream.of(arguments(Named.of("cool name", "parameter value")), arguments("default name")); } static Stream namedParametersAlias() { return Stream.of(arguments(named("cool name", "parameter value")), arguments("default name")); } // --------------------------------------------------------------------- @MethodSourceTest void assumptionFailureInMethodSourceFactoryMethod(String test) { } static List assumptionFailureInMethodSourceFactoryMethod() { Assumptions.abort("nothing to test"); return List.of(); } } /** * @since 5.9.1 * @see GitHub - Issue #3001 */ static class DuplicateMethodNamesMethodSourceTestCase { @ParameterizedTest @MethodSource void test(String value) { test(1, value); } @ParameterizedTest @MethodSource("test") void anotherTest(String value) { assertTrue(test(value, 1)); } @RepeatedTest(2) void test(TestReporter testReporter) { assertNotNull(testReporter); } @TestFactory Stream test(TestInfo testInfo) { return test().map(value -> dynamicTest(value, () -> test(1, value))); } // neither a test method nor a factory method. // intentionally void. private void test(int expectedLength, String value) { assertEquals(expectedLength, value.length()); } // neither a test method nor a factory method. // intentionally non-void and also not convertible to a Stream. private boolean test(String value, int expectedLength) { return (value.length() == expectedLength); } // legitimate factory method. private static Stream test() { return Stream.of("a", "b"); } } @TestMethodOrder(OrderAnnotation.class) static class FieldSourceTestCase { @Target(ElementType.METHOD) @Retention(RUNTIME) @ParameterizedTest(quoteTextArguments = false, name = "{arguments}") @FieldSource @interface FieldSourceTest { } @FieldSourceTest @Order(1) void oneDimensionalPrimitiveArray(int x) { fail("" + x); } @FieldSourceTest @Order(2) void twoDimensionalPrimitiveArray(int[] array) { fail(Arrays.toString(array)); } @FieldSourceTest @Order(3) void oneDimensionalObjectArray(Object o) { fail("" + o); } @FieldSourceTest @Order(4) void oneDimensionalStringArray(String s) { fail(s); } @FieldSourceTest @Order(5) void twoDimensionalObjectArray(String s, int x) { fail(s + ":" + x); } @FieldSourceTest @Order(6) void twoDimensionalStringArray(String s1, String s2) { fail(s1 + ":" + s2); } @FieldSourceTest @Order(7) void supplierOfStreamOfOneDimensionalPrimitiveArrays(int[] array) { fail(Arrays.toString(array)); } @FieldSourceTest @Order(8) void supplierOfStreamOfTwoDimensionalPrimitiveArrays(int[][] array) { fail(Arrays.deepToString(array)); } @FieldSourceTest @Order(9) void supplierOfStreamOfTwoDimensionalPrimitiveArraysWrappedInObjectArrays(int[][] array) { fail(Arrays.deepToString(array)); } @FieldSourceTest @Order(10) void supplierOfStreamOfTwoDimensionalPrimitiveArraysWrappedInArguments(int[][] array) { fail(Arrays.deepToString(array)); } @FieldSourceTest @Order(11) void supplierOfStreamOfOneDimensionalObjectArrays(String s, int x) { fail(s + ":" + x); } @FieldSourceTest @Order(12) void supplierOfStreamOfTwoDimensionalObjectArrays(Object[][] array) { fail(Arrays.deepToString(array)); } @FieldSourceTest @Order(13) void listOfNamedParameters(String string) { fail(string); } // --------------------------------------------------------------------- static int[] oneDimensionalPrimitiveArray = new int[] { 1, 2 }; static int[][] twoDimensionalPrimitiveArray = new int[][] { { 1, 2 }, { 3, 4 } }; static Object[] oneDimensionalObjectArray = new Object[] { "one", 2, "three" }; static Object[] oneDimensionalStringArray = new Object[] { "one", "two" }; static Object[][] twoDimensionalObjectArray = new Object[][] { { "one", 2 }, { "three", 4 } }; static String[][] twoDimensionalStringArray = new String[][] { { "one", "two" }, { "three", "four" } }; static Supplier> supplierOfStreamOfOneDimensionalPrimitiveArrays = // () -> Stream.of(new int[] { 1, 2 }, new int[] { 3, 4 }); static Supplier> supplierOfStreamOfTwoDimensionalPrimitiveArrays = // () -> Stream.of(new int[][] { { 1, 2 }, { 3, 4 } }, new int[][] { { 5, 6 }, { 7, 8 } }); static Supplier> supplierOfStreamOfTwoDimensionalPrimitiveArraysWrappedInObjectArrays = () -> Stream.of( new Object[] { new int[][] { { 1, 2 }, { 3, 4 } } }, new Object[] { new int[][] { { 5, 6 }, { 7, 8 } } }); static Supplier> supplierOfStreamOfTwoDimensionalPrimitiveArraysWrappedInArguments = () -> Stream.of( arguments((Object) new int[][] { { 1, 2 }, { 3, 4 } }), arguments((Object) new int[][] { { 5, 6 }, { 7, 8 } })); static Supplier> supplierOfStreamOfOneDimensionalObjectArrays = () -> Stream.of( new Object[] { "one", 2 }, new Object[] { "three", 4 }); static Supplier> supplierOfStreamOfTwoDimensionalObjectArrays = () -> Stream.of( new Object[][] { { "one", 2 }, { "three", 4 } }, new Object[][] { { "five", 6 }, { "seven", 8 } }); static List listOfNamedParameters = // List.of(arguments(named("cool name", "parameter value")), arguments("default name")); } @TestInstance(PER_CLASS) static class BaseLifecyclePerClassFieldSourceTestCase { final List field = List.of("base-1", "base-2"); @ParameterizedTest @FieldSource("field") void test(String value) { fail(value); } } static class SubclassOfBaseLifecyclePerClassFieldSourceTestCase extends BaseLifecyclePerClassFieldSourceTestCase { final List field = List.of("sub-1", "sub-2"); @ParameterizedTest @FieldSource("field") @Override void test(String value) { fail(value); } } static class EnclosingFieldSourceTestCase { @Nested @TestInstance(Lifecycle.PER_CLASS) class NestedLifecyclePerClassFieldSourceTestCase { // Non-static field final List fruits = List.of("apple", "banana"); @ParameterizedTest @FieldSource("fruits") void nonStaticFieldSource(String fruit) { fail(fruit); } } } static class UnusedArgumentsTestCase { @ParameterizedTest(quoteTextArguments = false) @ArgumentsSource(TwoUnusedStringArgumentsProvider.class) void testWithTwoUnusedStringArgumentsProvider(String argument) { fail(argument); } @ParameterizedTest(quoteTextArguments = false) @CsvSource({ "foo, unused1", "bar, unused2" }) void testWithCsvSourceContainingUnusedArguments(String argument) { fail(argument); } @ParameterizedTest(quoteTextArguments = false) @CsvFileSource(resources = "two-column.csv") void testWithCsvFileSourceContainingUnusedArguments(String argument) { fail(argument); } @ParameterizedTest(quoteTextArguments = false) @MethodSource("unusedArgumentsProviderMethod") void testWithMethodSourceProvidingUnusedArguments(String argument) { fail(argument); } static Stream unusedArgumentsProviderMethod() { return Stream.of(arguments("foo", "unused1"), arguments("bar", "unused2")); } @ParameterizedTest(quoteTextArguments = false) @FieldSource("unusedArgumentsProviderField") void testWithFieldSourceProvidingUnusedArguments(String argument) { fail(argument); } static Supplier> unusedArgumentsProviderField = // () -> Stream.of(arguments("foo", "unused1"), arguments("bar", "unused2")); @ParameterizedTest(argumentCountValidation = ArgumentCountValidationMode.STRICT) @CsvSource({ "foo, unused1" }) void testWithStrictArgumentCountValidation(String argument) { fail(argument); } @ParameterizedTest(quoteTextArguments = false, argumentCountValidation = ArgumentCountValidationMode.NONE) @CsvSource({ "foo, unused1" }) void testWithNoneArgumentCountValidation(String argument) { fail(argument); } @ParameterizedTest(quoteTextArguments = false) @CsvSource({ "foo, unused1", "bar" }) void testWithCsvSourceContainingDifferentNumbersOfArguments(String argument) { fail(argument); } @ParameterizedTest @ArgumentsSource(EvaluationReportingArgumentsProvider.class) void testWithEvaluationReportingArgumentsProvider(String argument) { fail(argument); } private static class EvaluationReportingArgumentsProvider implements ArgumentsProvider { @Override public Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context) { return Stream.of(() -> { context.publishReportEntry("evaluated", "true"); return List.of("foo", "unused").toArray(); }); } } } static class LifecycleTestCase { private static final List lifecycleEvents = new ArrayList<>(); private static final Set testMethods = new LinkedHashSet<>(); LifecycleTestCase(TestInfo testInfo) { lifecycleEvents.add("constructor:" + testInfo.getDisplayName()); } @BeforeAll static void beforeAll(TestInfo testInfo) { lifecycleEvents.add("beforeAll:" + testInfo.getDisplayName()); } @AfterAll static void afterAll(TestInfo testInfo) { lifecycleEvents.add("afterAll:" + testInfo.getDisplayName()); } @BeforeEach void beforeEach(TestInfo testInfo) { lifecycleEvents.add("beforeEach:" + testInfo.getDisplayName()); } @AfterEach void afterEach(TestInfo testInfo) { lifecycleEvents.add("afterEach:" + testInfo.getDisplayName()); } @ParameterizedTest(quoteTextArguments = false) @MethodSource("providerMethod") void test1(String argument, TestInfo testInfo) { performTest(argument, testInfo); } @ParameterizedTest(quoteTextArguments = false) @MethodSource("providerMethod") void test2(String argument, TestInfo testInfo) { performTest(argument, testInfo); } private void performTest(String argument, TestInfo testInfo) { var testMethod = testInfo.getTestMethod().orElseThrow().getName(); testMethods.add(testMethod); lifecycleEvents.add(testMethod + ":" + testInfo.getDisplayName()); fail(argument); } static Stream providerMethod() { lifecycleEvents.add("providerMethod"); return Stream.of("foo", "bar"); } } static class RepeatableSourcesTestCase { @ParameterizedTest(quoteTextArguments = false) @CsvFileSource(resources = "two-column.csv") @CsvFileSource(resources = "two-column-with-headers.csv", delimiter = '|', useHeadersInDisplayName = true, nullValues = "NIL") void testWithRepeatableCsvFileSource(String column1, String column2) { fail("%s %s".formatted(column1, column2)); } @CsvFileSource(resources = "two-column.csv") @CsvFileSource(resources = "two-column-with-headers.csv", delimiter = '|', useHeadersInDisplayName = true, nullValues = "NIL") @Retention(RUNTIME) @interface TwoCsvFileSources { } @ParameterizedTest(quoteTextArguments = false) @TwoCsvFileSources void testWithRepeatableCsvFileSourceAsMetaAnnotation(String column1, String column2) { fail("%s %s".formatted(column1, column2)); } @ParameterizedTest(quoteTextArguments = false) @CsvSource({ "a" }) @CsvSource({ "b" }) void testWithRepeatableCsvSource(String argument) { fail(argument); } @CsvSource({ "a" }) @CsvSource({ "b" }) @Retention(RUNTIME) @interface TwoCsvSources { } @ParameterizedTest(quoteTextArguments = false) @TwoCsvSources void testWithRepeatableCsvSourceAsMetaAnnotation(String argument) { fail(argument); } @ParameterizedTest(quoteTextArguments = false) @EnumSource(SmartAction.class) @EnumSource(QuickAction.class) void testWithRepeatableEnumSource(Action argument) { fail(argument.toString()); } @EnumSource(SmartAction.class) @EnumSource(QuickAction.class) @Retention(RUNTIME) @interface TwoEnumSources { } @ParameterizedTest(quoteTextArguments = false) @TwoEnumSources void testWithRepeatableEnumSourceAsMetaAnnotation(Action argument) { fail(argument.toString()); } interface Action { } private enum SmartAction implements Action { FOO } private enum QuickAction implements Action { BAR } @ParameterizedTest(quoteTextArguments = false) @MethodSource("someArgumentsMethodSource") @MethodSource("otherArgumentsMethodSource") void testWithRepeatableMethodSource(String argument) { fail(argument); } @MethodSource("someArgumentsMethodSource") @MethodSource("otherArgumentsMethodSource") @Retention(RUNTIME) @interface TwoMethodSources { } @ParameterizedTest(quoteTextArguments = false) @TwoMethodSources void testWithRepeatableMethodSourceAsMetaAnnotation(String argument) { fail(argument); } public static Stream someArgumentsMethodSource() { return Stream.of(Arguments.of("some")); } public static Stream otherArgumentsMethodSource() { return Stream.of(Arguments.of("other")); } @ParameterizedTest(quoteTextArguments = false) @FieldSource("someArgumentsContainer") @FieldSource("otherArgumentsContainer") void testWithRepeatableFieldSource(String argument) { fail(argument); } @FieldSource("someArgumentsContainer") @FieldSource("otherArgumentsContainer") @Retention(RUNTIME) @interface TwoFieldSources { } @ParameterizedTest(quoteTextArguments = false) @TwoFieldSources void testWithRepeatableFieldSourceAsMetaAnnotation(String argument) { fail(argument); } static List someArgumentsContainer = List.of("some"); static List otherArgumentsContainer = List.of("other"); @ParameterizedTest(quoteTextArguments = false) @ValueSource(strings = "foo") @ValueSource(strings = "bar") void testWithRepeatableValueSource(String argument) { fail(argument); } @ValueSource(strings = "foo") @ValueSource(strings = "bar") @Retention(RUNTIME) @interface TwoValueSources { } @ParameterizedTest(quoteTextArguments = false) @TwoValueSources void testWithRepeatableValueSourceAsMetaAnnotation(String argument) { fail(argument); } @ParameterizedTest(quoteTextArguments = false) @ValueSource(strings = "foo") @ValueSource(strings = "foo") @ValueSource(strings = "foo") @ValueSource(strings = "foo") @ValueSource(strings = "foo") void testWithSameRepeatableAnnotationMultipleTimes(String argument) { fail(argument); } @ParameterizedTest(quoteTextArguments = false) @ValueSource(strings = "a") @ValueSource(strings = "b") @CsvSource({ "c" }) @CsvSource({ "d" }) void testWithDifferentRepeatableAnnotations(String argument) { fail(argument); } @ParameterizedTest(quoteTextArguments = false) @ArgumentsSource(TwoSingleStringArgumentsProvider.class) @ArgumentsSource(TwoUnusedStringArgumentsProvider.class) void testWithRepeatableArgumentsSource(String argument) { fail(argument); } @ArgumentsSource(TwoSingleStringArgumentsProvider.class) @ArgumentsSource(TwoUnusedStringArgumentsProvider.class) @Retention(RUNTIME) @interface TwoArgumentsSources { } @ParameterizedTest(quoteTextArguments = false) @TwoArgumentsSources void testWithRepeatableArgumentsSourceAsMetaAnnotation(String argument) { fail(argument); } } static class SpiParameterInjectionTestCase { @RegisterExtension static final ParameterResolver spiParameterResolver = new ParameterResolver() { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return parameterContext.getDeclaringExecutable() instanceof Constructor // && String.class.equals(parameterContext.getParameter().getType()); } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return "resolved value"; } }; @ParameterizedTest(quoteTextArguments = false) @ArgumentsSource(ArgumentsProviderWithConstructorParameter.class) void argumentsProviderWithConstructorParameter(String argument) { assertEquals("resolved value", argument); } @ParameterizedTest(quoteTextArguments = false) @ValueSource(strings = "value") void argumentConverterWithConstructorParameter( @ConvertWith(ArgumentConverterWithConstructorParameter.class) String argument) { assertEquals("resolved value", argument); } @ParameterizedTest(quoteTextArguments = false) @ValueSource(strings = "value") void argumentsAggregatorWithConstructorParameter( @AggregateWith(ArgumentsAggregatorWithConstructorParameter.class) String argument) { assertEquals("resolved value", argument); } record ArgumentsProviderWithConstructorParameter(String value) implements ArgumentsProvider { @Override public Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context) { return Stream.of(arguments(value)); } } record ArgumentConverterWithConstructorParameter(String value) implements ArgumentConverter { @Override public Object convert(@Nullable Object source, ParameterContext context) throws ArgumentConversionException { return value; } } static class ArgumentsAggregatorWithConstructorParameter extends SimpleArgumentsAggregator { private final String value; ArgumentsAggregatorWithConstructorParameter(String value) { this.value = value; } @Override protected Object aggregateArguments(ArgumentsAccessor accessor, Class targetType, AnnotatedElementContext context, int parameterIndex) throws ArgumentsAggregationException { return this.value; } } } static class ZeroInvocationsTestCase { @ParameterizedTest(quoteTextArguments = false) @MethodSource("zeroArgumentsProvider") void testThatRequiresInvocations(String argument) { fail("This test should not be executed, because no arguments are provided."); } @ParameterizedTest(quoteTextArguments = false, allowZeroInvocations = true) @MethodSource("zeroArgumentsProvider") void testThatDoesNotRequireInvocations(String argument) { fail("This test should not be executed, because no arguments are provided."); } @ParameterizedTest(quoteTextArguments = false, allowZeroInvocations = true) @SuppressWarnings("JUnitMalformedDeclaration") void testThatHasNoArgumentsSource(String argument) { fail("This test should not be executed, because no arguments source is declared."); } public static Stream zeroArgumentsProvider() { return Stream.empty(); } } static class LocaleConversionTestCase { @ParameterizedTest(quoteTextArguments = false) @ValueSource(strings = "en-US") void testWithBcp47(Locale locale) { assertEquals("en", locale.getLanguage()); assertEquals("US", locale.getCountry()); } @ParameterizedTest(quoteTextArguments = false) @ValueSource(strings = "en-US") void testWithIso639(@ConvertWith(Iso639Converter.class) Locale locale) { assertEquals("en-us", locale.getLanguage()); assertEquals("", locale.getCountry()); } @NullMarked static class Iso639Converter extends TypedArgumentConverter { Iso639Converter() { super(String.class, Locale.class); } @SuppressWarnings("deprecation") @Override protected Locale convert(@Nullable String source) throws ArgumentConversionException { return new Locale(requireNonNull(source)); } } } private static class TwoSingleStringArgumentsProvider implements ArgumentsProvider { @Override public Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context) { return Stream.of(arguments("foo"), arguments("bar")); } } private static class TwoUnusedStringArgumentsProvider implements ArgumentsProvider { @Override public Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context) { return Stream.of(arguments("foo", "unused1"), arguments("bar", "unused2")); } } private static class StringLengthConverter implements ArgumentConverter { @Override public Object convert(@Nullable Object source, ParameterContext context) throws ArgumentConversionException { return String.valueOf(source).length(); } } private static class StringAggregator extends SimpleArgumentsAggregator { @Override protected Object aggregateArguments(ArgumentsAccessor accessor, Class targetType, AnnotatedElementContext context, int parameterIndex) throws ArgumentsAggregationException { return accessor.getString(0) + accessor.getString(1); } } private static class ErroneousConverter implements ArgumentConverter { @Override public Object convert(@Nullable Object source, ParameterContext context) throws ArgumentConversionException { throw new ArgumentConversionException("something went horribly wrong"); } } private static class AutoCloseableArgumentProvider implements ArgumentsProvider { @Override public Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context) { return Stream.of(arguments(new AutoCloseableArgument(), Named.of("unused", new AutoCloseableArgument()))); } } static class AutoCloseableArgument implements AutoCloseable { static int closeCounter = 0; @Override public void close() { closeCounter++; } } static class Book { private final String title; private Book(String title) { this.title = title; } static Book factory(String title) { return new Book(title); } } record Record(CharSequence title) { } static class FailureInBeforeEachTestCase { @BeforeEach void beforeEach() { fail("beforeEach"); } @ParameterizedTest(quoteTextArguments = false) @ArgumentsSource(AutoCloseableArgumentProvider.class) void test(AutoCloseableArgument autoCloseable) { assertNotNull(autoCloseable); assertEquals(0, AutoCloseableArgument.closeCounter); } } static class ExceptionInStaticInitializerTestCase { static { //noinspection ConstantValue if (true) { throw new RuntimeException("boom"); } } private static Stream getArguments() { return Stream.of("foo", "bar"); } @ParameterizedTest(quoteTextArguments = false) @MethodSource("getArguments") void test(String value) { fail("should not be called: " + value); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestSuite.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params; import org.junit.platform.suite.api.IncludeClassNamePatterns; import org.junit.platform.suite.api.IncludeEngines; import org.junit.platform.suite.api.SelectPackages; import org.junit.platform.suite.api.Suite; /** * Test suite for JUnit Jupiter parameterized test support. * *

Logging Configuration

* *

In order for our log4j2 configuration to be used in an IDE, you must * set the following system property before running any tests — for * example, in Run Configurations in Eclipse. * *

 * -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager
 * 
* * @since 5.0 */ @Suite @SelectPackages("org.junit.jupiter.params") @IncludeClassNamePatterns(".*Tests?") @IncludeEngines("junit-jupiter") class ParameterizedTestSuite { } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/params/aggregator/AggregatorIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.aggregator; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toMap; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.EventConditions.test; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.stream.IntStream; import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.extension.AnnotatedElementContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.parallel.ResourceLock; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.converter.ArgumentConversionException; import org.junit.jupiter.params.converter.ArgumentConverter; import org.junit.jupiter.params.converter.ConvertWith; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.testkit.engine.EngineExecutionResults; import org.junit.platform.testkit.engine.EngineTestKit; /** * Integration tests for {@link ArgumentsAccessor}, {@link AggregateWith}, * and {@link ArgumentsAggregator}. * * @since 5.2 */ public class AggregatorIntegrationTests { @ParameterizedTest @CsvSource({ // "Jane, Doe, 1980-04-16, F, red", // "Jack, Smith, 2000-11-22, M, blue" // }) void personAggregator(@AggregateWith(PersonAggregator.class) Person person) { testPersonAggregator(person); } @ParameterizedTest @CsvSource({ // "Jane, Doe, 1980-04-16, F, red", // "Jack, Smith, 2000-11-22, M, blue" // }) void personAggregatorRegisteredViaCustomAnnotation(@CsvToPerson Person person) { testPersonAggregator(person); } @ParameterizedTest @CsvSource({ // "42 Peachtree Street, Atlanta, 30318", // "99 Peachtree Road, Atlanta, 30318"// }) void addressAggregator(@CsvToAddress Address address) { testAddressAggregator(address); } @ParameterizedTest @CsvSource({ // "Jane, Doe, 1980-04-16, F, 42 Peachtree Street, Atlanta, 30318, red", // "Jack, Smith, 2000-11-22, M, 99 Peachtree Road, Atlanta, 30318, blue"// }) void personAggregatorAndAddressAggregator(@CsvToPerson Person person, @CsvToAddress @StartIndex(4) Address address) { testPersonAggregator(person); testAddressAggregator(address); } @ParameterizedTest(name = "Mixed Mode #1: {arguments}") @CsvSource({ // "gh-11111111, Jane, Doe, 1980-04-16, F, 42 Peachtree Street, Atlanta, 30318, red", // "gh-22222222, Jack, Smith, 2000-11-22, M, 99 Peachtree Road, Atlanta, 30318, blue"// }) void mixedMode(String issueNumber, @CsvToPerson @StartIndex(1) Person person, @CsvToAddress @StartIndex(5) Address address, TestInfo testInfo) { assertThat(issueNumber).startsWith("gh-"); testPersonAggregator(person); testAddressAggregator(address); assertThat(testInfo.getDisplayName()).startsWith("Mixed Mode #1"); } @ParameterizedTest @CsvSource({ "cat, bird, mouse", "mouse, cat, bird", "mouse, bird, cat" }) void mapAggregator(@AggregateWith(MapAggregator.class) Map map) { assertThat(map).containsOnly(entry("cat", 3), entry("bird", 4), entry("mouse", 5)); } @ParameterizedTest @CsvSource({ "1, 2, 3, 4, 5, 6, 7, 8, 9, 10" }) void argumentsAccessor(ArgumentsAccessor arguments) { assertEquals(55, IntStream.range(0, arguments.size()).map(arguments::getInteger).sum()); } @ParameterizedTest(name = "2 ArgumentsAccessors: {arguments}") @CsvSource({ "1, 2, 3, 4, 5, 6, 7, 8, 9, 10" }) void argumentsAccessors(ArgumentsAccessor arguments1, ArgumentsAccessor arguments2) { assertArrayEquals(arguments1.toArray(), arguments2.toArray()); } @ParameterizedTest(name = "ArgumentsAccessor and TestInfo: {arguments}") @CsvSource({ "1, 2, 3, 4, 5, 6, 7, 8, 9, 10" }) void argumentsAccessorAndTestInfo(ArgumentsAccessor arguments, TestInfo testInfo) { assertEquals(55, IntStream.range(0, arguments.size()).map(arguments::getInteger).sum()); assertThat(testInfo.getDisplayName()).startsWith("ArgumentsAccessor and TestInfo"); } @ParameterizedTest(name = "Indexed Arguments and ArgumentsAccessor: {arguments}") @CsvSource({ "1, 2, 3, 4, 5, 6, 7, 8, 9, 10" }) void indexedArgumentsAndArgumentsAccessor(int num1, int num2, ArgumentsAccessor arguments) { assertEquals(1, num1); assertEquals(2, num2); assertEquals(55, IntStream.range(0, arguments.size()).map(arguments::getInteger).sum()); } @ParameterizedTest(name = "Indexed Arguments, ArgumentsAccessor, and TestInfo: {arguments}") @CsvSource({ "1, 2, 3, 4, 5, 6, 7, 8, 9, 10" }) void indexedArgumentsArgumentsAccessorAndTestInfo(int num1, int num2, ArgumentsAccessor arguments, TestInfo testInfo) { assertEquals(1, num1); assertEquals(2, num2); assertEquals(55, IntStream.range(0, arguments.size()).map(arguments::getInteger).sum()); assertThat(testInfo.getDisplayName()).startsWith("Indexed Arguments, ArgumentsAccessor, and TestInfo"); } @ParameterizedTest(name = "Indexed Arguments, 2 ArgumentsAccessors, and TestInfo: {arguments}") @CsvSource({ "1, 2, 3, 4, 5, 6, 7, 8, 9, 10" }) void indexedArgumentsArgumentsAccessorsAndTestInfo(int num1, int num2, ArgumentsAccessor arguments1, ArgumentsAccessor arguments2, TestInfo testInfo) { assertEquals(1, num1); assertEquals(2, num2); assertArrayEquals(arguments1.toArray(), arguments2.toArray()); assertEquals(55, IntStream.range(0, arguments1.size()).map(arguments1::getInteger).sum()); assertThat(testInfo.getDisplayName()).startsWith("Indexed Arguments, 2 ArgumentsAccessors, and TestInfo"); } @ParameterizedTest @CsvSource({ "foo, bar" }) void nullAggregator(@AggregateWith(NullAggregator.class) Person person) { assertNull(person); } @Test void reportsExceptionForErroneousAggregator() { var results = execute( selectMethod(ErroneousTestCases.class, "testWithErroneousAggregator", Object.class.getName())); results.testEvents().assertThatEvents()// .haveExactly(1, event(test(), finishedWithFailure(instanceOf(ParameterResolutionException.class), // message("Error aggregating arguments for parameter at index 0: something went horribly wrong")))); } @ParameterizedTest @CsvSource({ // "first", // "second" // }) void argumentsAccessorInvocationIndex(ArgumentsAccessor arguments) { if ("first".equals(arguments.getString(0))) { assertEquals(1, arguments.getInvocationIndex()); } if ("second".equals(arguments.getString(0))) { assertEquals(2, arguments.getInvocationIndex()); } } private void testPersonAggregator(Person person) { if (person.firstName.equals("Jane")) { assertEquals("Jane Doe", person.getFullName()); assertEquals(1980, person.dateOfBirth.getYear()); assertEquals(Gender.F, person.gender); } if (person.firstName.equals("Jack")) { assertEquals("Jack Smith", person.getFullName()); assertEquals(2000, person.dateOfBirth.getYear()); assertEquals(Gender.M, person.gender); } } private void testAddressAggregator(Address address) { assertThat(address.street).contains("Peachtree"); assertEquals("Atlanta", address.city); assertEquals(30318, address.zipCode); } private EngineExecutionResults execute(DiscoverySelector... selectors) { return EngineTestKit.execute("junit-jupiter", request().selectors(selectors).build()); } // ------------------------------------------------------------------------- @NullUnmarked public static class Person { final String firstName; final String lastName; final Gender gender; final LocalDate dateOfBirth; Person(String firstName, String lastName, LocalDate dateOfBirth, Gender gender) { this.firstName = firstName; this.lastName = lastName; this.gender = gender; this.dateOfBirth = dateOfBirth; } String getFullName() { return this.firstName + " " + this.lastName; } } enum Gender { F, M } @NullUnmarked record Address(String street, String city, int zipCode) { } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) @interface StartIndex { int value(); } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) @AggregateWith(PersonAggregator.class) public @interface CsvToPerson { } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) @AggregateWith(AddressAggregator.class) @interface CsvToAddress { } static class PersonAggregator extends SimpleArgumentsAggregator { @Override protected Person aggregateArguments(ArgumentsAccessor accessor, Class targetType, AnnotatedElementContext context, int parameterIndex) { int startIndex = context.findAnnotation(StartIndex.class).map(StartIndex::value).orElse(0); // @formatter:off return new Person( accessor.getString(startIndex + 0), accessor.getString(startIndex + 1), accessor.get(startIndex + 2, LocalDate.class), accessor.get(startIndex + 3, Gender.class) ); // @formatter:on } } static class AddressAggregator extends SimpleArgumentsAggregator { @Override public Address aggregateArguments(ArgumentsAccessor arguments, Class targetType, AnnotatedElementContext context, int parameterIndex) { int startIndex = context.findAnnotation(StartIndex.class).map(StartIndex::value).orElse(0); // @formatter:off return new Address( arguments.getString(startIndex + 0), arguments.getString(startIndex + 1), requireNonNull(arguments.getInteger(startIndex + 2)) ); // @formatter:on } } /** * Maps from String to length of String. */ static class MapAggregator extends SimpleArgumentsAggregator { @Override protected Map aggregateArguments(ArgumentsAccessor arguments, Class targetType, AnnotatedElementContext context, int parameterIndex) { // @formatter:off return IntStream.range(0, arguments.size()) .mapToObj(arguments::getString) .collect(toMap(s -> s, String::length)); // @formatter:on } } static class NullAggregator extends SimpleArgumentsAggregator { @Override protected @Nullable Object aggregateArguments(ArgumentsAccessor accessor, Class targetType, AnnotatedElementContext context, int parameterIndex) { Preconditions.condition(!targetType.isPrimitive(), () -> "only supports reference types"); return null; } } static class ErroneousAggregator extends SimpleArgumentsAggregator { @Override protected Object aggregateArguments(ArgumentsAccessor accessor, Class targetType, AnnotatedElementContext context, int parameterIndex) throws ArgumentsAggregationException { throw new ArgumentsAggregationException("something went horribly wrong"); } } static class ErroneousTestCases { @ParameterizedTest @ValueSource(ints = 42) void testWithErroneousAggregator(@AggregateWith(ErroneousAggregator.class) Object ignored) { fail("this should never be called"); } } @Test @ResourceLock("InstanceCountingConverter.instanceCount") void aggregatorIsInstantiatedOnlyOnce() { InstanceCountingAggregator.instanceCount = 0; CountingTestCase.output.clear(); execute(selectMethod(CountingTestCase.class, "testWithCountingConverterAggregator", int.class.getName() + "," + Object.class.getName())); assertThat(InstanceCountingAggregator.instanceCount).isEqualTo(1); assertThat(CountingTestCase.output)// .containsExactly("noisy test(1, enigma)", "noisy test(2, enigma)", "noisy test(3, enigma)"); } @Test @ResourceLock("InstanceCountingConverter.instanceCount") void converterIsInstantiatedOnlyOnce() { InstanceCountingConverter.instanceCount = 0; CountingTestCase.output.clear(); execute(selectMethod(CountingTestCase.class, "testWithCountingConverterAggregator", int.class.getName() + "," + Object.class.getName())); assertThat(InstanceCountingConverter.instanceCount).isEqualTo(1); assertThat(CountingTestCase.output)// .containsExactly("noisy test(1, enigma)", "noisy test(2, enigma)", "noisy test(3, enigma)"); } static class InstanceCountingConverter implements ArgumentConverter { static int instanceCount; InstanceCountingConverter() { instanceCount++; } @Override public @Nullable Object convert(@Nullable Object source, ParameterContext context) throws ArgumentConversionException { return source; } } static class InstanceCountingAggregator extends SimpleArgumentsAggregator { static int instanceCount; InstanceCountingAggregator() { instanceCount++; } @Override protected Object aggregateArguments(ArgumentsAccessor accessor, Class targetType, AnnotatedElementContext context, int parameterIndex) throws ArgumentsAggregationException { return "enigma"; } } static class CountingTestCase { static final List output = new ArrayList<>(); @SuppressWarnings("JUnitMalformedDeclaration") @ParameterizedTest @ValueSource(ints = { 1, 2, 3 }) void testWithCountingConverterAggregator(@ConvertWith(InstanceCountingConverter.class) int i, @AggregateWith(InstanceCountingAggregator.class) Object o) { output.add("noisy test(" + i + ", " + o + ")"); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.aggregator; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertIterableEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import java.util.Arrays; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; /** * Unit tests for {@link DefaultArgumentsAccessor}. * * @since 5.2 */ class DefaultArgumentsAccessorTests { @SuppressWarnings("DataFlowIssue") @Test void argumentsMustNotBeNull() { assertPreconditionViolationFor(() -> defaultArgumentsAccessor(1, (Object[]) null)); } @Test void indexMustNotBeNegative() { ArgumentsAccessor arguments = defaultArgumentsAccessor(1, 1, 2); assertPreconditionViolationFor(() -> arguments.get(-1)).withMessage("index must be >= 0 and < 2"); } @Test void indexMustBeSmallerThanLength() { ArgumentsAccessor arguments = defaultArgumentsAccessor(1, 1, 2); assertPreconditionViolationFor(() -> arguments.get(2)).withMessage("index must be >= 0 and < 2"); } @Test void getNull() { assertNull(defaultArgumentsAccessor(1, new @Nullable Object[] { null }).get(0)); } @Test void getWithNullCastToWrapperType() { assertNull(defaultArgumentsAccessor(1, (Object[]) new @Nullable Integer[] { null }).get(0, Integer.class)); } @Test void get() { assertEquals(1, defaultArgumentsAccessor(1, 1).get(0)); } @Test void getWithCast() { assertEquals(Integer.valueOf(1), defaultArgumentsAccessor(1, 1).get(0, Integer.class)); assertEquals(Character.valueOf('A'), defaultArgumentsAccessor(1, 'A').get(0, Character.class)); } @Test void getWithCastToPrimitiveType() { Exception exception = assertThrows(ArgumentAccessException.class, () -> defaultArgumentsAccessor(1, 1).get(0, int.class)); assertThat(exception.getMessage()).isEqualTo( "Argument at index [0] with value [1] and type [java.lang.Integer] could not be converted or cast to type [int]."); exception = assertThrows(ArgumentAccessException.class, () -> defaultArgumentsAccessor(1, new @Nullable Object[] { null }).get(0, int.class)); assertThat(exception.getMessage()).isEqualTo( "Argument at index [0] with value [null] and type [null] could not be converted or cast to type [int]."); } @Test void getWithCastToIncompatibleType() { Exception exception = assertThrows(ArgumentAccessException.class, () -> defaultArgumentsAccessor(1, 1).get(0, Character.class)); assertThat(exception.getMessage()).isEqualTo( "Argument at index [0] with value [1] and type [java.lang.Integer] could not be converted or cast to type [java.lang.Character]."); } @Test void getCharacter() { assertEquals(Character.valueOf('A'), defaultArgumentsAccessor(1, 'A', 'B').getCharacter(0)); } @Test void getBoolean() { assertEquals(Boolean.TRUE, defaultArgumentsAccessor(1, true, false).getBoolean(0)); } @Test void getByte() { assertEquals(Byte.valueOf((byte) 42), defaultArgumentsAccessor(1, (byte) 42).getByte(0)); } @Test void getShort() { assertEquals(Short.valueOf((short) 42), defaultArgumentsAccessor(1, (short) 42).getShort(0)); } @Test void getInteger() { assertEquals(Integer.valueOf(42), defaultArgumentsAccessor(1, 42).getInteger(0)); } @Test void getLong() { assertEquals(Long.valueOf(42L), defaultArgumentsAccessor(1, 42L).getLong(0)); } @Test void getFloat() { assertEquals(Float.valueOf(42.0f), defaultArgumentsAccessor(1, 42.0f).getFloat(0)); } @Test void getDouble() { assertEquals(Double.valueOf(42.0), defaultArgumentsAccessor(1, 42.0).getDouble(0)); } @Test void getString() { assertEquals("foo", defaultArgumentsAccessor(1, "foo", "bar").getString(0)); } @Test void toArray() { var arguments = defaultArgumentsAccessor(1, "foo", "bar"); var copy = arguments.toArray(); assertArrayEquals(new String[] { "foo", "bar" }, copy); // Modify local copy: copy[0] = "Boom!"; assertEquals("foo", arguments.toArray()[0]); } @Test void toList() { var arguments = defaultArgumentsAccessor(1, "foo", "bar"); var copy = arguments.toList(); assertIterableEquals(Arrays.asList("foo", "bar"), copy); // Modify local copy: assertThrows(UnsupportedOperationException.class, () -> copy.set(0, "Boom!")); } @Test void size() { assertEquals(0, defaultArgumentsAccessor(1).size()); assertEquals(1, defaultArgumentsAccessor(1, 42).size()); assertEquals(5, defaultArgumentsAccessor(1, 'a', 'b', 'c', 'd', 'e').size()); } private static DefaultArgumentsAccessor defaultArgumentsAccessor(int invocationIndex, @Nullable Object... arguments) { var classLoader = DefaultArgumentsAccessorTests.class.getClassLoader(); return DefaultArgumentsAccessor.create(invocationIndex, classLoader, arguments); } @SuppressWarnings("unused") private static void foo() { } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.converter; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.junit.platform.commons.util.ClassLoaderUtils.getClassLoader; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.support.conversion.ConversionException; import org.junit.platform.commons.test.TestClassLoader; import org.junit.platform.commons.util.ClassLoaderUtils; /** * Unit tests for {@link DefaultArgumentConverter}. * * @since 5.0 */ class DefaultArgumentConverterTests { private final DefaultArgumentConverter underTest = spy(DefaultArgumentConverter.INSTANCE); @Test void isAwareOfNull() { assertConverts(null, Object.class, null); assertConverts(null, String.class, null); assertConverts(null, Boolean.class, null); } @Test void isAwareOfWrapperTypesForPrimitiveTypes() { assertConverts(true, boolean.class, true); assertConverts(false, boolean.class, false); assertConverts((byte) 1, byte.class, (byte) 1); assertConverts('o', char.class, 'o'); assertConverts((short) 1, short.class, (short) 1); assertConverts(1, int.class, 1); assertConverts(1L, long.class, 1L); assertConverts(1.0f, float.class, 1.0f); assertConverts(1.0d, double.class, 1.0d); } @Test void isAwareOfWideningConversions() { assertConverts((byte) 1, short.class, (byte) 1); assertConverts((short) 1, int.class, (short) 1); assertConverts((char) 1, int.class, (char) 1); assertConverts((byte) 1, long.class, (byte) 1); assertConverts(1, long.class, 1); assertConverts((char) 1, float.class, (char) 1); assertConverts(1, float.class, 1); assertConverts(1L, double.class, 1L); assertConverts(1.0f, double.class, 1.0f); } @ParameterizedTest(name = "[{index}] {0}") @ValueSource(classes = { char.class, boolean.class, short.class, byte.class, int.class, long.class, float.class, double.class, void.class }) void throwsExceptionForNullToPrimitiveTypeConversion(Class type) { assertThatExceptionOfType(ArgumentConversionException.class) // .isThrownBy(() -> convert(null, type)) // .withMessage("Cannot convert null to primitive value of type " + type.getCanonicalName()); verify(underTest, never()).convert(any(), any(), any(ClassLoader.class)); } @Test void throwsExceptionForNonStringsConversion() { assertThatExceptionOfType(ArgumentConversionException.class) // .isThrownBy(() -> convert(new Enigma(), String.class)) // .withMessage("No built-in converter for source type %s and target type java.lang.String", Enigma.class.getName()); verify(underTest, never()).convert(any(), any(), any(ClassLoader.class)); } @Test void delegatesStringsConversion() { doReturn(null).when(underTest).convert(any(), any(), any(ClassLoader.class)); convert("value", int.class); verify(underTest).convert("value", int.class, getClassLoader(DefaultArgumentConverterTests.class)); } @Test void throwsExceptionForDelegatedConversionFailure() { ConversionException exception = new ConversionException("fail"); doThrow(exception).when(underTest).convert(any(), any(), any(ClassLoader.class)); assertThatExceptionOfType(ArgumentConversionException.class) // .isThrownBy(() -> convert("value", int.class)) // .withCause(exception) // .withMessage(exception.getMessage()); verify(underTest).convert("value", int.class, getClassLoader(DefaultArgumentConverterTests.class)); } @Test void delegatesStringToClassWithCustomTypeFromDifferentClassLoaderConversion() throws Exception { String customTypeName = Enigma.class.getName(); try (var testClassLoader = TestClassLoader.forClasses(Enigma.class)) { var customType = testClassLoader.loadClass(customTypeName); assertThat(customType.getClassLoader()).isSameAs(testClassLoader); var declaringExecutable = ReflectionSupport.findMethod(customType, "foo").orElseThrow(); assertThat(declaringExecutable.getDeclaringClass().getClassLoader()).isSameAs(testClassLoader); doReturn(customType).when(underTest).convert(any(), any(), any(ClassLoader.class)); var clazz = (Class) convert(customTypeName, Class.class, testClassLoader); assertThat(clazz).isNotEqualTo(Enigma.class); assertThat(clazz).isNotNull().isEqualTo(customType); assertThat(clazz.getClassLoader()).isSameAs(testClassLoader); verify(underTest).convert(customTypeName, Class.class, testClassLoader); } } // ------------------------------------------------------------------------- private void assertConverts(@Nullable Object input, Class targetClass, @Nullable Object expectedOutput) { var result = convert(input, targetClass); assertThat(result) // .describedAs(input + " --(" + targetClass.getName() + ")--> " + expectedOutput) // .isEqualTo(expectedOutput); verify(underTest, never()).convert(any(), any(), any(ClassLoader.class)); } private @Nullable Object convert(@Nullable Object input, Class targetClass) { return convert(input, targetClass, ClassLoaderUtils.getClassLoader(getClass())); } private @Nullable Object convert(@Nullable Object input, Class targetClass, ClassLoader classLoader) { return underTest.convert(input, targetClass, classLoader); } @SuppressWarnings("unused") private static void foo() { } private static class Enigma { @SuppressWarnings("unused") void foo() { } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverterTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.converter; import static java.time.ZoneOffset.UTC; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetDateTime; import java.time.OffsetTime; import java.time.Year; import java.time.YearMonth; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.chrono.ChronoLocalDate; import java.time.chrono.ChronoLocalDateTime; import java.time.chrono.ChronoZonedDateTime; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; /** * @since 5.0 */ class JavaTimeArgumentConverterTests { @SuppressWarnings("DataFlowIssue") @Test void convertsStringToChronoLocalDate() { assertThat(convert("01.02.2017", "dd.MM.yyyy", ChronoLocalDate.class)) // .isEqualTo(LocalDate.of(2017, 2, 1)); } @SuppressWarnings("DataFlowIssue") @Test void convertsStringToChronoLocalDateTime() { assertThat(convert("01.02.2017 12:34:56.789", "dd.MM.yyyy HH:mm:ss.SSS", ChronoLocalDateTime.class)) // .isEqualTo(LocalDateTime.of(2017, 2, 1, 12, 34, 56, 789_000_000)); } @SuppressWarnings("DataFlowIssue") @Test void convertsStringToChronoZonedDateTime() { assertThat(convert("01.02.2017 12:34:56.789 Z", "dd.MM.yyyy HH:mm:ss.SSS X", ChronoZonedDateTime.class)) // .isEqualTo(ZonedDateTime.of(2017, 2, 1, 12, 34, 56, 789_000_000, UTC)); } @SuppressWarnings("DataFlowIssue") @Test void convertsStringToLocalDate() { assertThat(convert("01.02.2017", "dd.MM.yyyy", LocalDate.class)) // .isEqualTo(LocalDate.of(2017, 2, 1)); } @SuppressWarnings("DataFlowIssue") @Test void convertsStringToLocalDateTime() { assertThat(convert("01.02.2017 12:34:56.789", "dd.MM.yyyy HH:mm:ss.SSS", LocalDateTime.class)) // .isEqualTo(LocalDateTime.of(2017, 2, 1, 12, 34, 56, 789_000_000)); } @SuppressWarnings("DataFlowIssue") @Test void convertsStringToLocalTime() { assertThat(convert("12:34:56.789", "HH:mm:ss.SSS", LocalTime.class)) // .isEqualTo(LocalTime.of(12, 34, 56, 789_000_000)); } @SuppressWarnings("DataFlowIssue") @Test void convertsStringToOffsetDateTime() { assertThat(convert("01.02.2017 12:34:56.789 +02", "dd.MM.yyyy HH:mm:ss.SSS X", OffsetDateTime.class)) // .isEqualTo(OffsetDateTime.of(2017, 2, 1, 12, 34, 56, 789_000_000, ZoneOffset.ofHours(2))); } @SuppressWarnings("DataFlowIssue") @Test void convertsStringToOffsetTime() { assertThat(convert("12:34:56.789 -02", "HH:mm:ss.SSS X", OffsetTime.class)) // .isEqualTo(OffsetTime.of(12, 34, 56, 789_000_000, ZoneOffset.ofHours(-2))); } @SuppressWarnings("DataFlowIssue") @Test void convertsStringToYear() { assertThat(convert("2017", "yyyy", Year.class)) // .isEqualTo(Year.of(2017)); } @SuppressWarnings("DataFlowIssue") @Test void convertsStringToYearMonth() { assertThat(convert("03/2017", "MM/yyyy", YearMonth.class)) // .isEqualTo(YearMonth.of(2017, 3)); } @SuppressWarnings("DataFlowIssue") @Test void convertsStringToZonedDateTime() { assertThat(convert("01.02.2017 12:34:56.789 Europe/Berlin", "dd.MM.yyyy HH:mm:ss.SSS VV", ZonedDateTime.class)) // .isEqualTo(ZonedDateTime.of(2017, 2, 1, 12, 34, 56, 789_000_000, ZoneId.of("Europe/Berlin"))); } @Test void throwsExceptionOnInvalidTargetType() { var exception = assertThrows(ArgumentConversionException.class, () -> convert("2017", "yyyy", Integer.class)); assertThat(exception).hasMessage("Cannot convert to java.lang.Integer: 2017"); } /** * @since 5.12 */ @Test void throwsExceptionOnNullParameterWithoutNullable() { var exception = assertThrows(ArgumentConversionException.class, () -> convert(null, "dd.MM.yyyy", LocalDate.class)); assertThat(exception).hasMessage( "Cannot convert null to java.time.LocalDate; consider setting 'nullable = true'"); } /** * @since 5.12 */ @SuppressWarnings("DataFlowIssue") @Test void convertsNullableParameter() { assertThat(convert(null, "dd.MM.yyyy", true, LocalDate.class)).isNull(); } private @Nullable Object convert(@Nullable Object input, String pattern, Class targetClass) { return convert(input, pattern, false, targetClass); } private @Nullable Object convert(@Nullable Object input, String pattern, boolean nullable, Class targetClass) { var converter = new JavaTimeArgumentConverter(); var annotation = mock(JavaTimeConversionPattern.class); when(annotation.value()).thenReturn(pattern); when(annotation.nullable()).thenReturn(nullable); return converter.convert(input, targetClass, annotation); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/params/converter/TypedArgumentConverterTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.converter; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullFor; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.NullSource; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.commons.support.ReflectionSupport; /** * Tests for {@link TypedArgumentConverter}. * * @since 5.7 */ class TypedArgumentConverterTests { @Nested class UnitTests { private final StringLengthArgumentConverter converter = new StringLengthArgumentConverter(); /** * @since 5.8 */ @SuppressWarnings("DataFlowIssue") @Test void preconditions() { assertPreconditionViolationNotNullFor("sourceType", () -> new StringLengthArgumentConverter(null, Integer.class)); assertPreconditionViolationNotNullFor("targetType", () -> new StringLengthArgumentConverter(String.class, null)); } @Test void convertsSourceToTarget() { assertAll(// () -> assertConverts("abcd", 4), // () -> assertConverts("", 0), // () -> assertConverts(null, 0)// ); } private void assertConverts(@Nullable String input, int expected) { assertThat(this.converter.convert(input)).isEqualTo(expected); } @Test void sourceTypeMismatch() { Parameter parameter = findParameterOfMethod("stringToBoolean", Boolean.class); ParameterContext parameterContext = parameterContext(parameter); assertThatExceptionOfType(ArgumentConversionException.class)// .isThrownBy(() -> this.converter.convert(Boolean.TRUE, parameterContext))// .withMessage("StringLengthArgumentConverter cannot convert objects of type [java.lang.Boolean]. " + "Only source objects of type [java.lang.String] are supported."); } @Test void sourceTypeMismatchForArrayType() { Parameter parameter = findParameterOfMethod("stringToByteArray", Byte[].class); ParameterContext parameterContext = parameterContext(parameter); assertThatExceptionOfType(ArgumentConversionException.class)// .isThrownBy(() -> this.converter.convert(new String[][] {}, parameterContext))// .withMessage("StringLengthArgumentConverter cannot convert objects of type [java.lang.String[][]]. " + "Only source objects of type [java.lang.String] are supported."); } @Test void sourceTypeMismatchForPrimitiveArrayType() { Parameter parameter = findParameterOfMethod("stringToByteArray", Byte[].class); ParameterContext parameterContext = parameterContext(parameter); assertThatExceptionOfType(ArgumentConversionException.class)// .isThrownBy(() -> this.converter.convert(new byte[0], parameterContext))// .withMessage("StringLengthArgumentConverter cannot convert objects of type [byte[]]. " + "Only source objects of type [java.lang.String] are supported."); } @Test void targetTypeMismatch() { Parameter parameter = findParameterOfMethod("stringToBoolean", Boolean.class); ParameterContext parameterContext = parameterContext(parameter); assertThatExceptionOfType(ArgumentConversionException.class)// .isThrownBy(() -> this.converter.convert("enigma", parameterContext))// .withMessage("StringLengthArgumentConverter cannot convert to type [java.lang.Boolean]. " + "Only target type [java.lang.Integer] is supported."); } @Test void targetTypeMismatchForArrayType() { Parameter parameter = findParameterOfMethod("stringToByteArray", Byte[].class); ParameterContext parameterContext = parameterContext(parameter); assertThatExceptionOfType(ArgumentConversionException.class)// .isThrownBy(() -> this.converter.convert("enigma", parameterContext))// .withMessage("StringLengthArgumentConverter cannot convert to type [java.lang.Byte[]]. " + "Only target type [java.lang.Integer] is supported."); } @Test void targetTypeMismatchForPrimitiveArrayType() { Parameter parameter = findParameterOfMethod("stringToPrimitiveByteArray", byte[].class); ParameterContext parameterContext = parameterContext(parameter); assertThatExceptionOfType(ArgumentConversionException.class)// .isThrownBy(() -> this.converter.convert("enigma", parameterContext))// .withMessage("StringLengthArgumentConverter cannot convert to type [byte[]]. " + "Only target type [java.lang.Integer] is supported."); } private ParameterContext parameterContext(Parameter parameter) { ParameterContext parameterContext = mock(); when(parameterContext.getParameter()).thenReturn(parameter); return parameterContext; } private Parameter findParameterOfMethod(String methodName, Class... parameterTypes) { Method method = ReflectionSupport.findMethod(getClass(), methodName, parameterTypes).get(); return method.getParameters()[0]; } void stringToBoolean(Boolean b) { } void stringToByteArray(Byte[] array) { } void stringToPrimitiveByteArray(byte[] array) { } } /** * @since 5.8 */ @Nested class IntegrationTests { @ParameterizedTest @NullSource void nullStringToInteger(@StringLength Integer length) { assertThat(length).isEqualTo(0); } @ParameterizedTest @NullSource void nullStringToPrimitiveInt(@StringLength int length) { assertThat(length).isEqualTo(0); } @ParameterizedTest @NullSource void nullStringToPrimitiveLong(@StringLength long length) { assertThat(length).isEqualTo(0); } @ParameterizedTest @ValueSource(strings = "enigma") void stringToInteger(@StringLength Integer length) { assertThat(length).isEqualTo(6); } @ParameterizedTest @ValueSource(strings = "enigma") void stringToPrimitiveInt(@StringLength int length) { assertThat(length).isEqualTo(6); } @ParameterizedTest @ValueSource(strings = "enigma") void stringToPrimitiveLong(@StringLength long length) { assertThat(length).isEqualTo(6); } } @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @ConvertWith(StringLengthArgumentConverter.class) private @interface StringLength { } @NullMarked private static class StringLengthArgumentConverter extends TypedArgumentConverter { StringLengthArgumentConverter() { this(String.class, Integer.class); } StringLengthArgumentConverter(Class sourceType, Class targetType) { super(sourceType, targetType); } @Override protected Integer convert(@Nullable String source) throws ArgumentConversionException { return (source != null ? source.length() : 0); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProviderTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.params.provider.MockCsvAnnotationBuilder.csvSource; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atMostOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import java.util.stream.Stream; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.support.ParameterDeclarations; @DisplayName("AnnotationBasedArgumentsProvider") class AnnotationBasedArgumentsProviderTests { private final AnnotationBasedArgumentsProvider annotationBasedArgumentsProvider = new AnnotationBasedArgumentsProvider<>() { @Override protected Stream provideArguments( org.junit.jupiter.params.support.ParameterDeclarations parameters, ExtensionContext context, CsvSource annotation) { return Stream.of(Arguments.of(annotation)); } }; @SuppressWarnings("DataFlowIssue") @Test @DisplayName("should throw exception when null annotation is provided to accept method") void shouldThrowExceptionWhenNullAnnotationIsProvidedToAccept() { assertThatThrownBy(() -> annotationBasedArgumentsProvider.accept(null)) // .hasMessage("annotation must not be null"); } @Test @DisplayName("should invoke the provideArguments template method with the accepted annotation") void shouldInvokeTemplateMethodWithTheAnnotationProvidedToAccept() { var spiedProvider = spy(annotationBasedArgumentsProvider); var parameters = mock(org.junit.jupiter.params.support.ParameterDeclarations.class); var extensionContext = mock(ExtensionContext.class); var annotation = csvSource("0", "1", "2"); annotationBasedArgumentsProvider.accept(annotation); annotationBasedArgumentsProvider.provideArguments(parameters, extensionContext); verify(spiedProvider, atMostOnce()).provideArguments(eq(parameters), eq(extensionContext), eq(annotation)); } @Test @DisplayName("should invoke the provideArguments template method for every accepted annotation") void shouldInvokeTemplateMethodForEachAnnotationProvided() { var parameters = mock(ParameterDeclarations.class); var extensionContext = mock(ExtensionContext.class); var foo = csvSource("foo"); var bar = csvSource("bar"); annotationBasedArgumentsProvider.accept(foo); annotationBasedArgumentsProvider.accept(bar); var arguments = annotationBasedArgumentsProvider.provideArguments(parameters, extensionContext).toList(); assertThat(arguments).hasSize(2); assertThat(arguments.getFirst().get()[0]).isEqualTo(foo); assertThat(arguments.get(1).get()[0]).isEqualTo(bar); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/params/provider/ArgumentsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.junit.jupiter.params.provider.Arguments.of; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; /** * Unit tests for {@link Arguments}. * * @since 5.0 */ class ArgumentsTests { @Test void ofSupportsVarargs() { var arguments = of(1, "2", 3.0); assertArrayEquals(new Object[] { 1, "2", 3.0 }, arguments.get()); } @Test void argumentsSupportsVarargs() { var arguments = arguments(1, "2", 3.0); assertArrayEquals(new Object[] { 1, "2", 3.0 }, arguments.get()); } @Test void ofReturnsSameArrayUsedForCreating() { Object[] input = { 1, "2", 3.0 }; var arguments = of(input); assertThat(arguments.get()).isSameAs(input); } @Test void argumentsReturnsSameArrayUsedForCreating() { Object[] input = { 1, "2", 3.0 }; var arguments = arguments(input); assertThat(arguments.get()).isSameAs(input); } @Test void fromSupportsCollection() { Collection<@Nullable Object> input = Arrays.asList(1, "two", null, 3.0); var arguments = Arguments.from(input); assertArrayEquals(new Object[] { 1, "two", null, 3.0 }, arguments.get()); } @Test void fromSupportsIterable() { var input = new IterableWithNullableElements(1, "two", null, 3.0); var arguments = Arguments.from(input); assertArrayEquals(new Object[] { 1, "two", null, 3.0 }, arguments.get()); } @Test void fromSupportsListDefensiveCopy() { var input = new ArrayList<@Nullable Object>(asList(1, "two", null, 3.0)); var arguments = Arguments.from(input); // Modify input input.set(1, "changed"); input.add("new"); // Assert that arguments are unchanged assertArrayEquals(new Object[] { 1, "two", null, 3.0 }, arguments.get()); } @Test void argumentsFromSupportsCollection() { Collection<@Nullable Object> input = asList("a", 2, null); var arguments = Arguments.argumentsFrom(input); assertArrayEquals(new Object[] { "a", 2, null }, arguments.get()); } @Test void argumentsFromSupportsIterable() { var input = new IterableWithNullableElements("a", 2, null); var arguments = Arguments.argumentsFrom(input); assertArrayEquals(new Object[] { "a", 2, null }, arguments.get()); } @Test void argumentSetSupportsCollection() { Collection<@Nullable Object> input = asList("x", null, 42); var argumentSet = Arguments.argumentSetFrom("list-test", input); assertArrayEquals(new Object[] { "x", null, 42 }, argumentSet.get()); assertThat(argumentSet.getName()).isEqualTo("list-test"); } @Test void argumentSetSupportsIterable() { var input = new IterableWithNullableElements("x", null, 42); var argumentSet = Arguments.argumentSetFrom("list-test", input); assertArrayEquals(new Object[] { "x", null, 42 }, argumentSet.get()); assertThat(argumentSet.getName()).isEqualTo("list-test"); } @Test void toListReturnsMutableListOfArguments() { var arguments = Arguments.of("a", 2, null); var result = arguments.toList(); assertThat(result).containsExactly("a", 2, null); // preserves content result.add("extra"); // confirms mutability assertThat(result).contains("extra"); } @Test void toListDoesNotAffectInternalArgumentsState() { var arguments = Arguments.of("a", 2, null); var result = arguments.toList(); result.add("extra"); // mutate the returned list // Confirm that internal state was not modified var freshCopy = arguments.toList(); assertThat(freshCopy).containsExactly("a", 2, null); } @Test void toListWorksOnEmptyArguments() { var arguments = Arguments.of(); var result = arguments.toList(); assertThat(result).isEmpty(); result.add("extra"); assertThat(result).containsExactly("extra"); } private static final class IterableWithNullableElements implements Iterable<@Nullable Object> { private final Collection<@Nullable Object> collection; private IterableWithNullableElements(@Nullable Object... items) { this.collection = asList(items); } @Override public Iterator<@Nullable Object> iterator() { return collection.iterator(); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.junit.jupiter.params.provider.MockCsvAnnotationBuilder.csvSource; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.mockito.Mockito.mock; import java.util.stream.Stream; import org.assertj.core.api.ThrowingConsumer; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.support.ParameterNameAndArgument; import org.junit.platform.commons.JUnitException; /** * @since 5.0 */ class CsvArgumentsProviderTests { @Test void throwsExceptionForBlankLines() { var annotation = csvSource("foo", "bar", " "); assertThatExceptionOfType(JUnitException.class)// .isThrownBy(() -> provideArguments(annotation).toArray())// .withMessage("CSV record at index 3 must not be blank"); } @Test void throwsExceptionIfNeitherValueNorTextBlockIsDeclared() { var annotation = csvSource().build(); assertPreconditionViolationFor(() -> provideArguments(annotation).findAny())// .withMessage("@CsvSource must be declared with either `value` or `textBlock` but not both"); } @Test void throwsExceptionIfValueAndTextBlockAreDeclared() { var annotation = csvSource().lines("foo").textBlock(""" bar baz """).build(); assertPreconditionViolationFor(() -> provideArguments(annotation).findAny())// .withMessage("@CsvSource must be declared with either `value` or `textBlock` but not both"); } @Test void providesSingleArgument() { var annotation = csvSource("foo"); var arguments = provideArguments(annotation); assertThat(arguments).containsExactly(array("foo")); } @Test void providesSingleArgumentFromTextBlock() { var annotation = csvSource().textBlock("foo").build(); var arguments = provideArguments(annotation); assertThat(arguments).containsExactly(array("foo")); } @Test void providesMultipleArguments() { var annotation = csvSource("foo", "bar"); var arguments = provideArguments(annotation); assertThat(arguments).containsExactly(array("foo"), array("bar")); } @Test void providesMultipleArgumentsFromTextBlock() { var annotation = csvSource().textBlock(""" foo bar """).build(); var arguments = provideArguments(annotation); assertThat(arguments).containsExactly(array("foo"), array("bar")); } @Test void splitsAndTrimsArguments() { var annotation = csvSource(" foo , bar "); var arguments = provideArguments(annotation); assertThat(arguments).containsExactly(array("foo", "bar")); } /** * @see GitHub issue #3824 */ @Test void trimsLeadingWhitespaceAndControlCharacters() { var annotation = csvSource("'', 1", "\t'',\b2", "'',\u00003", " '', \t 4"); var arguments = provideArguments(annotation); assertThat(arguments).containsExactly(array("", "1"), array("", "2"), array("", "3"), array("", "4")); } /** * @see GitHub issue #3824 */ @Test void trimsTrailingWhitespaceAndControlCharacters() { var annotation = csvSource("1 ,'' ", "2\t,''\b", "3 ,''\u0000", "4,'' \t "); var arguments = provideArguments(annotation); assertThat(arguments).containsExactly(array("1", ""), array("2", ""), array("3", ""), array("4", "")); } @Test void preservesLeadingAndTrailingWhitespaceAndControlCharactersWhenRequested() { var annotation = csvSource().lines(" 1 , a ", "\t2\b, b ", "\u00003\u0007,c ", "4, \t d \t ") // .ignoreLeadingAndTrailingWhitespace(false).build(); var arguments = provideArguments(annotation); assertThat(arguments).containsExactly(// array(" 1 ", " a "), // array("\t2\b", " b "), // array("\u00003\u0007", "c "), // array("4", " \t d \t ")); } /** * @see GitHub issue #3824 */ @Test void trimVsStripSemanticsWithUnquotedText() { // \u0000 (null character) removed by trim(), preserved by strip() // \u00A0 (non-breaking space) preserved by trim(), removed by strip() var annotation = csvSource().lines("\u0000, \u0000foo\u0000, \u00A0bar\u00A0").build(); var arguments = provideArguments(annotation); assertThat(arguments).containsExactly(array("", "foo", "\u00A0bar\u00A0")); } /** * @see GitHub issue #3824 */ @Test void trimVsStripSemanticsWithQuotedText() { // \u0000 (null character) removed by trim(), preserved by strip() // \u00A0 (non-breaking space) preserved by trim(), removed by strip() var annotation = csvSource().lines("'\u0000', '\u0000 foo \u0000', '\t\u00A0bar\u0000'").build(); var arguments = provideArguments(annotation); assertThat(arguments).containsExactly(array("\u0000", "\u0000 foo \u0000", "\t\u00A0bar\u0000")); } /** * @see GitHub issue #3824 */ @Test void trimVsStripSemanticsWithUnquotedAndQuotedText() { // \u0000 (null character) removed by trim(), preserved by strip() // \u00A0 (non-breaking space) preserved by trim(), removed by strip() var annotation = csvSource().lines("\u0000'\u0000 foo', \u00A0' bar\u0000'").build(); var arguments = provideArguments(annotation); assertThat(arguments).containsExactly(array("\u0000 foo", "\u00A0' bar\u0000'")); } @Test void understandsQuotes() { var annotation = csvSource("'foo, bar'"); var arguments = provideArguments(annotation); assertThat(arguments).containsExactly(array("foo, bar")); } @Test void understandsQuotesInTextBlock() { var annotation = csvSource().textBlock(""" 'foo, bar' """).build(); var arguments = provideArguments(annotation); assertThat(arguments).containsExactly(array("foo, bar")); } @Test void understandsCustomQuotes() { var annotation = csvSource().quoteCharacter('~').lines("~foo, bar~").build(); var arguments = provideArguments(annotation); assertThat(arguments).containsExactly(array("foo, bar")); } @Test void understandsCustomQuotesInTextBlock() { var annotation = csvSource().quoteCharacter('"').textBlock(""" "foo, bar" """).build(); var arguments = provideArguments(annotation); assertThat(arguments).containsExactly(array("foo, bar")); } @Test void understandsEscapeCharacters() { var annotation = csvSource("'foo or ''bar''', baz"); var arguments = provideArguments(annotation); assertThat(arguments).containsExactly(array("foo or 'bar'", "baz")); } @Test void understandsEscapeCharactersWithCustomQuoteCharacter() { var annotation = csvSource().quoteCharacter('~').lines("~foo or ~~bar~~~, baz").build(); var arguments = provideArguments(annotation); assertThat(arguments).containsExactly(array("foo or ~bar~", "baz")); } @Test void doesNotTrimSpacesInsideQuotes() { var annotation = csvSource("''", "' '", "'blank '", "' not blank '"); var arguments = provideArguments(annotation); assertThat(arguments).containsExactly(array(""), array(" "), array("blank "), array(" not blank ")); } @Test void providesArgumentsWithCharacterDelimiter() { var annotation = csvSource().delimiter('|').lines("foo|bar", "bar|foo").build(); var arguments = provideArguments(annotation); assertThat(arguments).containsExactly(array("foo", "bar"), array("bar", "foo")); } @Test void providesArgumentsWithStringDelimiter() { var annotation = csvSource().delimiterString("~~~").lines("foo~~~ bar", "bar~~~ foo").build(); var arguments = provideArguments(annotation); assertThat(arguments).containsExactly(array("foo", "bar"), array("bar", "foo")); } @Test void throwsExceptionIfBothDelimitersAreSimultaneouslySet() { var annotation = csvSource().delimiter('|').delimiterString("~~~").build(); assertPreconditionViolationFor(() -> provideArguments(annotation).findAny())// .withMessageStartingWith("The delimiter and delimiterString attributes cannot be set simultaneously in")// .withMessageContaining("CsvSource"); } @Test void defaultEmptyValueAndDefaultNullValue() { var annotation = csvSource("'', null, ,, apple"); var arguments = provideArguments(annotation); assertThat(arguments).containsExactly(array("", "null", null, null, "apple")); } @Test void customEmptyValueAndDefaultNullValue() { var annotation = csvSource().emptyValue("EMPTY").lines("'', null, ,, apple").build(); var arguments = provideArguments(annotation); assertThat(arguments).containsExactly(array("EMPTY", "null", null, null, "apple")); } @Test void customNullValues() { var annotation = csvSource().nullValues("N/A", "NIL", "null")// .lines("apple, , NIL, '', N/A, banana, null").build(); var arguments = provideArguments(annotation); assertThat(arguments).containsExactly(array("apple", null, null, "", null, "banana", null)); } @Test void customNullValueInHeader() { var annotation = csvSource().useHeadersInDisplayName(true).nullValues("NIL").textBlock(""" FRUIT, NIL apple, 1 """).build(); assertThat(headersToValues(annotation)).containsExactly(array("FRUIT = apple", "null = 1")); } @Test void convertsEmptyValuesToNullInLinesAfterFirstLine() { var annotation = csvSource("'', ''", " , "); var arguments = provideArguments(annotation); assertThat(arguments).containsExactly(array("", ""), array(null, null)); } @Test void throwsExceptionIfSourceExceedsMaxCharsPerColumnConfig() { var annotation = csvSource().lines("413").maxCharsPerColumn(2).build(); assertThatExceptionOfType(CsvParsingException.class)// .isThrownBy(() -> provideArguments(annotation).findAny())// .withMessageStartingWith("Failed to parse CSV input configured via Mock for CsvSource")// .havingRootCause().satisfies(isCsvParseException()); } @Test void providesArgumentWithDefaultMaxCharsPerColumnConfig() { var annotation = csvSource().lines("0".repeat(4096)).delimiter(';').build(); var arguments = provideArguments(annotation); assertThat(arguments.toArray()).hasSize(1); } @Test void throwsExceptionWhenSourceExceedsDefaultMaxCharsPerColumnConfig() { var annotation = csvSource().lines("0".repeat(4097)).delimiter(';').build(); assertThatExceptionOfType(CsvParsingException.class)// .isThrownBy(() -> provideArguments(annotation).findAny())// .withMessageStartingWith("Failed to parse CSV input configured via Mock for CsvSource")// .havingRootCause().satisfies(isCsvParseException()); } @Test void providesArgumentsForExceedsSourceWithCustomMaxCharsPerColumnConfig() { var annotation = csvSource().lines("0".repeat(4097)).maxCharsPerColumn(4097).build(); var arguments = provideArguments(annotation); assertThat(arguments.toArray()).hasSize(1); } @ParameterizedTest @ValueSource(ints = { Integer.MIN_VALUE, -2, 0 }) void throwsExceptionWhenMaxCharsPerColumnIsNotPositiveNumberOrMinusOne(int maxCharsPerColumn) { var annotation = csvSource().lines("41").maxCharsPerColumn(maxCharsPerColumn).build(); assertPreconditionViolationFor(() -> provideArguments(annotation).findAny())// .withMessageStartingWith("maxCharsPerColumn must be a positive number or -1: " + maxCharsPerColumn); } @Test void ignoresCommentCharacterWhenUsingValueAttribute() { var annotation = csvSource("#foo", "#bar,baz", "baz,#quux"); var arguments = provideArguments(annotation); assertThat(arguments).containsExactly(array("#foo"), array("#bar", "baz"), array("baz", "#quux")); } @Test void honorsCommentCharacterWhenUsingTextBlockAttribute() { var annotation = csvSource().textBlock(""" #foo bar, #baz '#bar', baz """).build(); var arguments = provideArguments(annotation); assertThat(arguments).containsExactly(array("bar", "#baz"), array("#bar", "baz")); } @Test void honorsCustomCommentCharacter() { var annotation = csvSource().textBlock(""" *foo bar, *baz '*bar', baz """).commentCharacter('*').build(); var arguments = provideArguments(annotation); assertThat(arguments).containsExactly(array("bar", "*baz"), array("*bar", "baz")); } @Test void doesNotThrowExceptionWhenDelimiterAndCommentCharacterTheSameWhenUsingValueAttribute() { var annotation = csvSource().lines("foo#bar").delimiter('#').commentCharacter('#').build(); var arguments = provideArguments(annotation); assertThat(arguments).containsExactly(array("foo", "bar")); } @ParameterizedTest @MethodSource("invalidDelimiterAndQuoteCharacterCombinations") void doesNotThrowExceptionWhenDelimiterAndCommentCharacterAreTheSameWhenUsingValueAttribute(Object delimiter, char quoteCharacter) { var builder = csvSource().lines("foo").quoteCharacter(quoteCharacter); var annotation = delimiter instanceof Character c // ? builder.delimiter(c).build() // : builder.delimiterString(delimiter.toString()).build(); var message = "delimiter or delimiterString: '%s' and quoteCharacter: '%s' must differ"; assertPreconditionViolationFor(() -> provideArguments(annotation).findAny()) // .withMessage(message.formatted(delimiter, quoteCharacter)); } static Stream invalidDelimiterAndQuoteCharacterCombinations() { return Stream.of( // delimiter Arguments.of('*', '*'), // // delimiterString Arguments.of("*", '*')); } @ParameterizedTest @MethodSource("invalidDelimiterQuoteCharacterAndCommentCharacterCombinations") void throwsExceptionWhenControlCharactersAreTheSameWhenUsingTextBlockAttribute(Object delimiter, char quoteCharacter, char commentCharacter) { var builder = csvSource().textBlock(""" foo""").quoteCharacter(quoteCharacter).commentCharacter(commentCharacter); var annotation = delimiter instanceof Character c // ? builder.delimiter(c).build() // : builder.delimiterString(delimiter.toString()).build(); var message = "delimiter or delimiterString: '%s', quoteCharacter: '%s', and commentCharacter: '%s' " + // "must all differ"; assertPreconditionViolationFor(() -> provideArguments(annotation).findAny()) // .withMessage(message.formatted(delimiter, quoteCharacter, commentCharacter)); } static Stream invalidDelimiterQuoteCharacterAndCommentCharacterCombinations() { return Stream.of( // delimiter Arguments.of('#', '#', '#'), // Arguments.of('#', '#', '*'), // Arguments.of('*', '#', '#'), // Arguments.of('#', '*', '#'), // // delimiterString Arguments.of("#", '#', '*'), // Arguments.of("#", '*', '#') // ); } @Test void supportsCsvHeadersWhenUsingTextBlockAttribute() { var annotation = csvSource().useHeadersInDisplayName(true).textBlock(""" FRUIT, RANK apple, 1 banana, 2 """).build(); assertThat(headersToValues(annotation)).containsExactly(// array("FRUIT = apple", "RANK = 1"), // array("FRUIT = banana", "RANK = 2")// ); } @Test void supportsCsvHeadersWhenUsingValueAttribute() { var annotation = csvSource().useHeadersInDisplayName(true)// .lines("FRUIT, RANK", "apple, 1", "banana, 2").build(); assertThat(headersToValues(annotation)).containsExactly(// array("FRUIT = apple", "RANK = 1"), // array("FRUIT = banana", "RANK = 2")// ); } private Stream headersToValues(CsvSource csvSource) { var arguments = provideArguments(csvSource); return arguments.map(array -> { String[] strings = new String[array.length]; for (int i = 0; i < array.length; i++) { if (array[i] instanceof ParameterNameAndArgument parameterNameAndArgument) { strings[i] = parameterNameAndArgument.getName() + " = " + parameterNameAndArgument.getPayload(); } else { throw new IllegalStateException("Unexpected argument type: " + array[i].getClass().getName()); } } return strings; }); } @Test void throwsExceptionIfColumnCountExceedsHeaderCount() { var annotation = csvSource().useHeadersInDisplayName(true).textBlock(""" FRUIT, RANK apple, 1 banana, 2, BOOM! """).build(); assertPreconditionViolationFor(() -> provideArguments(annotation).findAny())// .withMessage( "The number of columns (3) exceeds the number of supplied headers (2) in CSV record: [banana, 2, BOOM!]"); } private Stream provideArguments(CsvSource annotation) { var provider = new CsvArgumentsProvider(); provider.accept(annotation); return provider.provideArguments(mock(), mock(ExtensionContext.class)).map(Arguments::get); } @SuppressWarnings("unchecked") private static @Nullable T[] array(@Nullable T... elements) { return elements; } static ThrowingConsumer isCsvParseException() { return ex -> ex.getClass().getName().contains("de.siegmar.fastcsv.reader.CsvParseException"); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.params.provider.CsvArgumentsProviderTests.isCsvParseException; import static org.junit.jupiter.params.provider.MockCsvAnnotationBuilder.csvFileSource; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotEmptyFor; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullOrBlankFor; import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvFileArgumentsProvider.InputStreamProvider; import org.junit.jupiter.params.support.ParameterNameAndArgument; import org.junit.platform.commons.JUnitException; /** * @since 5.0 */ class CsvFileArgumentsProviderTests { @Test void providesArgumentsForEachSupportedLineSeparator() { var annotation = csvFileSource()// .resources("test.csv")// .build(); var arguments = provideArguments(annotation, "foo, bar \n baz, qux \r quux, corge \r\n grault, garply"); assertThat(arguments).containsExactly(// array("foo", "bar"), // array("baz", "qux"), // array("quux", "corge"), // array("grault", "garply")// ); } @Test void providesArgumentsForNewlineAndComma() { var annotation = csvFileSource()// .resources("test.csv")// .delimiter(',')// .build(); var arguments = provideArguments(annotation, "foo, bar \n baz, qux \n"); assertThat(arguments).containsExactly(array("foo", "bar"), array("baz", "qux")); } @Test void providesArgumentsForCarriageReturnAndSemicolon() { var annotation = csvFileSource()// .resources("test.csv")// .delimiter(';')// .build(); var arguments = provideArguments(annotation, "foo; bar \r baz; qux"); assertThat(arguments).containsExactly(array("foo", "bar"), array("baz", "qux")); } @Test void providesArgumentsWithCustomQuoteCharacter() { var annotation = csvFileSource()// .resources("test.csv")// .quoteCharacter('\'')// .build(); var arguments = provideArguments(annotation, "foo, 'bar \"and\" baz', qux \n 'lemon lime', banana, apple"); assertThat(arguments).containsExactly(array("foo", "bar \"and\" baz", "qux"), array("lemon lime", "banana", "apple")); } @Test void providesArgumentsWithStringDelimiter() { var annotation = csvFileSource()// .resources("test.csv")// .delimiterString(",")// .build(); var arguments = provideArguments(annotation, "foo, bar \n baz, qux \n"); assertThat(arguments).containsExactly(array("foo", "bar"), array("baz", "qux")); } @Test void throwsExceptionIfBothDelimitersAreSimultaneouslySet() { var annotation = csvFileSource()// .resources("test.csv")// .delimiter(',')// .delimiterString(";")// .build(); assertPreconditionViolationFor(() -> provideArguments(annotation, "foo").findAny())// .withMessageStartingWith("The delimiter and delimiterString attributes cannot be set simultaneously in")// .withMessageContaining("CsvFileSource"); } @Test void ignoresCommentedOutEntries() { var annotation = csvFileSource()// .resources("test.csv")// .delimiter(',')// .build(); var arguments = provideArguments(annotation, "foo, bar \n#baz, qux"); assertThat(arguments).containsExactly(array("foo", "bar")); } @Test void honorsCustomCommentCharacter() { var annotation = csvFileSource()// .resources("test.csv")// .commentCharacter(';')// .delimiter(',')// .build(); var arguments = provideArguments(annotation, "foo, bar \n;baz, qux"); assertThat(arguments).containsExactly(array("foo", "bar")); } @ParameterizedTest @MethodSource("org.junit.jupiter.params.provider.CsvArgumentsProviderTests#" + "invalidDelimiterQuoteCharacterAndCommentCharacterCombinations") void throwsExceptionWhenControlCharactersNotDiffer(Object delimiter, char quoteCharacter, char commentCharacter) { var builder = csvFileSource().resources("test.csv") // .quoteCharacter(quoteCharacter).commentCharacter(commentCharacter); var annotation = delimiter instanceof Character c // ? builder.delimiter(c).build() // : builder.delimiterString(delimiter.toString()).build(); var message = "delimiter or delimiterString: '%s', quoteCharacter: '%s', and commentCharacter: '%s' " + "must all differ"; assertPreconditionViolationFor(() -> provideArguments(annotation, "foo").findAny()) // .withMessage(message.formatted(delimiter, quoteCharacter, commentCharacter)); } @Test void closesInputStreamForClasspathResource() { var closed = new AtomicBoolean(false); InputStream inputStream = new ByteArrayInputStream("foo".getBytes()) { @Override public void close() { closed.set(true); } }; var annotation = csvFileSource().resources("test.csv").build(); var arguments = provideArguments(inputStream, annotation); assertThat(arguments.count()).isEqualTo(1); assertThat(closed.get()).describedAs("closed").isTrue(); } @Test void closesInputStreamForFile(@TempDir Path tempDir) { var closed = new AtomicBoolean(false); InputStream inputStream = new ByteArrayInputStream("foo".getBytes()) { @Override public void close() { closed.set(true); } }; var annotation = csvFileSource().files(tempDir.resolve("test.csv").toAbsolutePath().toString()).build(); var arguments = provideArguments(inputStream, annotation); assertThat(arguments.count()).isEqualTo(1); assertThat(closed.get()).describedAs("closed").isTrue(); } @Test void readsFromSingleClasspathResource() { var annotation = csvFileSource()// .encoding("ISO-8859-1")// .resources("single-column.csv")// .build(); var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); assertThat(arguments).containsExactly(array("foo"), array("bar"), array("baz"), array("qux"), array("")); } @Test void readsFromSingleFileWithAbsolutePath(@TempDir Path tempDir) throws Exception { var csvFile = writeClasspathResourceToFile("single-column.csv", tempDir.resolve("single-column.csv")); var annotation = csvFileSource()// .encoding("ISO-8859-1")// .files(csvFile.toAbsolutePath().toString())// .build(); var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); assertThat(arguments).containsExactly(array("foo"), array("bar"), array("baz"), array("qux"), array("")); } @Test void readsFromClasspathResourcesAndFiles(@TempDir Path tempDir) throws Exception { var csvFile = writeClasspathResourceToFile("single-column.csv", tempDir.resolve("single-column.csv")); var annotation = csvFileSource()// .encoding("ISO-8859-1")// .resources("single-column.csv")// .files(csvFile.toAbsolutePath().toString())// .build(); var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); assertThat(arguments).hasSize(2 * 5); } @Test void readsFromSingleFileWithRelativePath() throws Exception { var csvFile = writeClasspathResourceToFile("single-column.csv", Path.of("single-column.csv")); try { var annotation = csvFileSource()// .encoding("ISO-8859-1")// .files(csvFile.getFileName().toString())// .build(); var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); assertThat(arguments).containsExactly(array("foo"), array("bar"), array("baz"), array("qux"), array("")); } finally { Files.delete(csvFile); } } @Test void readsFromSingleClasspathResourceWithCustomEmptyValue() { var annotation = csvFileSource()// .encoding("ISO-8859-1")// .resources("single-column.csv")// .emptyValue("EMPTY")// .build(); var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); assertThat(arguments).containsExactly(array("foo"), array("bar"), array("baz"), array("qux"), array("EMPTY")); } @Test void readsFromMultipleClasspathResources() { var annotation = csvFileSource()// .encoding("ISO-8859-1")// .resources("single-column.csv", "single-column.csv")// .build(); var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); assertThat(arguments).hasSize(10); } @Test void readsFromSingleClasspathResourceWithHeaders() { var annotation = csvFileSource()// .encoding("ISO-8859-1")// .resources("single-column.csv")// .numLinesToSkip(1)// .build(); var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); assertThat(arguments).containsExactly(array("bar"), array("baz"), array("qux"), array("")); } @Test void readsFromSingleClasspathResourceWithMoreHeadersThanLines() { var annotation = csvFileSource()// .encoding("ISO-8859-1")// .resources("single-column.csv")// .numLinesToSkip(10)// .build(); var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); assertThat(arguments).isEmpty(); } @Test void readsFromMultipleClasspathResourcesWithHeaders() { var annotation = csvFileSource()// .encoding("ISO-8859-1")// .resources("single-column.csv", "single-column.csv")// .numLinesToSkip(1)// .build(); var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); assertThat(arguments).containsExactly(array("bar"), array("baz"), array("qux"), array(""), array("bar"), array("baz"), array("qux"), array("")); } @Test void supportsCsvHeadersInDisplayNames() { var annotation = csvFileSource()// .encoding("ISO-8859-1")// .resources("single-column.csv")// .useHeadersInDisplayName(true)// .build(); var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); Stream argumentsAsStrings = arguments.map(array -> { String[] strings = new String[array.length]; for (int i = 0; i < array.length; i++) { if (array[i] instanceof ParameterNameAndArgument parameterNameAndArgument) { strings[i] = parameterNameAndArgument.getName() + " = " + parameterNameAndArgument.getPayload(); } else { throw new IllegalStateException("Unexpected argument type: " + array[i].getClass().getName()); } } return strings; }); assertThat(argumentsAsStrings).containsExactly(array("foo = bar"), array("foo = baz"), array("foo = qux"), array("foo = ")); } @Test void throwsExceptionForMissingClasspathResource() { var annotation = csvFileSource()// .resources("/does-not-exist.csv")// .build(); assertPreconditionViolationFor(() -> provideArguments(new CsvFileArgumentsProvider(), annotation).toArray())// .withMessageContaining("Classpath resource [/does-not-exist.csv] does not exist"); } @Test void throwsExceptionForBlankClasspathResource() { var annotation = csvFileSource()// .resources(" ")// .build(); assertPreconditionViolationNotNullOrBlankFor("Classpath resource [ ]", () -> provideArguments(new CsvFileArgumentsProvider(), annotation).toArray()); } @Test void throwsExceptionForMissingFile() { var annotation = csvFileSource()// .files("does-not-exist.csv")// .build(); var exception = assertThrows(JUnitException.class, () -> provideArguments(new CsvFileArgumentsProvider(), annotation).toArray()); assertThat(exception).hasMessageContaining("File [does-not-exist.csv] could not be read"); } @Test void throwsExceptionForBlankFile() { var annotation = csvFileSource()// .files(" ")// .build(); assertPreconditionViolationNotNullOrBlankFor("File [ ]", () -> provideArguments(new CsvFileArgumentsProvider(), annotation).toArray()); } @Test void throwsExceptionIfResourcesAndFilesAreEmpty() { var annotation = csvFileSource()// .resources()// .files()// .build(); assertPreconditionViolationNotEmptyFor("Resources or files", () -> provideArguments(new CsvFileArgumentsProvider(), annotation).toArray()); } @Test void throwsExceptionForInvalidCharset() { var annotation = csvFileSource()// .encoding("Bogus-Charset")// .resources("/bogus-charset.csv")// .build(); assertPreconditionViolationFor(() -> provideArguments(new CsvFileArgumentsProvider(), annotation).toArray())// .withMessageContaining("The charset supplied in Mock for CsvFileSource")// .withMessageEndingWith("is invalid"); } @Test void throwsExceptionForInvalidCsvFormat() { var annotation = csvFileSource()// .resources("broken.csv")// .build(); var exception = assertThrows(CsvParsingException.class, () -> provideArguments(new CsvFileArgumentsProvider(), annotation).toArray()); assertThat(exception)// .hasMessageStartingWith("Failed to parse CSV input configured via Mock for CsvFileSource")// .rootCause().satisfies(isCsvParseException()); } @Test void emptyValueIsAnEmptyWithCustomNullValueString() { var annotation = csvFileSource()// .resources("test.csv")// .delimiter(',')// .nullValues("NIL")// .build(); var arguments = provideArguments(annotation, "apple, , NIL, ''\nNIL, NIL, foo, bar"); assertThat(arguments).containsExactly(array("apple", null, null, "''"), array(null, null, "foo", "bar")); } @Test void readsLineFromDefaultMaxCharsFileWithDefaultConfig(@TempDir Path tempDir) throws Exception { var csvFile = writeClasspathResourceToFile("default-max-chars.csv", tempDir.resolve("default-max-chars.csv")); var annotation = csvFileSource()// .encoding("ISO-8859-1")// .resources("default-max-chars.csv")// .files(csvFile.toAbsolutePath().toString())// .build(); var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); assertThat(arguments).hasSize(2); } @Test void readsLineFromExceedsMaxCharsFileWithCustomExplicitConfig(@TempDir Path tempDir) throws Exception { var csvFile = writeClasspathResourceToFile("exceeds-default-max-chars.csv", tempDir.resolve("exceeds-default-max-chars.csv")); var annotation = csvFileSource()// .encoding("ISO-8859-1")// .resources("exceeds-default-max-chars.csv")// .maxCharsPerColumn(4097)// .files(csvFile.toAbsolutePath().toString())// .build(); var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); assertThat(arguments).hasSize(2); } @Test void readsLineFromExceedsMaxCharsFileWithCustomUnlimitedConfig(@TempDir Path tempDir) throws Exception { var csvFile = tempDir.resolve("test.csv"); try (var out = Files.newBufferedWriter(csvFile)) { var chunks = 10; var chunk = "a".repeat(8192); for (long i = 0; i < chunks; i++) { out.write(chunk); } } var annotation = csvFileSource()// .encoding("ISO-8859-1")// .maxCharsPerColumn(-1)// .files(csvFile.toAbsolutePath().toString())// .build(); var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); assertThat(arguments).hasSize(1); } @ParameterizedTest @ValueSource(ints = { Integer.MIN_VALUE, -2, 0 }) void throwsExceptionWhenMaxCharsPerColumnIsNotPositiveNumberOrMinusOne(int maxCharsPerColumn, @TempDir Path tempDir) throws Exception { var csvFile = writeClasspathResourceToFile("exceeds-default-max-chars.csv", tempDir.resolve("exceeds-default-max-chars.csv")); var annotation = csvFileSource()// .encoding("ISO-8859-1")// .resources("exceeds-default-max-chars.csv")// .maxCharsPerColumn(maxCharsPerColumn)// .files(csvFile.toAbsolutePath().toString())// .build(); assertPreconditionViolationFor(() -> provideArguments(new CsvFileArgumentsProvider(), annotation).findAny())// .withMessageStartingWith("maxCharsPerColumn must be a positive number or -1: " + maxCharsPerColumn); } @Test void throwsExceptionForExceedsMaxCharsFileWithDefaultConfig(@TempDir Path tempDir) throws Exception { var csvFile = writeClasspathResourceToFile("exceeds-default-max-chars.csv", tempDir.resolve("exceeds-default-max-chars.csv")); var annotation = csvFileSource()// .encoding("ISO-8859-1")// .resources("exceeds-default-max-chars.csv")// .files(csvFile.toAbsolutePath().toString())// .build(); var exception = assertThrows(CsvParsingException.class, () -> provideArguments(new CsvFileArgumentsProvider(), annotation).toArray()); assertThat(exception)// .hasMessageStartingWith("Failed to parse CSV input configured via Mock for CsvFileSource")// .rootCause().satisfies(isCsvParseException()); } @Test void ignoresLeadingAndTrailingSpaces(@TempDir Path tempDir) throws Exception { var csvFile = writeClasspathResourceToFile("leading-trailing-spaces.csv", tempDir.resolve("leading-trailing-spaces.csv")); var annotation = csvFileSource()// .encoding("ISO-8859-1")// .resources("leading-trailing-spaces.csv")// .files(csvFile.toAbsolutePath().toString())// .ignoreLeadingAndTrailingWhitespace(true)// .build(); var arguments = provideArguments(new ByteArrayInputStream(Files.readAllBytes(csvFile)), annotation); assertThat(arguments).containsExactly(array("ab", "cd"), array("ef", "gh")); } @Test void trimsLeadingAndTrailingSpaces(@TempDir Path tempDir) throws Exception { var csvFile = writeClasspathResourceToFile("leading-trailing-spaces.csv", tempDir.resolve("leading-trailing-spaces.csv")); var annotation = csvFileSource()// .encoding("ISO-8859-1")// .resources("leading-trailing-spaces.csv")// .files(csvFile.toAbsolutePath().toString())// .delimiter(',')// .ignoreLeadingAndTrailingWhitespace(false)// .build(); var arguments = provideArguments(new ByteArrayInputStream(Files.readAllBytes(csvFile)), annotation); assertThat(arguments).containsExactly(array(" ab ", " cd"), array("ef ", "gh")); } private Stream provideArguments(CsvFileSource annotation, String content) { return provideArguments(new ByteArrayInputStream(content.getBytes(UTF_8)), annotation); } private Stream provideArguments(InputStream inputStream, CsvFileSource annotation) { var provider = new CsvFileArgumentsProvider(new InputStreamProvider() { @Override public InputStream openClasspathResource(Class baseClass, String path) { assertThat(path).isEqualTo(annotation.resources()[0]); return inputStream; } @Override public InputStream openFile(String path) { assertThat(path).isEqualTo(annotation.files()[0]); return inputStream; } }); return provideArguments(provider, annotation); } private Stream provideArguments(CsvFileArgumentsProvider provider, CsvFileSource annotation) { provider.accept(annotation); var context = mock(ExtensionContext.class); when(context.getTestClass()).thenReturn(Optional.of(CsvFileArgumentsProviderTests.class)); doCallRealMethod().when(context).getRequiredTestClass(); return provider.provideArguments(mock(), context).map(Arguments::get); } @SuppressWarnings("unchecked") private static @Nullable T[] array(@Nullable T... elements) { return elements; } private static Path writeClasspathResourceToFile(String name, Path target) throws Exception { try (var in = CsvFileArgumentsProviderTests.class.getResourceAsStream(name)) { Files.copy(in, target); } return target; } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/params/provider/EnumArgumentsProviderTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.junit.jupiter.params.provider.EnumArgumentsProviderTests.EnumWithFourConstants.BAR; import static org.junit.jupiter.params.provider.EnumArgumentsProviderTests.EnumWithFourConstants.BAZ; import static org.junit.jupiter.params.provider.EnumArgumentsProviderTests.EnumWithFourConstants.FOO; import static org.junit.jupiter.params.provider.EnumArgumentsProviderTests.EnumWithFourConstants.QUX; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.Arrays; import java.util.Optional; import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.provider.EnumSource.Mode; import org.junit.jupiter.params.support.ParameterDeclaration; import org.junit.jupiter.params.support.ParameterDeclarations; /** * @since 5.0 */ class EnumArgumentsProviderTests { final ParameterDeclarations parameters = mock(); final ExtensionContext extensionContext = mock(); @Test void providesAllEnumConstants() { var arguments = provideArguments(EnumWithFourConstants.class); assertThat(arguments).containsExactly(new Object[] { FOO }, new Object[] { BAR }, new Object[] { BAZ }, new Object[] { QUX }); } @Test void provideSingleEnumConstant() { var arguments = provideArguments(EnumWithFourConstants.class, "FOO"); assertThat(arguments).containsExactly(new Object[] { FOO }); } @Test void provideAllEnumConstantsWithNamingAll() { var arguments = provideArguments(EnumWithFourConstants.class, "FOO", "BAR"); assertThat(arguments).containsExactly(new Object[] { FOO }, new Object[] { BAR }); } @Test void duplicateConstantNameIsDetected() { assertPreconditionViolationFor( () -> provideArguments(EnumWithFourConstants.class, "FOO", "BAR", "FOO").findAny())// .withMessageContaining("Duplicate enum constant name(s) found"); } @Test void invalidConstantNameIsDetected() { assertPreconditionViolationFor(() -> provideArguments(EnumWithFourConstants.class, "FO0", "B4R").findAny())// .withMessageContaining("Invalid enum constant name(s) in"); } @Test void invalidPatternIsDetected() { assertPreconditionViolationFor( () -> provideArguments(EnumWithFourConstants.class, Mode.MATCH_ALL, "(", ")").findAny())// .withMessageContaining("Pattern compilation failed"); } @Test void providesEnumConstantsBasedOnTestMethod() { org.junit.jupiter.params.support.ParameterDeclaration firstParameterDeclaration = mock(); when(firstParameterDeclaration.getParameterType()).thenAnswer(__ -> EnumWithFourConstants.class); when(parameters.getFirst()).thenReturn(Optional.of(firstParameterDeclaration)); var arguments = provideArguments(NullEnum.class); assertThat(arguments).containsExactly(new Object[] { FOO }, new Object[] { BAR }, new Object[] { BAZ }, new Object[] { QUX }); } @Test void incorrectParameterTypeIsDetected() { ParameterDeclaration firstParameterDeclaration = mock(); when(firstParameterDeclaration.getParameterType()).thenAnswer(__ -> Object.class); when(parameters.getFirst()).thenReturn(Optional.of(firstParameterDeclaration)); assertPreconditionViolationFor(() -> provideArguments(NullEnum.class).findAny())// .withMessageStartingWith("First parameter must reference an Enum type"); } @Test void methodsWithoutParametersAreDetected() { when(parameters.getSourceElementDescription()).thenReturn("method"); assertPreconditionViolationFor(() -> provideArguments(NullEnum.class).findAny())// .withMessageStartingWith("There must be at least one declared parameter for method"); } @Test void providesEnumConstantsStartingFromBar() { var arguments = provideArguments(EnumWithFourConstants.class, "BAR", "", Mode.INCLUDE); assertThat(arguments).containsExactly(new Object[] { BAR }, new Object[] { BAZ }, new Object[] { QUX }); } @Test void providesEnumConstantsEndingAtBaz() { var arguments = provideArguments(EnumWithFourConstants.class, "", "BAZ", Mode.INCLUDE); assertThat(arguments).containsExactly(new Object[] { FOO }, new Object[] { BAR }, new Object[] { BAZ }); } @Test void providesEnumConstantsFromBarToBaz() { var arguments = provideArguments(EnumWithFourConstants.class, "BAR", "BAZ", Mode.INCLUDE); assertThat(arguments).containsExactly(new Object[] { BAR }, new Object[] { BAZ }); } @Test void providesEnumConstantsFromFooToBazWhileExcludingBar() { var arguments = provideArguments(EnumWithFourConstants.class, "FOO", "BAZ", Mode.EXCLUDE, "BAR"); assertThat(arguments).containsExactly(new Object[] { FOO }, new Object[] { BAZ }); } @Test void providesNoEnumConstant() { var arguments = provideArguments(EnumWithNoConstant.class); assertThat(arguments).isEmpty(); } @Test void invalidConstantNameIsDetectedInRange() { assertPreconditionViolationFor( () -> provideArguments(EnumWithFourConstants.class, "FOO", "BAZ", Mode.EXCLUDE, "QUX").findAny())// .withMessageContaining("Invalid enum constant name(s) in"); } @Test void invalidStartingRangeIsDetected() { assertThatIllegalArgumentException()// .isThrownBy(() -> provideArguments(EnumWithFourConstants.class, "B4R", "", Mode.INCLUDE).findAny())// .withMessageContaining("No enum constant"); } @Test void invalidEndingRangeIsDetected() { assertThatIllegalArgumentException()// .isThrownBy(() -> provideArguments(EnumWithFourConstants.class, "", "B4R", Mode.INCLUDE).findAny())// .withMessageContaining("No enum constant"); } @Test void invalidRangeOrderIsDetected() { assertPreconditionViolationFor( () -> provideArguments(EnumWithFourConstants.class, "BAR", "FOO", Mode.INCLUDE).findAny())// .withMessageContaining("Invalid enum range"); } @Test void invalidRangeIsDetectedWhenEnumWithNoConstantIsProvided() { assertPreconditionViolationFor( () -> provideArguments(EnumWithNoConstant.class, "BAR", "FOO", Mode.INCLUDE).findAny())// .withMessageContaining("No enum constant"); } static class TestCase { void methodWithoutParameters() { } } enum EnumWithFourConstants { FOO, BAR, BAZ, QUX } enum EnumWithNoConstant { } private > Stream provideArguments(Class enumClass, String... names) { return provideArguments(enumClass, Mode.INCLUDE, names); } private > Stream provideArguments(Class enumClass, Mode mode, String... names) { return provideArguments(enumClass, "", "", mode, names); } private > Stream provideArguments(Class enumClass, String from, String to, Mode mode, String... names) { var annotation = mock(EnumSource.class); when(annotation.value()).thenAnswer(__ -> enumClass); when(annotation.from()).thenReturn(from); when(annotation.to()).thenReturn(to); when(annotation.mode()).thenReturn(mode); when(annotation.names()).thenReturn(names); when(annotation.toString()).thenReturn( "@EnumSource(value=%s.class, from=%s, to=%s, mode=%s, names=%s)".formatted(enumClass.getSimpleName(), from, to, mode, Arrays.toString(names))); var provider = new EnumArgumentsProvider(); provider.accept(annotation); return provider.provideArguments(parameters, extensionContext).map(Arguments::get); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/params/provider/EnumSourceTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static java.util.stream.Collectors.toSet; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.params.provider.EnumSource.Mode.EXCLUDE; import static org.junit.jupiter.params.provider.EnumSource.Mode.INCLUDE; import static org.junit.jupiter.params.provider.EnumSource.Mode.MATCH_ALL; import static org.junit.jupiter.params.provider.EnumSource.Mode.MATCH_ANY; import static org.junit.jupiter.params.provider.EnumSource.Mode.MATCH_NONE; import static org.junit.jupiter.params.provider.EnumSourceTests.EnumWithThreeConstants.BAR; import static org.junit.jupiter.params.provider.EnumSourceTests.EnumWithThreeConstants.BAZ; import static org.junit.jupiter.params.provider.EnumSourceTests.EnumWithThreeConstants.FOO; import java.util.EnumSet; import java.util.Set; import java.util.function.Function; import org.junit.jupiter.api.Test; /** * @since 5.0 */ class EnumSourceTests { @Test void includeNamesWithAll() { assertAll("include names with all", // () -> assertTrue(INCLUDE.select(FOO, allOf(EnumWithThreeConstants::name))), () -> assertTrue(INCLUDE.select(BAR, allOf(EnumWithThreeConstants::name))), () -> assertTrue(INCLUDE.select(BAZ, allOf(EnumWithThreeConstants::name))) // ); } @Test void includeNamesWithSingleton() { assertAll("include names with singleton", // () -> assertTrue(INCLUDE.select(FOO, Set.of(FOO.name()))), () -> assertTrue(INCLUDE.select(BAR, Set.of(BAR.name()))), () -> assertTrue(INCLUDE.select(BAZ, Set.of(BAZ.name()))) // ); assertAll("include names with singleton complement", // () -> assertFalse(INCLUDE.select(BAR, Set.of(FOO.name()))), () -> assertFalse(INCLUDE.select(BAZ, Set.of(FOO.name()))) // ); } @Test void excludeNames() { assertAll("exclude name with none excluded", // () -> assertTrue(EXCLUDE.select(FOO, Set.of())), // () -> assertTrue(EXCLUDE.select(BAR, Set.of())), // () -> assertTrue(EXCLUDE.select(BAZ, Set.of())) // ); assertAll("exclude name with FOO excluded", // () -> assertFalse(EXCLUDE.select(FOO, Set.of(FOO.name()))), () -> assertTrue(EXCLUDE.select(BAR, Set.of(FOO.name()))), () -> assertTrue(EXCLUDE.select(BAZ, Set.of(FOO.name()))) // ); } @Test void matchesAll() { assertAll("matches all", // () -> assertTrue(MATCH_ALL.select(FOO, Set.of("F.."))), () -> assertTrue(MATCH_ALL.select(BAR, Set.of("B.."))), () -> assertTrue(MATCH_ALL.select(BAZ, Set.of("B.."))) // ); assertAll("matches all fails if not all match", // () -> assertFalse(MATCH_ALL.select(FOO, Set.of("F..", "."))), () -> assertFalse(MATCH_ALL.select(BAR, Set.of("B..", "."))), () -> assertFalse(MATCH_ALL.select(BAZ, Set.of("B..", "."))) // ); } @Test void matchesAny() { assertAll("matches any", // () -> assertTrue(MATCH_ANY.select(FOO, Set.of("B..", "^F.*"))), () -> assertTrue(MATCH_ANY.select(BAR, Set.of("B", "B.", "B.."))), () -> assertTrue(MATCH_ANY.select(BAZ, Set.of("^.+[zZ]$")))); } @Test void matchesNone() { assertAll("matches none fails if any match", // () -> assertFalse(MATCH_NONE.select(FOO, Set.of("F.."))), () -> assertFalse(MATCH_NONE.select(FOO, Set.of("B..", "F.."))), () -> assertFalse(MATCH_NONE.select(BAZ, Set.of("B.", "F.", "^.+[zZ]$")))); assertAll("matches none", // () -> assertTrue(MATCH_NONE.select(FOO, Set.of())), // () -> assertTrue(MATCH_NONE.select(FOO, Set.of("F."))), () -> assertTrue(MATCH_NONE.select(FOO, Set.of("B.."))), () -> assertTrue(MATCH_NONE.select(BAZ, Set.of(".", "B.", "F.")))); } enum EnumWithThreeConstants { FOO, BAR, BAZ } static Set allOf(Function mapper) { return EnumSet.allOf(EnumWithThreeConstants.class).stream().map(mapper).collect(toSet()); } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/params/provider/FieldArgumentsProviderTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.engine.extension.MutableExtensionRegistry.createRegistryWithDefaultExtensions; import static org.junit.platform.commons.support.ReflectionSupport.findMethod; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.lang.reflect.Method; import java.util.Iterator; import java.util.List; import java.util.Optional; import java.util.function.Supplier; import java.util.stream.DoubleStream; import java.util.stream.IntStream; import java.util.stream.LongStream; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.engine.execution.DefaultExecutableInvoker; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.support.ParameterDeclarations; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.test.TestClassLoader; /** * Unit tests for {@link FieldArgumentsProvider}. * * @since 5.11 */ class FieldArgumentsProviderTests { @Test void providesArgumentsUsingStreamSupplier() { var arguments = provideArguments("stringStreamSupplier"); assertThat(arguments).containsExactly(array("foo"), array("bar")); } @Test void providesArgumentsUsingIntStreamSupplier() { var arguments = provideArguments("intStreamSupplier"); assertThat(arguments).containsExactly(array(1), array(2)); } @Test void providesArgumentsUsingLongStreamSupplier() { var arguments = provideArguments("longStreamSupplier"); assertThat(arguments).containsExactly(array(1L), array(2L)); } @Test void providesArgumentsUsingDoubleStreamSupplier() { var arguments = provideArguments("doubleStreamSupplier"); assertThat(arguments).containsExactly(array(1.2), array(3.4)); } @Test void providesArgumentsUsingStreamSupplierOfIntArrays() { var arguments = provideArguments("intArrayStreamSupplier"); assertThat(arguments).containsExactly( // new Object[] { new int[] { 1, 2 } }, // new Object[] { new int[] { 3, 4 } } // ); } @Test void providesArgumentsUsingStreamSupplierOfTwoDimensionalIntArrays() { var arguments = provideArguments("twoDimensionalIntArrayStreamSupplier"); assertThat(arguments).containsExactly( // array((Object) new int[][] { { 1, 2 }, { 2, 3 } }), // array((Object) new int[][] { { 4, 5 }, { 5, 6 } }) // ); } @Test void providesArgumentsUsingStreamSupplierOfObjectArrays() { var arguments = provideArguments("objectArrayStreamSupplier"); assertThat(arguments).containsExactly(array("foo", 42), array("bar", 23)); } @Test void providesArgumentsUsingStreamSupplierOfTwoDimensionalObjectArrays() { var arguments = provideArguments("twoDimensionalObjectArrayStreamSupplier"); assertThat(arguments).containsExactly( // array((Object) array(array("a", 1), array("b", 2))), // array((Object) array(array("c", 3), array("d", 4))) // ); } @Test void providesArgumentsUsingStreamSupplierOfArguments() { var arguments = provideArguments("argumentsStreamSupplier"); assertThat(arguments).containsExactly(array("foo", 42), array("bar", 23)); } @Test void providesArgumentsUsingIterable() { var arguments = provideArguments("stringIterable"); assertThat(arguments).containsExactly(array("foo"), array("bar")); } @Test void providesArgumentsUsingMultipleFields() { var arguments = provideArguments("stringStreamSupplier", "stringIterable"); assertThat(arguments).containsExactly(array("foo"), array("bar"), array("foo"), array("bar")); } @Test void providesArgumentsUsingIterableOfObjectArrays() { var arguments = provideArguments("objectArrayIterable"); assertThat(arguments).containsExactly(array("foo", 42), array("bar", 23)); } @Test void providesArgumentsUsingListOfStrings() { var arguments = provideArguments("stringList"); assertThat(arguments).containsExactly(array("foo"), array("bar")); } @Test void providesArgumentsUsingListOfObjectArrays() { var arguments = provideArguments("objectArrayList"); assertThat(arguments).containsExactly(array("foo", 42), array("bar", 23)); } @Test void providesArgumentsFromNonStaticFieldWhenStaticIsNotRequired() { var lifecyclePerClass = true; var arguments = provideArguments(NonStaticTestCase.class, lifecyclePerClass, "nonStaticStringStreamSupplier"); assertThat(arguments).containsExactly(array("foo"), array("bar")); } @Test void providesArgumentsUsingDefaultFieldName() { var testClass = DefaultFieldNameTestCase.class; var methodName = "testDefaultFieldName"; var testMethod = findMethod(testClass, methodName, String.class).get(); var arguments = provideArguments(testClass, testMethod, false, new String[0]); assertThat(arguments).containsExactly(array("foo"), array("bar")); } @Test void providesArgumentsUsingExternalField() { var arguments = provideArguments(ExternalFields.class.getName() + "#strings"); assertThat(arguments).containsExactly(array("string1"), array("string2")); } @Test void providesArgumentsUsingExternalFieldInTypeFromDifferentClassLoader() throws Exception { try (var testClassLoader = TestClassLoader.forClasses(TestCase.class, ExternalFields.class)) { var testClass = testClassLoader.loadClass(TestCase.class.getName()); var fullyQualifiedFieldName = ExternalFields.class.getName() + "#strings"; assertThat(testClass.getClassLoader()).isSameAs(testClassLoader); var arguments = provideArguments(testClass, false, fullyQualifiedFieldName); assertThat(arguments).containsExactly(array("string1"), array("string2")); var field = FieldArgumentsProvider.findField(testClass, fullyQualifiedFieldName); assertThat(field).isNotNull(); assertThat(field.getName()).isEqualTo("strings"); var declaringClass = field.getDeclaringClass(); assertThat(declaringClass.getName()).isEqualTo(ExternalFields.class.getName()); assertThat(declaringClass).isNotEqualTo(ExternalFields.class); assertThat(declaringClass.getClassLoader()).isSameAs(testClassLoader); } } @Test void providesArgumentsUsingExternalFieldFromStaticNestedClass() { var arguments = provideArguments(ExternalFields.Nested.class.getName() + "#strings"); assertThat(arguments).containsExactly(array("nested string1"), array("nested string2")); } @Test void providesArgumentsUsingExternalAndInternalFieldsCombined() { var arguments = provideArguments("stringStreamSupplier", ExternalFields.class.getName() + "#strings"); assertThat(arguments).containsExactly(array("foo"), array("bar"), array("string1"), array("string2")); } @Nested class PrimitiveArrays { @Test void providesArgumentsUsingBooleanArray() { var arguments = provideArguments("booleanArray"); assertThat(arguments).containsExactly(array(Boolean.TRUE), array(Boolean.FALSE)); } @Test void providesArgumentsUsingByteArray() { var arguments = provideArguments("byteArray"); assertThat(arguments).containsExactly(array((byte) 1), array(Byte.MIN_VALUE)); } @Test void providesArgumentsUsingCharArray() { var arguments = provideArguments("charArray"); assertThat(arguments).containsExactly(array((char) 1), array(Character.MIN_VALUE)); } @Test void providesArgumentsUsingDoubleArray() { var arguments = provideArguments("doubleArray"); assertThat(arguments).containsExactly(array(1d), array(Double.MIN_VALUE)); } @Test void providesArgumentsUsingFloatArray() { var arguments = provideArguments("floatArray"); assertThat(arguments).containsExactly(array(1f), array(Float.MIN_VALUE)); } @Test void providesArgumentsUsingIntArray() { var arguments = provideArguments("intArray"); assertThat(arguments).containsExactly(array(47), array(Integer.MIN_VALUE)); } @Test void providesArgumentsUsingLongArray() { var arguments = provideArguments("longArray"); assertThat(arguments).containsExactly(array(47L), array(Long.MIN_VALUE)); } @Test void providesArgumentsUsingShortArray() { var arguments = provideArguments("shortArray"); assertThat(arguments).containsExactly(array((short) 47), array(Short.MIN_VALUE)); } } @Nested class ObjectArrays { @Test void providesArgumentsUsingObjectArray() { var arguments = provideArguments("objectArray"); assertThat(arguments).containsExactly(array(42), array("bar")); } @Test void providesArgumentsUsingStringArray() { var arguments = provideArguments("stringArray"); assertThat(arguments).containsExactly(array("foo"), array("bar")); } @Test void providesArgumentsUsing2dStringArray() { var arguments = provideArguments("twoDimensionalStringArray"); assertThat(arguments).containsExactly(array("foo", "bar"), array("baz", "qux")); } @Test void providesArgumentsUsing2dObjectArray() { var arguments = provideArguments("twoDimensionalObjectArray"); assertThat(arguments).containsExactly(array("foo", 42), array("bar", 23)); } } @Nested class ErrorCases { @Test void throwsExceptionWhenNonStaticLocalFieldIsReferencedWithLifecyclePerMethodSemantics() { var lifecyclePerClass = false; assertPreconditionViolationFor(() -> provideArguments(NonStaticTestCase.class, lifecyclePerClass, "nonStaticStringStreamSupplier").toArray())// .withMessageContainingAll("Field '", "' must be static: local @FieldSource fields must be static ", "unless the PER_CLASS @TestInstance lifecycle mode is used; ", "external @FieldSource fields must always be static."); } @Test void throwsExceptionWhenNonStaticExternalFieldIsReferencedWithLifecyclePerMethodSemantics() { var factoryClass = NonStaticTestCase.class.getName(); var field = factoryClass + "#nonStaticStringStreamSupplier"; var lifecyclePerClass = false; assertPreconditionViolationFor(() -> provideArguments(TestCase.class, lifecyclePerClass, field).toArray())// .withMessageContainingAll("Field '", "' must be static: local @FieldSource fields must be static ", "unless the PER_CLASS @TestInstance lifecycle mode is used; ", "external @FieldSource fields must always be static."); } @Test void throwsExceptionWhenNonStaticExternalFieldIsReferencedWithLifecyclePerClassSemantics() { var factoryClass = NonStaticTestCase.class.getName(); var field = factoryClass + "#nonStaticStringStreamSupplier"; boolean lifecyclePerClass = true; assertPreconditionViolationFor(() -> provideArguments(TestCase.class, lifecyclePerClass, field).toArray())// .withMessageContainingAll("Field '", "' must be static: local @FieldSource fields must be static ", "unless the PER_CLASS @TestInstance lifecycle mode is used; ", "external @FieldSource fields must always be static."); } @ParameterizedTest @ValueSource(strings = { "org.example.MyUtils", "org.example.MyUtils#", "#fieldName" }) void throwsExceptionWhenFullyQualifiedFieldNameSyntaxIsInvalid(String fieldName) { assertPreconditionViolationFor(() -> provideArguments(fieldName).toArray())// .withMessage(""" [%s] is not a valid fully qualified field name: \ it must start with a fully qualified class name followed by a \ '#' and then the field name.""", fieldName, TestCase.class.getName()); } @Test void throwsExceptionWhenClassForExternalFieldCannotBeLoaded() { var exception = assertThrows(JUnitException.class, () -> provideArguments("com.example.NonExistentClass#strings").toArray()); assertThat(exception.getMessage()).isEqualTo("Could not load class [com.example.NonExistentClass]"); } @Test void throwsExceptionWhenLocalFieldDoesNotExist() { assertPreconditionViolationFor(() -> provideArguments("nonExistentField").toArray())// .withMessage("Could not find field named [nonExistentField] in class [%s]", TestCase.class.getName()); } @ParameterizedTest @ValueSource(strings = { "nonExistentField", "strings()" }) void throwsExceptionWhenExternalFieldDoesNotExist(String fieldName) { String factoryClass = ExternalFields.class.getName(); assertPreconditionViolationFor(() -> provideArguments(factoryClass + "#" + fieldName).toArray())// .withMessage("Could not find field named [%s] in class [%s]", fieldName, factoryClass); } @Test void throwsExceptionWhenLocalFieldHasNullValue() { String field = "nullList"; String factoryClass = TestCase.class.getName(); assertPreconditionViolationFor(() -> provideArguments(field).toArray())// .withMessage("The value of field [%s] in class [%s] must not be null", field, factoryClass); } @Test void throwsExceptionWhenLocalFieldHasInvalidReturnType() { String field = "object"; String factoryClass = TestCase.class.getName(); assertPreconditionViolationFor(() -> provideArguments(field).toArray())// .withMessage("The value of field [%s] in class [%s] must be convertible to a Stream", field, factoryClass); } @Test void throwsExceptionWhenExternalFieldHasInvalidReturnType() { String factoryClass = ExternalFields.class.getName(); String fieldName = "object"; String field = factoryClass + "#" + fieldName; assertPreconditionViolationFor(() -> provideArguments(TestCase.class, false, field).toArray())// .withMessage("The value of field [%s] in class [%s] must be convertible to a Stream", fieldName, factoryClass); } @ParameterizedTest @ValueSource(strings = { "stream", "intStream", "longStream", "doubleStream" }) void throwsExceptionWhenLocalFieldHasStreamReturnType(String field) { String factoryClass = TestCase.class.getName(); assertPreconditionViolationFor(() -> provideArguments(field).toArray())// .withMessage("The value of field [%s] in class [%s] must not be a stream", field, factoryClass); } @Test void throwsExceptionWhenLocalFieldHasIteratorReturnType() { String field = "iterator"; String factoryClass = TestCase.class.getName(); assertPreconditionViolationFor(() -> provideArguments(field).toArray())// .withMessage("The value of field [%s] in class [%s] must not be an Iterator", field, factoryClass); } } // ------------------------------------------------------------------------- private static Object[] array(Object... objects) { return objects; } private static Stream provideArguments(String... fieldNames) { return provideArguments(TestCase.class, false, fieldNames); } private static Stream provideArguments(Class testClass, boolean allowNonStaticMethod, String... fieldNames) { // Ensure we have a non-null test method, even if it's not a real test method. // If this throws an exception, make sure that the supplied test class defines a "void test()" method. Method testMethod = ReflectionSupport.findMethod(testClass, "test").get(); return provideArguments(testClass, testMethod, allowNonStaticMethod, fieldNames); } private static Stream provideArguments(Class testClass, Method testMethod, boolean allowNonStaticMethod, String... fieldNames) { var extensionRegistry = createRegistryWithDefaultExtensions(mock()); var fieldSource = mock(FieldSource.class); when(fieldSource.value()).thenReturn(fieldNames); var parameters = mock(ParameterDeclarations.class); var extensionContext = mock(ExtensionContext.class); when(extensionContext.getTestClass()).thenReturn(Optional.of(testClass)); when(extensionContext.getTestMethod()).thenReturn(Optional.of(testMethod)); when(extensionContext.getExecutableInvoker()).thenReturn( new DefaultExecutableInvoker(extensionContext, extensionRegistry)); doCallRealMethod().when(extensionContext).getRequiredTestMethod(); doCallRealMethod().when(extensionContext).getRequiredTestClass(); var testInstance = allowNonStaticMethod ? ReflectionSupport.newInstance(testClass) : null; when(extensionContext.getTestInstance()).thenReturn(Optional.ofNullable(testInstance)); var lifeCycle = allowNonStaticMethod ? Lifecycle.PER_CLASS : Lifecycle.PER_METHOD; when(extensionContext.getTestInstanceLifecycle()).thenReturn(Optional.of(lifeCycle)); var provider = new FieldArgumentsProvider(); provider.accept(fieldSource); return provider.provideArguments(parameters, extensionContext).map(Arguments::get); } // ------------------------------------------------------------------------- static class DefaultFieldNameTestCase { // Test void testDefaultFieldName(String param) { } // Field static List testDefaultFieldName = List.of("foo", "bar"); } static class TestCase { void test() { } // --- Invalid --------------------------------------------------------- @Nullable static List nullList = null; static Object object = -1; static Stream stream = Stream.of("foo", "bar"); static DoubleStream doubleStream = DoubleStream.of(1.2, 3.4); static IntStream intStream = IntStream.of(1, 2); static LongStream longStream = LongStream.of(1L, 2L); static Iterator iterator = List.of("foo", "bar").iterator(); // --- Stream Supplier ------------------------------------------------- static Supplier> stringStreamSupplier = () -> Stream.of("foo", "bar"); static Supplier doubleStreamSupplier = () -> DoubleStream.of(1.2, 3.4); static Supplier longStreamSupplier = () -> LongStream.of(1L, 2L); static Supplier intStreamSupplier = () -> IntStream.of(1, 2); static Supplier> intArrayStreamSupplier = // () -> Stream.of(new int[] { 1, 2 }, new int[] { 3, 4 }); static Supplier> twoDimensionalIntArrayStreamSupplier = // () -> Stream.of(new int[][] { { 1, 2 }, { 2, 3 } }, new int[][] { { 4, 5 }, { 5, 6 } }); static Supplier> objectArrayStreamSupplier = // () -> Stream.of(new Object[] { "foo", 42 }, new Object[] { "bar", 23 }); static Supplier> twoDimensionalObjectArrayStreamSupplier = // () -> Stream.of(new Object[][] { { "a", 1 }, { "b", 2 } }, new Object[][] { { "c", 3 }, { "d", 4 } }); static Supplier> argumentsStreamSupplier = // () -> objectArrayStreamSupplier.get().map(Arguments::of); // --- Collection / Iterable ------------------------------------------- static List stringList = List.of("foo", "bar"); static List objectArrayList = List.of(array("foo", 42), array("bar", 23)); static Iterable stringIterable = stringList::iterator; static Iterable objectArrayIterable = objectArrayList::iterator; // --- Array of primitives --------------------------------------------- static boolean[] booleanArray = new boolean[] { true, false }; static byte[] byteArray = new byte[] { (byte) 1, Byte.MIN_VALUE }; static char[] charArray = new char[] { (char) 1, Character.MIN_VALUE }; static double[] doubleArray = new double[] { 1d, Double.MIN_VALUE }; static float[] floatArray = new float[] { 1f, Float.MIN_VALUE }; static int[] intArray = new int[] { 47, Integer.MIN_VALUE }; static long[] longArray = new long[] { 47L, Long.MIN_VALUE }; static short[] shortArray = new short[] { (short) 47, Short.MIN_VALUE }; // --- Array of objects ------------------------------------------------ static Object[] objectArray = new Object[] { 42, "bar" }; static String[] stringArray = new String[] { "foo", "bar" }; static String[][] twoDimensionalStringArray = new String[][] { { "foo", "bar" }, { "baz", "qux" } }; static Object[][] twoDimensionalObjectArray = new Object[][] { { "foo", 42 }, { "bar", 23 } }; } // This test case mimics @TestInstance(Lifecycle.PER_CLASS) static class NonStaticTestCase { void test() { } Supplier> nonStaticStringStreamSupplier = () -> Stream.of("foo", "bar"); } static class ExternalFields { static Object object = -1; static List strings = List.of("string1", "string2"); static class Nested { static List strings = List.of("nested string1", "nested string2"); } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/params/provider/MethodArgumentsProviderTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.engine.extension.MutableExtensionRegistry.createRegistryWithDefaultExtensions; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.junit.platform.commons.util.ReflectionUtils.findMethod; import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.DoubleStream; import java.util.stream.IntStream; import java.util.stream.LongStream; import java.util.stream.Stream; import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.jupiter.engine.execution.DefaultExecutableInvoker; import org.junit.jupiter.engine.extension.MutableExtensionRegistry; import org.junit.jupiter.params.ParameterizedTest; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.test.TestClassLoader; import org.junit.platform.commons.util.ReflectionUtils; /** * @since 5.0 */ class MethodArgumentsProviderTests { private @Nullable MutableExtensionRegistry extensionRegistry; @Test void providesArgumentsUsingStream() { var arguments = provideArguments("stringStreamProvider"); assertThat(arguments).containsExactly(array("foo"), array("bar")); } @Test void providesArgumentsUsingDoubleStream() { var arguments = provideArguments("doubleStreamProvider"); assertThat(arguments).containsExactly(array(1.2), array(3.4)); } @Test void providesArgumentsUsingLongStream() { var arguments = provideArguments("longStreamProvider"); assertThat(arguments).containsExactly(array(1L), array(2L)); } @Test void providesArgumentsUsingIntStream() { var arguments = provideArguments("intStreamProvider"); assertThat(arguments).containsExactly(array(1), array(2)); } /** * @since 5.3.2 */ @Test void providesArgumentsUsingStreamOfIntArrays() { var arguments = provideArguments("intArrayStreamProvider"); assertThat(arguments).containsExactly( // new Object[] { new int[] { 1, 2 } }, // new Object[] { new int[] { 3, 4 } } // ); } /** * @since 5.3.2 */ @Test void providesArgumentsUsingStreamOfTwoDimensionalIntArrays() { var arguments = provideArguments("twoDimensionalIntArrayStreamProvider"); assertThat(arguments).containsExactly( // array((Object) new int[][] { { 1, 2 }, { 2, 3 } }), // array((Object) new int[][] { { 4, 5 }, { 5, 6 } }) // ); } @Test void providesArgumentsUsingStreamOfObjectArrays() { var arguments = provideArguments("objectArrayStreamProvider"); assertThat(arguments).containsExactly(array("foo", 42), array("bar", 23)); } /** * @since 5.3.2 */ @Test void providesArgumentsUsingStreamOfTwoDimensionalObjectArrays() { var arguments = provideArguments("twoDimensionalObjectArrayStreamProvider"); assertThat(arguments).containsExactly( // array((Object) array(array("a", 1), array("b", 2))), // array((Object) array(array("c", 3), array("d", 4))) // ); } @Test void providesArgumentsUsingStreamOfArguments() { var arguments = provideArguments("argumentsStreamProvider"); assertThat(arguments).containsExactly(array("foo", 42), array("bar", 23)); } @Test void providesArgumentsUsingIterable() { var arguments = provideArguments("stringIterableProvider"); assertThat(arguments).containsExactly(array("foo"), array("bar")); } @Test void providesArgumentsUsingIterator() { var arguments = provideArguments("stringIteratorProvider"); assertThat(arguments).containsExactly(array("foo"), array("bar")); } @Test void providesArgumentsUsingMultipleFactoryMethods() { var arguments = provideArguments("stringStreamProvider", "stringIterableProvider"); assertThat(arguments).containsExactly(array("foo"), array("bar"), array("foo"), array("bar")); } @Test void providesArgumentsUsingIterableOfObjectArrays() { var arguments = provideArguments("objectArrayIterableProvider"); assertThat(arguments).containsExactly(array("foo", 42), array("bar", 23)); } @Test void providesArgumentsUsingListOfStrings() { var arguments = provideArguments("stringArrayListProvider"); assertThat(arguments).containsExactly(array("foo"), array("bar")); } @Test void providesArgumentsUsingListOfObjectArrays() { var arguments = provideArguments("objectArrayListProvider"); assertThat(arguments).containsExactly(array("foo", 42), array("bar", 23)); } @Test void throwsExceptionWhenNonStaticLocalFactoryMethodIsReferencedWithLifecyclePerMethodSemantics() { var lifecyclePerClass = false; assertPreconditionViolationFor(// () -> provideArguments(NonStaticTestCase.class, lifecyclePerClass, "nonStaticStringStreamProvider").toArray())// .withMessageContainingAll(// "Method '", // "' must be static: local factory methods must be static ", // "unless the PER_CLASS @TestInstance lifecycle mode is used; ", // "external factory methods must always be static."// ); } @Test void throwsExceptionWhenNonStaticExternalFactoryMethodIsReferencedWithLifecyclePerMethodSemantics() { var factoryClass = NonStaticTestCase.class.getName(); var factoryMethod = factoryClass + "#nonStaticStringStreamProvider"; var lifecyclePerClass = false; assertPreconditionViolationFor(// () -> provideArguments(TestCase.class, lifecyclePerClass, factoryMethod).toArray())// .withMessageContainingAll(// "Method '", // "' must be static: local factory methods must be static ", // "unless the PER_CLASS @TestInstance lifecycle mode is used; ", // "external factory methods must always be static."// ); } @Test void throwsExceptionWhenNonStaticExternalFactoryMethodIsReferencedWithLifecyclePerClassSemantics() { var factoryClass = NonStaticTestCase.class.getName(); var factoryMethod = factoryClass + "#nonStaticStringStreamProvider"; boolean lifecyclePerClass = true; assertPreconditionViolationFor(// () -> provideArguments(TestCase.class, lifecyclePerClass, factoryMethod).toArray())// .withMessageContainingAll(// "Method '", // "' must be static: local factory methods must be static ", // "unless the PER_CLASS @TestInstance lifecycle mode is used; ", // "external factory methods must always be static."// ); } @Test void providesArgumentsFromNonStaticFactoryMethodWhenStaticIsNotRequired() { var arguments = provideArguments(NonStaticTestCase.class, true, "nonStaticStringStreamProvider"); assertThat(arguments).containsExactly(array("foo"), array("bar")); } @Test void providesArgumentsUsingDefaultFactoryMethodName() { var testClass = DefaultFactoryMethodNameTestCase.class; var methodName = "testDefaultFactoryMethodName"; var testMethod = findMethod(testClass, methodName, String.class).get(); var arguments = provideArguments(testClass, testMethod, false, ""); assertThat(arguments).containsExactly(array("foo"), array("bar")); } @Test void providesArgumentsUsingExternalFactoryMethod() { var arguments = provideArguments(ExternalFactoryMethods.class.getName() + "#stringsProvider"); assertThat(arguments).containsExactly(array("string1"), array("string2")); } @Test void providesArgumentsUsingExternalFactoryMethodInTypeFromDifferentClassLoader() throws Exception { try (var testClassLoader = TestClassLoader.forClasses(TestCase.class, ExternalFactoryMethods.class)) { var testClass = testClassLoader.loadClass(TestCase.class.getName()); var testMethod = ReflectionUtils.findMethod(testClass, "test").get(); var fullyQualifiedMethodName = ExternalFactoryMethods.class.getName() + "#stringsProvider"; assertThat(testClass.getClassLoader()).isSameAs(testClassLoader); var arguments = provideArguments(testClass, false, fullyQualifiedMethodName); assertThat(arguments).containsExactly(array("string1"), array("string2")); var factoryMethod = MethodArgumentsProvider.findFactoryMethodByFullyQualifiedName(testClass, Optional.of(testMethod), fullyQualifiedMethodName); assertThat(factoryMethod).isNotNull(); assertThat(factoryMethod.getName()).isEqualTo("stringsProvider"); assertThat(factoryMethod.getParameterTypes()).isEmpty(); var declaringClass = factoryMethod.getDeclaringClass(); assertThat(declaringClass.getName()).isEqualTo(ExternalFactoryMethods.class.getName()); assertThat(declaringClass).isNotEqualTo(ExternalFactoryMethods.class); assertThat(declaringClass.getClassLoader()).isSameAs(testClassLoader); } } @Test void providesArgumentsUsingExternalFactoryMethodWithParentheses() { var arguments = provideArguments(ExternalFactoryMethods.class.getName() + "#stringsProvider()"); assertThat(arguments).containsExactly(array("string1"), array("string2")); } @Test void providesArgumentsUsingExternalFactoryMethodFromStaticNestedClass() { var arguments = provideArguments(ExternalFactoryMethods.class.getName() + "$Nested#stringsProvider()"); assertThat(arguments).containsExactly(array("nested string1"), array("nested string2")); } @Test void providesArgumentsUsingExternalAndInternalFactoryMethodsCombined() { var arguments = provideArguments("stringStreamProvider", ExternalFactoryMethods.class.getName() + "#stringsProvider"); assertThat(arguments).containsExactly(array("foo"), array("bar"), array("string1"), array("string2")); } @Nested class PrimitiveArrays { @Test void providesArgumentsUsingBooleanArray() { var arguments = provideArguments("booleanArrayProvider"); assertThat(arguments).containsExactly(array(Boolean.TRUE), array(Boolean.FALSE)); } @Test void providesArgumentsUsingByteArray() { var arguments = provideArguments("byteArrayProvider"); assertThat(arguments).containsExactly(array((byte) 1), array(Byte.MIN_VALUE)); } @Test void providesArgumentsUsingCharArray() { var arguments = provideArguments("charArrayProvider"); assertThat(arguments).containsExactly(array((char) 1), array(Character.MIN_VALUE)); } @Test void providesArgumentsUsingDoubleArray() { var arguments = provideArguments("doubleArrayProvider"); assertThat(arguments).containsExactly(array(1d), array(Double.MIN_VALUE)); } @Test void providesArgumentsUsingFloatArray() { var arguments = provideArguments("floatArrayProvider"); assertThat(arguments).containsExactly(array(1f), array(Float.MIN_VALUE)); } @Test void providesArgumentsUsingIntArray() { var arguments = provideArguments("intArrayProvider"); assertThat(arguments).containsExactly(array(47), array(Integer.MIN_VALUE)); } @Test void providesArgumentsUsingLongArray() { var arguments = provideArguments("longArrayProvider"); assertThat(arguments).containsExactly(array(47L), array(Long.MIN_VALUE)); } @Test void providesArgumentsUsingShortArray() { var arguments = provideArguments("shortArrayProvider"); assertThat(arguments).containsExactly(array((short) 47), array(Short.MIN_VALUE)); } } @Nested class ObjectArrays { @Test void providesArgumentsUsingObjectArray() { var arguments = provideArguments("objectArrayProvider"); assertThat(arguments).containsExactly(array(42), array("bar")); } @Test void providesArgumentsUsingStringArray() { var arguments = provideArguments("stringArrayProvider"); assertThat(arguments).containsExactly(array("foo"), array("bar")); } @Test void providesArgumentsUsing2dObjectArray() { var arguments = provideArguments("twoDimensionalObjectArrayProvider"); assertThat(arguments).containsExactly(array("foo", 42), array("bar", 23)); } } @Nested class ParameterResolution { private final Method testMethod = findMethod(TestCase.class, "test").get(); @BeforeEach void registerParameterResolver() { extensionRegistry = createRegistryWithDefaultExtensions(mock()); extensionRegistry.registerExtension(StringResolver.class); extensionRegistry.registerExtension(StringArrayResolver.class); extensionRegistry.registerExtension(IntArrayResolver.class); } @Test void providesArgumentsInferringDefaultFactoryMethodThatAcceptsArgument() { Method testMethod = findMethod(TestCase.class, "overloadedStringStreamProvider", Object.class).get(); String factoryMethodName = ""; // signals to use default var arguments = provideArguments(testMethod, factoryMethodName); assertThat(arguments).containsExactly(array("foo!"), array("bar!")); } @Test void providesArgumentsUsingSimpleNameForFactoryMethodThatAcceptsArgumentWithoutSpecifyingParameterList() { var arguments = provideArguments("stringStreamProviderWithParameter"); assertThat(arguments).containsExactly(array("foo!"), array("bar!")); } @Test void providesArgumentsUsingFullyQualifiedNameForFactoryMethodThatAcceptsArgumentWithoutSpecifyingParameterList() { var arguments = provideArguments(TestCase.class.getName() + "#stringStreamProviderWithParameter"); assertThat(arguments).containsExactly(array("foo!"), array("bar!")); } @Test void providesArgumentsUsingFullyQualifiedNameSpecifyingParameter() { var arguments = provideArguments( TestCase.class.getName() + "#stringStreamProviderWithParameter(java.lang.String)"); assertThat(arguments).containsExactly(array("foo!"), array("bar!")); } @Test void providesArgumentsUsingLocalQualifiedNameSpecifyingParameter() { var arguments = provideArguments(testMethod, "stringStreamProviderWithParameter(java.lang.String)"); assertThat(arguments).containsExactly(array("foo!"), array("bar!")); } @Test void providesArgumentsUsingFullyQualifiedNameForOverloadedFactoryMethodSpecifyingEmptyParameterList() { var arguments = provideArguments( TestCase.class.getName() + "#stringStreamProviderWithOrWithoutParameter()"); assertThat(arguments).containsExactly(array("foo"), array("bar")); } @Test void providesArgumentsUsingLocalQualifiedNameForOverloadedFactoryMethodSpecifyingEmptyParameterList() { var arguments = provideArguments(this.testMethod, "stringStreamProviderWithOrWithoutParameter()"); assertThat(arguments).containsExactly(array("foo"), array("bar")); } @Test void providesArgumentsUsingFullyQualifiedNameForOverloadedFactoryMethodSpecifyingParameter() { var arguments = provideArguments( TestCase.class.getName() + "#stringStreamProviderWithOrWithoutParameter(java.lang.String)"); assertThat(arguments).containsExactly(array("foo!"), array("bar!")); } @Test void providesArgumentsUsingLocalQualifiedNameForOverloadedFactoryMethodSpecifyingParameter() { var arguments = provideArguments(testMethod, "stringStreamProviderWithOrWithoutParameter(java.lang.String)"); assertThat(arguments).containsExactly(array("foo!"), array("bar!")); } @Test void failsToProvideArgumentsUsingFullyQualifiedNameSpecifyingInvalidParameterType() { String method = TestCase.class.getName() + "#stringStreamProviderWithParameter(example.FooBar)"; var exception = assertThrows(JUnitException.class, () -> provideArguments(method).toArray()); assertThat(exception).hasMessage(""" Failed to load parameter type [example.FooBar] for method [stringStreamProviderWithParameter] \ in class [org.junit.jupiter.params.provider.MethodArgumentsProviderTests$TestCase]."""); } @Test void failsToProvideArgumentsUsingLocalQualifiedNameSpecifyingInvalidParameterType() { var method = "stringStreamProviderWithParameter(example.FooBar)"; var exception = assertThrows(JUnitException.class, () -> provideArguments(this.testMethod, method).toArray()); assertThat(exception).hasMessage(""" Failed to load parameter type [example.FooBar] for method [stringStreamProviderWithParameter] \ in class [org.junit.jupiter.params.provider.MethodArgumentsProviderTests$TestCase]."""); } @Test void failsToProvideArgumentsUsingFullyQualifiedNameSpecifyingIncorrectParameterType() { String method = TestCase.class.getName() + "#stringStreamProviderWithParameter(java.lang.Integer)"; assertPreconditionViolationFor(() -> provideArguments(method).toArray())// .withMessage(""" Could not find factory method [stringStreamProviderWithParameter(java.lang.Integer)] in \ class [org.junit.jupiter.params.provider.MethodArgumentsProviderTests$TestCase]"""); } @Test void failsToProvideArgumentsUsingLocalQualifiedNameSpecifyingIncorrectParameterType() { var method = "stringStreamProviderWithParameter(java.lang.Integer)"; assertPreconditionViolationFor(() -> provideArguments(this.testMethod, method).toArray())// .withMessage(""" Could not find factory method [stringStreamProviderWithParameter(java.lang.Integer)] in \ class [org.junit.jupiter.params.provider.MethodArgumentsProviderTests$TestCase]"""); } @ParameterizedTest @ValueSource(strings = { "org.junit.jupiter.params.provider.MethodArgumentsProviderTests$TestCase#stringStreamProviderWithArrayParameter(java.lang.String[])", "org.junit.jupiter.params.provider.MethodArgumentsProviderTests$TestCase#stringStreamProviderWithArrayParameter([Ljava.lang.String;)", }) void providesArgumentsUsingFullyQualifiedNameSpecifyingObjectArrayParameter(String method) { var arguments = provideArguments(method); assertThat(arguments).containsExactly(array("foo :)"), array("bar :)")); } @ParameterizedTest @ValueSource(strings = { // "stringStreamProviderWithArrayParameter(java.lang.String[])", "stringStreamProviderWithArrayParameter([Ljava.lang.String;)" }) void providesArgumentsUsingLocalQualifiedNameSpecifyingObjectArrayParameter(String method) { var arguments = provideArguments(this.testMethod, method); assertThat(arguments).containsExactly(array("foo :)"), array("bar :)")); } @ParameterizedTest @ValueSource(strings = { "org.junit.jupiter.params.provider.MethodArgumentsProviderTests$TestCase#stringStreamProviderWithArrayParameter(int[])", "org.junit.jupiter.params.provider.MethodArgumentsProviderTests$TestCase#stringStreamProviderWithArrayParameter([I)", }) void providesArgumentsUsingFullyQualifiedNameSpecifyingPrimitiveArrayParameter(String method) { var arguments = provideArguments(method); assertThat(arguments).containsExactly(array("foo 42"), array("bar 42")); } @ParameterizedTest @ValueSource(strings = { // "stringStreamProviderWithArrayParameter(int[])", // "stringStreamProviderWithArrayParameter([I)" }) void providesArgumentsUsingLocalQualifiedNameSpecifyingPrimitiveArrayParameter(String method) { var arguments = provideArguments(this.testMethod, method); assertThat(arguments).containsExactly(array("foo 42"), array("bar 42")); } @ParameterizedTest @ValueSource(strings = { "java.lang.String,java.lang.String", "java.lang.String, java.lang.String", "java.lang.String, java.lang.String" }) void providesArgumentsUsingFullyQualifiedNameSpecifyingMultipleParameters(String params) { var method = TestCase.class.getName() + "#stringStreamProviderWithOrWithoutParameter(" + params + ")"; var arguments = provideArguments(method); assertThat(arguments).containsExactly(array("foo!!"), array("bar!!")); } @ParameterizedTest @ValueSource(strings = { "java.lang.String,java.lang.String", "java.lang.String, java.lang.String", "java.lang.String, java.lang.String" }) void providesArgumentsUsingLocalQualifiedNameSpecifyingMultipleParameters(String params) { var arguments = provideArguments(this.testMethod, "stringStreamProviderWithOrWithoutParameter(" + params + ")"); assertThat(arguments).containsExactly(array("foo!!"), array("bar!!")); } @Test void providesArgumentsUsingFullyQualifiedNameForOverloadedFactoryMethodWhenParameterListIsNotSpecified() { var arguments = provideArguments(TestCase.class.getName() + "#stringStreamProviderWithOrWithoutParameter"); assertThat(arguments).containsExactly(array("foo"), array("bar")); } @Test void providesArgumentsUsingLocalQualifiedNameForOverloadedFactoryMethodWhenParameterListIsNotSpecified() { var arguments = provideArguments("stringStreamProviderWithOrWithoutParameter").toArray(); assertThat(arguments).containsExactly(array("foo"), array("bar")); } } @Nested class ErrorCases { @Test void throwsExceptionWhenFullyQualifiedMethodNameSyntaxIsInvalid() { assertPreconditionViolationFor(() -> provideArguments("org.example.wrongSyntax").toArray())// .withMessage(// "[org.example.wrongSyntax] is not a valid fully qualified method name: "// + "it must start with a fully qualified class name followed by a '#' and then the method name, "// + "optionally followed by a parameter list enclosed in parentheses."// ); } @Test void throwsExceptionWhenClassForExternalFactoryMethodCannotBeLoaded() { var exception = assertThrows(JUnitException.class, () -> provideArguments("com.example.NonExistentClass#stringsProvider").toArray()); assertThat(exception.getMessage()).isEqualTo("Could not load class [com.example.NonExistentClass]"); } @Test void throwsExceptionWhenExternalFactoryMethodDoesNotExist() { String factoryClass = ExternalFactoryMethods.class.getName(); assertPreconditionViolationFor(() -> provideArguments(factoryClass + "#nonExistentMethod").toArray())// .withMessage("Could not find factory method [nonExistentMethod] in class [%s]", factoryClass); } @Test void throwsExceptionWhenLocalFactoryMethodDoesNotExist() { assertPreconditionViolationFor(() -> provideArguments("nonExistentMethod").toArray())// .withMessage("Could not find factory method [nonExistentMethod] in class [%s]", TestCase.class.getName()); } @Test void throwsExceptionWhenExternalFactoryMethodAcceptingSingleArgumentDoesNotExist() { String factoryClass = ExternalFactoryMethods.class.getName(); assertPreconditionViolationFor(() -> provideArguments(factoryClass + "#nonExistentMethod(int)").toArray())// .withMessage("Could not find factory method [nonExistentMethod(int)] in class [%s]", factoryClass); } @Test void throwsExceptionWhenLocalFactoryMethodAcceptingSingleArgumentDoesNotExist() { assertPreconditionViolationFor(() -> provideArguments("nonExistentMethod(int)").toArray())// .withMessage("Could not find factory method [nonExistentMethod(int)] in class [%s]", TestCase.class.getName()); } @Test void throwsExceptionWhenExternalFactoryMethodAcceptingMultipleArgumentsDoesNotExist() { String factoryClass = ExternalFactoryMethods.class.getName(); assertPreconditionViolationFor( () -> provideArguments(factoryClass + "#nonExistentMethod(int, java.lang.String)").toArray())// .withMessage( "Could not find factory method [nonExistentMethod(int, java.lang.String)] in class [%s]", factoryClass); } @Test void throwsExceptionWhenLocalFactoryMethodAcceptingMultipleArgumentsDoesNotExist() { assertPreconditionViolationFor(() -> provideArguments("nonExistentMethod(java.lang.String,int)").toArray())// .withMessage( "Could not find factory method [nonExistentMethod(java.lang.String,int)] in class [%s]", TestCase.class.getName()); } @Test void throwsExceptionWhenExternalFactoryMethodHasInvalidReturnType() { String testClass = TestCase.class.getName(); String factoryClass = ExternalFactoryMethods.class.getName(); String factoryMethod = factoryClass + "#factoryWithInvalidReturnType"; assertPreconditionViolationFor(() -> provideArguments(TestCase.class, false, factoryMethod).toArray())// .withMessage(""" Could not find valid factory method [%s] for test class [%s] \ but found the following invalid candidate: \ static java.lang.Object %s.factoryWithInvalidReturnType()\ """.formatted(factoryMethod, testClass, factoryClass)); } @Test void throwsExceptionWhenLocalFactoryMethodHasInvalidReturnType() { String testClass = TestCase.class.getName(); String factoryClass = testClass; String factoryMethod = "factoryWithInvalidReturnType"; assertPreconditionViolationFor(() -> provideArguments(factoryMethod).toArray())// .withMessage(""" Could not find valid factory method [%s] for test class [%s] \ but found the following invalid candidate: \ static java.lang.Object %s.factoryWithInvalidReturnType()\ """.formatted(factoryMethod, factoryClass, factoryClass)); } @Test void throwsExceptionWhenMultipleDefaultFactoryMethodCandidatesExist() { var testClass = MultipleDefaultFactoriesTestCase.class; var methodName = "test"; var testMethod = findMethod(testClass, methodName, String.class).get(); assertPreconditionViolationFor(() -> provideArguments(testClass, testMethod, false, "").toArray())// .withMessageContainingAll(// "2 factory methods named [test] were found in class [", testClass.getName() + "]: ", // "$MultipleDefaultFactoriesTestCase.test()", // "$MultipleDefaultFactoriesTestCase.test(int)"// ); } @Test void throwsExceptionWhenMultipleInvalidDefaultFactoryMethodCandidatesExist() { var testClass = MultipleInvalidDefaultFactoriesTestCase.class; var methodName = "test"; var testMethod = findMethod(testClass, methodName, String.class).get(); assertPreconditionViolationFor(() -> provideArguments(testClass, testMethod, false, "").toArray())// .withMessageContainingAll(// "Could not find valid factory method [test] in class [", testClass.getName() + "]", // "but found the following invalid candidates: ", // "$MultipleInvalidDefaultFactoriesTestCase.test()", // "$MultipleInvalidDefaultFactoriesTestCase.test(int)"// ); } } // ------------------------------------------------------------------------- private static Object[] array(Object... objects) { return objects; } private Stream<@Nullable Object[]> provideArguments(String... factoryMethodNames) { return provideArguments(TestCase.class, false, factoryMethodNames); } private Stream<@Nullable Object[]> provideArguments(Method testMethod, String factoryMethodName) { return provideArguments(TestCase.class, testMethod, false, factoryMethodName); } private Stream<@Nullable Object[]> provideArguments(Class testClass, boolean allowNonStaticMethod, String... factoryMethodNames) { // Ensure we have a non-null test method, even if it's not a real test method. // If this throws an exception, make sure that the supplied test class defines a "void test()" method. Method testMethod = ReflectionUtils.findMethod(testClass, "test").get(); return provideArguments(testClass, testMethod, allowNonStaticMethod, factoryMethodNames); } private Stream<@Nullable Object[]> provideArguments(Class testClass, Method testMethod, boolean allowNonStaticMethod, String... factoryMethodNames) { var methodSource = mock(MethodSource.class); when(methodSource.value()).thenReturn(factoryMethodNames); var extensionContext = mock(ExtensionContext.class); when(extensionContext.getTestClass()).thenReturn(Optional.of(testClass)); when(extensionContext.getTestMethod()).thenReturn(Optional.of(testMethod)); when(extensionContext.getExecutableInvoker()).thenReturn(getExecutableInvoker(extensionContext)); doCallRealMethod().when(extensionContext).getRequiredTestClass(); var testInstance = allowNonStaticMethod ? ReflectionUtils.newInstance(testClass) : null; when(extensionContext.getTestInstance()).thenReturn(Optional.ofNullable(testInstance)); var lifeCycle = allowNonStaticMethod ? Lifecycle.PER_CLASS : Lifecycle.PER_METHOD; when(extensionContext.getTestInstanceLifecycle()).thenReturn(Optional.of(lifeCycle)); var provider = new MethodArgumentsProvider(); provider.accept(methodSource); return provider.provideArguments(mock(), extensionContext).map(Arguments::get); } @SuppressWarnings("DataFlowIssue") private DefaultExecutableInvoker getExecutableInvoker(ExtensionContext extensionContext) { return new DefaultExecutableInvoker(extensionContext, extensionRegistry); } // ------------------------------------------------------------------------- static class DefaultFactoryMethodNameTestCase { // Test void testDefaultFactoryMethodName(String param) { } // Factory static Stream testDefaultFactoryMethodName() { return Stream.of("foo", "bar"); } } static class MultipleDefaultFactoriesTestCase { // Test void test(String param) { } // Factory static Stream test() { return Stream.of(); } // Another Factory static Stream test(int num) { return Stream.of(); } } @NullUnmarked static class MultipleInvalidDefaultFactoriesTestCase { // Test void test(String param) { } // NOT a Factory static String test() { return null; } // Also NOT a Factory static Object test(int num) { return null; } } static class TestCase { void test() { } // --- Invalid --------------------------------------------------------- static Object factoryWithInvalidReturnType() { return -1; } // --- Stream ---------------------------------------------------------- static Stream stringStreamProvider() { return Stream.of("foo", "bar"); } static Stream stringStreamProviderWithParameter(String parameter) { return Stream.of("foo" + parameter, "bar" + parameter); } static Stream stringStreamProviderWithArrayParameter(String[] parameter) { String suffix = Arrays.stream(parameter).collect(Collectors.joining()); return Stream.of("foo " + suffix, "bar " + suffix); } static Stream stringStreamProviderWithArrayParameter(int[] parameter) { return stringStreamProviderWithArrayParameter( Arrays.stream(parameter).mapToObj(String::valueOf).toArray(String[]::new)); } static Stream stringStreamProviderWithOrWithoutParameter() { return stringStreamProvider(); } static Stream stringStreamProviderWithOrWithoutParameter(String parameter) { return stringStreamProviderWithParameter(parameter); } static Stream stringStreamProviderWithOrWithoutParameter(String parameter1, String parameter2) { return stringStreamProviderWithParameter(parameter1 + parameter2); } // Overloaded method, but not a valid return type for a factory method static void stringStreamProviderWithOrWithoutParameter(String parameter1, int parameter2) { } // @ParameterizedTest // @MethodSource // use default, inferred factory method void overloadedStringStreamProvider(Object parameter) { // test implementation } // Default factory method for overloadedStringStreamProvider(Object) static Stream overloadedStringStreamProvider(String parameter) { return stringStreamProviderWithParameter(parameter); } static DoubleStream doubleStreamProvider() { return DoubleStream.of(1.2, 3.4); } static LongStream longStreamProvider() { return LongStream.of(1L, 2L); } static IntStream intStreamProvider() { return IntStream.of(1, 2); } static Stream intArrayStreamProvider() { return Stream.of(new int[] { 1, 2 }, new int[] { 3, 4 }); } static Stream twoDimensionalIntArrayStreamProvider() { return Stream.of(new int[][] { { 1, 2 }, { 2, 3 } }, new int[][] { { 4, 5 }, { 5, 6 } }); } static Stream objectArrayStreamProvider() { return Stream.of(new Object[] { "foo", 42 }, new Object[] { "bar", 23 }); } static Stream twoDimensionalObjectArrayStreamProvider() { return Stream.of(new Object[][] { { "a", 1 }, { "b", 2 } }, new Object[][] { { "c", 3 }, { "d", 4 } }); } static Stream argumentsStreamProvider() { return objectArrayStreamProvider().map(Arguments::of); } // --- Iterable / Collection ------------------------------------------- static Iterable stringIterableProvider() { return TestCase::stringIteratorProvider; } static Iterable objectArrayIterableProvider() { return objectArrayListProvider(); } static List stringArrayListProvider() { return Arrays.asList("foo", "bar"); } static List objectArrayListProvider() { return Arrays.asList(array("foo", 42), array("bar", 23)); } // --- Iterator -------------------------------------------------------- static Iterator stringIteratorProvider() { return Arrays.asList("foo", "bar").iterator(); } // --- Array of primitives --------------------------------------------- static boolean[] booleanArrayProvider() { return new boolean[] { true, false }; } static byte[] byteArrayProvider() { return new byte[] { (byte) 1, Byte.MIN_VALUE }; } static char[] charArrayProvider() { return new char[] { (char) 1, Character.MIN_VALUE }; } static double[] doubleArrayProvider() { return new double[] { 1d, Double.MIN_VALUE }; } static float[] floatArrayProvider() { return new float[] { 1f, Float.MIN_VALUE }; } static int[] intArrayProvider() { return new int[] { 47, Integer.MIN_VALUE }; } static long[] longArrayProvider() { return new long[] { 47L, Long.MIN_VALUE }; } static short[] shortArrayProvider() { return new short[] { (short) 47, Short.MIN_VALUE }; } // --- Array of objects ------------------------------------------------ static Object[] objectArrayProvider() { return new Object[] { 42, "bar" }; } static String[] stringArrayProvider() { return new String[] { "foo", "bar" }; } static Object[][] twoDimensionalObjectArrayProvider() { return new Object[][] { { "foo", 42 }, { "bar", 23 } }; } } // This test case mimics @TestInstance(Lifecycle.PER_CLASS) static class NonStaticTestCase { void test() { } Stream nonStaticStringStreamProvider() { return Stream.of("foo", "bar"); } } static class ExternalFactoryMethods { static Object factoryWithInvalidReturnType() { return -1; } static Stream stringsProvider() { return Stream.of("string1", "string2"); } static class Nested { static Stream stringsProvider() { return Stream.of("nested string1", "nested string2"); } } } static class StringResolver implements ParameterResolver { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return parameterContext.getParameter().getType() == String.class; } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return "!"; } } static class StringArrayResolver implements ParameterResolver { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return parameterContext.getParameter().getType() == String[].class; } @Override public String[] resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return new String[] { ":", ")" }; } } static class IntArrayResolver implements ParameterResolver { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return parameterContext.getParameter().getType() == int[].class; } @Override public int[] resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return new int[] { 4, 2 }; } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/params/provider/MockCsvAnnotationBuilder.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.lang.annotation.Annotation; /** * @since 5.6 */ abstract class MockCsvAnnotationBuilder> { static CsvSource csvSource(String... lines) { return csvSource().lines(lines).build(); } static MockCsvSourceBuilder csvSource() { return new MockCsvSourceBuilder(); } static MockCsvFileSourceBuilder csvFileSource() { return new MockCsvFileSourceBuilder(); } // ------------------------------------------------------------------------- private boolean useHeadersInDisplayName = false; private char quoteCharacter = '\0'; protected char delimiter = '\0'; protected String delimiterString = ""; protected String emptyValue = ""; protected String[] nullValues = new String[0]; protected int maxCharsPerColumn = 4096; protected boolean ignoreLeadingAndTrailingWhitespace = true; private char commentCharacter = '#'; private MockCsvAnnotationBuilder() { } protected abstract B getSelf(); B useHeadersInDisplayName(boolean useHeadersInDisplayName) { this.useHeadersInDisplayName = useHeadersInDisplayName; return getSelf(); } B quoteCharacter(char quoteCharacter) { this.quoteCharacter = quoteCharacter; return getSelf(); } B delimiter(char delimiter) { this.delimiter = delimiter; return getSelf(); } B delimiterString(String delimiterString) { this.delimiterString = delimiterString; return getSelf(); } B emptyValue(String emptyValue) { this.emptyValue = emptyValue; return getSelf(); } B nullValues(String... nullValues) { this.nullValues = nullValues; return getSelf(); } B maxCharsPerColumn(int maxCharsPerColumn) { this.maxCharsPerColumn = maxCharsPerColumn; return getSelf(); } B ignoreLeadingAndTrailingWhitespace(boolean ignoreLeadingAndTrailingWhitespace) { this.ignoreLeadingAndTrailingWhitespace = ignoreLeadingAndTrailingWhitespace; return getSelf(); } B commentCharacter(char commentCharacter) { this.commentCharacter = commentCharacter; return getSelf(); } abstract A build(); // ------------------------------------------------------------------------- static class MockCsvSourceBuilder extends MockCsvAnnotationBuilder { private String[] lines = new String[0]; private String textBlock = ""; private MockCsvSourceBuilder() { super.quoteCharacter = '\''; } @Override protected MockCsvSourceBuilder getSelf() { return this; } MockCsvSourceBuilder lines(String... lines) { this.lines = lines; return this; } MockCsvSourceBuilder textBlock(String textBlock) { this.textBlock = textBlock; return this; } @Override CsvSource build() { var annotation = mock(CsvSource.class); // Common when(annotation.useHeadersInDisplayName()).thenReturn(super.useHeadersInDisplayName); when(annotation.quoteCharacter()).thenReturn(super.quoteCharacter); when(annotation.delimiter()).thenReturn(super.delimiter); when(annotation.delimiterString()).thenReturn(super.delimiterString); when(annotation.emptyValue()).thenReturn(super.emptyValue); when(annotation.nullValues()).thenReturn(super.nullValues); when(annotation.maxCharsPerColumn()).thenReturn(super.maxCharsPerColumn); when(annotation.ignoreLeadingAndTrailingWhitespace()).thenReturn(super.ignoreLeadingAndTrailingWhitespace); when(annotation.commentCharacter()).thenReturn(super.commentCharacter); // @CsvSource when(annotation.value()).thenReturn(this.lines); when(annotation.textBlock()).thenReturn(this.textBlock); return annotation; } } static class MockCsvFileSourceBuilder extends MockCsvAnnotationBuilder { private String[] resources = {}; private String[] files = {}; private String encoding = "UTF-8"; private int numLinesToSkip = 0; private MockCsvFileSourceBuilder() { super.quoteCharacter = '"'; } @Override protected MockCsvFileSourceBuilder getSelf() { return this; } MockCsvFileSourceBuilder resources(String... resources) { this.resources = resources; return this; } MockCsvFileSourceBuilder files(String... files) { this.files = files; return this; } MockCsvFileSourceBuilder encoding(String encoding) { this.encoding = encoding; return this; } MockCsvFileSourceBuilder numLinesToSkip(int numLinesToSkip) { this.numLinesToSkip = numLinesToSkip; return this; } @Override CsvFileSource build() { var annotation = mock(CsvFileSource.class); // Common when(annotation.useHeadersInDisplayName()).thenReturn(super.useHeadersInDisplayName); when(annotation.quoteCharacter()).thenReturn(super.quoteCharacter); when(annotation.delimiter()).thenReturn(super.delimiter); when(annotation.delimiterString()).thenReturn(super.delimiterString); when(annotation.emptyValue()).thenReturn(super.emptyValue); when(annotation.nullValues()).thenReturn(super.nullValues); when(annotation.maxCharsPerColumn()).thenReturn(super.maxCharsPerColumn); when(annotation.ignoreLeadingAndTrailingWhitespace()).thenReturn(super.ignoreLeadingAndTrailingWhitespace); when(annotation.commentCharacter()).thenReturn(super.commentCharacter); // @CsvFileSource when(annotation.resources()).thenReturn(this.resources); when(annotation.files()).thenReturn(this.files); when(annotation.encoding()).thenReturn(this.encoding); when(annotation.numLinesToSkip()).thenReturn(this.numLinesToSkip); return annotation; } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/params/provider/ValueArgumentsProviderTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.provider; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; /** * @since 5.0 */ class ValueArgumentsProviderTests { @Test void multipleInputsAreNotAllowed() { assertPreconditionViolationFor(() -> provideArguments(new short[1], new byte[0], new int[1], new long[0], new float[0], new double[0], new char[0], new boolean[0], new String[0], new Class[0]).findAny())// .withMessageContaining( "Exactly one type of input must be provided in the @ValueSource annotation, but there were 2"); } @Test void onlyEmptyInputsAreNotAllowed() { assertPreconditionViolationFor(() -> provideArguments(new short[0], new byte[0], new int[0], new long[0], new float[0], new double[0], new char[0], new boolean[0], new String[0], new Class[0]).findAny())// .withMessageContaining( "Exactly one type of input must be provided in the @ValueSource annotation, but there were 0"); } /** * @since 5.1 */ @Test void providesShorts() { var arguments = provideArguments(new short[] { 23, 42 }, new byte[0], new int[0], new long[0], new float[0], new double[0], new char[0], new boolean[0], new String[0], new Class[0]); assertThat(arguments).containsExactly(array((short) 23), array((short) 42)); } /** * @since 5.1 */ @Test void providesBytes() { var arguments = provideArguments(new short[0], new byte[] { 23, 42 }, new int[0], new long[0], new float[0], new double[0], new char[0], new boolean[0], new String[0], new Class[0]); assertThat(arguments).containsExactly(array((byte) 23), array((byte) 42)); } @Test void providesInts() { var arguments = provideArguments(new short[0], new byte[0], new int[] { 23, 42 }, new long[0], new float[0], new double[0], new char[0], new boolean[0], new String[0], new Class[0]); assertThat(arguments).containsExactly(array(23), array(42)); } @Test void providesLongs() { var arguments = provideArguments(new short[0], new byte[0], new int[0], new long[] { 23, 42 }, new float[0], new double[0], new char[0], new boolean[0], new String[0], new Class[0]); assertThat(arguments).containsExactly(array(23L), array(42L)); } /** * @since 5.1 */ @Test void providesFloats() { var arguments = provideArguments(new short[0], new byte[0], new int[0], new long[0], new float[] { 23.32F, 42.24F }, new double[0], new char[0], new boolean[0], new String[0], new Class[0]); assertThat(arguments).containsExactly(array(23.32F), array(42.24F)); } @Test void providesDoubles() { var arguments = provideArguments(new short[0], new byte[0], new int[0], new long[0], new float[0], new double[] { 23.32, 42.24 }, new char[0], new boolean[0], new String[0], new Class[0]); assertThat(arguments).containsExactly(array(23.32), array(42.24)); } /** * @since 5.1 */ @Test void providesChars() { var arguments = provideArguments(new short[0], new byte[0], new int[0], new long[0], new float[0], new double[0], new char[] { 'a', 'b', 'c' }, new boolean[0], new String[0], new Class[0]); assertThat(arguments).containsExactly(array('a'), array('b'), array('c')); } /** * @since 5.5 */ @Test void providesBooleans() { var arguments = provideArguments(new short[0], new byte[0], new int[0], new long[0], new float[0], new double[0], new char[0], new boolean[] { true, false }, new String[0], new Class[0]); assertThat(arguments).containsExactly(array(true), array(false)); } @Test void providesStrings() { var arguments = provideArguments(new short[0], new byte[0], new int[0], new long[0], new float[0], new double[0], new char[0], new boolean[0], new String[] { "foo", "bar" }, new Class[0]); assertThat(arguments).containsExactly(array("foo"), array("bar")); } /** * @since 5.1 */ @Test void providesClasses() { var arguments = provideArguments(new short[0], new byte[0], new int[0], new long[0], new float[0], new double[0], new char[0], new boolean[0], new String[0], new Class[] { Integer.class, getClass() }); assertThat(arguments).containsExactly(array(Integer.class), array(getClass())); } private static Stream provideArguments(short[] shorts, byte[] bytes, int[] ints, long[] longs, float[] floats, double[] doubles, char[] chars, boolean[] booleans, String[] strings, Class[] classes) { var annotation = mock(ValueSource.class); when(annotation.shorts()).thenReturn(shorts); when(annotation.bytes()).thenReturn(bytes); when(annotation.ints()).thenReturn(ints); when(annotation.longs()).thenReturn(longs); when(annotation.floats()).thenReturn(floats); when(annotation.doubles()).thenReturn(doubles); when(annotation.chars()).thenReturn(chars); when(annotation.booleans()).thenReturn(booleans); when(annotation.strings()).thenReturn(strings); when(annotation.classes()).thenReturn(classes); var provider = new ValueArgumentsProvider(); provider.accept(annotation); return provider.provideArguments(mock(), mock(ExtensionContext.class)).map(Arguments::get); } private static Object[] array(Object... objects) { return objects; } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/params/support/AnnotationConsumerInitializerTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.support; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.params.support.AnnotationConsumerInitializer.initialize; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import java.util.function.Supplier; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Named; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.converter.AnnotationBasedArgumentConverter; import org.junit.jupiter.params.converter.JavaTimeConversionPattern; import org.junit.jupiter.params.provider.AnnotationBasedArgumentsProvider; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.FieldSource; import org.junit.platform.commons.JUnitException; @DisplayName("AnnotationConsumerInitializer") class AnnotationConsumerInitializerTests { @Test @DisplayName("should initialize annotation consumer") void shouldInitializeAnnotationConsumer() throws NoSuchMethodException { var instance = new SomeAnnotationConsumer(); var method = SubjectClass.class.getDeclaredMethod("foo"); var initialisedAnnotationConsumer = initialize(method, instance); assertThat(initialisedAnnotationConsumer.annotation) // .isInstanceOfSatisfying(CsvSource.class, // source -> assertThat(source.value()).containsExactly("a", "b")); } @ParameterizedTest @FieldSource("argumentsProviders") @DisplayName("should initialize annotation-based ArgumentsProvider") void shouldInitializeAnnotationBasedArgumentsProvider(AbstractAnnotationBasedArgumentsProvider instance) throws NoSuchMethodException { var method = SubjectClass.class.getDeclaredMethod("foo"); var initialisedAnnotationConsumer = initialize(method, instance); initialisedAnnotationConsumer.provideArguments(mock(), mock(ExtensionContext.class)).findAny(); assertThat(initialisedAnnotationConsumer.annotations) // .hasSize(1) // .element(0) // .isInstanceOfSatisfying(CsvSource.class, // source -> assertThat(source.value()).containsExactly("a", "b")); } @Test @DisplayName("should initialize annotation-based ArgumentConverter") void shouldInitializeAnnotationBasedArgumentConverter() throws NoSuchMethodException { var instance = new SomeAnnotationBasedArgumentConverter(); var parameter = SubjectClass.class.getDeclaredMethod("bar", LocalDate.class).getParameters()[0]; var initialisedAnnotationConsumer = initialize(parameter, instance); ParameterContext parameterContext = mock(); when(parameterContext.getParameter()).thenReturn(parameter); initialisedAnnotationConsumer.convert("source", parameterContext); assertThat(initialisedAnnotationConsumer.annotation) // .isInstanceOfSatisfying(JavaTimeConversionPattern.class, // annotation -> assertThat(annotation.value()).isEqualTo("pattern")); } @Test @DisplayName("should throw exception when method is not annotated") void shouldThrowExceptionWhenMethodIsNotAnnotated() throws NoSuchMethodException { var instance = new SomeAnnotationConsumer(); var method = SubjectClass.class.getDeclaredMethod("noAnnotation", String.class); assertThatThrownBy(() -> initialize(method, instance)).isInstanceOf(JUnitException.class); } @Test @DisplayName("should throw exception when parameter is not annotated") void shouldThrowExceptionWhenParameterIsNotAnnotated() throws NoSuchMethodException { var instance = new SomeAnnotationConsumer(); var parameter = SubjectClass.class.getDeclaredMethod("noAnnotation", String.class).getParameters()[0]; assertThatThrownBy(() -> initialize(parameter, instance)).isInstanceOf(JUnitException.class); } @ParameterizedTest @FieldSource("argumentsProviders") void shouldInitializeForEachAnnotations(AbstractAnnotationBasedArgumentsProvider provider) throws NoSuchMethodException { var instance = spy(provider); var method = SubjectClass.class.getDeclaredMethod("repeatableAnnotation", String.class); initialize(method, instance); verify(instance, times(2)).accept(any(CsvSource.class)); } static Supplier>> argumentsProviders = () -> List.of( // Named.of("current", new SomeAnnotationBasedArgumentsProvider()), // Named.of("deprecated", new DeprecatedAnnotationBasedArgumentsProvider()) // ); private static abstract class AbstractAnnotationBasedArgumentsProvider extends AnnotationBasedArgumentsProvider { List annotations = new ArrayList<>(); } private static class SomeAnnotationBasedArgumentsProvider extends AbstractAnnotationBasedArgumentsProvider { @Override protected Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context, CsvSource annotation) { annotations.add(annotation); return Stream.empty(); } } private static class DeprecatedAnnotationBasedArgumentsProvider extends AbstractAnnotationBasedArgumentsProvider { @Override @SuppressWarnings("deprecation") protected Stream provideArguments(ExtensionContext context, CsvSource annotation) { annotations.add(annotation); return Stream.empty(); } } private static class SomeAnnotationBasedArgumentConverter extends AnnotationBasedArgumentConverter { @Nullable JavaTimeConversionPattern annotation; @Override protected @Nullable Object convert(@Nullable Object source, Class targetType, JavaTimeConversionPattern annotation) { this.annotation = annotation; return null; } } private static class SomeAnnotationConsumer implements AnnotationConsumer { @Nullable CsvSource annotation; @Override public void accept(CsvSource csvSource) { annotation = csvSource; } } @SuppressWarnings("unused") private static class SubjectClass { @CsvSource({ "a", "b" }) void foo() { } void bar(@JavaTimeConversionPattern("pattern") LocalDate date) { } void noAnnotation(String param) { } @CsvSource("a") @CsvSource("b") void repeatableAnnotation(String param) { } } } ================================================ FILE: jupiter-tests/src/test/java/org/junit/jupiter/params/support/DeprecatedParameterInfoIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.support; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import org.jspecify.annotations.NullMarked; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.BeforeClassTemplateInvocationCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.jupiter.params.Parameter; import org.junit.jupiter.params.ParameterizedClass; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; /** * @since 5.13 */ class DeprecatedParameterInfoIntegrationTests extends AbstractJupiterTestEngineTests { @Test void storesParameterInfoInExtensionContextStoreOnDifferentLevels() { var results = executeTestsForClass(TestCase.class); results.allEvents().debug().assertStatistics(stats -> stats.started(7).succeeded(7)); } @ParameterizedClass @ValueSource(ints = 1) @ExtendWith(ParameterInfoConsumingExtension.class) record TestCase(int i) { @Nested @ParameterizedClass @ValueSource(ints = 2) class Inner { @Parameter int j; @ParameterizedTest @ValueSource(ints = 3) void test(int k) { assertEquals(1, i); assertEquals(2, j); assertEquals(3, k); } } } @SuppressWarnings("removal") @NullMarked private static class ParameterInfoConsumingExtension implements BeforeClassTemplateInvocationCallback, BeforeEachCallback { @Override public void beforeClassTemplateInvocation(ExtensionContext parameterizedClassInvocationContext) { if (TestCase.Inner.class.equals(parameterizedClassInvocationContext.getRequiredTestClass())) { assertParameterInfo(parameterizedClassInvocationContext, "j", 2); var nestedParameterizedClassContext = parameterizedClassInvocationContext.getParent().orElseThrow(); assertParameterInfo(nestedParameterizedClassContext, "i", 1); parameterizedClassInvocationContext = nestedParameterizedClassContext.getParent().orElseThrow(); } assertParameterInfo(parameterizedClassInvocationContext, "i", 1); var outerParameterizedClassContext = parameterizedClassInvocationContext.getParent().orElseThrow(); assertNull(ParameterInfo.get(outerParameterizedClassContext)); } @Override public void beforeEach(ExtensionContext parameterizedTestInvocationContext) { assertParameterInfo(parameterizedTestInvocationContext, "k", 3); var parameterizedTestContext = parameterizedTestInvocationContext.getParent().orElseThrow(); assertParameterInfo(parameterizedTestContext, "j", 2); var nestedParameterizedClassInvocationContext = parameterizedTestContext.getParent().orElseThrow(); assertParameterInfo(nestedParameterizedClassInvocationContext, "j", 2); var nestedParameterizedClassContext = nestedParameterizedClassInvocationContext.getParent().orElseThrow(); assertParameterInfo(nestedParameterizedClassContext, "i", 1); var outerParameterizedClassInvocationContext = nestedParameterizedClassContext.getParent().orElseThrow(); assertParameterInfo(outerParameterizedClassInvocationContext, "i", 1); var outerParameterizedClassContext = outerParameterizedClassInvocationContext.getParent().orElseThrow(); assertNull(ParameterInfo.get(outerParameterizedClassContext)); } private static void assertParameterInfo(ExtensionContext context, String parameterName, int argumentValue) { var parameterInfo = ParameterInfo.get(context); assertNotNull(parameterInfo); var declaration = parameterInfo.getDeclarations().get(0).orElseThrow(); assertEquals(parameterName, declaration.getParameterName().orElseThrow()); assertEquals(int.class, declaration.getParameterType()); assertEquals(argumentValue, parameterInfo.getArguments().getInteger(0)); } } } ================================================ FILE: jupiter-tests/src/test/kotlin/org/junit/jupiter/api/kotlin/GenericInlineValueClassTests.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.kotlin import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource /** * Tests for generic inline value classes. * These work because they compile to Object in JVM, bypassing strict type validation. */ class GenericInlineValueClassTests { @MethodSource("resultProvider") @ParameterizedTest fun testResult(result: Result) { assertEquals("success", result.getOrThrow()) } @MethodSource("multipleResultsProvider") @ParameterizedTest fun testMultipleResults( result1: Result, result2: Result ) { assertEquals("data", result1.getOrThrow()) assertEquals(42, result2.getOrThrow()) } @MethodSource("nullableResultProvider") @ParameterizedTest fun testNullableResult(result: Result?) { assertEquals("test", result?.getOrNull()) } @MethodSource("customGenericProvider") @ParameterizedTest fun testCustomGenericContainer(container: Container) { assertEquals("content", container.value) } companion object { @JvmStatic fun resultProvider() = listOf( Arguments.of(Result.success("success")) ) @JvmStatic fun multipleResultsProvider() = listOf( Arguments.of( Result.success("data"), Result.success(42) ) ) @JvmStatic fun nullableResultProvider() = listOf( Arguments.of(Result.success("test")) ) @JvmStatic fun customGenericProvider() = listOf( Arguments.of(Container("content")) ) } } @JvmInline value class Container( val value: T ) ================================================ FILE: jupiter-tests/src/test/kotlin/org/junit/jupiter/api/kotlin/KotlinAssertTimeoutAssertionsTests.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ 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 import java.util.concurrent.CountDownLatch import java.util.concurrent.atomic.AtomicBoolean /** * Unit tests for Kotlin-specific `assertTimeout*` assertions. * * @since 5.5 */ internal class KotlinAssertTimeoutAssertionsTests { // --- executable ---------------------------------------------------------- @Test fun assertTimeoutForExecutableThatCompletesBeforeTheTimeout() { changed.get().set(false) assertTimeout(ofMillis(500)) { changed.get().set(true) } assertTrue(changed.get().get(), "should have executed in the same thread") assertTimeout(ofMillis(500), "message") { } assertTimeout(ofMillis(500), "message") { } } @Test fun assertTimeoutForExecutableThatThrowsAnException() { val exception = assertThrows { assertTimeout(ofMillis(500)) { throw RuntimeException("not this time") } } assertMessageEquals(exception, "not this time") } @Test fun assertTimeoutForExecutableThatThrowsAnAssertionFailedError() { val exception = assertThrows { assertTimeout(ofMillis(500)) { fail("enigma") } } assertMessageEquals(exception, "enigma") } @Test fun assertTimeoutForExecutableThatCompletesAfterTheTimeout() { val error = assertThrows { assertTimeout(ofMillis(10)) { this.nap() } } assertMessageStartsWith(error, "execution exceeded timeout of 10 ms by") } @Test fun assertTimeoutWithMessageForExecutableThatCompletesAfterTheTimeout() { val error = assertThrows { assertTimeout(ofMillis(10), "Tempus Fugit") { this.nap() } } assertMessageStartsWith(error, "Tempus Fugit ==> execution exceeded timeout of 10 ms by") } @Test fun assertTimeoutWithMessageSupplierForExecutableThatCompletesAfterTheTimeout() { val error = assertThrows { assertTimeout(ofMillis(10), { "Tempus" + " " + "Fugit" }) { this.nap() } } assertMessageStartsWith(error, "Tempus Fugit ==> execution exceeded timeout of 10 ms by") } // --- supplier ------------------------------------------------------------ @Test fun assertTimeoutForSupplierThatCompletesBeforeTheTimeout() { changed.get().set(false) val result = assertTimeout(ofMillis(500)) { changed.get().set(true) "Tempus Fugit" } assertTrue(changed.get().get(), "should have executed in the same thread") assertEquals("Tempus Fugit", result) assertEquals("Tempus Fugit", assertTimeout(ofMillis(500), "message") { "Tempus Fugit" }) assertEquals("Tempus Fugit", assertTimeout(ofMillis(500), { "message" }, { "Tempus Fugit" })) } @Test fun assertTimeoutForSupplierThatThrowsAnException() { val exception = assertThrows { assertTimeout(ofMillis(500)) { ExceptionUtils.throwAsUncheckedException(RuntimeException("not this time")) } } assertMessageEquals(exception, "not this time") } @Test fun assertTimeoutForSupplierThatThrowsAnAssertionFailedError() { val exception = assertThrows { assertTimeout(ofMillis(500)) { fail("enigma") } } assertMessageEquals(exception, "enigma") } @Test fun assertTimeoutForSupplierThatCompletesAfterTheTimeout() { val error = assertThrows { assertTimeout(ofMillis(10)) { nap() } } assertMessageStartsWith(error, "execution exceeded timeout of 10 ms by") } @Test fun assertTimeoutWithMessageForSupplierThatCompletesAfterTheTimeout() { val error = assertThrows { assertTimeout(ofMillis(10), "Tempus Fugit") { nap() } } assertMessageStartsWith(error, "Tempus Fugit ==> execution exceeded timeout of 10 ms by") } @Test fun assertTimeoutWithMessageSupplierForSupplierThatCompletesAfterTheTimeout() { val error = assertThrows { assertTimeout(ofMillis(10), { "Tempus" + " " + "Fugit" }) { nap() } } assertMessageStartsWith(error, "Tempus Fugit ==> execution exceeded timeout of 10 ms by") } @Test fun `assertTimeout with value assignment in lambda`() { val value: Int assertTimeout(ofMillis(500)) { value = 10 // Val can be assigned in the message supplier lambda. assertEquals(10, value) } } @Test fun `assertTimeout with message and value assignment in lambda`() { var value = 0 assertTimeout(ofMillis(500), "message") { value = 10 } assertEquals(10, value) } @Test fun `assertTimeout with message supplier and value assignment in lambda`() { var value = 0 @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") 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 fun assertTimeoutPreemptivelyForExecutableThatCompletesBeforeTheTimeout() { changed.get().set(false) assertTimeoutPreemptively(ofMillis(500)) { changed.get().set(true) } assertFalse(changed.get().get(), "should have executed in a different thread") assertTimeoutPreemptively(ofMillis(500), "message") {} assertTimeoutPreemptively(ofMillis(500), { "message" }) {} } @Test fun assertTimeoutPreemptivelyForExecutableThatThrowsAnException() { val exception = assertThrows { assertTimeoutPreemptively(ofMillis(500)) { throw RuntimeException("not this time") } } assertMessageEquals(exception, "not this time") } @Test fun assertTimeoutPreemptivelyForExecutableThatThrowsAnAssertionFailedError() { val exception = assertThrows { assertTimeoutPreemptively(ofMillis(500)) { fail("enigma") } } assertMessageEquals(exception, "enigma") } @Test fun assertTimeoutPreemptivelyForExecutableThatCompletesAfterTheTimeout() { val error = assertThrows { assertTimeoutPreemptively(ofMillis(10)) { waitForInterrupt() } } assertMessageEquals(error, "execution timed out after 10 ms") } @Test fun assertTimeoutPreemptivelyWithMessageForExecutableThatCompletesAfterTheTimeout() { val error = assertThrows { assertTimeoutPreemptively(ofMillis(10), "Tempus Fugit") { waitForInterrupt() } } assertMessageEquals(error, "Tempus Fugit ==> execution timed out after 10 ms") } @Test fun assertTimeoutPreemptivelyWithMessageSupplierForExecutableThatCompletesAfterTheTimeout() { val error = assertThrows { assertTimeoutPreemptively(ofMillis(10), { "Tempus" + " " + "Fugit" }) { waitForInterrupt() } } assertMessageEquals(error, "Tempus Fugit ==> execution timed out after 10 ms") } @Test fun assertTimeoutPreemptivelyWithMessageSupplierForExecutableThatCompletesBeforeTheTimeout() { assertTimeoutPreemptively(ofMillis(500), { "Tempus" + " " + "Fugit" }) {} } // -- supplier - preemptively --- @Test fun assertTimeoutPreemptivelyForSupplierThatCompletesBeforeTheTimeout() { changed.get().set(false) val result = assertTimeoutPreemptively(ofMillis(500)) { changed.get().set(true) "Tempus Fugit" } assertFalse(changed.get().get(), "should have executed in a different thread") assertEquals("Tempus Fugit", result) assertEquals("Tempus Fugit", assertTimeoutPreemptively(ofMillis(500), "message") { "Tempus Fugit" }) assertEquals("Tempus Fugit", assertTimeoutPreemptively(ofMillis(500), { "message" }) { "Tempus Fugit" }) } @Test fun assertTimeoutPreemptivelyForSupplierThatThrowsAnException() { val exception = assertThrows { assertTimeoutPreemptively(ofMillis(500)) { ExceptionUtils.throwAsUncheckedException(RuntimeException("not this time")) } } assertMessageEquals(exception, "not this time") } @Test fun assertTimeoutPreemptivelyForSupplierThatThrowsAnAssertionFailedError() { val exception = assertThrows { assertTimeoutPreemptively(ofMillis(500)) { fail("enigma") } } assertMessageEquals(exception, "enigma") } @Test fun assertTimeoutPreemptivelyForSupplierThatCompletesAfterTheTimeout() { val error = assertThrows { assertTimeoutPreemptively(ofMillis(10)) { waitForInterrupt() } } assertMessageEquals(error, "execution timed out after 10 ms") } @Test fun assertTimeoutPreemptivelyWithMessageForSupplierThatCompletesAfterTheTimeout() { val error = assertThrows { assertTimeoutPreemptively(ofMillis(10), "Tempus Fugit") { waitForInterrupt() } } assertMessageEquals(error, "Tempus Fugit ==> execution timed out after 10 ms") } @Test fun assertTimeoutPreemptivelyWithMessageSupplierForSupplierThatCompletesAfterTheTimeout() { val error = assertThrows { assertTimeoutPreemptively(ofMillis(10), { "Tempus" + " " + "Fugit" }) { waitForInterrupt() } } assertMessageEquals(error, "Tempus Fugit ==> execution timed out after 10 ms") } @Test fun `assertTimeoutPreemptively with message supplier and value initialization in lambda`() { @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") 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. */ private fun nap() { val start = System.nanoTime() // workaround for imprecise clocks (yes, Windows, I'm talking about you) do { Thread.sleep(100) } while (System.nanoTime() - start < 100_000_000L) } private fun waitForInterrupt() { try { CountDownLatch(1).await() } catch (ignore: InterruptedException) { // ignore } } companion object { private val changed = ThreadLocal.withInitial { AtomicBoolean(false) } } } ================================================ FILE: jupiter-tests/src/test/kotlin/org/junit/jupiter/api/kotlin/KotlinAssertionsTests.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ 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 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.assertThrowsExactly import org.junit.jupiter.api.fail import org.opentest4j.AssertionFailedError import org.opentest4j.MultipleFailuresError import java.util.stream.Stream import kotlin.reflect.KClass /** * Unit tests for JUnit Jupiter [org.junit.jupiter.api] top-level assertion functions. */ class KotlinAssertionsTests { // Bonus: no null check tests as these get handled by the compiler! @Test fun `assertAll with functions that do not throw exceptions`() { assertAll(Stream.of({ assertTrue(true) }, { assertFalse(false) })) assertAll("heading", Stream.of({ assertTrue(true) }, { assertFalse(false) })) assertAll(setOf({ assertTrue(true) }, { assertFalse(false) })) assertAll("heading", setOf({ assertTrue(true) }, { assertFalse(false) })) assertAll({ assertTrue(true) }, { assertFalse(false) }) assertAll("heading", { assertTrue(true) }, { assertFalse(false) }) } @Test fun `assertAll with functions that throw AssertionErrors`() { val multipleFailuresError = assertThrows { assertAll( { assertFalse(true) }, { assertFalse(true) } ) } assertExpectedExceptionTypes(multipleFailuresError, AssertionFailedError::class, AssertionFailedError::class) } @Test fun `assertThrows and fail`() { assertThrows { fail("message") } assertThrows { fail("message", AssertionError()) } assertThrows { fail("message", null) } assertThrows("should fail") { fail({ "message" }) } assertThrows({ "should fail" }) { fail(AssertionError()) } assertThrows({ "should fail" }) { fail(null as Throwable?) } } @Test fun assertThrowsExactly() { assertThrowsExactly { fail("message") } assertThrowsExactly("should fail") { fail("message") } assertThrowsExactly({ "should fail" }) { fail("message") } } @Test fun `expected context exception testing`() = runBlocking { assertThrows("Should fail async") { suspend { fail("Should fail async") }() } } @TestFactory fun `assertDoesNotThrow behaves as expected`(): Stream = Stream.of( dynamicContainer( "succeeds when no exception thrown", Stream.of( dynamicTest("for no arguments variant") { val actual = assertDoesNotThrow { 1 } assertEquals(1, actual) }, dynamicTest("for no arguments variant (suspended)") { runBlocking { val actual = assertDoesNotThrow { suspend { 1 }() } assertEquals(1, actual) } }, dynamicTest("for message variant") { val actual = assertDoesNotThrow("message") { 2 } assertEquals(2, actual) }, dynamicTest("for message variant (suspended)") { runBlocking { val actual = assertDoesNotThrow("message") { suspend { 2 }() } assertEquals(2, actual) } }, dynamicTest("for message supplier variant") { val actual = assertDoesNotThrow({ "message" }) { 3 } assertEquals(3, actual) }, dynamicTest("for message supplier variant (suspended)") { runBlocking { val actual = assertDoesNotThrow({ "message" }) { suspend { 3 }() } assertEquals(3, actual) } } ) ), dynamicContainer( "fails when an exception is thrown", Stream.of( dynamicTest("for no arguments variant") { val exception = assertThrows { assertDoesNotThrow { fail("fail") } } assertMessageEquals( exception, "Unexpected exception thrown: org.opentest4j.AssertionFailedError: fail" ) }, dynamicTest("for no arguments variant (suspended)") { runBlocking { val exception = assertThrows { assertDoesNotThrow { suspend { fail("fail") }() } } assertMessageEquals( exception, "Unexpected exception thrown: org.opentest4j.AssertionFailedError: fail" ) } }, dynamicTest("for message variant") { val exception = assertThrows { assertDoesNotThrow("Does not throw") { fail("fail") } } assertMessageEquals( exception, "Does not throw ==> Unexpected exception thrown: org.opentest4j.AssertionFailedError: fail" ) }, dynamicTest("for message variant (suspended)") { runBlocking { val exception = assertThrows { assertDoesNotThrow("Does not throw") { suspend { fail("fail") }() } } assertMessageEquals( exception, "Does not throw ==> Unexpected exception thrown: org.opentest4j.AssertionFailedError: fail" ) } }, dynamicTest("for message supplier variant") { val exception = assertThrows { assertDoesNotThrow({ "Does not throw" }) { fail("fail") } } assertMessageEquals( exception, "Does not throw ==> Unexpected exception thrown: org.opentest4j.AssertionFailedError: fail" ) }, dynamicTest("for message supplier variant (suspended)") { runBlocking { val exception = assertThrows { assertDoesNotThrow({ "Does not throw" }) { suspend { fail("fail") }() } } assertMessageEquals( exception, "Does not throw ==> Unexpected exception thrown: org.opentest4j.AssertionFailedError: fail" ) } } ) ) ) @Test fun `assertAll with stream of functions that throw AssertionErrors`() { val multipleFailuresError = assertThrows("Should have thrown multiple errors") { assertAll(Stream.of({ assertFalse(true) }, { assertFalse(true) })) } assertExpectedExceptionTypes(multipleFailuresError, AssertionFailedError::class, AssertionFailedError::class) } @Test fun `assertAll with collection of functions that throw AssertionErrors`() { val multipleFailuresError = assertThrows("Should have thrown multiple errors") { assertAll(setOf({ assertFalse(true) }, { assertFalse(true) })) } assertExpectedExceptionTypes(multipleFailuresError, AssertionFailedError::class, AssertionFailedError::class) } @Test fun `assertThrows with function that does not throw an exception`() { val assertionMessage = "This will not throw an exception" val error = assertThrows("assertThrows did not throw the correct exception") { assertThrows(assertionMessage) { } // This should never execute: expectAssertionFailedError() } assertMessageStartsWith(error, assertionMessage) } @Test fun `assertInstanceOf succeeds`() { assertInstanceOf(listOf("whatever")) assertInstanceOf(listOf("whatever"), "No random access") assertInstanceOf(listOf("whatever")) { "No random access" } } @Test fun `assertInstanceOf fails wrong type value`() { val result = assertThrows { assertInstanceOf(StringBuilder(), "Should be a String") } assertMessageStartsWith(result, "Should be a String") } @Test fun `assertInstanceOf fails null value`() { val result = assertThrows { assertInstanceOf(null, "Should be a String") } assertMessageStartsWith(result, "Should be a String") } @Test fun `assertInstanceOf with compiler smart cast`() { val maybeString: Any = "string" assertInstanceOf(maybeString) assertFalse(maybeString.isEmpty()) // A smart cast to a String object. } @Test fun `assertInstanceOf with compiler nullable smart cast`() { val maybeString: Any? = "string" assertInstanceOf(maybeString) assertFalse(maybeString.isEmpty()) // A smart cast to a non-nullable String object. } @Test fun `assertInstanceOf with a null value`() { val error = assertThrows { assertInstanceOf(null) } assertMessageStartsWith(error, "Unexpected null value") } @Test 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" @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") val valueInMessageSupplier: Int assertInstanceOf(maybeString) { valueInMessageSupplier = 20 // Val can be assigned in the message supplier lambda. "maybeString is not an instance of String" } assertFalse(maybeString.isEmpty()) // A smart cast to a String object. } @Test fun `assertNull with compiler smart cast`() { val nullableString: String? = null assertNull(nullableString) // Even safe call is not allowed because compiler knows that nullableString is always null. // 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 @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") 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() } companion object { fun assertExpectedExceptionTypes( multipleFailuresError: MultipleFailuresError, vararg exceptionTypes: KClass ) = AssertionTestUtils.assertExpectedExceptionTypes( multipleFailuresError, *exceptionTypes.map { it.java }.toTypedArray() ) } } ================================================ FILE: jupiter-tests/src/test/kotlin/org/junit/jupiter/api/kotlin/KotlinDynamicTests.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ 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 import java.math.BigInteger as BigInt import java.math.RoundingMode as Rounding /** * Unit tests for JUnit Jupiter [org.junit.jupiter.api.TestFactory] use in kotlin classes. * * @since 5.12 */ class KotlinDynamicTests { @Nested inner class SequenceReturningTestFactoryTests { @TestFactory fun `Dynamic tests returned as Kotlin sequence`() = generateSequence(0) { it + 2 } .map { dynamicTest("$it should be even") { assertEquals(0, it % 2) } } .take(10) @TestFactory fun `Consecutive fibonacci nr ratios, should converge to golden ratio as n increases`(): Sequence { val scale = 5 val goldenRatio = (ONE + 5.toBigDecimal().sqrt(MathContext(scale + 10, Rounding.HALF_UP))) .divide(2.toBigDecimal(), scale, Rounding.HALF_UP) fun shouldApproximateGoldenRatio( cur: BigDecimal, next: BigDecimal ) = next.divide(cur, scale, Rounding.HALF_UP).let { dynamicTest("$cur / $next = $it should approximate the golden ratio in $scale decimals") { assertEquals(goldenRatio, it) } } return generateSequence(BigInt.ONE to BigInt.ONE) { (cur, next) -> next to cur + next } .map { (cur) -> cur.toBigDecimal() } .zipWithNext(::shouldApproximateGoldenRatio) .drop(14) .take(10) } } } ================================================ FILE: jupiter-tests/src/test/kotlin/org/junit/jupiter/api/kotlin/KotlinFailAssertionsTests.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.kotlin 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 class KotlinFailAssertionsTests { @Test fun `fail with string`() { val message = "test" val ex = assertThrows { fail(message) } assertMessageEquals(ex, message) } @Test fun `fail with message supplier`() { val message = "test" val ex = assertThrows { fail { message } } assertMessageEquals(ex, message) } @Test fun `fail with null string`() { val ex = assertThrows { fail(null as String?) } assertEmptyMessage(ex) } @Test fun `fail with null message supplier`() { val ex = assertThrows { fail(null as (() -> String)?) } assertEmptyMessage(ex) } @Test fun `fail with string and throwable`() { val message = "message" val throwableCause = "cause" val ex = assertThrows { fail(message, Throwable(throwableCause)) } assertMessageEquals(ex, message) val cause = ex.cause assertMessageContains(cause!!, throwableCause) } @Test fun `fail with throwable`() { val throwableCause = "cause" val ex = assertThrows { fail(Throwable(throwableCause)) } assertEmptyMessage(ex) val cause = ex.cause assertMessageContains(cause!!, throwableCause) } @Test fun `fail with string and null throwable`() { val message = "message" val ex = assertThrows { fail(message, null) } assertMessageEquals(ex, message) if (ex.cause != null) { throw AssertionError("Cause should have been null") } } @Test fun `fail with null string and throwable`() { val throwableCause = "cause" val ex = assertThrows { fail(null, Throwable(throwableCause)) } assertEmptyMessage(ex) val cause = ex.cause assertMessageContains(cause!!, throwableCause) } @Test fun `fail usable as a stream expression`() { val count = Stream .empty() .peek { _ -> fail("peek should never be called") } .filter { _ -> fail("filter should never be called", Throwable("cause")) } .map { _ -> fail(Throwable("map should never be called")) } .sorted { _, _ -> fail { "sorted should never be called" } } .count() assertEquals(0L, count) } @Test fun `fail usable as a sequence expression`() { val count = emptyList() .asSequence() .onEach { _ -> fail("peek should never be called") } .filter { _ -> fail("filter should never be called", Throwable("cause")) } .map { _ -> fail(Throwable("map should never be called")) } .count() assertEquals(0, count) } } ================================================ FILE: jupiter-tests/src/test/kotlin/org/junit/jupiter/api/kotlin/KotlinSuspendFunctionsTests.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ 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 import org.junit.jupiter.params.Parameter import org.junit.jupiter.params.ParameterizedClass import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.ValueSource import org.junit.platform.engine.reporting.ReportEntry import org.junit.platform.testkit.engine.EngineExecutionResults import java.util.stream.Stream class KotlinSuspendFunctionsTests : AbstractJupiterTestEngineTests() { @Test fun suspendingTestMethodsAreSupported() { val results = executeTestsForClass(TestMethodTestCase::class) assertAllTestsPassed(results, 1) assertThat(getPublishedEvents(results)).containsExactly("test") } @Test fun suspendingOpenTestMethodsAreSupported() { val results = executeTestsForClass(OpenTestMethodTestCase::class) assertAllTestsPassed(results, 1) assertThat(getPublishedEvents(results)).containsExactly("test") } @Test fun suspendingTestTemplateMethodsAreSupported() { val results = executeTestsForClass(TestTemplateTestCase::class) assertAllTestsPassed(results, 2) assertThat(getPublishedEvents(results)).containsExactly("foo", "bar") } @Test fun suspendingTestFactoryMethodsAreSupported() { val results = executeTestsForClass(TestFactoryTestCase::class) assertAllTestsPassed(results, 2) assertThat(getPublishedEvents(results)).containsExactly("test", "foo", "bar") } @Test fun suspendingLifecycleMethodsAreSupported() { val results = executeTestsForClass(LifecycleMethodsTestCase::class) assertAllTestsPassed(results, 1) assertThat(getPublishedEvents(results)).containsExactly("beforeAll", "beforeEach", "test", "afterEach", "afterAll") } @Test fun suspendingParameterizedLifecycleMethodsAreSupported() { val results = executeTestsForClass(ParameterizedLifecycleMethodsTestCase::class) assertAllTestsPassed(results, 2) assertThat( getPublishedEvents(results) ).containsExactly("beforeInvocation[1]", "test[1]", "afterInvocation[1]", "beforeInvocation[2]", "test[2]", "afterInvocation[2]") } private fun assertAllTestsPassed( results: EngineExecutionResults, numTests: Long ) { results.testEvents().assertStatistics { it.started(numTests).succeeded(numTests) } } private fun getPublishedEvents(results: EngineExecutionResults) = results .allEvents() .reportingEntryPublished() // .map { it.getRequiredPayload(ReportEntry::class.java) } // .map(ReportEntry::getKeyValuePairs) .map { it["value"] } @Suppress("JUnitMalformedDeclaration") class TestMethodTestCase { @Test suspend fun test(reporter: TestReporter) { suspendingPublish(reporter, "test") } } @Suppress("JUnitMalformedDeclaration") class TestTemplateTestCase { @ParameterizedTest @ValueSource(strings = ["foo", "bar"]) suspend fun test( message: String, reporter: TestReporter ) { suspendingPublish(reporter, message) } } class TestFactoryTestCase { @TestFactory suspend fun test(reporter: TestReporter): Stream { suspendingPublish(reporter, "test") return Stream.of("foo", "bar").map { dynamicTest(it) { runBlocking { suspendingPublish(reporter, it) } } } } } @Suppress("JUnitMalformedDeclaration") @TestInstance(PER_CLASS) class LifecycleMethodsTestCase { @BeforeAll suspend fun beforeAll(reporter: TestReporter) { suspendingPublish(reporter, "beforeAll") } @BeforeEach suspend fun beforeEach(reporter: TestReporter) { suspendingPublish(reporter, "beforeEach") } @Test suspend fun test(reporter: TestReporter) { suspendingPublish(reporter, "test") } @AfterEach suspend fun afterEach(reporter: TestReporter) { suspendingPublish(reporter, "afterEach") } @AfterAll suspend fun afterAll(reporter: TestReporter) { suspendingPublish(reporter, "afterAll") } } @Suppress("JUnitMalformedDeclaration", "RedundantSuspendModifier") @ParameterizedClass @ValueSource(ints = [1, 2]) @TestInstance(PER_CLASS) class ParameterizedLifecycleMethodsTestCase { @BeforeParameterizedClassInvocation suspend fun beforeInvocation() {} @BeforeParameterizedClassInvocation suspend fun beforeInvocation( parameter: Int, reporter: TestReporter ) { suspendingPublish(reporter, "beforeInvocation[$parameter]") } @AfterParameterizedClassInvocation suspend fun afterInvocation() {} @AfterParameterizedClassInvocation suspend fun afterInvocation( parameter: Int, reporter: TestReporter ) { suspendingPublish(reporter, "afterInvocation[$parameter]") } @Parameter var parameter: Int = 0 @Test suspend fun test(reporter: TestReporter) { suspendingPublish(reporter, "test[$parameter]") } } @Suppress("JUnitMalformedDeclaration") open class OpenTestMethodTestCase { @Test // https://github.com/junit-team/junit-framework/issues/5102 open suspend fun `check getRandomPositiveInt`(reporter: TestReporter) { suspendingPublish(reporter, "test") } } @Suppress("RedundantSuspendModifier") companion object { suspend fun suspendingPublish( reporter: TestReporter, message: String ) { reporter.publishEntry(message) } } } ================================================ FILE: jupiter-tests/src/test/kotlin/org/junit/jupiter/api/kotlin/PrimitiveWrapperInlineValueClassTests.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.api.kotlin import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Disabled import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource /** * Tests for primitive-wrapper inline value classes. * * Currently disabled: These fail because Kotlin compiles them to primitives * (UInt→int, UserId→long), causing JUnit's type validation to fail before * reaching the invocation logic. * * Supporting these would require modifications to JUnit's core type validation system. * * @see Issue #5081 */ @Disabled("Primitive-wrapper inline value classes are not yet supported") class PrimitiveWrapperInlineValueClassTests { @MethodSource("uintProvider") @ParameterizedTest fun testUInt(value: UInt) { assertEquals(42u, value) } @MethodSource("userIdProvider") @ParameterizedTest fun testUserId(userId: UserId) { assertEquals(123L, userId.value) } @MethodSource("emailProvider") @ParameterizedTest fun testEmail(email: Email) { assertEquals("test@example.com", email.value) } companion object { @JvmStatic fun uintProvider() = listOf(Arguments.of(42u)) @JvmStatic fun userIdProvider() = listOf(Arguments.of(UserId(123L))) @JvmStatic fun emailProvider() = listOf(Arguments.of(Email("test@example.com"))) } } @JvmInline value class UserId( val value: Long ) @JvmInline value class Email( val value: String ) ================================================ FILE: jupiter-tests/src/test/kotlin/org/junit/jupiter/engine/kotlin/ArbitraryNamingKotlinTestCase.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.kotlin import org.junit.jupiter.api.Test class ArbitraryNamingKotlinTestCase { companion object { @JvmField val METHOD_NAME = "\uD83E\uDD86 ~|~test with a really, (really) terrible name & that needs to be changed!~|~" } @Suppress("DANGEROUS_CHARACTERS") @Test fun `🦆 ~|~test with a really, (really) terrible name & that needs to be changed!~|~`() { } @Test fun `test name ends with parentheses()`() { } } ================================================ FILE: jupiter-tests/src/test/kotlin/org/junit/jupiter/engine/kotlin/InstancePerClassKotlinTestCase.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.kotlin 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.Test import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS @TestInstance(PER_CLASS) class InstancePerClassKotlinTestCase { companion object { @JvmField val TEST_INSTANCES: MutableMap> = HashMap() } @BeforeAll fun beforeAll() { increment("beforeAll") } @BeforeEach fun beforeEach() { increment("beforeEach") } @AfterEach fun afterEach() { increment("afterEach") } @AfterAll fun afterAll() { increment("afterAll") } @Test fun firstTest() { increment("test") } @Test fun secondTest() { increment("test") } private fun increment(name: String) { TEST_INSTANCES .computeIfAbsent(this, { _ -> HashMap() }) .compute(name, { _, oldValue -> (oldValue ?: 0) + 1 }) } } ================================================ FILE: jupiter-tests/src/test/kotlin/org/junit/jupiter/engine/kotlin/InstancePerMethodKotlinTestCase.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.kotlin 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.Test class InstancePerMethodKotlinTestCase { companion object { @JvmField val TEST_INSTANCES: MutableMap> = LinkedHashMap() @JvmStatic @BeforeAll fun beforeAll() { increment(this, "beforeAll") } @JvmStatic @AfterAll fun afterAll() { increment(this, "afterAll") } private fun increment( instance: Any, name: String ) { TEST_INSTANCES .computeIfAbsent(instance, { _ -> LinkedHashMap() }) .compute(name, { _, oldValue -> (oldValue ?: 0) + 1 }) } } @BeforeEach fun beforeEach() { increment(this, "beforeEach") } @AfterEach fun afterEach() { increment(this, "afterEach") } @Test fun firstTest() { increment(this, "test") } @Test fun secondTest() { increment(this, "test") } } ================================================ FILE: jupiter-tests/src/test/kotlin/org/junit/jupiter/engine/kotlin/KotlinDefaultImplsTestCase.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.kotlin import org.junit.jupiter.api.Test class KotlinDefaultImplsTestCase { class DefaultImpls { @Test fun test() { } } } ================================================ FILE: jupiter-tests/src/test/kotlin/org/junit/jupiter/engine/kotlin/KotlinInterfaceImplementationTestCase.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.kotlin class KotlinInterfaceImplementationTestCase : KotlinInterfaceTestCase ================================================ FILE: jupiter-tests/src/test/kotlin/org/junit/jupiter/engine/kotlin/KotlinInterfaceTestCase.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.engine.kotlin import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInfo interface KotlinInterfaceTestCase { @Test fun test(testInfo: TestInfo) { } } ================================================ FILE: jupiter-tests/src/test/kotlin/org/junit/jupiter/params/ParameterizedClassKotlinIntegrationTests.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInfo import org.junit.jupiter.params.provider.ValueSource import org.junit.platform.engine.discovery.DiscoverySelectors.selectClass import org.junit.platform.testkit.engine.EngineTestKit class ParameterizedClassKotlinIntegrationTests { @Test fun supportsDataClasses() { val results = EngineTestKit .engine("junit-jupiter") .selectors(selectClass(TestCase::class.java)) .execute() results.containerEvents().assertStatistics { it.started(4).succeeded(4) } results.testEvents().assertStatistics { it.started(4).succeeded(2).failed(2) } } @ParameterizedClass @ValueSource(ints = [-1, 1]) data class TestCase( val value: Int, val testInfo: TestInfo ) { @Test fun test1() { assertEquals("test1()", testInfo.displayName) assertTrue(value < 0, "negative") } @Test fun test2() { assertEquals("test2()", testInfo.displayName) assertTrue(value < 0, "negative") } } } ================================================ FILE: jupiter-tests/src/test/kotlin/org/junit/jupiter/params/ParameterizedInvocationNameFormatterIntegrationTests.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.TestInfo import org.junit.jupiter.params.provider.ValueSource class ParameterizedInvocationNameFormatterIntegrationTests { @ValueSource(strings = ["foo", "bar"]) @ParameterizedTest fun defaultDisplayName( param: String, info: TestInfo ) { if (param.equals("foo")) { assertEquals("[1] param = \"foo\"", info.displayName) } else { assertEquals("[2] param = \"bar\"", info.displayName) } } @ValueSource(strings = ["foo", "bar"]) @ParameterizedTest(name = "{0}") fun `1st argument`( param: String, info: TestInfo ) { if (param.equals("foo")) { assertEquals("\"foo\"", info.displayName) } else { assertEquals("\"bar\"", info.displayName) } } @ValueSource(strings = ["foo", "bar"]) @ParameterizedTest(name = "{displayName}") fun `it's an {enigma} '{0}'`( @Suppress("UNUSED_PARAMETER") param: String, info: TestInfo ) { assertEquals("it's an {enigma} '{0}'(String, TestInfo)", info.displayName) } @ValueSource(strings = ["foo", "bar"]) @ParameterizedTest(name = "{displayName} - {0}") fun `displayName and 1st 'argument'`( param: String, info: TestInfo ) { if (param.equals("foo")) { assertEquals("displayName and 1st 'argument'(String, TestInfo) - \"foo\"", info.displayName) } else { assertEquals("displayName and 1st 'argument'(String, TestInfo) - \"bar\"", info.displayName) } } @ValueSource(strings = ["foo", "bar"]) @ParameterizedTest(name = "{0} - {displayName}", quoteTextArguments = false) fun `1st 'argument' and displayName`( param: String, info: TestInfo ) { if (param.equals("foo")) { assertEquals("foo - 1st 'argument' and displayName(String, TestInfo)", info.displayName) } else { assertEquals("bar - 1st 'argument' and displayName(String, TestInfo)", info.displayName) } } } ================================================ FILE: jupiter-tests/src/test/kotlin/org/junit/jupiter/params/ParameterizedTestKotlinSequenceIntegrationTests.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.params.provider.Arguments.arguments import org.junit.jupiter.params.provider.FieldSource import org.junit.jupiter.params.provider.MethodSource import java.time.Month /** * Tests for Kotlin compatibility of ParameterizedTest */ object ParameterizedTestKotlinSequenceIntegrationTests { @ParameterizedTest @MethodSource("dataProvidedByKotlinSequenceMethod") fun `a method source can be supplied by a Sequence-returning method`( value: Int, month: Month ) { assertEquals(value, month.value) } @JvmStatic private fun dataProvidedByKotlinSequenceMethod() = dataProvidedByKotlinSequenceField @JvmStatic val dataProvidedByKotlinSequenceField = sequenceOf( arguments(1, Month.JANUARY), arguments(3, Month.MARCH), arguments(8, Month.AUGUST), arguments(5, Month.MAY), arguments(12, Month.DECEMBER) ) @ParameterizedTest @FieldSource("dataProvidedByKotlinSequenceField") fun `a field source can be supplied by a Sequence-typed field`( value: Int, month: Month ) { assertEquals(value, month.value) } } ================================================ FILE: jupiter-tests/src/test/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessorKotlinTests.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.aggregator import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.extension.ExtensionContext import org.mockito.Mockito.mock /** * Unit tests for using [ArgumentsAccessor] from Kotlin. */ class ArgumentsAccessorKotlinTests { @Test fun `get() with reified type and index`() { assertEquals(1, defaultArgumentsAccessor(1, 1).get(0)) assertEquals('A', defaultArgumentsAccessor(1, 'A').get(0)) } @Test fun `get() with reified type and index for incompatible type`() { val exception = assertThrows { defaultArgumentsAccessor(1, Integer.valueOf(1)).get(0) } assertThat(exception).hasMessage( "Argument at index [0] with value [1] and type [java.lang.Integer] could not be converted or cast to type [java.lang.Character]." ) } @Test fun `get() with index`() { assertEquals(1, defaultArgumentsAccessor(1, 1).get(0)) assertEquals('A', defaultArgumentsAccessor(1, 'A').get(0)) } @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") @Test fun `get() with index and class reference`() { assertEquals(1, defaultArgumentsAccessor(1, 1).get(0, Integer::class.java)) assertEquals('A', defaultArgumentsAccessor(1, 'A').get(0, Character::class.java)) } fun defaultArgumentsAccessor( invocationIndex: Int, vararg arguments: Any ): DefaultArgumentsAccessor { val context = mock(ExtensionContext::class.java) val classLoader = ArgumentsAccessorKotlinTests::class.java.classLoader return DefaultArgumentsAccessor.create(invocationIndex, classLoader, arguments) } fun foo() { } } ================================================ FILE: jupiter-tests/src/test/kotlin/org/junit/jupiter/params/aggregator/DisplayNameTests.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ @file:Suppress("unused") package org.junit.jupiter.params.aggregator import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.TestInfo import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.MethodSource // https://github.com/junit-team/junit-framework/issues/1836 object DisplayNameTests { @JvmStatic fun data() = arrayOf( arrayOf("A", 1), arrayOf("B", 2), arrayOf("C", 3), arrayOf("", 4), // empty is okay arrayOf(null, 5) // null was the problem ) @ParameterizedTest @MethodSource("data") fun test( str: String?, number: Int, info: TestInfo ) { if (str == null) { assertEquals("[$number] str = null, number = $number", info.displayName) } else { assertEquals("[$number] str = \"$str\", number = $number", info.displayName) } } } ================================================ FILE: jupiter-tests/src/test/kotlin/org/junit/jupiter/params/converter/TypedArgumentConverterKotlinTests.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.params.converter import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertNull import org.junit.jupiter.api.extension.ParameterContext import org.mockito.Mockito.mock import org.mockito.Mockito.`when` class TypedArgumentConverterKotlinTests { @Test fun converts() { val parameterContext = mock(ParameterContext::class.java) val parameter = this.javaClass.getDeclaredMethod("foo", String::class.java).parameters[0] `when`(parameterContext.parameter).thenReturn(parameter) assertNull(NullableTypeConverter().convert(null, parameterContext)) assertEquals("null", NonNullableTypeConverter().convert(null, parameterContext)) } @Suppress("unused") private fun foo(param: String) = Unit class NullableTypeConverter : TypedArgumentConverter(String::class.java, String::class.java) { override fun convert(source: String?) = source } class NonNullableTypeConverter : TypedArgumentConverter(String::class.java, String::class.java) { override fun convert(source: String?) = source.toString() } } ================================================ FILE: jupiter-tests/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension ================================================ org.junit.jupiter.engine.extension.ServiceLoaderExtension org.junit.jupiter.engine.extension.ConfigLoaderExtension ================================================ FILE: jupiter-tests/src/test/resources/junit-platform.properties ================================================ junit.jupiter.extensions.autodetection.enabled=true junit.platform.output.capture.stdout=true junit.platform.output.capture.stderr=true ================================================ FILE: jupiter-tests/src/test/resources/log4j2-test.xml ================================================ ================================================ FILE: jupiter-tests/src/test/resources/org/junit/jupiter/params/provider/broken.csv ================================================ # more than 512 columns x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x, ================================================ FILE: jupiter-tests/src/test/resources/org/junit/jupiter/params/provider/default-max-chars.csv ================================================ "41,6,8469,0,22,6336,9177,null,3,15,31,5734,6509,8770,9379,2,4,10,20,28,39,1490,5775,6410,6986,8493,8823,9298,9975,1,null,null,5,8,12,16,21,26,29,34,40,506,4491,5737,6034,6384,6434,6853,8178,8492,8503,8779,9106,9283,9339,9774,9982,null,null,null,null,7,9,11,13,null,17,null,null,23,27,null,30,32,35,null,null,176,837,3317,5715,5735,5743,5944,6310,6338,6398,6425,6436,6802,6917,7473,8397,8472,null,8494,8711,8776,8782,9009,9153,9210,9289,9336,9371,9400,9953,9980,9993,null,null,null,null,null,null,null,14,null,18,null,25,null,null,null,null,null,33,null,36,53,316,739,1409,3011,3924,4845,5730,null,5736,5741,5754,5807,5968,6056,6329,6337,6345,6387,6400,6420,6428,6435,6467,6575,6814,6911,6970,7111,7761,8283,8461,8470,8473,null,8496,8674,8722,8773,8778,8780,8803,8924,9078,9111,9172,9185,9270,9285,9296,9307,9337,9369,9376,9391,9633,9933,9967,9979,9981,9989,9994,null,null,null,19,24,null,null,null,null,37,46,140,199,505,669,814,1281,1430,1976,3096,3627,3934,4646,5455,5719,5733,null,null,5740,5742,5752,5758,5790,5828,5948,5974,6051,6278,6326,6331,null,null,6339,6365,6385,6388,6399,6405,6417,6424,6426,6433,null,null,6441,6492,6572,6739,6808,6845,6867,6915,6931,6985,7086,7463,7758,7797,8179,8349,8421,8467,null,8471,null,8476,8495,8497,8576,8708,8719,8761,8771,8774,8777,null,null,8781,8793,8806,8882,8937,9054,9084,9107,9134,9166,9175,9181,9187,9255,9282,9284,9287,9292,9297,9305,9319,null,9338,9342,9370,9375,9378,9380,9396,9546,9713,9798,9946,9956,9973,9978,null,null,null,9986,9991,null,9999,null,null,null,null,null,38,45,49,111,175,196,203,352,null,631,673,799,832,1056,1366,1415,1480,1819,2471,3016,3223,3532,3770,3931,4018,4592,4835,5233,5477,5718,5728,5732,null,5739,null,null,null,5745,5753,5756,5766,5785,5803,5823,5875,5946,5950,5971,5994,6043,6052,6124,6298,6317,6328,6330,6332,null,6341,6348,6369,null,6386,null,6396,null,null,6404,6408,6415,6419,6422,null,null,6427,6430,null,6440,6458,6471,6503,6520,6574,6721,6750,6807,6810,6843,6849,6863,6893,6912,6916,6919,6947,6983,null,6993,7088,7124,7466,7703,7759,7787,7962,null,8195,8331,8362,8411,8453,8462,8468,null,null,8474,8482,null,null,null,8500,8573,8619,8682,8710,8712,8721,8755,8767,null,8772,null,8775,null,null,null,null,8787,8795,8805,8819,8862,8886,8929,8978,9014,9076,9080,9087,null,9110,9112,9139,9163,9170,9173,9176,9178,9183,9186,9188,9240,9267,9278,null,null,null,9286,9288,9290,9293,null,null,9303,9306,9312,9326,null,null,9340,9366,null,null,9374,null,9377,null,null,9383,9393,9397,9493,9601,9652,9726,9789,9905,9942,9947,9954,9959,9969,9974,9976,null,9984,9988,9990,9992,9997,null,null,null,44,null,48,52,92,125,156,null,183,197,202,235,325,366,629,656,670,712,746,802,816,835,1026,1212,1293,1403,1414,1420,1471,1483,1604,1903,2432,2870,3014,3062,3219,3311,3336,3565,3730,3918,3927,3933,3975,4330,4546,4627,4709,4840,5158,5399,5456,5697,5716,null,5727,5729,5731,null,5738,null,5744,5746,null,null,5755,5757,5762,5774,5778,5786,5795,5805,5822,5824,5848,5895,5945,5947,5949,5961,5969,5973,5976,6014,6042,6049,null,6053,6117,6139,6290,6308,6314,6320,6327,null,null,null,null,6335,6340,6344,6346,6354,6366,6383,null,null,6390,6397,6401,null,6406,6409,6413,6416,6418,null,6421,6423,null,null,6429,6431,6438,null,6443,6462,6470,6472,6497,6506,6512,6528,6573,null,6648,6733,6748,6767,6805,null,6809,6813,6828,6844,6847,6852,6854,6864,6875,6910,null,6914,null,null,6918,6922,6944,6968,6976,6984,6991,7047,7087,7091,7112,7260,7464,7467,7656,7740,null,7760,7774,7795,7876,8110,8187,8202,8315,8341,8359,8391,8410,8414,8448,8455,null,8463,null,null,null,8475,8478,8483,8498,8502,8531,8575,8600,8624,8681,8688,8709,null,null,8716,8720,null,8735,8760,8766,8768,null,null,null,null,8784,8791,8794,8797,8804,null,8815,8820,8825,8867,8884,8887,8927,8936,8974,8986,9013,9041,9067,9077,9079,9081,9086,9093,9109,null,null,9119,9138,9144,9159,9164,9167,9171,null,9174,null,null,null,9179,9182,9184,null,null,null,9189,9223,9247,9258,9269,9275,9279,null,null,null,null,null,9291,null,9295,9301,9304,null,null,9310,9315,9324,9330,null,9341,9358,9367,9372,null,null,null,9382,9386,9392,9395,null,9398,9488,9509,9547,9616,9643,9702,94,122" ================================================ FILE: jupiter-tests/src/test/resources/org/junit/jupiter/params/provider/exceeds-default-max-chars.csv ================================================ "41,6,8469,0,22,6336,9177,null,3,15,31,5734,6509,8770,9379,2,4,10,20,28,39,1490,5775,6410,6986,8493,8823,9298,9975,1,null,null,5,8,12,16,21,26,29,34,40,506,4491,5737,6034,6384,6434,6853,8178,8492,8503,8779,9106,9283,9339,9774,9982,null,null,null,null,7,9,11,13,null,17,null,null,23,27,null,30,32,35,null,null,176,837,3317,5715,5735,5743,5944,6310,6338,6398,6425,6436,6802,6917,7473,8397,8472,null,8494,8711,8776,8782,9009,9153,9210,9289,9336,9371,9400,9953,9980,9993,null,null,null,null,null,null,null,14,null,18,null,25,null,null,null,null,null,33,null,36,53,316,739,1409,3011,3924,4845,5730,null,5736,5741,5754,5807,5968,6056,6329,6337,6345,6387,6400,6420,6428,6435,6467,6575,6814,6911,6970,7111,7761,8283,8461,8470,8473,null,8496,8674,8722,8773,8778,8780,8803,8924,9078,9111,9172,9185,9270,9285,9296,9307,9337,9369,9376,9391,9633,9933,9967,9979,9981,9989,9994,null,null,null,19,24,null,null,null,null,37,46,140,199,505,669,814,1281,1430,1976,3096,3627,3934,4646,5455,5719,5733,null,null,5740,5742,5752,5758,5790,5828,5948,5974,6051,6278,6326,6331,null,null,6339,6365,6385,6388,6399,6405,6417,6424,6426,6433,null,null,6441,6492,6572,6739,6808,6845,6867,6915,6931,6985,7086,7463,7758,7797,8179,8349,8421,8467,null,8471,null,8476,8495,8497,8576,8708,8719,8761,8771,8774,8777,null,null,8781,8793,8806,8882,8937,9054,9084,9107,9134,9166,9175,9181,9187,9255,9282,9284,9287,9292,9297,9305,9319,null,9338,9342,9370,9375,9378,9380,9396,9546,9713,9798,9946,9956,9973,9978,null,null,null,9986,9991,null,9999,null,null,null,null,null,38,45,49,111,175,196,203,352,null,631,673,799,832,1056,1366,1415,1480,1819,2471,3016,3223,3532,3770,3931,4018,4592,4835,5233,5477,5718,5728,5732,null,5739,null,null,null,5745,5753,5756,5766,5785,5803,5823,5875,5946,5950,5971,5994,6043,6052,6124,6298,6317,6328,6330,6332,null,6341,6348,6369,null,6386,null,6396,null,null,6404,6408,6415,6419,6422,null,null,6427,6430,null,6440,6458,6471,6503,6520,6574,6721,6750,6807,6810,6843,6849,6863,6893,6912,6916,6919,6947,6983,null,6993,7088,7124,7466,7703,7759,7787,7962,null,8195,8331,8362,8411,8453,8462,8468,null,null,8474,8482,null,null,null,8500,8573,8619,8682,8710,8712,8721,8755,8767,null,8772,null,8775,null,null,null,null,8787,8795,8805,8819,8862,8886,8929,8978,9014,9076,9080,9087,null,9110,9112,9139,9163,9170,9173,9176,9178,9183,9186,9188,9240,9267,9278,null,null,null,9286,9288,9290,9293,null,null,9303,9306,9312,9326,null,null,9340,9366,null,null,9374,null,9377,null,null,9383,9393,9397,9493,9601,9652,9726,9789,9905,9942,9947,9954,9959,9969,9974,9976,null,9984,9988,9990,9992,9997,null,null,null,44,null,48,52,92,125,156,null,183,197,202,235,325,366,629,656,670,712,746,802,816,835,1026,1212,1293,1403,1414,1420,1471,1483,1604,1903,2432,2870,3014,3062,3219,3311,3336,3565,3730,3918,3927,3933,3975,4330,4546,4627,4709,4840,5158,5399,5456,5697,5716,null,5727,5729,5731,null,5738,null,5744,5746,null,null,5755,5757,5762,5774,5778,5786,5795,5805,5822,5824,5848,5895,5945,5947,5949,5961,5969,5973,5976,6014,6042,6049,null,6053,6117,6139,6290,6308,6314,6320,6327,null,null,null,null,6335,6340,6344,6346,6354,6366,6383,null,null,6390,6397,6401,null,6406,6409,6413,6416,6418,null,6421,6423,null,null,6429,6431,6438,null,6443,6462,6470,6472,6497,6506,6512,6528,6573,null,6648,6733,6748,6767,6805,null,6809,6813,6828,6844,6847,6852,6854,6864,6875,6910,null,6914,null,null,6918,6922,6944,6968,6976,6984,6991,7047,7087,7091,7112,7260,7464,7467,7656,7740,null,7760,7774,7795,7876,8110,8187,8202,8315,8341,8359,8391,8410,8414,8448,8455,null,8463,null,null,null,8475,8478,8483,8498,8502,8531,8575,8600,8624,8681,8688,8709,null,null,8716,8720,null,8735,8760,8766,8768,null,null,null,null,8784,8791,8794,8797,8804,null,8815,8820,8825,8867,8884,8887,8927,8936,8974,8986,9013,9041,9067,9077,9079,9081,9086,9093,9109,null,null,9119,9138,9144,9159,9164,9167,9171,null,9174,null,null,null,9179,9182,9184,null,null,null,9189,9223,9247,9258,9269,9275,9279,null,null,null,null,null,9291,null,9295,9301,9304,null,null,9310,9315,9324,9330,null,9341,9358,9367,9372,null,null,null,9382,9386,9392,9395,null,9398,9488,9509,9547,9616,9643,9702,94,1223" ================================================ FILE: jupiter-tests/src/test/resources/org/junit/jupiter/params/provider/leading-trailing-spaces.csv ================================================ ab , cd ef ,gh ================================================ FILE: jupiter-tests/src/test/resources/org/junit/jupiter/params/provider/single-column.csv ================================================ foo bar baz qux "" ================================================ FILE: jupiter-tests/src/test/resources/org/junit/jupiter/params/two-column-with-headers.csv ================================================ #--------------------------------- FRUIT | RANK #--------------------------------- apple | 1 #--------------------------------- banana | 2 #--------------------------------- cherry | 3.14159265358979323846 #--------------------------------- | 0 #--------------------------------- NIL | 0 #--------------------------------- ================================================ FILE: jupiter-tests/src/test/resources/org/junit/jupiter/params/two-column.csv ================================================ foo,1 bar,2 baz,3 qux,4 ================================================ FILE: platform-tests/platform-tests.gradle.kts ================================================ import junitbuild.extensions.capitalized import org.gradle.api.tasks.PathSensitivity.RELATIVE import org.gradle.internal.os.OperatingSystem import org.gradle.plugins.ide.eclipse.model.Classpath import org.gradle.plugins.ide.eclipse.model.SourceFolder plugins { id("junitbuild.kotlin-library-conventions") id("junitbuild.junit4-compatibility") id("junitbuild.testing-conventions") id("junitbuild.jmh-conventions") } val processStarter by sourceSets.creating { java { srcDir("src/processStarter/java") } } java { registerFeature(processStarter.name) { usingSourceSet(processStarter) } } val woodstox = configurations.dependencyScope("woodstox") val woodstoxRuntimeClasspath = configurations.resolvable("woodstoxRuntimeClasspath") { extendsFrom(configurations.testRuntimeClasspath.get()) extendsFrom(woodstox.get()) } dependencies { // --- Things we are testing -------------------------------------------------- testImplementation(projects.junitPlatformCommons) testImplementation(projects.junitPlatformConsole) testImplementation(projects.junitPlatformEngine) testImplementation(projects.junitPlatformLauncher) testImplementation(projects.junitPlatformSuiteEngine) // --- Things we are testing with --------------------------------------------- testImplementation(projects.junitPlatformTestkit) testImplementation(testFixtures(projects.junitPlatformCommons)) testImplementation(testFixtures(projects.junitPlatformEngine)) testImplementation(testFixtures(projects.junitPlatformLauncher)) testImplementation(projects.junitJupiterEngine) testImplementation(testFixtures(projects.junitJupiterEngine)) testImplementation(testFixtures(projects.junitJupiterParams)) testImplementation(libs.apiguardian) testImplementation(libs.classgraph) testImplementation(libs.jfrunit) { exclude(group = "org.junit.vintage") } testImplementation(libs.joox) testImplementation(libs.openTestReporting.tooling.core) testImplementation(libs.picocli) testImplementation(libs.bundles.xmlunit) testImplementation(kotlin("stdlib")) testImplementation(testFixtures(projects.junitJupiterApi)) testImplementation(testFixtures(projects.junitPlatformReporting)) testImplementation(projects.platformTests) { capabilities { requireFeature("process-starter") } } // --- Test run-time dependencies --------------------------------------------- val mavenizedProjects: List by rootProject mavenizedProjects.filter { it.path != projects.junitPlatformConsoleStandalone.path }.forEach { // Add all projects to the classpath for tests using classpath scanning testRuntimeOnly(it) } testRuntimeOnly(libs.groovy) { because("`ReflectionUtilsTests.findNestedClassesWithInvalidNestedClassFile` needs it") } woodstox(libs.woodstox) // --- https://openjdk.java.net/projects/code-tools/jmh/ ---------------------- jmh(projects.junitJupiterApi) jmh(libs.junit4) // --- ProcessStarter dependencies -------------------------------------------- processStarter.implementationConfigurationName(libs.groovy) { because("it provides convenience methods to handle process output") } processStarter.implementationConfigurationName(libs.commons.io) { because("it uses TeeOutputStream") } processStarter.implementationConfigurationName(libs.opentest4j) { because("it throws TestAbortedException") } } jmh { duplicateClassesStrategy = DuplicatesStrategy.WARN fork = 1 warmupIterations = 1 iterations = 5 } tasks { withType().configureEach { useJUnitPlatform { excludeTags("exclude") } jvmArgs("-Xmx1g") develocity { testDistribution { // Retry in a new JVM on Windows to improve chances of successful retries when // cached resources are used (e.g. in ClasspathScannerTests) retryInSameJvm = !OperatingSystem.current().isWindows } } } test { // Additional inputs for remote execution with Test Distribution inputs.dir("src/test/resources").withPathSensitivity(RELATIVE) } test_4_12 { useJUnitPlatform { includeTags("junit4") } } val testWoodstox by registering(Test::class) { val test by testing.suites.existing(JvmTestSuite::class) testClassesDirs = files(test.map { it.sources.output.classesDirs }) classpath = files(sourceSets.main.map { it.output }) + files(test.map { it.sources.output }) + woodstoxRuntimeClasspath.get() group = JavaBasePlugin.VERIFICATION_GROUP setIncludes(listOf("**/org/junit/platform/reporting/**")) } check { dependsOn(testWoodstox) } named(processStarter.compileJavaTaskName).configure { options.release = javaLibrary.testJavaVersion.map { it.majorVersion.toInt() } } named("checkstyle${processStarter.name.capitalized()}").configure { config = resources.text.fromFile(checkstyle.configDirectory.file("checkstyleMain.xml")) } } eclipse { classpath.file.whenMerged { this as Classpath entries.filterIsInstance().forEach { if (it.path == "src/test/resources") { // Exclude Foo.java and FooBar.java in the modules-2500 folder. it.excludes.add("**/Foo*.java") } } } } ================================================ FILE: platform-tests/src/jmh/java/org/junit/jupiter/jmh/AssertionBenchmarks.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.jupiter.jmh; import org.junit.Assert; import org.junit.jupiter.api.Assertions; import org.openjdk.jmh.annotations.Benchmark; /** * JMH benchmarks for assertions. * * @since 5.1 */ public class AssertionBenchmarks { @Benchmark public void junit4_assertTrue_boolean() { Assert.assertTrue(true); } @Benchmark public void junitJupiter_assertTrue_boolean() { Assertions.assertTrue(true); } @Benchmark public void junit4_assertTrue_String_boolean() { Assert.assertTrue("message", true); } @Benchmark public void junitJupiter_assertTrue_boolean_String() { Assertions.assertTrue(true, "message"); } @Benchmark public void junitJupiter_assertTrue_boolean_Supplier() { Assertions.assertTrue(true, () -> "message"); } @Benchmark public void junitJupiter_assertTrue_BooleanSupplier_String() { Assertions.assertTrue(() -> true, "message"); } @Benchmark public void junitJupiter_assertTrue_BooleanSupplier_Supplier() { Assertions.assertTrue(() -> true, () -> "message"); } } ================================================ FILE: platform-tests/src/processStarter/java/org/junit/platform/tests/process/OutputFiles.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.tests.process; import java.nio.file.Path; public record OutputFiles(Path stdOut, Path stdErr) { } ================================================ FILE: platform-tests/src/processStarter/java/org/junit/platform/tests/process/ProcessResult.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.tests.process; import java.util.List; public record ProcessResult(int exitCode, String stdOut, String stdErr) { public List stdOutLines() { return stdOut.lines().toList(); } public List stdErrLines() { return stdErr.lines().toList(); } } ================================================ FILE: platform-tests/src/processStarter/java/org/junit/platform/tests/process/ProcessStarter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.tests.process; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.io.UncheckedIOException; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.BiFunction; import java.util.stream.Stream; import org.apache.commons.io.output.TeeOutputStream; import org.codehaus.groovy.runtime.ProcessGroovyMethods; public class ProcessStarter { public static final Charset OUTPUT_ENCODING = Charset.forName(System.getProperty("native.encoding")); private Path executable; private Path workingDir; private final List arguments = new ArrayList<>(); private final Map environment = new LinkedHashMap<>(); private Optional outputFiles = Optional.empty(); public ProcessStarter executable(Path executable) { this.executable = executable; return this; } public ProcessStarter workingDir(Path workingDir) { this.workingDir = workingDir; return this; } public ProcessStarter addArguments(String... arguments) { this.arguments.addAll(List.of(arguments)); return this; } public ProcessStarter putEnvironment(String key, Path value) { return putEnvironment(key, value.toAbsolutePath().toString()); } public ProcessStarter putEnvironment(String key, String value) { environment.put(key, value); return this; } public ProcessStarter putEnvironment(Map values) { environment.putAll(values); return this; } public ProcessStarter redirectOutput(OutputFiles outputFiles) { this.outputFiles = Optional.of(outputFiles); return this; } public ProcessResult startAndWait() throws InterruptedException { return start().waitFor(); } public WatchedProcess start() { var command = Stream.concat(Stream.of(executable.toString()), arguments.stream()).toList(); try { var builder = new ProcessBuilder().command(command); if (workingDir != null) { builder.directory(workingDir.toFile()); } builder.environment().putAll(environment); var process = builder.start(); var out = forwardAndCaptureOutput(process, System.out, outputFiles.map(OutputFiles::stdOut), ProcessGroovyMethods::consumeProcessOutputStream); var err = forwardAndCaptureOutput(process, System.err, outputFiles.map(OutputFiles::stdErr), ProcessGroovyMethods::consumeProcessErrorStream); return new WatchedProcess(process, out, err); } catch (IOException e) { throw new UncheckedIOException("Failed to start process: " + command, e); } } private static WatchedOutput forwardAndCaptureOutput(Process process, PrintStream delegate, Optional outputFile, BiFunction captureAction) { var capturingStream = new ByteArrayOutputStream(); Optional fileStream = outputFile.map(path -> { try { return Files.newOutputStream(path); } catch (IOException e) { throw new UncheckedIOException("Failed to open output file: " + path, e); } }); var attachedStream = tee(delegate, fileStream.map(it -> tee(capturingStream, it)).orElse(capturingStream)); var thread = captureAction.apply(process, attachedStream); return new WatchedOutput(thread, capturingStream, fileStream); } private static OutputStream tee(OutputStream out, OutputStream branch) { return new TeeOutputStream(out, branch); } } ================================================ FILE: platform-tests/src/processStarter/java/org/junit/platform/tests/process/WatchedOutput.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.tests.process; import static org.junit.platform.tests.process.ProcessStarter.OUTPUT_ENCODING; import java.io.ByteArrayOutputStream; import java.io.OutputStream; import java.util.Optional; record WatchedOutput(Thread thread, ByteArrayOutputStream stream, Optional fileStream) { String streamAsString() { return stream.toString(OUTPUT_ENCODING); } } ================================================ FILE: platform-tests/src/processStarter/java/org/junit/platform/tests/process/WatchedProcess.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.tests.process; import java.io.IOException; import java.io.OutputStream; import java.util.Optional; public class WatchedProcess { private final Process process; private final WatchedOutput out; private final WatchedOutput err; WatchedProcess(Process process, WatchedOutput out, WatchedOutput err) { this.process = process; this.out = out; this.err = err; } ProcessResult waitFor() throws InterruptedException { try { int exitCode; try { try { exitCode = process.waitFor(); } catch (InterruptedException e) { process.destroyForcibly(); throw e; } } finally { try { out.thread().join(); } finally { err.thread().join(); } } return new ProcessResult(exitCode, out.streamAsString(), err.streamAsString()); } finally { process.destroyForcibly(); closeQuietly(out.fileStream()); closeQuietly(err.fileStream()); } } @SuppressWarnings("EmptyCatch") private static void closeQuietly(Optional fileStream) { if (fileStream.isEmpty()) { return; } try { fileStream.get().close(); } catch (IOException ignore) { } } } ================================================ FILE: platform-tests/src/processStarter/java/org/junit/platform/tests/process/package-info.java ================================================ /** * Utilities for working with processes. * * @since 1.12 */ package org.junit.platform.tests.process; ================================================ FILE: platform-tests/src/test/java/DefaultPackageTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; /** * Simple test case that is used to verify proper support for classpath scanning * within the default package. * * @since 1.0 */ @Disabled("Only used reflectively by other tests") class DefaultPackageTestCase { @Test void test() { // do nothing } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/JUnitPlatformTestSuite.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform; import org.junit.platform.suite.api.IncludeClassNamePatterns; import org.junit.platform.suite.api.IncludeEngines; import org.junit.platform.suite.api.SelectPackages; import org.junit.platform.suite.api.Suite; /** * Test suite for the JUnit Platform. * *

Logging Configuration

* *

In order for our log4j2 configuration to be used in an IDE, you must * set the following system property before running any tests — for * example, in Run Configurations in Eclipse. * *

 * -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager
 * 
* * @since 1.0 */ @Suite @SelectPackages("org.junit.platform") @IncludeClassNamePatterns(".*Tests?") @IncludeEngines("junit-jupiter") class JUnitPlatformTestSuite { } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/StackTracePruningTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertLinesMatch; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.testkit.engine.EngineExecutionResults; import org.junit.platform.testkit.engine.EngineTestKit; /** * Test cases for stacktrace pruning. * *

Note: the package {@code org.junit.platform} this class resides in is * chosen on purpose. If it was in {@code org.junit.platform.launcher} * stack traces would be fully pruned. * * @since 5.10 */ class StackTracePruningTests { @Test void shouldPruneStackTraceByDefault() { EngineExecutionResults results = EngineTestKit.engine("junit-jupiter") // .selectors(selectMethod(FailingTestTestCase.class, "failingAssertion")) // .execute(); List stackTrace = extractStackTrace(results); assertStackTraceDoesNotContain(stackTrace, "jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:"); } @Test void shouldPruneStackTraceWhenEnabled() { EngineExecutionResults results = EngineTestKit.engine("junit-jupiter") // .configurationParameter("junit.platform.stacktrace.pruning.enabled", "true") // .selectors(selectMethod(FailingTestTestCase.class, "failingAssertion")) // .execute(); List stackTrace = extractStackTrace(results); assertStackTraceDoesNotContain(stackTrace, "jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:"); } @Test void shouldNotPruneStackTraceWhenDisabled() { EngineExecutionResults results = EngineTestKit.engine("junit-jupiter") // .configurationParameter("junit.platform.stacktrace.pruning.enabled", "false") // .selectors(selectMethod(FailingTestTestCase.class, "failingAssertion")) // .execute(); List stackTrace = extractStackTrace(results); assertStackTraceMatch(stackTrace, """ \\Qorg.junit.jupiter.api.Assertions.fail(Assertions.java:\\E.+ >>>> \\Qorg.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:\\E.+ >>>> """); } @Test void shouldAlwaysKeepJupiterAssertionStackTraceElement() { EngineExecutionResults results = EngineTestKit.engine("junit-jupiter") // .configurationParameter("junit.platform.stacktrace.pruning.enabled", "true") // .selectors(selectMethod(FailingTestTestCase.class, "failingAssertion")) // .execute(); List stackTrace = extractStackTrace(results); assertStackTraceMatch(stackTrace, """ \\Qorg.junit.jupiter.api.Assertions.fail(Assertions.java:\\E.+ >>>> """); } @Test void shouldAlwaysKeepJupiterAssumptionStackTraceElement() { EngineExecutionResults results = EngineTestKit.engine("junit-jupiter") // .configurationParameter("junit.platform.stacktrace.pruning.enabled", "true") // .selectors(selectMethod(FailingTestTestCase.class, "failingAssumption")) // .execute(); List stackTrace = extractStackTrace(results); assertStackTraceMatch(stackTrace, """ >>>> \\Qorg.junit.jupiter.api.Assumptions.assumeTrue(Assumptions.java:\\E.+ >>>> """); } @Test void shouldKeepExactlyEverythingBetweenTestCallAndFirstAssertionCall() { EngineExecutionResults results = EngineTestKit.engine("junit-jupiter") // .configurationParameter("junit.platform.stacktrace.pruning.enabled", "true") // .selectors(selectMethod(FailingTestTestCase.class, "failingAssertion")) // .execute(); List stackTrace = extractStackTrace(results); assertStackTraceMatch(stackTrace, """ \\Qorg.junit.jupiter.api.Assertions.fail(Assertions.java:\\E.+ \\Qorg.junit.platform.StackTracePruningTests$FailingTestTestCase.failingAssertion(StackTracePruningTests.java:\\E.+ """); } @ParameterizedTest @ValueSource(strings = { "org.junit.platform.StackTracePruningTests$FailingBeforeEachTestCase", "org.junit.platform.StackTracePruningTests$FailingBeforeEachTestCase$NestedTestCase", "org.junit.platform.StackTracePruningTests$FailingBeforeEachTestCase$NestedTestCase$NestedNestedTestCase" }) void shouldKeepExactlyEverythingAfterLifecycleMethodCall(Class methodClass) { EngineExecutionResults results = EngineTestKit.engine("junit-jupiter") // .configurationParameter("junit.platform.stacktrace.pruning.enabled", "true") // .selectors(selectMethod(methodClass, "test")) // .execute(); List stackTrace = extractStackTrace(results); assertStackTraceMatch(stackTrace, """ \\Qorg.junit.jupiter.api.Assertions.fail(Assertions.java:\\E.+ \\Qorg.junit.platform.StackTracePruningTests$FailingBeforeEachTestCase.setUp(StackTracePruningTests.java:\\E.+ """); } @Test void shouldPruneStackTracesOfSuppressedExceptions() { EngineExecutionResults results = EngineTestKit.engine("junit-jupiter") // .configurationParameter("junit.platform.stacktrace.pruning.enabled", "true") // .selectors(selectMethod(FailingTestTestCase.class, "multipleFailingAssertions")) // .execute(); Throwable throwable = getThrowable(results); for (Throwable suppressed : throwable.getSuppressed()) { List stackTrace = Arrays.asList(suppressed.getStackTrace()); assertStackTraceDoesNotContain(stackTrace, "jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:"); } } private static List extractStackTrace(EngineExecutionResults results) { return Arrays.asList(getThrowable(results).getStackTrace()); } private static Throwable getThrowable(EngineExecutionResults results) { var failedTestEvent = results.testEvents().failed().list().getFirst(); var testResult = failedTestEvent.getRequiredPayload(TestExecutionResult.class); return testResult.getThrowable().orElseThrow(); } private static void assertStackTraceMatch(List stackTrace, String expectedLines) { List stackStraceAsLines = stackTrace.stream() // .map(StackTraceElement::toString) // .toList(); assertLinesMatch(expectedLines.lines().toList(), stackStraceAsLines); } private static void assertStackTraceDoesNotContain(List stackTrace, @SuppressWarnings("SameParameterValue") String element) { String stackStraceAsString = stackTrace.stream() // .map(StackTraceElement::toString) // .collect(Collectors.joining()); assertThat(stackStraceAsString).doesNotContain(element); } // ------------------------------------------------------------------- static class FailingTestTestCase { @Test void failingAssertion() { Assertions.fail(); } @Test void multipleFailingAssertions() { Assertions.assertAll(Assertions::fail, Assertions::fail); } @Test void failingAssumption() { Assumptions.assumeTrue(() -> { throw new RuntimeException(); }); } } static class FailingBeforeEachTestCase { @BeforeEach void setUp() { Assertions.fail(); } @Test void test() { } @Nested class NestedTestCase { @Test void test() { } @Nested class NestedNestedTestCase { @Test void test() { } } } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/TestEngineTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform; import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.Optional; import org.junit.jupiter.api.Test; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.UniqueId; /** * Test cases for default implementations of {@link org.junit.platform.engine.TestEngine}. * *

Note: the package {@code org.junit.platform} this class resides in is * chosen on purpose. If it was in {@code org.junit.platform.engine} the default * implementation will pick up values defined by the real Jupiter test engine. * * @since 1.1 */ class TestEngineTests { @Test void defaults() { TestEngine engine = new DefaultEngine(); assertEquals(Optional.empty(), engine.getGroupId()); assertEquals(Optional.empty(), engine.getArtifactId()); assertEquals(Optional.of("DEVELOPMENT"), engine.getVersion()); } private static class DefaultEngine implements TestEngine { @Override public String getId() { return getClass().getSimpleName(); } @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { throw new UnsupportedOperationException("discover"); } @Override public void execute(ExecutionRequest request) { throw new UnsupportedOperationException("execute"); } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/annotation/TestableAnnotationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.annotation; import static org.junit.jupiter.api.Assertions.assertNotNull; import org.junit.jupiter.api.Test; /** * Integration tests that indirectly verify that the {@link Testable @Testable} * annotation may be declared on any element type. * * @since 1.7 */ class TestableAnnotationTests { @Test void testMethod() { assertNotNull(new TestableEverywhere().toString()); } @Testable static class TestableEverywhere { @Testable final int field = 0; @Testable TestableEverywhere() { } @Testable void test(@Testable int parameter) { @Testable @SuppressWarnings("unused") var var = "var"; } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/function/TryTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.function; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.fail; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.Test; import org.junit.platform.commons.JUnitException; class TryTests { @Test void successfulTriesCanBeTransformed() throws Exception { var success = Try.success("foo"); assertThat(success.get()).isEqualTo("foo"); assertThat(success.getNonNull()).isEqualTo("foo"); assertThat(success.getOrThrow(RuntimeException::new)).isEqualTo("foo"); assertThat(success.getNonNullOrThrow(RuntimeException::new)).isEqualTo("foo"); assertThat(success.toOptional()).contains("foo"); assertThat(success.andThen(v -> { assertThat(v).isEqualTo("foo"); return Try.success("bar"); }).get()).isEqualTo("bar"); assertThat(success.andThenTry(v -> { assertThat(v).isEqualTo("foo"); return "bar"; }).get()).isEqualTo("bar"); assertThat(success.orElse(() -> fail("should not be called"))).isSameAs(success); assertThat(success.orElseTry(() -> fail("should not be called"))).isSameAs(success); var value = new AtomicReference(); assertThat(success.ifSuccess(value::set)).isSameAs(success); assertThat(value.get()).isEqualTo("foo"); assertThat(success.ifFailure(cause -> fail("should not be called"))).isSameAs(success); } @Test void failedTriesCanBeTransformed() throws Exception { var cause = new JUnitException("foo"); var failure = Try.failure(cause); assertThat(assertThrows(JUnitException.class, failure::get)).isSameAs(cause); assertThat(assertThrows(RuntimeException.class, () -> failure.getOrThrow(RuntimeException::new))).isInstanceOf( RuntimeException.class).hasCause(cause); assertThat(failure.toOptional()).isEmpty(); assertThat(failure.andThen(v -> fail("should not be called"))).isSameAs(failure); assertThat(failure.andThenTry(v -> fail("should not be called"))).isSameAs(failure); assertThat(failure.orElse(() -> Try.success("bar")).get()).isEqualTo("bar"); assertThat(failure.orElseTry(() -> "bar").get()).isEqualTo("bar"); assertThat(failure.ifSuccess(v -> fail("should not be called"))).isSameAs(failure); var exception = new AtomicReference(); assertThat(failure.ifFailure(exception::set)).isSameAs(failure); assertThat(exception.get()).isSameAs(cause); } @SuppressWarnings("DataFlowIssue") @Test void successfulTriesCanStoreNull() throws Exception { var success = Try.success(null); assertThat(success.get()).isNull(); assertThrows(JUnitException.class, success::getNonNull); assertThat(success.getOrThrow(RuntimeException::new)).isNull(); assertThrows(RuntimeException.class, () -> success.getNonNullOrThrow(RuntimeException::new)); assertThat(success.toOptional()).isEmpty(); } @Test void triesWithSameContentAreEqual() { var cause = new Exception(); Callable failingCallable = () -> { throw cause; }; var success = Try.call(() -> "foo"); assertThat(success).isEqualTo(success).hasSameHashCodeAs(success); assertThat(success).isEqualTo(Try.success("foo")); assertThat(success).isNotEqualTo(Try.failure(cause)); var failure = Try.call(failingCallable); assertThat(failure).isEqualTo(failure).hasSameHashCodeAs(failure); assertThat(failure).isNotEqualTo(Try.success("foo")); assertThat(failure).isEqualTo(Try.failure(cause)); } @SuppressWarnings("DataFlowIssue") @Test void methodPreconditionsAreChecked() { assertThrows(JUnitException.class, () -> Try.call(null)); var success = Try.success("foo"); assertThrows(JUnitException.class, () -> success.andThen(null)); assertThrows(JUnitException.class, () -> success.andThenTry(null)); assertThrows(JUnitException.class, () -> success.ifSuccess(null)); var failure = Try.failure(new Exception()); assertThrows(JUnitException.class, () -> failure.orElse(null)); assertThrows(JUnitException.class, () -> failure.orElseTry(null)); assertThrows(JUnitException.class, () -> failure.ifFailure(null)); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/support/AnnotationSupportTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.support; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullFor; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.List; import java.util.Optional; import org.jspecify.annotations.NullUnmarked; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.platform.commons.util.AnnotationUtils; import org.junit.platform.commons.util.ReflectionUtils; /** * @since 1.0 */ class AnnotationSupportTests { @SuppressWarnings("DataFlowIssue") @Test void isAnnotatedPreconditions() { var optional = Optional.of(Probe.class); assertPreconditionViolationNotNullFor("annotationType", () -> AnnotationSupport.isAnnotated(optional, null)); assertPreconditionViolationNotNullFor("annotationType", () -> AnnotationSupport.isAnnotated(Probe.class, null)); } @Test void isAnnotatedDelegates() { var element = Probe.class; var optional = Optional.of(element); assertEquals(AnnotationUtils.isAnnotated(optional, Tag.class), AnnotationSupport.isAnnotated(optional, Tag.class)); assertEquals(AnnotationUtils.isAnnotated(optional, Override.class), AnnotationSupport.isAnnotated(optional, Override.class)); assertEquals(AnnotationUtils.isAnnotated(element, Tag.class), AnnotationSupport.isAnnotated(element, Tag.class)); assertEquals(AnnotationUtils.isAnnotated(element, Override.class), AnnotationSupport.isAnnotated(element, Override.class)); } @SuppressWarnings("DataFlowIssue") @Test void findAnnotationOnElementPreconditions() { var optional = Optional.of(Probe.class); assertPreconditionViolationNotNullFor("annotationType", () -> AnnotationSupport.findAnnotation(optional, null)); assertPreconditionViolationNotNullFor("annotationType", () -> AnnotationSupport.findAnnotation(Probe.class, null)); } @Test void findAnnotationOnElementDelegates() { var element = Probe.class; var optional = Optional.of(element); assertEquals(AnnotationUtils.findAnnotation(optional, Tag.class), AnnotationSupport.findAnnotation(optional, Tag.class)); assertEquals(AnnotationUtils.findAnnotation(optional, Override.class), AnnotationSupport.findAnnotation(optional, Override.class)); assertEquals(AnnotationUtils.findAnnotation(element, Tag.class), AnnotationSupport.findAnnotation(element, Tag.class)); assertEquals(AnnotationUtils.findAnnotation(element, Override.class), AnnotationSupport.findAnnotation(element, Override.class)); } @SuppressWarnings({ "deprecation", "DataFlowIssue" }) @Test void findAnnotationOnClassWithSearchModePreconditions() { assertPreconditionViolationNotNullFor("annotationType", () -> AnnotationSupport.findAnnotation(Probe.class, null, SearchOption.INCLUDE_ENCLOSING_CLASSES)); assertPreconditionViolationNotNullFor("SearchOption", () -> AnnotationSupport.findAnnotation(Probe.class, Override.class, (SearchOption) null)); } @SuppressWarnings("DataFlowIssue") @Test void findAnnotationOnClassWithEnclosingInstanceTypesPreconditions() { assertPreconditionViolationNotNullFor("enclosingInstanceTypes", () -> AnnotationSupport.findAnnotation(Probe.class, Override.class, (List>) null)); } @SuppressWarnings("deprecation") @Test void findAnnotationOnClassWithSearchModeDelegates() { Class clazz = Probe.class; assertEquals(AnnotationUtils.findAnnotation(clazz, Tag.class, false), AnnotationSupport.findAnnotation(clazz, Tag.class, SearchOption.DEFAULT)); assertEquals(AnnotationUtils.findAnnotation(clazz, Tag.class, true), AnnotationSupport.findAnnotation(clazz, Tag.class, SearchOption.INCLUDE_ENCLOSING_CLASSES)); assertEquals(AnnotationUtils.findAnnotation(clazz, Override.class, false), AnnotationSupport.findAnnotation(clazz, Override.class, SearchOption.DEFAULT)); assertEquals(AnnotationUtils.findAnnotation(clazz, Override.class, true), AnnotationSupport.findAnnotation(clazz, Override.class, SearchOption.INCLUDE_ENCLOSING_CLASSES)); clazz = Probe.InnerClass.class; assertEquals(AnnotationUtils.findAnnotation(clazz, Tag.class, false), AnnotationSupport.findAnnotation(clazz, Tag.class, SearchOption.DEFAULT)); assertEquals(AnnotationUtils.findAnnotation(clazz, Tag.class, true), AnnotationSupport.findAnnotation(clazz, Tag.class, SearchOption.INCLUDE_ENCLOSING_CLASSES)); assertEquals(AnnotationUtils.findAnnotation(clazz, Override.class, false), AnnotationSupport.findAnnotation(clazz, Override.class, SearchOption.DEFAULT)); assertEquals(AnnotationUtils.findAnnotation(clazz, Override.class, true), AnnotationSupport.findAnnotation(clazz, Override.class, SearchOption.INCLUDE_ENCLOSING_CLASSES)); } @NullUnmarked @Test void findAnnotationOnClassWithEnclosingInstanceTypes() { assertThat(AnnotationSupport.findAnnotation(Probe.class, Tag.class, List.of())) // .contains(Probe.class.getDeclaredAnnotation(Tag.class)); assertThat(AnnotationSupport.findAnnotation(Probe.InnerClass.class, Tag.class, List.of())) // .isEmpty(); assertThat(AnnotationSupport.findAnnotation(Probe.InnerClass.class, Tag.class, List.of(Probe.class))) // .contains(Probe.class.getDeclaredAnnotation(Tag.class)); } @SuppressWarnings("DataFlowIssue") @Test void findPublicAnnotatedFieldsPreconditions() { assertPreconditionViolationNotNullFor("Class", () -> AnnotationSupport.findPublicAnnotatedFields(null, String.class, FieldMarker.class)); assertPreconditionViolationNotNullFor("fieldType", () -> AnnotationSupport.findPublicAnnotatedFields(Probe.class, null, FieldMarker.class)); assertPreconditionViolationNotNullFor("annotationType", () -> AnnotationSupport.findPublicAnnotatedFields(Probe.class, String.class, null)); } @Test void findPublicAnnotatedFieldsDelegates() { assertEquals(AnnotationUtils.findPublicAnnotatedFields(Probe.class, String.class, FieldMarker.class), AnnotationSupport.findPublicAnnotatedFields(Probe.class, String.class, FieldMarker.class)); assertEquals(AnnotationUtils.findPublicAnnotatedFields(Probe.class, Throwable.class, Override.class), AnnotationSupport.findPublicAnnotatedFields(Probe.class, Throwable.class, Override.class)); } @SuppressWarnings("DataFlowIssue") @Test void findAnnotatedMethodsPreconditions() { assertPreconditionViolationNotNullFor("Class", () -> AnnotationSupport.findAnnotatedMethods(null, Tag.class, HierarchyTraversalMode.TOP_DOWN)); assertPreconditionViolationNotNullFor("annotationType", () -> AnnotationSupport.findAnnotatedMethods(Probe.class, null, HierarchyTraversalMode.TOP_DOWN)); assertPreconditionViolationNotNullFor("HierarchyTraversalMode", () -> AnnotationSupport.findAnnotatedMethods(Probe.class, Tag.class, null)); } @Test void findAnnotatedMethodsDelegates() { assertEquals( AnnotationUtils.findAnnotatedMethods(Probe.class, Tag.class, ReflectionUtils.HierarchyTraversalMode.TOP_DOWN), AnnotationSupport.findAnnotatedMethods(Probe.class, Tag.class, HierarchyTraversalMode.TOP_DOWN)); assertEquals( AnnotationUtils.findAnnotatedMethods(Probe.class, Tag.class, ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP), AnnotationSupport.findAnnotatedMethods(Probe.class, Tag.class, HierarchyTraversalMode.BOTTOM_UP)); assertEquals( AnnotationUtils.findAnnotatedMethods(Probe.class, Override.class, ReflectionUtils.HierarchyTraversalMode.TOP_DOWN), AnnotationSupport.findAnnotatedMethods(Probe.class, Override.class, HierarchyTraversalMode.TOP_DOWN)); assertEquals( AnnotationUtils.findAnnotatedMethods(Probe.class, Override.class, ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP), AnnotationSupport.findAnnotatedMethods(Probe.class, Override.class, HierarchyTraversalMode.BOTTOM_UP)); } @Test void findRepeatableAnnotationsDelegates() throws Throwable { var bMethod = Probe.class.getDeclaredMethod("bMethod"); assertEquals(AnnotationUtils.findRepeatableAnnotations(bMethod, Tag.class), AnnotationSupport.findRepeatableAnnotations(bMethod, Tag.class)); var expected = assertPreconditionViolationFor( () -> AnnotationUtils.findRepeatableAnnotations(bMethod, Override.class)); var actual = assertPreconditionViolationFor( () -> AnnotationSupport.findRepeatableAnnotations(bMethod, Override.class)); assertSame(expected.getClass(), actual.getClass(), "expected same exception class"); assertEquals(expected.toString(), actual.toString(), "expected equal exception toString representation"); } @SuppressWarnings("DataFlowIssue") @Test void findRepeatableAnnotationsPreconditions() { assertPreconditionViolationNotNullFor("annotationType", () -> AnnotationSupport.findRepeatableAnnotations(Probe.class, null)); } @Test void findAnnotatedFieldsDelegates() { assertEquals(AnnotationUtils.findAnnotatedFields(Probe.class, FieldMarker.class, f -> true), AnnotationSupport.findAnnotatedFields(Probe.class, FieldMarker.class)); assertEquals(AnnotationUtils.findAnnotatedFields(Probe.class, Override.class, f -> true), AnnotationSupport.findAnnotatedFields(Probe.class, Override.class)); assertEquals( AnnotationUtils.findAnnotatedFields(Probe.class, FieldMarker.class, f -> true, ReflectionUtils.HierarchyTraversalMode.TOP_DOWN), AnnotationSupport.findAnnotatedFields(Probe.class, FieldMarker.class, f -> true, HierarchyTraversalMode.TOP_DOWN)); assertEquals( AnnotationUtils.findAnnotatedFields(Probe.class, Override.class, f -> true, ReflectionUtils.HierarchyTraversalMode.TOP_DOWN), AnnotationSupport.findAnnotatedFields(Probe.class, Override.class, f -> true, HierarchyTraversalMode.TOP_DOWN)); } @SuppressWarnings("DataFlowIssue") @Test void findAnnotatedFieldsPreconditions() { assertPreconditionViolationNotNullFor("Class", () -> AnnotationSupport.findAnnotatedFields(null, FieldMarker.class)); assertPreconditionViolationNotNullFor("annotationType", () -> AnnotationSupport.findAnnotatedFields(Probe.class, null)); assertPreconditionViolationNotNullFor("Class", () -> AnnotationSupport.findAnnotatedFields(null, Override.class, f -> true, HierarchyTraversalMode.TOP_DOWN)); assertPreconditionViolationNotNullFor("annotationType", () -> AnnotationSupport.findAnnotatedFields(Probe.class, null, f -> true, HierarchyTraversalMode.TOP_DOWN)); assertPreconditionViolationNotNullFor("HierarchyTraversalMode", () -> AnnotationSupport.findAnnotatedFields(Probe.class, Override.class, f -> true, null)); } @Test void findAnnotatedFieldValuesForNonStaticFields() { var instance = new Fields(); assertThat(AnnotationSupport.findAnnotatedFieldValues(instance, Tag.class)).isEmpty(); assertThat(AnnotationSupport.findAnnotatedFieldValues(instance, FieldMarker.class))// .containsExactlyInAnyOrder("i1", "i2"); } @Test void findAnnotatedFieldValuesForStaticFields() { assertThat(AnnotationSupport.findAnnotatedFieldValues(Fields.class, Tag.class)).isEmpty(); assertThat(AnnotationSupport.findAnnotatedFieldValues(Fields.class, FieldMarker.class))// .containsExactlyInAnyOrder("s1", "s2"); } @Test void findAnnotatedFieldValuesForNonStaticFieldsByType() { var instance = new Fields(); assertThat(AnnotationSupport.findAnnotatedFieldValues(instance, FieldMarker.class, Number.class)).isEmpty(); assertThat(AnnotationSupport.findAnnotatedFieldValues(instance, FieldMarker.class, String.class))// .containsExactlyInAnyOrder("i1", "i2"); } @Test void findAnnotatedFieldValuesForStaticFieldsByType() { assertThat(AnnotationSupport.findAnnotatedFieldValues(Fields.class, FieldMarker.class, Number.class)).isEmpty(); assertThat(AnnotationSupport.findAnnotatedFieldValues(Fields.class, FieldMarker.class, String.class))// .containsExactlyInAnyOrder("s1", "s2"); } @SuppressWarnings("DataFlowIssue") @Test void findAnnotatedFieldValuesPreconditions() { assertPreconditionViolationNotNullFor("instance", () -> AnnotationSupport.findAnnotatedFieldValues((Object) null, FieldMarker.class)); assertPreconditionViolationNotNullFor("annotationType", () -> AnnotationSupport.findAnnotatedFieldValues(this, null)); assertPreconditionViolationNotNullFor("Class", () -> AnnotationSupport.findAnnotatedFieldValues(null, FieldMarker.class)); assertPreconditionViolationNotNullFor("annotationType", () -> AnnotationSupport.findAnnotatedFieldValues(Probe.class, null)); assertPreconditionViolationNotNullFor("instance", () -> AnnotationSupport.findAnnotatedFieldValues((Object) null, FieldMarker.class, Number.class)); assertPreconditionViolationNotNullFor("annotationType", () -> AnnotationSupport.findAnnotatedFieldValues(this, null, Number.class)); assertPreconditionViolationNotNullFor("fieldType", () -> AnnotationSupport.findAnnotatedFieldValues(this, FieldMarker.class, null)); assertPreconditionViolationNotNullFor("Class", () -> AnnotationSupport.findAnnotatedFieldValues(null, FieldMarker.class, Number.class)); assertPreconditionViolationNotNullFor("annotationType", () -> AnnotationSupport.findAnnotatedFieldValues(Probe.class, null, Number.class)); assertPreconditionViolationNotNullFor("fieldType", () -> AnnotationSupport.findAnnotatedFieldValues(Probe.class, FieldMarker.class, null)); } // ------------------------------------------------------------------------- @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @interface FieldMarker { } @Tag("class-tag") static class Probe { @FieldMarker public static String publicAnnotatedStaticField = "static"; @FieldMarker public String publicAnnotatedInstanceField = "instance"; @Tag("method-tag") void aMethod() { } @Tag("method-tag-1") @Tag("method-tag-2") void bMethod() { } @SuppressWarnings("InnerClassMayBeStatic") class InnerClass { } } static class Fields { @FieldMarker static String staticField1 = "s1"; @FieldMarker static String staticField2 = "s2"; @FieldMarker String instanceField1 = "i1"; @FieldMarker String instanceField2 = "i2"; } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/support/ClassSupportTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.support; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullFor; import java.util.List; import java.util.function.Function; import org.junit.jupiter.api.Test; import org.junit.platform.commons.util.ClassUtils; /** * @since 1.1 */ class ClassSupportTests { @SuppressWarnings("DataFlowIssue") @Test void nullSafeToStringPreconditions() { Function, ? extends String> mapper = null; assertPreconditionViolationNotNullFor("Mapping function", () -> ClassSupport.nullSafeToString(mapper, String.class, List.class)); } @Test void nullSafeToStringDelegates() { assertEquals(ClassUtils.nullSafeToString(String.class, List.class), ClassSupport.nullSafeToString(String.class, List.class)); Function, String> classToStringMapper = aClass -> aClass.getSimpleName() + "-Test"; assertEquals(ClassUtils.nullSafeToString(classToStringMapper, String.class, List.class), ClassSupport.nullSafeToString(classToStringMapper, String.class, List.class)); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/support/ModifierSupportTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.support; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullFor; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.commons.util.ReflectionUtils; /** * Unit tests for {@link ModifierSupport}. * * @since 1.4 */ class ModifierSupportTests { @SuppressWarnings("DataFlowIssue") @Test void isPublicPreconditions() { assertPreconditionViolationNotNullFor("Class", () -> ModifierSupport.isPublic((Class) null)); assertPreconditionViolationNotNullFor("Member", () -> ModifierSupport.isPublic((Member) null)); } @Classes void isPublicDelegates(Class clazz) { assertEquals(ReflectionUtils.isPublic(clazz), ModifierSupport.isPublic(clazz)); } @Methods void isPublicDelegates(Method method) { assertEquals(ReflectionUtils.isPublic(method), ModifierSupport.isPublic(method)); } @SuppressWarnings("DataFlowIssue") @Test void isPrivatePreconditions() { assertPreconditionViolationNotNullFor("Class", () -> ModifierSupport.isPrivate((Class) null)); assertPreconditionViolationNotNullFor("Member", () -> ModifierSupport.isPrivate((Member) null)); } @Classes void isPrivateDelegates(Class clazz) { assertEquals(ReflectionUtils.isPrivate(clazz), ModifierSupport.isPrivate(clazz)); } @Methods void isPrivateDelegates(Method method) { assertEquals(ReflectionUtils.isPrivate(method), ModifierSupport.isPrivate(method)); } @SuppressWarnings("DataFlowIssue") @Test void isNotPrivatePreconditions() { assertPreconditionViolationNotNullFor("Class", () -> ModifierSupport.isNotPrivate((Class) null)); assertPreconditionViolationNotNullFor("Member", () -> ModifierSupport.isNotPrivate((Member) null)); } @Classes void isNotPrivateDelegates(Class clazz) { assertEquals(ReflectionUtils.isNotPrivate(clazz), ModifierSupport.isNotPrivate(clazz)); } @Methods void isNotPrivateDelegates(Method method) { assertEquals(ReflectionUtils.isNotPrivate(method), ModifierSupport.isNotPrivate(method)); } @SuppressWarnings("DataFlowIssue") @Test void isAbstractPreconditions() { assertPreconditionViolationNotNullFor("Class", () -> ModifierSupport.isAbstract((Class) null)); assertPreconditionViolationNotNullFor("Member", () -> ModifierSupport.isAbstract((Member) null)); } @Classes void isAbstractDelegates(Class clazz) { assertEquals(ReflectionUtils.isAbstract(clazz), ModifierSupport.isAbstract(clazz)); } @Methods void isAbstractDelegates(Method method) { assertEquals(ReflectionUtils.isAbstract(method), ModifierSupport.isAbstract(method)); } @SuppressWarnings("DataFlowIssue") @Test void isNotAbstractPreconditions() { assertPreconditionViolationNotNullFor("Class", () -> ModifierSupport.isNotAbstract((Class) null)); assertPreconditionViolationNotNullFor("Member", () -> ModifierSupport.isNotAbstract((Member) null)); } @Classes void isNotAbstractDelegates(Class clazz) { assertEquals(ReflectionUtils.isNotAbstract(clazz), ModifierSupport.isNotAbstract(clazz)); } @Methods void isNotAbstractDelegates(Method method) { assertEquals(ReflectionUtils.isNotAbstract(method), ModifierSupport.isNotAbstract(method)); } @SuppressWarnings("DataFlowIssue") @Test void isStaticPreconditions() { assertPreconditionViolationNotNullFor("Class", () -> ModifierSupport.isStatic((Class) null)); assertPreconditionViolationNotNullFor("Member", () -> ModifierSupport.isStatic((Member) null)); } @Classes void isStaticDelegates(Class clazz) { assertEquals(ReflectionUtils.isStatic(clazz), ModifierSupport.isStatic(clazz)); } @Methods void isStaticDelegates(Method method) { assertEquals(ReflectionUtils.isStatic(method), ModifierSupport.isStatic(method)); } @SuppressWarnings("DataFlowIssue") @Test void isNotStaticPreconditions() { assertPreconditionViolationNotNullFor("Class", () -> ModifierSupport.isNotStatic((Class) null)); assertPreconditionViolationNotNullFor("Member", () -> ModifierSupport.isNotStatic((Member) null)); } @Classes void isNotStaticDelegates(Class clazz) { assertEquals(ReflectionUtils.isNotStatic(clazz), ModifierSupport.isNotStatic(clazz)); } @Methods void isNotStaticDelegates(Method method) { assertEquals(ReflectionUtils.isNotStatic(method), ModifierSupport.isNotStatic(method)); } @SuppressWarnings("DataFlowIssue") @Test void isFinalPreconditions() { assertPreconditionViolationNotNullFor("Class", () -> ModifierSupport.isFinal((Class) null)); assertPreconditionViolationNotNullFor("Member", () -> ModifierSupport.isFinal((Member) null)); } @Classes void isFinalDelegates(Class clazz) { assertEquals(ReflectionUtils.isFinal(clazz), ModifierSupport.isFinal(clazz)); } @Methods void isFinalDelegates(Method method) { assertEquals(ReflectionUtils.isFinal(method), ModifierSupport.isFinal(method)); } @SuppressWarnings("DataFlowIssue") @Test void isNotFinalPreconditions() { assertPreconditionViolationNotNullFor("Class", () -> ModifierSupport.isNotFinal((Class) null)); assertPreconditionViolationNotNullFor("Member", () -> ModifierSupport.isNotFinal((Member) null)); } @Classes void isNotFinalDelegates(Class clazz) { assertEquals(ReflectionUtils.isNotFinal(clazz), ModifierSupport.isNotFinal(clazz)); } @Methods void isNotFinalDelegates(Method method) { assertEquals(ReflectionUtils.isNotFinal(method), ModifierSupport.isNotFinal(method)); } // ------------------------------------------------------------------------- // Intentionally non-static @SuppressWarnings("InnerClassMayBeStatic") public class PublicClass { public void publicMethod() { } } @SuppressWarnings("InnerClassMayBeStatic") private class PrivateClass { @SuppressWarnings("unused") private void privateMethod() { } } @SuppressWarnings("InnerClassMayBeStatic") protected class ProtectedClass { @SuppressWarnings("unused") protected void protectedMethod() { } } @SuppressWarnings("InnerClassMayBeStatic") class PackageVisibleClass { @SuppressWarnings("unused") void packageVisibleMethod() { } } abstract static class AbstractClass { abstract void abstractMethod(); } static class StaticClass { static void staticMethod() { } } @SuppressWarnings("InnerClassMayBeStatic") final class FinalClass { @SuppressWarnings({ "FinalMethodInFinalClass", "RedundantModifier" }) final void finalMethod() { } } // ------------------------------------------------------------------------- @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @ParameterizedTest @ValueSource(classes = { PublicClass.class, PrivateClass.class, ProtectedClass.class, PackageVisibleClass.class, AbstractClass.class, StaticClass.class, FinalClass.class }) @interface Classes { } @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @ParameterizedTest @MethodSource("methods") @interface Methods { } static Stream methods() throws Exception { // @formatter:off return Stream.of( PublicClass.class.getMethod("publicMethod"), PrivateClass.class.getDeclaredMethod("privateMethod"), ProtectedClass.class.getDeclaredMethod("protectedMethod"), PackageVisibleClass.class.getDeclaredMethod("packageVisibleMethod"), AbstractClass.class.getDeclaredMethod("abstractMethod"), StaticClass.class.getDeclaredMethod("staticMethod"), FinalClass.class.getDeclaredMethod("finalMethod") ); // @formatter:on } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/support/ReflectionSupportTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.support; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.platform.commons.support.ReflectionSupport.toSupportResourcesList; import static org.junit.platform.commons.support.ReflectionSupport.toSupportResourcesStream; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullFor; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullOrBlankFor; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullOrEmptyFor; import static org.junit.platform.commons.util.ClassLoaderUtils.getDefaultClassLoader; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.URI; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.function.Predicate; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; import org.junit.platform.commons.io.ResourceFilter; import org.junit.platform.commons.util.ReflectionUtils; /** * @since 1.0 */ class ReflectionSupportTests { private static final Predicate> allTypes = type -> true; @SuppressWarnings("removal") private static final Predicate allResources = __ -> true; private static final Predicate allNames = name -> true; private static final Predicate allMethods = name -> true; private static final Predicate allFields = name -> true; static final String staticField = "static"; final String instanceField = "instance"; @Test void tryToLoadClassDelegates() { assertEquals(ReflectionUtils.tryToLoadClass("-").toOptional(), ReflectionSupport.tryToLoadClass("-").toOptional()); assertEquals(ReflectionUtils.tryToLoadClass("A").toOptional(), ReflectionSupport.tryToLoadClass("A").toOptional()); assertEquals(ReflectionUtils.tryToLoadClass("java.nio.Bits"), ReflectionSupport.tryToLoadClass("java.nio.Bits")); } @SuppressWarnings("DataFlowIssue") @Test void tryToLoadClassPreconditions() { assertPreconditionViolationNotNullOrBlankFor("Class name", () -> ReflectionSupport.tryToLoadClass(null)); assertPreconditionViolationNotNullOrBlankFor("Class name", () -> ReflectionSupport.tryToLoadClass("")); } /** * @since 1.10 */ @Test void tryToLoadClassWithExplicitClassLoaderDelegates() { ClassLoader classLoader = getClass().getClassLoader(); assertEquals(ReflectionUtils.tryToLoadClass("-", classLoader).toOptional(), ReflectionSupport.tryToLoadClass("-", classLoader).toOptional()); assertEquals(ReflectionUtils.tryToLoadClass("A", classLoader).toOptional(), ReflectionSupport.tryToLoadClass("A", classLoader).toOptional()); assertEquals(ReflectionUtils.tryToLoadClass("java.nio.Bits", classLoader), ReflectionSupport.tryToLoadClass("java.nio.Bits", classLoader)); } /** * @since 1.10 */ @SuppressWarnings("DataFlowIssue") @Test void tryToLoadClassWithExplicitClassLoaderPreconditions() { var cl = getClass().getClassLoader(); assertPreconditionViolationNotNullOrBlankFor("Class name", () -> ReflectionSupport.tryToLoadClass(null, cl)); assertPreconditionViolationNotNullOrBlankFor("Class name", () -> ReflectionSupport.tryToLoadClass("", cl)); assertPreconditionViolationNotNullFor("ClassLoader", () -> ReflectionSupport.tryToLoadClass("int", null)); } @TestFactory List findAllClassesInClasspathRootDelegates() throws Throwable { List tests = new ArrayList<>(); List paths = new ArrayList<>(); paths.add(Path.of(".").toRealPath()); paths.addAll(ReflectionUtils.getAllClasspathRootDirectories()); for (var path : paths) { var root = path.toUri(); tests.add(DynamicTest.dynamicTest(createDisplayName(root), () -> assertEquals(ReflectionUtils.findAllClassesInClasspathRoot(root, allTypes, allNames), ReflectionSupport.findAllClassesInClasspathRoot(root, allTypes, allNames)))); } return tests; } /** * @since 1.12 */ @SuppressWarnings({ "DataFlowIssue", "removal" }) @Test void tryToGetResourcesPreconditions() { assertPreconditionViolationNotNullOrBlankFor("Resource name", () -> ReflectionSupport.tryToGetResources(null)); assertPreconditionViolationNotNullOrBlankFor("Resource name", () -> ReflectionSupport.tryToGetResources("")); assertPreconditionViolationNotNullFor("Class loader", () -> ReflectionSupport.tryToGetResources("default-package.resource", null)); assertPreconditionViolationNotNullFor("Class loader", () -> ReflectionSupport.tryToGetResources("default-package.resource", null)); } /** * @since 1.12 */ @SuppressWarnings("removal") @Test void tryToGetResources() { assertEquals( ReflectionUtils.tryToGetResources("default-package.resource").toOptional().map( ReflectionSupport::toSupportResourcesSet), ReflectionSupport.tryToGetResources("default-package.resource").toOptional()); assertEquals( ReflectionUtils.tryToGetResources("default-package.resource", getDefaultClassLoader()).toOptional().map( ReflectionSupport::toSupportResourcesSet), // ReflectionSupport.tryToGetResources("default-package.resource", getDefaultClassLoader()).toOptional()); } @SuppressWarnings("DataFlowIssue") @Test void findAllClassesInClasspathRootPreconditions() { var path = Path.of(".").toUri(); assertPreconditionViolationNotNullFor("root", () -> ReflectionSupport.findAllClassesInClasspathRoot(null, allTypes, allNames)); assertPreconditionViolationNotNullFor("class predicate", () -> ReflectionSupport.findAllClassesInClasspathRoot(path, null, allNames)); assertPreconditionViolationNotNullFor("name predicate", () -> ReflectionSupport.findAllClassesInClasspathRoot(path, allTypes, null)); } /** * @since 1.11 */ @SuppressWarnings("removal") @TestFactory List findAllResourcesInClasspathRootDelegates() throws Throwable { List tests = new ArrayList<>(); List paths = new ArrayList<>(); paths.add(Path.of(".").toRealPath()); paths.addAll(ReflectionUtils.getAllClasspathRootDirectories()); for (var path : paths) { var root = path.toUri(); tests.add(DynamicTest.dynamicTest(createDisplayName(root), () -> assertThat(toSupportResourcesList( ReflectionUtils.findAllResourcesInClasspathRoot(root, ResourceFilter.of(__ -> true)))) // .containsExactlyElementsOf( ReflectionSupport.findAllResourcesInClasspathRoot(root, allResources)))); } return tests; } /** * @since 1.11 */ @SuppressWarnings({ "DataFlowIssue", "removal" }) @Test void findAllResourcesInClasspathRootPreconditions() { var path = Path.of(".").toUri(); assertPreconditionViolationNotNullFor("root", () -> ReflectionSupport.findAllResourcesInClasspathRoot(null, allResources)); assertPreconditionViolationNotNullFor("resourceFilter", () -> ReflectionSupport.findAllResourcesInClasspathRoot(path, null)); } /** * @since 1.11 */ @SuppressWarnings("removal") @TestFactory List streamAllResourcesInClasspathRootDelegates() throws Throwable { List tests = new ArrayList<>(); List paths = new ArrayList<>(); paths.add(Path.of(".").toRealPath()); paths.addAll(ReflectionUtils.getAllClasspathRootDirectories()); for (var path : paths) { var root = path.toUri(); tests.add(DynamicTest.dynamicTest(createDisplayName(root), () -> assertThat(toSupportResourcesStream( ReflectionUtils.streamAllResourcesInClasspathRoot(root, ResourceFilter.of(__ -> true)))) // .containsExactlyElementsOf( ReflectionSupport.streamAllResourcesInClasspathRoot(root, allResources).toList()))); } return tests; } /** * @since 1.11 */ @SuppressWarnings({ "DataFlowIssue", "removal" }) @Test void streamAllResourcesInClasspathRootPreconditions() { var path = Path.of(".").toUri(); assertPreconditionViolationNotNullFor("root", () -> ReflectionSupport.streamAllResourcesInClasspathRoot(null, allResources)); assertPreconditionViolationNotNullFor("resourceFilter", () -> ReflectionSupport.streamAllResourcesInClasspathRoot(path, null)); } @Test void findAllClassesInPackageDelegates() { assertNotEquals(0, ReflectionSupport.findAllClassesInPackage("org.junit", allTypes, allNames).size()); assertEquals(ReflectionUtils.findAllClassesInPackage("org.junit", allTypes, allNames), ReflectionSupport.findAllClassesInPackage("org.junit", allTypes, allNames)); } @SuppressWarnings("DataFlowIssue") @Test void findAllClassesInPackagePreconditions() { assertPreconditionViolationNotNullOrBlankFor("basePackageName", () -> ReflectionSupport.findAllClassesInPackage(null, allTypes, allNames)); assertPreconditionViolationNotNullFor("class predicate", () -> ReflectionSupport.findAllClassesInPackage("org.junit", null, allNames)); assertPreconditionViolationNotNullFor("name predicate", () -> ReflectionSupport.findAllClassesInPackage("org.junit", allTypes, null)); } /** * @since 1.11 */ @SuppressWarnings("removal") @Test void findAllResourcesInPackageDelegates() { assertNotEquals(0, ReflectionSupport.findAllResourcesInPackage("org.junit", allResources).size()); assertEquals( toSupportResourcesList( ReflectionUtils.findAllResourcesInPackage("org.junit", ResourceFilter.of(__ -> true))), ReflectionSupport.findAllResourcesInPackage("org.junit", allResources)); } /** * @since 1.11 */ @SuppressWarnings({ "DataFlowIssue", "removal" }) @Test void findAllResourcesInPackagePreconditions() { assertPreconditionViolationNotNullOrBlankFor("basePackageName", () -> ReflectionSupport.findAllResourcesInPackage(null, allResources)); assertPreconditionViolationNotNullFor("resourceFilter", () -> ReflectionSupport.findAllResourcesInPackage("org.junit", null)); } /** * @since 1.11 */ @SuppressWarnings("removal") @Test void streamAllResourcesInPackageDelegates() { assertNotEquals(0, ReflectionSupport.streamAllResourcesInPackage("org.junit", allResources).count()); assertEquals( toSupportResourcesStream( ReflectionUtils.streamAllResourcesInPackage("org.junit", ResourceFilter.of(__ -> true))).toList(), ReflectionSupport.streamAllResourcesInPackage("org.junit", allResources).toList()); } /** * @since 1.11 */ @SuppressWarnings({ "DataFlowIssue", "removal" }) @Test void streamAllResourcesInPackagePreconditions() { assertPreconditionViolationNotNullOrBlankFor("basePackageName", () -> ReflectionSupport.streamAllResourcesInPackage(null, allResources)); assertPreconditionViolationNotNullFor("resourceFilter", () -> ReflectionSupport.streamAllResourcesInPackage("org.junit", null)); } @Test void findAllClassesInModuleDelegates() { assertEquals(ReflectionUtils.findAllClassesInModule("org.junit.platform.commons", allTypes, allNames), ReflectionSupport.findAllClassesInModule("org.junit.platform.commons", allTypes, allNames)); } @SuppressWarnings("DataFlowIssue") @Test void findAllClassesInModulePreconditions() { assertPreconditionViolationNotNullOrEmptyFor("Module name", () -> ReflectionSupport.findAllClassesInModule((String) null, allTypes, allNames)); assertPreconditionViolationNotNullFor("Module", () -> ReflectionSupport.findAllClassesInModule((Module) null, allTypes, allNames)); assertPreconditionViolationNotNullFor("class predicate", () -> ReflectionSupport.findAllClassesInModule("org.junit.platform.commons", null, allNames)); assertPreconditionViolationNotNullFor("name predicate", () -> ReflectionSupport.findAllClassesInModule("org.junit.platform.commons", allTypes, null)); } /** * @since 1.11 */ @SuppressWarnings("removal") @Test void findAllResourcesInModuleDelegates() { assertEquals( ReflectionUtils.findAllResourcesInModule("org.junit.platform.commons", ResourceFilter.of(__ -> true)), ReflectionSupport.findAllResourcesInModule("org.junit.platform.commons", allResources)); } /** * @since 1.11 */ @SuppressWarnings({ "DataFlowIssue", "removal" }) @Test void findAllResourcesInModulePreconditions() { assertPreconditionViolationNotNullOrEmptyFor("Module name", () -> ReflectionSupport.findAllResourcesInModule(null, allResources)); assertPreconditionViolationNotNullFor("resourceFilter", () -> ReflectionSupport.findAllResourcesInModule("org.junit.platform.commons", null)); } /** * @since 1.11 */ @SuppressWarnings("removal") @Test void streamAllResourcesInModuleDelegates() { assertEquals( toSupportResourcesStream(ReflectionUtils.streamAllResourcesInModule("org.junit.platform.commons", ResourceFilter.of(__ -> true))).toList(), ReflectionSupport.streamAllResourcesInModule("org.junit.platform.commons", allResources).toList()); } /** * @since 1.11 */ @SuppressWarnings({ "DataFlowIssue", "removal" }) @Test void streamAllResourcesInModulePreconditions() { assertPreconditionViolationNotNullOrEmptyFor("Module name", () -> ReflectionSupport.streamAllResourcesInModule(null, allResources)); assertPreconditionViolationNotNullFor("resourceFilter", () -> ReflectionSupport.streamAllResourcesInModule("org.junit.platform.commons", null)); } @Test void newInstanceDelegates() { assertEquals(ReflectionUtils.newInstance(String.class, "foo"), ReflectionSupport.newInstance(String.class, "foo")); } @SuppressWarnings("DataFlowIssue") @Test void newInstancePreconditions() { assertPreconditionViolationNotNullFor("Class", () -> ReflectionSupport.newInstance(null)); assertPreconditionViolationNotNullFor("Argument array", () -> ReflectionSupport.newInstance(String.class, (Object[]) null)); assertPreconditionViolationNotNullFor("Individual arguments", () -> ReflectionSupport.newInstance(String.class, new Object[] { null })); } @Test void invokeMethodDelegates() throws Exception { var method = Boolean.class.getMethod("valueOf", String.class); assertEquals(ReflectionUtils.invokeMethod(method, null, "true"), ReflectionSupport.invokeMethod(method, null, "true")); } @SuppressWarnings("DataFlowIssue") @Test void invokeMethodPreconditions() throws Exception { assertPreconditionViolationNotNullFor("Method", () -> ReflectionSupport.invokeMethod(null, null, "true")); var method = Boolean.class.getMethod("toString"); assertPreconditionViolationFor(() -> ReflectionSupport.invokeMethod(method, null))// .withMessage("Cannot invoke non-static method [" + method.toGenericString() + "] on a null target."); } @Test void findFieldsDelegates() { assertEquals( ReflectionUtils.findFields(ReflectionSupportTests.class, allFields, ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP), ReflectionSupport.findFields(ReflectionSupportTests.class, allFields, HierarchyTraversalMode.BOTTOM_UP)); assertEquals( ReflectionUtils.findFields(ReflectionSupportTests.class, allFields, ReflectionUtils.HierarchyTraversalMode.TOP_DOWN), ReflectionSupport.findFields(ReflectionSupportTests.class, allFields, HierarchyTraversalMode.TOP_DOWN)); } @SuppressWarnings("DataFlowIssue") @Test void findFieldsPreconditions() { assertPreconditionViolationNotNullFor("Class", () -> ReflectionSupport.findFields(null, allFields, HierarchyTraversalMode.BOTTOM_UP)); assertPreconditionViolationNotNullFor("Class", () -> ReflectionSupport.findFields(null, allFields, HierarchyTraversalMode.TOP_DOWN)); assertPreconditionViolationNotNullFor("Predicate", () -> ReflectionSupport.findFields(ReflectionSupportTests.class, null, HierarchyTraversalMode.BOTTOM_UP)); assertPreconditionViolationNotNullFor("Predicate", () -> ReflectionSupport.findFields(ReflectionSupportTests.class, null, HierarchyTraversalMode.TOP_DOWN)); assertPreconditionViolationNotNullFor("HierarchyTraversalMode", () -> ReflectionSupport.findFields(ReflectionSupportTests.class, allFields, null)); } @Test void tryToReadFieldValueDelegates() throws Exception { var staticField = getClass().getDeclaredField("staticField"); assertEquals(ReflectionUtils.tryToReadFieldValue(staticField, null), ReflectionSupport.tryToReadFieldValue(staticField, null)); var instanceField = getClass().getDeclaredField("instanceField"); assertEquals(ReflectionUtils.tryToReadFieldValue(instanceField, this), ReflectionSupport.tryToReadFieldValue(instanceField, this)); } @SuppressWarnings("DataFlowIssue") @Test void tryToReadFieldValuePreconditions() throws Exception { assertPreconditionViolationNotNullFor("Field", () -> ReflectionSupport.tryToReadFieldValue(null, this)); var instanceField = getClass().getDeclaredField("instanceField"); assertPreconditionViolationFor(() -> ReflectionSupport.tryToReadFieldValue(instanceField, null))// .withMessageStartingWith("Cannot read non-static field")// .withMessageEndingWith("on a null instance."); } @Test void findMethodDelegates() { assertEquals(ReflectionUtils.findMethod(Boolean.class, "valueOf", String.class.getName()), ReflectionSupport.findMethod(Boolean.class, "valueOf", String.class.getName())); assertEquals(ReflectionUtils.findMethod(Boolean.class, "valueOf", String.class), ReflectionSupport.findMethod(Boolean.class, "valueOf", String.class)); } @SuppressWarnings("DataFlowIssue") @Test void findMethodPreconditions() { assertPreconditionViolationNotNullFor("Class", () -> ReflectionSupport.findMethod(null, "valueOf", String.class.getName())); assertPreconditionViolationNotNullOrBlankFor("Method name", () -> ReflectionSupport.findMethod(Boolean.class, "", String.class.getName())); assertPreconditionViolationNotNullOrBlankFor("Method name", () -> ReflectionSupport.findMethod(Boolean.class, " ", String.class.getName())); assertPreconditionViolationNotNullFor("Class", () -> ReflectionSupport.findMethod(null, "valueOf", String.class)); assertPreconditionViolationNotNullOrBlankFor("Method name", () -> ReflectionSupport.findMethod(Boolean.class, "", String.class)); assertPreconditionViolationNotNullOrBlankFor("Method name", () -> ReflectionSupport.findMethod(Boolean.class, " ", String.class)); assertPreconditionViolationNotNullFor("Parameter types array", () -> ReflectionSupport.findMethod(Boolean.class, "valueOf", (Class[]) null)); assertPreconditionViolationNotNullFor("Individual parameter types", () -> ReflectionSupport.findMethod(Boolean.class, "valueOf", new Class[] { null })); } @Test void findMethodsDelegates() { assertEquals( ReflectionUtils.findMethods(ReflectionSupportTests.class, allMethods, ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP), ReflectionSupport.findMethods(ReflectionSupportTests.class, allMethods, HierarchyTraversalMode.BOTTOM_UP)); assertEquals( ReflectionUtils.findMethods(ReflectionSupportTests.class, allMethods, ReflectionUtils.HierarchyTraversalMode.TOP_DOWN), ReflectionSupport.findMethods(ReflectionSupportTests.class, allMethods, HierarchyTraversalMode.TOP_DOWN)); } @SuppressWarnings("DataFlowIssue") @Test void findMethodsPreconditions() { assertPreconditionViolationNotNullFor("Class", () -> ReflectionSupport.findMethods(null, allMethods, HierarchyTraversalMode.BOTTOM_UP)); assertPreconditionViolationNotNullFor("Class", () -> ReflectionSupport.findMethods(null, allMethods, HierarchyTraversalMode.TOP_DOWN)); assertPreconditionViolationNotNullFor("Predicate", () -> ReflectionSupport.findMethods(ReflectionSupportTests.class, null, HierarchyTraversalMode.BOTTOM_UP)); assertPreconditionViolationNotNullFor("Predicate", () -> ReflectionSupport.findMethods(ReflectionSupportTests.class, null, HierarchyTraversalMode.TOP_DOWN)); assertPreconditionViolationNotNullFor("HierarchyTraversalMode", () -> ReflectionSupport.findMethods(ReflectionSupportTests.class, allMethods, null)); } @Test void findNestedClassesDelegates() { assertEquals(ReflectionUtils.findNestedClasses(ClassWithNestedClasses.class, ReflectionUtils::isStatic), ReflectionSupport.findNestedClasses(ClassWithNestedClasses.class, ReflectionUtils::isStatic)); } @SuppressWarnings("DataFlowIssue") @Test void findNestedClassesPreconditions() { assertPreconditionViolationNotNullFor("Class", () -> ReflectionSupport.findNestedClasses(null, ReflectionUtils::isStatic)); assertPreconditionViolationNotNullFor("Predicate", () -> ReflectionSupport.findNestedClasses(ClassWithNestedClasses.class, null)); } private static String createDisplayName(URI root) { var displayName = root.getPath(); if (displayName.length() > 42) { displayName = "..." + displayName.substring(displayName.length() - 42); } return displayName; } static class ClassWithNestedClasses { @SuppressWarnings({ "InnerClassMayBeStatic", "unused" }) class Nested1 { } @SuppressWarnings("unused") static class Nested2 { } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/support/ResourceInteroperabilityTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.support; import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import java.net.URI; import org.junit.jupiter.api.Test; import org.junit.platform.commons.io.Resource; class ResourceInteroperabilityTests { @Test void newAndOldResourcesAreLogicallyEquivalent() { var oldResource = new DefaultResource("foo", URI.create("foo")); var newResource = Resource.of("foo", URI.create("foo")); var differentResource = Resource.of("foo", URI.create("bar")); assertEqualsAndHashCode(oldResource, newResource, differentResource); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/support/ResourceSupportTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.support; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullFor; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullOrBlankFor; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullOrEmptyFor; import static org.junit.platform.commons.util.ClassLoaderUtils.getDefaultClassLoader; import java.net.URI; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; import org.junit.platform.commons.io.ResourceFilter; import org.junit.platform.commons.util.ReflectionUtils; class ResourceSupportTests { private static final ResourceFilter allResources = ResourceFilter.of(__ -> true); /** * @since 1.12 */ @SuppressWarnings("DataFlowIssue") @Test void tryToGetResourcesPreconditions() { assertPreconditionViolationNotNullOrBlankFor("Resource name", () -> ResourceSupport.tryToGetResources(null)); assertPreconditionViolationNotNullOrBlankFor("Resource name", () -> ResourceSupport.tryToGetResources("")); assertPreconditionViolationNotNullFor("Class loader", () -> ResourceSupport.tryToGetResources("default-package.resource", null)); assertPreconditionViolationNotNullFor("Class loader", () -> ResourceSupport.tryToGetResources("default-package.resource", null)); } /** * @since 1.12 */ @Test void tryToGetResources() { assertEquals(ReflectionUtils.tryToGetResources("default-package.resource").toOptional(), ResourceSupport.tryToGetResources("default-package.resource").toOptional()); assertEquals( ReflectionUtils.tryToGetResources("default-package.resource", getDefaultClassLoader()).toOptional(), // ResourceSupport.tryToGetResources("default-package.resource", getDefaultClassLoader()).toOptional()); } /** * @since 1.11 */ @TestFactory List findAllResourcesInClasspathRootDelegates() throws Throwable { List tests = new ArrayList<>(); List paths = new ArrayList<>(); paths.add(Path.of(".").toRealPath()); paths.addAll(ReflectionUtils.getAllClasspathRootDirectories()); for (var path : paths) { var root = path.toUri(); tests.add(DynamicTest.dynamicTest(createDisplayName(root), () -> assertThat(ReflectionUtils.findAllResourcesInClasspathRoot(root, allResources)) // .containsExactlyElementsOf( ResourceSupport.findAllResourcesInClasspathRoot(root, allResources)))); } return tests; } /** * @since 1.11 */ @SuppressWarnings("DataFlowIssue") @Test void findAllResourcesInClasspathRootPreconditions() { var path = Path.of(".").toUri(); assertPreconditionViolationNotNullFor("root", () -> ResourceSupport.findAllResourcesInClasspathRoot(null, allResources)); assertPreconditionViolationNotNullFor("resourceFilter", () -> ResourceSupport.findAllResourcesInClasspathRoot(path, null)); } /** * @since 1.11 */ @TestFactory List streamAllResourcesInClasspathRootDelegates() throws Throwable { List tests = new ArrayList<>(); List paths = new ArrayList<>(); paths.add(Path.of(".").toRealPath()); paths.addAll(ReflectionUtils.getAllClasspathRootDirectories()); for (var path : paths) { var root = path.toUri(); tests.add(DynamicTest.dynamicTest(createDisplayName(root), () -> assertThat(ReflectionUtils.streamAllResourcesInClasspathRoot(root, allResources)) // .containsExactlyElementsOf( ResourceSupport.streamAllResourcesInClasspathRoot(root, allResources).toList()))); } return tests; } /** * @since 1.11 */ @SuppressWarnings("DataFlowIssue") @Test void streamAllResourcesInClasspathRootPreconditions() { var path = Path.of(".").toUri(); assertPreconditionViolationNotNullFor("root", () -> ResourceSupport.streamAllResourcesInClasspathRoot(null, allResources)); assertPreconditionViolationNotNullFor("resourceFilter", () -> ResourceSupport.streamAllResourcesInClasspathRoot(path, null)); } /** * @since 1.11 */ @Test void findAllResourcesInPackageDelegates() { assertNotEquals(0, ResourceSupport.findAllResourcesInPackage("org.junit", allResources).size()); assertEquals(ReflectionUtils.findAllResourcesInPackage("org.junit", allResources), ResourceSupport.findAllResourcesInPackage("org.junit", allResources)); } /** * @since 1.11 */ @SuppressWarnings("DataFlowIssue") @Test void findAllResourcesInPackagePreconditions() { assertPreconditionViolationNotNullOrBlankFor("basePackageName", () -> ResourceSupport.findAllResourcesInPackage(null, allResources)); assertPreconditionViolationNotNullFor("resourceFilter", () -> ResourceSupport.findAllResourcesInPackage("org.junit", null)); } /** * @since 1.11 */ @Test void streamAllResourcesInPackageDelegates() { assertNotEquals(0, ResourceSupport.streamAllResourcesInPackage("org.junit", allResources).count()); assertEquals(ReflectionUtils.streamAllResourcesInPackage("org.junit", allResources).toList(), ResourceSupport.streamAllResourcesInPackage("org.junit", allResources).toList()); } /** * @since 1.11 */ @SuppressWarnings("DataFlowIssue") @Test void streamAllResourcesInPackagePreconditions() { assertPreconditionViolationNotNullOrBlankFor("basePackageName", () -> ResourceSupport.streamAllResourcesInPackage(null, allResources)); assertPreconditionViolationNotNullFor("resourceFilter", () -> ResourceSupport.streamAllResourcesInPackage("org.junit", null)); } /** * @since 1.11 */ @Test void findAllResourcesInModuleDelegates() { assertEquals(ReflectionUtils.findAllResourcesInModule("org.junit.platform.commons", allResources), ResourceSupport.findAllResourcesInModule("org.junit.platform.commons", allResources)); } /** * @since 1.11 */ @SuppressWarnings("DataFlowIssue") @Test void findAllResourcesInModulePreconditions() { assertPreconditionViolationNotNullOrEmptyFor("Module name", () -> ResourceSupport.findAllResourcesInModule((String) null, allResources)); assertPreconditionViolationNotNullFor("Module", () -> ResourceSupport.findAllResourcesInModule((Module) null, allResources)); assertPreconditionViolationNotNullFor("Resource filter", () -> ResourceSupport.findAllResourcesInModule("org.junit.platform.commons", null)); } /** * @since 1.11 */ @Test void streamAllResourcesInModuleDelegates() { assertEquals(ReflectionUtils.streamAllResourcesInModule("org.junit.platform.commons", allResources).toList(), ResourceSupport.streamAllResourcesInModule("org.junit.platform.commons", allResources).toList()); } /** * @since 1.11 */ @SuppressWarnings("DataFlowIssue") @Test void streamAllResourcesInModulePreconditions() { assertPreconditionViolationNotNullOrEmptyFor("Module name", () -> ResourceSupport.streamAllResourcesInModule(null, allResources)); assertPreconditionViolationNotNullFor("Resource filter", () -> ResourceSupport.streamAllResourcesInModule("org.junit.platform.commons", null)); } private static String createDisplayName(URI root) { var displayName = root.getPath(); if (displayName.length() > 42) { displayName = "..." + displayName.substring(displayName.length() - 42); } return displayName; } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/support/conversion/ConversionSupportTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.support.conversion; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import java.io.File; import java.lang.reflect.Method; import java.math.BigDecimal; import java.math.BigInteger; import java.net.URI; import java.net.URL; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.time.Duration; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.MonthDay; import java.time.OffsetDateTime; import java.time.OffsetTime; import java.time.Period; import java.time.Year; import java.time.YearMonth; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.Currency; import java.util.Locale; import java.util.UUID; import java.util.concurrent.TimeUnit; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.test.TestClassLoader; import org.junit.platform.commons.util.ClassLoaderUtils; /** * Unit tests for {@link ConversionSupport}. * * @since 5.12 */ class ConversionSupportTests { @Test void isAwareOfNull() { assertConverts(null, Object.class, null); assertConverts(null, String.class, null); assertConverts(null, Boolean.class, null); } @Test void convertsStringsToPrimitiveTypes() { assertConverts("true", boolean.class, true); assertConverts("false", boolean.class, false); assertConverts("o", char.class, 'o'); assertConverts("1", byte.class, (byte) 1); assertConverts("1_0", byte.class, (byte) 10); assertConverts("1", short.class, (short) 1); assertConverts("1_2", short.class, (short) 12); assertConverts("42", int.class, 42); assertConverts("700_050_000", int.class, 700_050_000); assertConverts("42", long.class, 42L); assertConverts("4_2", long.class, 42L); assertConverts("42.23", float.class, 42.23f); assertConverts("42.2_3", float.class, 42.23f); assertConverts("42.23", double.class, 42.23); assertConverts("42.2_3", double.class, 42.23); } @Test void convertsStringsToPrimitiveWrapperTypes() { assertConverts("true", Boolean.class, true); assertConverts("false", Boolean.class, false); assertConverts("o", Character.class, 'o'); assertConverts("1", Byte.class, (byte) 1); assertConverts("1_0", Byte.class, (byte) 10); assertConverts("1", Short.class, (short) 1); assertConverts("1_2", Short.class, (short) 12); assertConverts("42", Integer.class, 42); assertConverts("700_050_000", Integer.class, 700_050_000); assertConverts("42", Long.class, 42L); assertConverts("4_2", Long.class, 42L); assertConverts("42.23", Float.class, 42.23f); assertConverts("42.2_3", Float.class, 42.23f); assertConverts("42.23", Double.class, 42.23); assertConverts("42.2_3", Double.class, 42.23); } @ParameterizedTest(name = "[{index}] {0}") @ValueSource(classes = { char.class, boolean.class, short.class, byte.class, int.class, long.class, float.class, double.class, void.class }) void throwsExceptionForNullToPrimitiveTypeConversion(Class type) { assertThatExceptionOfType(ConversionException.class) // .isThrownBy(() -> convert(null, type)) // .withMessage("Cannot convert null to primitive value of type " + type.getCanonicalName()); } @ParameterizedTest(name = "[{index}] {0}") @ValueSource(classes = { Boolean.class, Character.class, Short.class, Byte.class, Integer.class, Long.class, Float.class, Double.class }) void throwsExceptionWhenConvertingTheWordNullToPrimitiveWrapperType(Class type) { assertThatExceptionOfType(ConversionException.class) // .isThrownBy(() -> convert("null", type)) // .withMessage("Failed to convert String \"null\" to type " + type.getCanonicalName()); assertThatExceptionOfType(ConversionException.class) // .isThrownBy(() -> convert("NULL", type)) // .withMessage("Failed to convert String \"NULL\" to type " + type.getCanonicalName()); } @Test void throwsExceptionOnInvalidStringForPrimitiveTypes() { assertThatExceptionOfType(ConversionException.class) // .isThrownBy(() -> convert("ab", char.class)) // .withMessage("Failed to convert String \"ab\" to type char") // .havingCause() // .withMessage("String must have length of 1: ab"); assertThatExceptionOfType(ConversionException.class) // .isThrownBy(() -> convert("tru", boolean.class)) // .withMessage("Failed to convert String \"tru\" to type boolean") // .havingCause() // .withMessage("String must be 'true' or 'false' (ignoring case): tru"); assertThatExceptionOfType(ConversionException.class) // .isThrownBy(() -> convert("null", boolean.class)) // .withMessage("Failed to convert String \"null\" to type boolean") // .havingCause() // .withMessage("String must be 'true' or 'false' (ignoring case): null"); assertThatExceptionOfType(ConversionException.class) // .isThrownBy(() -> convert("NULL", boolean.class)) // .withMessage("Failed to convert String \"NULL\" to type boolean") // .havingCause() // .withMessage("String must be 'true' or 'false' (ignoring case): NULL"); } @Test void throwsExceptionWhenImplicitConversionIsUnsupported() { assertThatExceptionOfType(ConversionException.class) // .isThrownBy(() -> convert("foo", Enigma.class)) // .withMessage("No built-in converter for source type java.lang.String and target type %s", Enigma.class.getName()); } /** * @since 5.4 */ @Test @SuppressWarnings("OctalInteger") // We test parsing octal integers here as well as hex. void convertsEncodedStringsToIntegralTypes() { assertConverts("0x1f", byte.class, (byte) 0x1F); assertConverts("-0x1F", byte.class, (byte) -0x1F); assertConverts("010", byte.class, (byte) 010); assertConverts("0x1f00", short.class, (short) 0x1F00); assertConverts("-0x1F00", short.class, (short) -0x1F00); assertConverts("01000", short.class, (short) 01000); assertConverts("0x1f000000", int.class, 0x1F000000); assertConverts("-0x1F000000", int.class, -0x1F000000); assertConverts("010000000", int.class, 010000000); assertConverts("0x1f000000000", long.class, 0x1F000000000L); assertConverts("-0x1F000000000", long.class, -0x1F000000000L); assertConverts("0100000000000", long.class, 0100000000000L); } @Test void convertsStringsToEnumConstants() { assertConverts("DAYS", TimeUnit.class, TimeUnit.DAYS); } // --- java.io and java.nio ------------------------------------------------ @Test void convertsStringToCharset() { assertConverts("ISO-8859-1", Charset.class, StandardCharsets.ISO_8859_1); assertConverts("UTF-8", Charset.class, StandardCharsets.UTF_8); } @Test void convertsStringToFile() { assertConverts("file", File.class, new File("file")); assertConverts("/file", File.class, new File("/file")); assertConverts("/some/file", File.class, new File("/some/file")); } @Test void convertsStringToPath() { assertConverts("path", Path.class, Path.of("path")); assertConverts("/path", Path.class, Path.of("/path")); assertConverts("/some/path", Path.class, Path.of("/some/path")); } // --- java.lang ----------------------------------------------------------- @Test void convertsStringToClass() { assertConverts("java.lang.Integer", Class.class, Integer.class); assertConverts("java.lang.Void", Class.class, Void.class); assertConverts("java.lang.Thread$State", Class.class, Thread.State.class); assertConverts("byte", Class.class, byte.class); assertConverts("void", Class.class, void.class); assertConverts("char[]", Class.class, char[].class); assertConverts("java.lang.Long[][]", Class.class, Long[][].class); assertConverts("[[[I", Class.class, int[][][].class); assertConverts("[[Ljava.lang.String;", Class.class, String[][].class); } @Test void convertsStringToClassWithCustomTypeFromDifferentClassLoader() throws Exception { String customTypeName = Enigma.class.getName(); try (var testClassLoader = TestClassLoader.forClasses(Enigma.class)) { var customType = testClassLoader.loadClass(customTypeName); assertThat(customType.getClassLoader()).isSameAs(testClassLoader); var declaringExecutable = ReflectionSupport.findMethod(customType, "foo").orElseThrow(); assertThat(declaringExecutable.getDeclaringClass().getClassLoader()).isSameAs(testClassLoader); var clazz = (Class) convert(customTypeName, Class.class, classLoader(declaringExecutable)); assertThat(clazz).isNotNull().isNotEqualTo(Enigma.class).isEqualTo(customType); assertThat(clazz.getClassLoader()).isSameAs(testClassLoader); } } // --- java.math ----------------------------------------------------------- @Test void convertsStringToBigDecimal() { assertConverts("123.456e789", BigDecimal.class, new BigDecimal("123.456e789")); } @Test void convertsStringToBigInteger() { assertConverts("1234567890123456789", BigInteger.class, new BigInteger("1234567890123456789")); } // --- java.net ------------------------------------------------------------ @Test void convertsStringToURI() { assertConverts("https://docs.oracle.com/en/java/javase/12/", URI.class, URI.create("https://docs.oracle.com/en/java/javase/12/")); } @Test void convertsStringToURL() throws Exception { assertConverts("https://junit.org", URL.class, URI.create("https://junit.org").toURL()); } // --- java.time ----------------------------------------------------------- @Test void convertsStringsToJavaTimeInstances() { assertConverts("PT1234.5678S", Duration.class, Duration.ofSeconds(1234, 567800000)); assertConverts("1970-01-01T00:00:00Z", Instant.class, Instant.ofEpochMilli(0)); assertConverts("2017-03-14", LocalDate.class, LocalDate.of(2017, 3, 14)); assertConverts("2017-03-14T12:34:56.789", LocalDateTime.class, LocalDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000)); assertConverts("12:34:56.789", LocalTime.class, LocalTime.of(12, 34, 56, 789_000_000)); assertConverts("--03-14", MonthDay.class, MonthDay.of(3, 14)); assertConverts("2017-03-14T12:34:56.789Z", OffsetDateTime.class, OffsetDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000, ZoneOffset.UTC)); assertConverts("12:34:56.789Z", OffsetTime.class, OffsetTime.of(12, 34, 56, 789_000_000, ZoneOffset.UTC)); assertConverts("P2M6D", Period.class, Period.of(0, 2, 6)); assertConverts("2017", Year.class, Year.of(2017)); assertConverts("2017-03", YearMonth.class, YearMonth.of(2017, 3)); assertConverts("2017-03-14T12:34:56.789Z", ZonedDateTime.class, ZonedDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000, ZoneOffset.UTC)); assertConverts("Europe/Berlin", ZoneId.class, ZoneId.of("Europe/Berlin")); assertConverts("+02:30", ZoneOffset.class, ZoneOffset.ofHoursMinutes(2, 30)); } // --- java.util ----------------------------------------------------------- @Test void convertsStringToCurrency() { assertConverts("JPY", Currency.class, Currency.getInstance("JPY")); } @Test void convertsStringToLocale() { assertConverts("en", Locale.class, Locale.ENGLISH); assertConverts("en-US", Locale.class, Locale.US); } @Test void convertsStringToUUID() { var uuid = "d043e930-7b3b-48e3-bdbe-5a3ccfb833db"; assertConverts(uuid, UUID.class, UUID.fromString(uuid)); } // ------------------------------------------------------------------------- private void assertConverts(@Nullable String input, Class targetClass, @Nullable Object expectedOutput) { var result = convert(input, targetClass); assertThat(result) // .describedAs(input + " --(" + targetClass.getName() + ")--> " + expectedOutput) // .isEqualTo(expectedOutput); } private @Nullable Object convert(@Nullable String input, Class targetClass) { return convert(input, targetClass, classLoader()); } private @Nullable Object convert(@Nullable String input, Class targetClass, ClassLoader classLoader) { return ConversionSupport.convert(input, targetClass, classLoader); } private static ClassLoader classLoader() { Method declaringExecutable = ReflectionSupport.findMethod(ConversionSupportTests.class, "foo").orElseThrow(); return classLoader(declaringExecutable); } private static ClassLoader classLoader(Method declaringExecutable) { return ClassLoaderUtils.getClassLoader(declaringExecutable.getDeclaringClass()); } @SuppressWarnings("unused") private static void foo() { } private static class Enigma { @SuppressWarnings("unused") void foo() { } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/support/conversion/FallbackStringToObjectConverterTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.support.conversion; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.platform.commons.support.ReflectionSupport.findMethod; import static org.junit.platform.commons.support.conversion.FallbackStringToObjectConverter.DeprecationStatus.EXCLUDE_DEPRECATED; import static org.junit.platform.commons.support.conversion.FallbackStringToObjectConverter.DeprecationStatus.INCLUDE_DEPRECATED; import static org.junit.platform.commons.util.ReflectionUtils.getDeclaredConstructor; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.Objects; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.platform.commons.support.conversion.FallbackStringToObjectConverter.IsFactoryConstructor; import org.junit.platform.commons.support.conversion.FallbackStringToObjectConverter.IsFactoryMethod; import org.junit.platform.commons.util.ReflectionUtils; /** * Unit tests for {@link FallbackStringToObjectConverter}, {@link IsFactoryMethod}, * and {@link IsFactoryConstructor}. * * @since 1.11 (originally since JUnit Jupiter 5.1) */ class FallbackStringToObjectConverterTests { private static final IsFactoryMethod isBookFactoryMethod = new IsFactoryMethod(Book.class, String.class, INCLUDE_DEPRECATED); private static final FallbackStringToObjectConverter converter = new FallbackStringToObjectConverter(); @Test void isNotFactoryMethodForWrongParameterType() { assertThat(isBookFactoryMethod).rejects(bookMethod("factory", Object.class)); assertThat(isBookFactoryMethod).rejects(bookMethod("factory", Number.class)); assertThat(isBookFactoryMethod).rejects(bookMethod("factory", StringBuilder.class)); assertThat(new IsFactoryMethod(Record2.class, String.class, INCLUDE_DEPRECATED)).rejects(record2Method("from")); assertThat(new IsFactoryMethod(Record2.class, String.class, EXCLUDE_DEPRECATED)).rejects(record2Method("from")); } @Test void isNotFactoryMethodForPrivateMethod() { assertThat(isBookFactoryMethod).rejects(bookMethod("privateFactory")); } @Test void isNotFactoryMethodForNonStaticMethod() { assertThat(isBookFactoryMethod).rejects(bookMethod("nonStaticFactory")); } @Test void isFactoryMethodForValidMethodsNoDeprecated() { assertThat(new IsFactoryMethod(Book.class, String.class, INCLUDE_DEPRECATED))// .accepts(bookMethod("factory", String.class)); assertThat(new IsFactoryMethod(Book.class, String.class, EXCLUDE_DEPRECATED))// .accepts(bookMethod("factory", String.class)); assertThat(new IsFactoryMethod(Book.class, CharSequence.class, INCLUDE_DEPRECATED))// .accepts(bookMethod("factory", CharSequence.class)); assertThat(new IsFactoryMethod(Book.class, CharSequence.class, EXCLUDE_DEPRECATED))// .accepts(bookMethod("factory", CharSequence.class)); assertThat(new IsFactoryMethod(Newspaper.class, String.class, INCLUDE_DEPRECATED))// .accepts(newspaperMethod("from"), newspaperMethod("of")); assertThat(new IsFactoryMethod(Newspaper.class, String.class, EXCLUDE_DEPRECATED))// .accepts(newspaperMethod("from"), newspaperMethod("of")); assertThat(new IsFactoryMethod(Magazine.class, String.class, INCLUDE_DEPRECATED))// .accepts(magazineMethod("from"), magazineMethod("of")); assertThat(new IsFactoryMethod(Magazine.class, String.class, EXCLUDE_DEPRECATED))// .accepts(magazineMethod("from"), magazineMethod("of")); assertThat(new IsFactoryMethod(Record2.class, CharSequence.class, INCLUDE_DEPRECATED))// .accepts(record2Method("from")); assertThat(new IsFactoryMethod(Record2.class, CharSequence.class, EXCLUDE_DEPRECATED))// .accepts(record2Method("from")); } @Test void isFactoryMethodForValidMethodsWithDeprecated() { assertThat(new IsFactoryMethod(Book2.class, String.class, INCLUDE_DEPRECATED))// .accepts(bookWithDeprecatedMethod("factory", String.class)); assertThat(new IsFactoryMethod(Book2.class, String.class, EXCLUDE_DEPRECATED))// .accepts(bookWithDeprecatedMethod("factory", String.class)); assertThat(new IsFactoryMethod(Book2.class, CharSequence.class, INCLUDE_DEPRECATED))// .accepts(bookWithDeprecatedMethod("factory", CharSequence.class)); assertThat(new IsFactoryMethod(Book2.class, CharSequence.class, EXCLUDE_DEPRECATED))// .accepts(bookWithDeprecatedMethod("factory", CharSequence.class)); assertThat(new IsFactoryMethod(Book2.class, CharSequence.class, EXCLUDE_DEPRECATED))// .rejects(bookWithDeprecatedMethod("factory", StringBuilder.class)); assertThat(new IsFactoryMethod(Book2.class, String.class, INCLUDE_DEPRECATED))// .accepts(bookWithDeprecatedMethod("factoryDeprecated", String.class)); assertThat(new IsFactoryMethod(Book2.class, String.class, EXCLUDE_DEPRECATED))// .rejects(bookWithDeprecatedMethod("factoryDeprecated", String.class)); assertThat(new IsFactoryMethod(Book2.class, CharSequence.class, INCLUDE_DEPRECATED))// .accepts(bookWithDeprecatedMethod("factoryDeprecated", CharSequence.class)); assertThat(new IsFactoryMethod(Book2.class, CharSequence.class, EXCLUDE_DEPRECATED))// .rejects(bookWithDeprecatedMethod("factoryDeprecated", CharSequence.class)); assertThat(new IsFactoryMethod(Book2.class, CharSequence.class, EXCLUDE_DEPRECATED))// .rejects(bookWithDeprecatedMethod("factoryDeprecated", CharSequence.class)); } @Test void isNotFactoryConstructorForPrivateConstructor() { assertThat(new IsFactoryConstructor(Magazine.class, String.class)).rejects(constructor(Magazine.class)); } @Test void isNotFactoryConstructorForWrongParameterType() { assertThat(new IsFactoryConstructor(Record1.class, String.class))// .rejects(getDeclaredConstructor(Record1.class)); assertThat(new IsFactoryConstructor(Record2.class, String.class))// .rejects(getDeclaredConstructor(Record2.class)); assertThat(new IsFactoryConstructor(Record3.class, String.class))// .rejects(getDeclaredConstructor(Record3.class)); } @Test void isFactoryConstructorForValidConstructors() { assertThat(new IsFactoryConstructor(Book.class, String.class))// .accepts(constructor(Book.class)); assertThat(new IsFactoryConstructor(Journal.class, String.class))// .accepts(constructor(Journal.class)); assertThat(new IsFactoryConstructor(Newspaper.class, String.class))// .accepts(constructor(Newspaper.class)); assertThat(new IsFactoryConstructor(Record1.class, CharSequence.class))// .accepts(getDeclaredConstructor(Record1.class)); assertThat(new IsFactoryConstructor(Record2.class, CharSequence.class))// .accepts(getDeclaredConstructor(Record2.class)); } @Test void convertsStringToBookViaStaticFactoryMethod() throws Exception { assertConverts("enigma", Book.class, new Book("factory(String): enigma")); } @Test void convertsStringToBookWithDeprecatedViaConstructor() throws Exception { // constructor takes precedence over factory method when there are two factory methods, and one is deprecated assertConverts("enigma", Book2.class, new Book2("enigma")); } @Test void convertsStringToRecord2ViaStaticFactoryMethodAcceptingCharSequence() throws Exception { assertConvertsRecord2("enigma", Record2.from(new StringBuffer("enigma"))); } @Test void convertsStringToJournalViaFactoryConstructor() throws Exception { assertConverts("enigma", Journal.class, new Journal("enigma")); } @Test void convertsStringToRecord1ViaFactoryConstructorAcceptingCharSequence() throws Exception { assertConvertsRecord1("enigma", new Record1(new StringBuffer("enigma"))); } @Test void convertsStringToNewspaperViaConstructorIgnoringMultipleFactoryMethods() throws Exception { assertConverts("enigma", Newspaper.class, new Newspaper("enigma")); } @Test void convertsDeprecatedToNewspaper() throws Exception { // when only one method @Deprecated is irrelevant, @Deprecated from(String) > @Deprecated from(CharSequence) assertConverts("enigma", Newspaper1.class, new Newspaper1("from(String): enigma")); } @Test void convertsToNewspaperPreferNonDeprecatedToDeprecated() throws Exception { // when two String factories: parse(String) > @Deprecated from(String) assertConverts("enigma", Newspaper2.class, new Newspaper2("parse(String): enigma")); } @Test void convertsToNewspaperPreferOnlyCharSequenceToNonDeprecatedString() throws Exception { // when two String and one CharSequence factories: parse(CharSequence) > parse(String)/@Deprecated from(String) assertConverts("enigma", Newspaper3.class, new Newspaper3("parse(CharSequence): enigma")); } @Test void convertsToNewspaperPreferOnlyCharSequenceToDeprecatedString() throws Exception { // when two String and two CharSequence factories: parse(CharSequence) > @Deprecated parse(String)/@Deprecated from(String)/@Deprecated from(CharSequence) assertConverts("enigma", Newspaper4.class, new Newspaper4("parse(CharSequence): enigma")); } @Test @DisplayName("Cannot convert String to Diary because Diary has neither a static factory method nor a factory constructor") void cannotConvertStringToDiary() { assertThat(converter.canConvertTo(Diary.class)).isFalse(); } @Test @DisplayName("Cannot convert String to Magazine because Magazine has multiple static factory methods") void cannotConvertStringToMagazine() { assertThat(converter.canConvertTo(Magazine.class)).isFalse(); } // ------------------------------------------------------------------------- private static Constructor constructor(Class clazz) { return ReflectionUtils.findConstructors(clazz, ctr -> ctr.getParameterCount() == 1 && ctr.getParameterTypes()[0] == String.class).getFirst(); } private static Method bookMethod(String methodName) { return bookMethod(methodName, String.class); } private static Method bookMethod(String methodName, Class parameterType) { return findMethod(Book.class, methodName, parameterType).orElseThrow(); } private static Method bookWithDeprecatedMethod(String methodName, Class parameterType) { return findMethod(Book2.class, methodName, parameterType).orElseThrow(); } private static Method newspaperMethod(String methodName) { return findMethod(Newspaper.class, methodName, String.class).orElseThrow(); } private static Method magazineMethod(String methodName) { return findMethod(Magazine.class, methodName, String.class).orElseThrow(); } private static Method record2Method(String methodName) { return findMethod(Record2.class, methodName, CharSequence.class).orElseThrow(); } private static void assertConverts(String input, Class targetType, Object expectedOutput) throws Exception { assertCanConvertTo(targetType); var result = converter.convert(input, targetType); assertThat(result) // .as(input + " (" + targetType.getSimpleName() + ") --> " + expectedOutput) // .isEqualTo(expectedOutput); } private static void assertConvertsRecord1(String input, Record1 expected) throws Exception { Class targetType = Record1.class; assertCanConvertTo(targetType); Record1 result = (Record1) converter.convert(input, targetType); assertThat(result).isNotNull(); assertThat(result.title.toString()).isEqualTo(expected.title.toString()); } private static void assertConvertsRecord2(String input, Record2 expected) throws Exception { Class targetType = Record2.class; assertCanConvertTo(targetType); var result = converter.convert(input, targetType); assertThat(result).isEqualTo(expected); } private static void assertCanConvertTo(Class targetType) { assertThat(converter.canConvertTo(targetType)).as("canConvertTo(%s)", targetType.getSimpleName()).isTrue(); } static class Book { private final String title; Book(String title) { this.title = title; } // static and non-private static Book factory(String title) { return new Book("factory(String): " + title); } /** * Static and non-private, but intentionally overloads {@link #factory(String)} * with a {@link CharSequence} argument to ensure that we don't introduce a * regression in 6.0, since the String-based factory method should take * precedence over a CharSequence-based factory method. */ static Book factory(CharSequence title) { return new Book("factory(CharSequence): " + title); } // wrong parameter type static Book factory(Object obj) { throw new UnsupportedOperationException(); } // wrong parameter type static Book factory(Number number) { throw new UnsupportedOperationException(); } /** * Wrong parameter type, intentionally a subtype of {@link CharSequence} * other than {@link String}. */ static Book factory(StringBuilder builder) { throw new UnsupportedOperationException(); } @SuppressWarnings("unused") private static Book privateFactory(String title) { return new Book(title); } Book nonStaticFactory(String title) { return new Book(title); } @Override public boolean equals(Object obj) { return (this == obj) || (obj instanceof Book that && Objects.equals(this.title, that.title)); } @Override public int hashCode() { return Objects.hash(title); } @Override public String toString() { return "Book [title=" + this.title + "]"; } } static class Journal { private final String title; Journal(String title) { this.title = title; } /** * Intentionally overloads {@link #Journal(String)} with a {@link CharSequence} * argument to ensure that we don't introduce a regression in 6.0, since the * String-based constructor should take precedence over a CharSequence-based * constructor. */ Journal(CharSequence title) { this("Journal(CharSequence): " + title); } @Override public boolean equals(Object obj) { return (this == obj) || (obj instanceof Journal that && Objects.equals(this.title, that.title)); } @Override public int hashCode() { return Objects.hash(title); } @Override public String toString() { return "Journal [title=" + this.title + "]"; } } static class Newspaper { private final String title; Newspaper(String title) { this.title = title; } static Newspaper from(String title) { return new Newspaper(title); } static Newspaper of(String title) { return new Newspaper(title); } @Override public boolean equals(Object obj) { return (this == obj) || (obj instanceof Newspaper that && Objects.equals(this.title, that.title)); } @Override public int hashCode() { return Objects.hash(title); } @Override public String toString() { return "Newspaper [title=" + this.title + "]"; } } static class Magazine { private Magazine(String title) { } static Magazine from(String title) { return new Magazine(title); } static Magazine of(String title) { return new Magazine(title); } } record Record1(CharSequence title) { } record Record2(CharSequence title) { static Record2 from(CharSequence title) { return new Record2("Record2(CharSequence): " + title); } } record Record3(StringBuilder title) { static Record2 from(StringBuilder title) { return new Record2("Record2(StringBuilder): " + title); } } static class Diary { } static class Book2 { private final String title; Book2(String title) { this.title = title; } static Book2 factory(String title) { return new Book2("factory(String): " + title); } @Deprecated static Book2 factoryDeprecated(String title) { return new Book2("factoryDeprecated(String): " + title); } /** * Static and non-private, but intentionally overloads {@link #factory(String)} * with a {@link CharSequence} argument to ensure that we don't introduce a * regression in 6.0, since the String-based factory method should take * precedence over a CharSequence-based factory method. */ static Book2 factory(CharSequence title) { return new Book2("factory(CharSequence): " + title); } @Deprecated static Book2 factoryDeprecated(CharSequence title) { return new Book2("factoryDeprecated(CharSequence): " + title); } // wrong parameter type static Book2 factory(Object obj) { throw new UnsupportedOperationException(); } // wrong parameter type static Book2 factory(Number number) { throw new UnsupportedOperationException(); } /** * Wrong parameter type, intentionally a subtype of {@link CharSequence} * other than {@link String}. */ static Book2 factory(StringBuilder builder) { throw new UnsupportedOperationException(); } @SuppressWarnings("unused") private static Book2 privateFactory(String title) { return new Book2(title); } Book2 nonStaticFactory(String title) { return new Book2(title); } @Override public boolean equals(Object obj) { return (this == obj) || (obj instanceof Book2 that && Objects.equals(this.title, that.title)); } @Override public int hashCode() { return Objects.hash(title); } @Override public String toString() { return "Book2 [title=" + this.title + "]"; } } static class Newspaper1 { private final String title; private Newspaper1(String title) { // constructor must be private for factory/deprecated logic to kick in this.title = title; } // only String factory, thus being deprecated is irrelevant @Deprecated static Newspaper1 from(String title) { return new Newspaper1("from(String): " + title); } // only CharSequence factory, thus being deprecated is irrelevant, but String version takes precedence @Deprecated static Newspaper1 from(CharSequence title) { return new Newspaper1("from(CharSequence): " + title); } @Override public boolean equals(Object obj) { return (this == obj) || (obj instanceof Newspaper1 that && Objects.equals(this.title, that.title)); } @Override public int hashCode() { return Objects.hash(title); } @Override public String toString() { return "Newspaper1 [title=" + this.title + "]"; } } static class Newspaper2 { private final String title; private Newspaper2(String title) { // constructor must be private for factory/deprecated logic to kick in this.title = title; } @Deprecated static Newspaper2 from(String title) { return new Newspaper2("from(String): " + title); } @Deprecated static Newspaper2 other(String title) { return new Newspaper2("parse(CharSequence): " + title); } // String factory without deprecated has precedence over String factory with deprecated static Newspaper2 parse(String title) { return new Newspaper2("parse(String): " + title); } @Override public boolean equals(Object obj) { return (this == obj) || (obj instanceof Newspaper2 that && Objects.equals(this.title, that.title)); } @Override public int hashCode() { return Objects.hash(title); } @Override public String toString() { return "Newspaper2 [title=" + this.title + "]"; } } static class Newspaper3 { private final String title; private Newspaper3(String title) { // constructor must be private for factory/deprecated logic to kick in this.title = title; } @Deprecated static Newspaper3 from(String title) { return new Newspaper3("from(String): " + title); } static Newspaper3 parse(String title) { return new Newspaper3("parse(String): " + title); } // CharSequence factory without deprecated alternative has precedence // over String factory with deprecated alternative static Newspaper3 parse(CharSequence title) { return new Newspaper3("parse(CharSequence): " + title); } @Override public boolean equals(Object obj) { return (this == obj) || (obj instanceof Newspaper3 that && Objects.equals(this.title, that.title)); } @Override public int hashCode() { return Objects.hash(title); } @Override public String toString() { return "Newspaper3 [title=" + this.title + "]"; } } static class Newspaper4 { private final String title; private Newspaper4(String title) { // constructor must be private for factory/deprecated logic to kick in this.title = title; } @Deprecated static Newspaper4 from(String title) { return new Newspaper4("from(String): " + title); } @Deprecated static Newspaper4 parse(String title) { return new Newspaper4("parse(String): " + title); } @Deprecated static Newspaper4 from(CharSequence title) { return new Newspaper4("from(CharSequence): " + title); } // CharSequence factory with deprecated alternative has precedence // over deprecated String factory static Newspaper4 parse(CharSequence title) { return new Newspaper4("parse(CharSequence): " + title); } @Override public boolean equals(Object obj) { return (this == obj) || (obj instanceof Newspaper4 that && Objects.equals(this.title, that.title)); } @Override public int hashCode() { return Objects.hash(title); } @Override public String toString() { return "Newspaper4 [title=" + this.title + "]"; } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/util/AnnotationUtilsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertIterableEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.junit.platform.commons.util.AnnotationUtils.findAnnotatedFields; import static org.junit.platform.commons.util.AnnotationUtils.findAnnotatedMethods; import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; import static org.junit.platform.commons.util.AnnotationUtils.findPublicAnnotatedFields; import static org.junit.platform.commons.util.AnnotationUtils.findRepeatableAnnotations; import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP; import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.TOP_DOWN; import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.math.BigDecimal; import java.util.List; import java.util.Optional; import java.util.function.Predicate; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.platform.commons.util.pkg1.ClassLevelDir; import org.junit.platform.commons.util.pkg1.InstanceLevelDir; import org.junit.platform.commons.util.pkg1.SuperclassWithStaticPackagePrivateBeforeMethod; import org.junit.platform.commons.util.pkg1.SuperclassWithStaticPackagePrivateTempDirField; import org.junit.platform.commons.util.pkg1.subpkg.SubclassWithNonStaticPackagePrivateBeforeMethod; import org.junit.platform.commons.util.pkg1.subpkg.SubclassWithNonStaticPackagePrivateTempDirField; /** * Unit tests for {@link AnnotationUtils}. * * @since 1.0 */ class AnnotationUtilsTests { private static final Predicate isStringField = field -> field.getType() == String.class; @Test void findAnnotationForNullOptional() { assertThat(findAnnotation((Optional) null, Annotation1.class)).isEmpty(); } @Test void findAnnotationForEmptyOptional() { assertThat(findAnnotation(Optional.empty(), Annotation1.class)).isEmpty(); } @Test void findAnnotationForNullAnnotatedElement() { assertThat(findAnnotation((AnnotatedElement) null, Annotation1.class)).isEmpty(); } @Test void findAnnotationOnClassWithoutAnnotation() { assertThat(findAnnotation(Annotation1Class.class, Annotation2.class)).isNotPresent(); } @Test void findAnnotationIndirectlyPresentOnOptionalClass() { Optional> optional = Optional.of(SubInheritedAnnotationClass.class); assertThat(findAnnotation(optional, InheritedAnnotation.class)).isPresent(); } @Test void findAnnotationIndirectlyPresentOnClass() { assertThat(findAnnotation(SubInheritedAnnotationClass.class, InheritedAnnotation.class)).isPresent(); } /** * Test for https://github.com/junit-team/junit-framework/issues/1133 */ @Test void findInheritedAnnotationMetaPresentOnNonInheritedComposedAnnotationPresentOnSuperclass() { assertThat(findAnnotation(SubNonInheritedCompositionOfInheritedAnnotationClass.class, InheritedAnnotation.class)).isPresent(); } @Test void findAnnotationDirectlyPresentOnClass() { assertThat(findAnnotation(Annotation1Class.class, Annotation1.class)).isPresent(); } @Test void findAnnotationMetaPresentOnClass() { assertThat(findAnnotation(ComposedAnnotationClass.class, Annotation1.class)).isPresent(); } /** * Note: there is no findAnnotationIndirectlyMetaPresentOnMethod * counterpart because the {@code @Inherited} annotation has no effect if * the annotation type is used to annotate anything other than a class. * * @see Inherited */ @Test void findAnnotationIndirectlyMetaPresentOnClass() { assertThat(findAnnotation(SubInheritedComposedAnnotationClass.class, Annotation1.class)).isPresent(); } @Test void findAnnotationDirectlyPresentOnImplementedInterface() { assertThat(findAnnotation(TestingTraitClass.class, Annotation1.class)).isPresent(); } @Test void findAnnotationMetaPresentOnImplementedInterface() { assertThat(findAnnotation(ComposedTestingTraitClass.class, Annotation1.class)).isPresent(); } @Test void findAnnotationDirectlyPresentOnMethod() throws Exception { var method = Annotation2Class.class.getDeclaredMethod("method"); assertThat(findAnnotation(method, Annotation1.class)).isPresent(); } @Test void findAnnotationMetaPresentOnMethod() throws Exception { var method = ComposedAnnotationClass.class.getDeclaredMethod("method"); assertThat(findAnnotation(method, Annotation1.class)).isPresent(); } @Test void findAnnotationMetaPresentOnOptionalMethod() throws Exception { var method = ComposedAnnotationClass.class.getDeclaredMethod("method"); assertThat(findAnnotation(Optional.of(method), Annotation1.class)).isPresent(); } @Test void findAnnotationDirectlyPresentOnEnclosingClass() throws Exception { Class clazz = Annotation1Class.InnerClass.class; assertThat(findAnnotation(clazz, Annotation1.class, false)).isNotPresent(); assertThat(findAnnotation(clazz, Annotation1.class, true)).isPresent(); clazz = Annotation1Class.InnerClass.InnerInnerClass.class; assertThat(findAnnotation(clazz, Annotation1.class, false)).isNotPresent(); assertThat(findAnnotation(clazz, Annotation1.class, true)).isPresent(); clazz = Annotation1Class.NestedClass.class; assertThat(findAnnotation(clazz, Annotation1.class, false)).isNotPresent(); assertThat(findAnnotation(clazz, Annotation1.class, true)).isNotPresent(); } @Test void findAnnotationMetaPresentOnEnclosingClass() throws Exception { Class clazz = ComposedAnnotationClass.InnerClass.class; assertThat(findAnnotation(clazz, Annotation1.class, false)).isNotPresent(); assertThat(findAnnotation(clazz, Annotation1.class, true)).isPresent(); clazz = ComposedAnnotationClass.InnerClass.InnerInnerClass.class; assertThat(findAnnotation(clazz, Annotation1.class, false)).isNotPresent(); assertThat(findAnnotation(clazz, Annotation1.class, true)).isPresent(); } @Test void isAnnotatedForClassWithoutAnnotation() { assertFalse(isAnnotated(Annotation1Class.class, Annotation2.class)); } @Test void isAnnotatedWhenIndirectlyPresentOnClass() { assertTrue(isAnnotated(SubInheritedAnnotationClass.class, InheritedAnnotation.class)); } @Test void isAnnotatedWhenDirectlyPresentOnClass() { assertTrue(isAnnotated(Annotation1Class.class, Annotation1.class)); } @Test void isAnnotatedWhenMetaPresentOnClass() { assertTrue(isAnnotated(ComposedAnnotationClass.class, Annotation1.class)); } @Test void isAnnotatedWhenDirectlyPresentOnMethod() throws Exception { assertTrue(isAnnotated(Annotation2Class.class.getDeclaredMethod("method"), Annotation1.class)); } @Test void isAnnotatedWhenMetaPresentOnMethod() throws Exception { assertTrue(isAnnotated(ComposedAnnotationClass.class.getDeclaredMethod("method"), Annotation1.class)); } @Test void findRepeatableAnnotationsForNotRepeatableAnnotation() { assertPreconditionViolationFor(() -> findRepeatableAnnotations(getClass(), Inherited.class))// .withMessage(Inherited.class.getName() + " must be @Repeatable"); } @Test void findRepeatableAnnotationsForNullOptionalAnnotatedElement() { assertThat(findRepeatableAnnotations((Optional) null, Tag.class)).isEmpty(); } @Test void findRepeatableAnnotationsForEmptyOptionalAnnotatedElement() { assertThat(findRepeatableAnnotations(Optional.empty(), Tag.class)).isEmpty(); } @Test void findRepeatableAnnotationsForNullAnnotatedElement() { assertThat(findRepeatableAnnotations((AnnotatedElement) null, Tag.class)).isEmpty(); } @Test void findRepeatableAnnotationsWithSingleTag() { assertTagsFound(SingleTaggedClass.class, "a"); } @Test void findRepeatableAnnotationsWithSingleComposedTag() { assertTagsFound(SingleComposedTaggedClass.class, "fast"); } @Test void findRepeatableAnnotationsWithSingleComposedTagOnImplementedInterface() { assertTagsFound(TaggedInterfaceClass.class, "fast"); } @Test void findRepeatableAnnotationsWithLocalComposedTagAndComposedTagOnImplementedInterface() { assertTagsFound(LocalTagOnTaggedInterfaceClass.class, "fast", "smoke"); } @Test void findRepeatableAnnotationsWithMultipleTags() { assertTagsFound(MultiTaggedClass.class, "a", "b", "c"); } @Test void findRepeatableAnnotationsWithMultipleComposedTags() { assertTagsFound(MultiComposedTaggedClass.class, "fast", "smoke"); assertTagsFound(FastAndSmokyTaggedClass.class, "fast", "smoke"); } @Test void findRepeatableAnnotationsWithContainer() { assertTagsFound(ContainerTaggedClass.class, "a", "b", "c", "d"); } @Test void findRepeatableAnnotationsWithComposedTagBeforeContainer() { assertTagsFound(ContainerAfterComposedTaggedClass.class, "fast", "a", "b", "c"); } private void assertTagsFound(Class clazz, String... tags) { assertEquals(List.of(tags), findRepeatableAnnotations(clazz, Tag.class).stream().map(Tag::value).toList(), () -> "Tags found for class " + clazz.getName()); } @Test void findInheritedRepeatableAnnotationsWithSingleAnnotationOnSuperclass() { assertExtensionsFound(SingleExtensionClass.class, "a"); assertExtensionsFound(SubSingleExtensionClass.class, "a"); } @Test void findInheritedRepeatableAnnotationsWithMultipleAnnotationsOnSuperclass() { assertExtensionsFound(MultiExtensionClass.class, "a", "b", "c"); assertExtensionsFound(SubMultiExtensionClass.class, "a", "b", "c", "x", "y", "z"); } @Test void findInheritedRepeatableAnnotationsWithContainerAnnotationOnSuperclass() { assertExtensionsFound(ContainerExtensionClass.class, "a", "b", "c"); assertExtensionsFound(SubContainerExtensionClass.class, "a", "b", "c", "x"); } @Test void findInheritedRepeatableAnnotationsWithSingleComposedAnnotation() { assertExtensionsFound(SingleComposedExtensionClass.class, "foo"); } /** * @since 1.5 */ @Test void findInheritedRepeatableAnnotationsWithComposedAnnotationsInNestedContainer() { assertExtensionsFound(MultipleFoos1.class, "foo"); assertExtensionsFound(MultipleFoos2.class, "foo"); } @Test void findInheritedRepeatableAnnotationsWithSingleComposedAnnotationOnSuperclass() { assertExtensionsFound(SubSingleComposedExtensionClass.class, "foo"); } @Test void findInheritedRepeatableAnnotationsWithMultipleComposedAnnotations() { assertExtensionsFound(MultiComposedExtensionClass.class, "foo", "bar"); } @Test void findInheritedRepeatableAnnotationsWithMultipleComposedAnnotationsOnSuperclass() { assertExtensionsFound(SubMultiComposedExtensionClass.class, "foo", "bar"); } @Test void findInheritedRepeatableAnnotationsWithMultipleComposedAnnotationsOnSuperclassAndLocalContainerAndComposed() { assertExtensionsFound(ContainerPlusSubMultiComposedExtensionClass.class, "foo", "bar", "x", "y", "z"); } private void assertExtensionsFound(Class clazz, String... tags) { assertEquals(List.of(tags), findRepeatableAnnotations(clazz, ExtendWith.class).stream().map(ExtendWith::value).toList(), () -> "Extensions found for class " + clazz.getName()); } @SuppressWarnings("DataFlowIssue") @Test void findAnnotatedMethodsForNullClass() { assertPreconditionViolationFor(() -> findAnnotatedMethods(null, Annotation1.class, TOP_DOWN)); } @SuppressWarnings("DataFlowIssue") @Test void findAnnotatedMethodsForNullAnnotationType() { assertPreconditionViolationFor(() -> findAnnotatedMethods(ClassWithAnnotatedMethods.class, null, TOP_DOWN)); } @Test void findAnnotatedMethodsForAnnotationThatIsNotPresent() { assertThat(findAnnotatedMethods(ClassWithAnnotatedMethods.class, Fast.class, TOP_DOWN)).isEmpty(); } @Test void findAnnotatedMethodsForAnnotationOnMethodsInClassUsingHierarchyDownMode() throws Exception { var method2 = ClassWithAnnotatedMethods.class.getDeclaredMethod("method2"); var method3 = ClassWithAnnotatedMethods.class.getDeclaredMethod("method3"); var methods = findAnnotatedMethods(ClassWithAnnotatedMethods.class, Annotation2.class, TOP_DOWN); assertThat(methods).containsOnly(method2, method3); } @Test void findAnnotatedMethodsForAnnotationOnMethodsInClassHierarchyUsingHierarchyUpMode() throws Exception { var method1 = ClassWithAnnotatedMethods.class.getDeclaredMethod("method1"); var method3 = ClassWithAnnotatedMethods.class.getDeclaredMethod("method3"); var superMethod = SuperclassWithAnnotatedMethod.class.getDeclaredMethod("superMethod"); var methods = findAnnotatedMethods(ClassWithAnnotatedMethods.class, Annotation1.class, BOTTOM_UP); assertEquals(3, methods.size()); assertThat(methods.subList(0, 2)).containsOnly(method1, method3); assertEquals(superMethod, methods.get(2)); } @Test void findAnnotatedMethodsForAnnotationUsedInClassAndSuperclassHierarchyDown() throws Exception { var method1 = ClassWithAnnotatedMethods.class.getDeclaredMethod("method1"); var method3 = ClassWithAnnotatedMethods.class.getDeclaredMethod("method3"); var superMethod = SuperclassWithAnnotatedMethod.class.getDeclaredMethod("superMethod"); var methods = findAnnotatedMethods(ClassWithAnnotatedMethods.class, Annotation1.class, TOP_DOWN); assertEquals(3, methods.size()); assertEquals(superMethod, methods.getFirst()); assertThat(methods.subList(1, 3)).containsOnly(method1, method3); } /* * see https://github.com/junit-team/junit-framework/issues/3553 */ @Test void findAnnotatedMethodsDoesNotAllowInstanceMethodToHideStaticMethod() throws Exception { final String BEFORE = "before"; Class superclass = SuperclassWithStaticPackagePrivateBeforeMethod.class; Method beforeAllMethod = superclass.getDeclaredMethod(BEFORE); Class subclass = SubclassWithNonStaticPackagePrivateBeforeMethod.class; Method beforeEachMethod = subclass.getDeclaredMethod(BEFORE); // Prerequisite var methods = findAnnotatedMethods(superclass, BeforeAll.class, TOP_DOWN); assertThat(methods).containsExactly(beforeAllMethod); // Actual use cases for this test methods = findAnnotatedMethods(subclass, BeforeAll.class, TOP_DOWN); assertThat(methods).containsExactly(beforeAllMethod); methods = findAnnotatedMethods(subclass, BeforeEach.class, TOP_DOWN); assertThat(methods).containsExactly(beforeEachMethod); } @Test void findAnnotatedMethodsForAnnotationUsedInInterface() throws Exception { var interfaceMethod = InterfaceWithAnnotatedDefaultMethod.class.getDeclaredMethod("interfaceMethod"); var methods = findAnnotatedMethods(ClassWithAnnotatedMethods.class, Annotation3.class, BOTTOM_UP); assertThat(methods).containsExactly(interfaceMethod); } // === findAnnotatedFields() =============================================== @SuppressWarnings("DataFlowIssue") @Test void findAnnotatedFieldsForNullClass() { assertPreconditionViolationFor(() -> findAnnotatedFields(null, Annotation1.class, isStringField, TOP_DOWN)); } @SuppressWarnings("DataFlowIssue") @Test void findAnnotatedFieldsForNullAnnotationType() { assertPreconditionViolationFor( () -> findAnnotatedFields(ClassWithAnnotatedFields.class, null, isStringField, TOP_DOWN)); } @SuppressWarnings("DataFlowIssue") @Test void findAnnotatedFieldsForNullPredicate() { assertPreconditionViolationFor( () -> findAnnotatedFields(ClassWithAnnotatedFields.class, Annotation1.class, null, TOP_DOWN)); } @Test void findAnnotatedFieldsForAnnotationThatIsNotPresent() { assertThat(findAnnotatedFields(ClassWithAnnotatedFields.class, Fast.class, isStringField, TOP_DOWN)).isEmpty(); } @Test void findAnnotatedFieldsForAnnotationOnFieldsInClassUsingHierarchyDownMode() throws Exception { var field2 = ClassWithAnnotatedFields.class.getDeclaredField("field2"); var field3 = ClassWithAnnotatedFields.class.getDeclaredField("field3"); var fields = findAnnotatedFields(ClassWithAnnotatedFields.class, Annotation2.class, isStringField, TOP_DOWN); assertThat(fields).containsOnly(field2, field3); } @Test void findAnnotatedFieldsForAnnotationOnFieldsInClassHierarchyUsingHierarchyUpMode() throws Exception { var field1 = ClassWithAnnotatedFields.class.getDeclaredField("field1"); var field3 = ClassWithAnnotatedFields.class.getDeclaredField("field3"); var superField = SuperclassWithAnnotatedField.class.getDeclaredField("superField"); var fields = findAnnotatedFields(ClassWithAnnotatedFields.class, Annotation1.class, isStringField, BOTTOM_UP); assertEquals(3, fields.size()); assertThat(fields.subList(0, 2)).containsOnly(field1, field3); assertEquals(superField, fields.get(2)); } @Test void findAnnotatedFieldsForAnnotationUsedInClassAndSuperclassHierarchyDown() throws Exception { var field1 = ClassWithAnnotatedFields.class.getDeclaredField("field1"); var field3 = ClassWithAnnotatedFields.class.getDeclaredField("field3"); var superField = SuperclassWithAnnotatedField.class.getDeclaredField("superField"); var fields = findAnnotatedFields(ClassWithAnnotatedFields.class, Annotation1.class, isStringField, TOP_DOWN); assertEquals(3, fields.size()); assertEquals(superField, fields.getFirst()); assertThat(fields.subList(1, 3)).containsOnly(field1, field3); } @Test void findAnnotatedFieldsForAnnotationUsedInInterface() throws Exception { var interfaceField = InterfaceWithAnnotatedField.class.getDeclaredField("interfaceField"); var fields = findAnnotatedFields(ClassWithAnnotatedFields.class, Annotation3.class, isStringField, BOTTOM_UP); assertThat(fields).containsExactly(interfaceField); } @Test void findAnnotatedFieldsFindsAllFieldsInTypeHierarchy() { assertThat(findShadowingAnnotatedFields(Annotation1.class))// .containsExactly("super", "foo", "baz", "super-shadow", "foo-shadow", "baz-shadow"); assertThat(findShadowingAnnotatedFields(Annotation2.class))// .containsExactly("bar", "baz", "bar-shadow", "baz-shadow"); assertThat(findShadowingAnnotatedFields(Annotation3.class))// .containsExactly("interface", "interface-shadow"); } private List findShadowingAnnotatedFields(Class annotationType) { var fields = findAnnotatedFields(ClassWithShadowedAnnotatedFields.class, annotationType, isStringField); var values = ReflectionUtils.readFieldValues(fields, new ClassWithShadowedAnnotatedFields()); return values.stream().map(String::valueOf).toList(); } /* * see https://github.com/junit-team/junit-framework/issues/3553 */ @Test void findAnnotatedFieldsDoesNotAllowInstanceFieldToHideStaticField() throws Exception { final String TEMP_DIR = "tempDir"; Class superclass = SuperclassWithStaticPackagePrivateTempDirField.class; Field staticField = superclass.getDeclaredField(TEMP_DIR); Class subclass = SubclassWithNonStaticPackagePrivateTempDirField.class; Field nonStaticField = subclass.getDeclaredField(TEMP_DIR); // Prerequisite var fields = findAnnotatedFields(superclass, ClassLevelDir.class, field -> true); assertThat(fields).containsExactly(staticField); // Actual use cases for this test fields = findAnnotatedFields(subclass, ClassLevelDir.class, field -> true); assertThat(fields).containsExactly(staticField); fields = findAnnotatedFields(subclass, InstanceLevelDir.class, field -> true); assertThat(fields).containsExactly(nonStaticField); } // === findPublicAnnotatedFields() ========================================= @SuppressWarnings("DataFlowIssue") @Test void findPublicAnnotatedFieldsForNullClass() { assertPreconditionViolationFor(() -> findPublicAnnotatedFields(null, String.class, Annotation1.class)); } @SuppressWarnings("DataFlowIssue") @Test void findPublicAnnotatedFieldsForNullFieldType() { assertPreconditionViolationFor(() -> findPublicAnnotatedFields(getClass(), null, Annotation1.class)); } @SuppressWarnings("DataFlowIssue") @Test void findPublicAnnotatedFieldsForNullAnnotationType() { assertPreconditionViolationFor(() -> findPublicAnnotatedFields(getClass(), String.class, null)); } @Test void findPublicAnnotatedFieldsForPrivateField() { var fields = findPublicAnnotatedFields(getClass(), Boolean.class, Annotation1.class); assertNotNull(fields); assertEquals(0, fields.size()); } @Test void findPublicAnnotatedFieldsForDirectlyAnnotatedFieldOfWrongFieldType() { var fields = findPublicAnnotatedFields(getClass(), BigDecimal.class, Annotation1.class); assertNotNull(fields); assertEquals(0, fields.size()); } @Test void findPublicAnnotatedFieldsForDirectlyAnnotatedField() { var fields = findPublicAnnotatedFields(getClass(), String.class, Annotation1.class); assertNotNull(fields); assertIterableEquals(List.of("directlyAnnotatedField"), asNames(fields)); } @Test void findPublicAnnotatedFieldsForMetaAnnotatedField() { var fields = findPublicAnnotatedFields(getClass(), Number.class, Annotation1.class); assertNotNull(fields); assertEquals(1, fields.size()); assertIterableEquals(List.of("metaAnnotatedField"), asNames(fields)); } @Test void findPublicAnnotatedFieldsForDirectlyAnnotatedFieldInInterface() { var fields = findPublicAnnotatedFields(InterfaceWithAnnotatedFields.class, String.class, Annotation1.class); assertNotNull(fields); assertIterableEquals(List.of("foo"), asNames(fields)); } @Test void findPublicAnnotatedFieldsForDirectlyAnnotatedFieldsInClassAndInterface() { var fields = findPublicAnnotatedFields(ClassWithPublicAnnotatedFieldsFromInterface.class, String.class, Annotation1.class); assertNotNull(fields); assertThat(asNames(fields)).containsExactlyInAnyOrder("foo", "bar"); } private List asNames(List fields) { return fields.stream().map(Field::getName).toList(); } // ------------------------------------------------------------------------- @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @interface AnnotationWithDefaultValue { String value() default "default"; } @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) @interface Annotation1 { } @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) @interface Annotation2 { } @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) @interface Annotation3 { } @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Inherited @interface InheritedAnnotation { } @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) @Annotation1 @interface ComposedAnnotation { } @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Annotation1 @Inherited @interface InheritedComposedAnnotation { } @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @InheritedAnnotation // DO NOT make this @Inherited. @interface NonInheritedCompositionOfInheritedAnnotation { } @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) // DO NOT make this @Inherited. @interface Tags { Tag[] value(); } @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Repeatable(Tags.class) // DO NOT make this @Inherited. @interface Tag { String value(); } @Retention(RetentionPolicy.RUNTIME) @Tag("fast") @interface Fast { } @Retention(RetentionPolicy.RUNTIME) @Tag("smoke") @interface Smoke { } @Retention(RetentionPolicy.RUNTIME) @Fast @Smoke @interface FastAndSmoky { } @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Inherited @interface Extensions { ExtendWith[] value(); } @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Inherited @Repeatable(Extensions.class) @interface ExtendWith { String value(); } @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Inherited @ExtendWith("foo") @Repeatable(FooExtensions.class) @interface ExtendWithFoo { String info() default ""; } @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Inherited @interface FooExtensions { ExtendWithFoo[] value(); } @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) // Intentionally NOT @Inherited in order to ensure that the algorithm for // findRepeatableAnnotations() in fact lives up to the claims in the // Javadoc regarding searching for repeatable annotations with implicit // "inheritance" if the repeatable annotation is @Inherited but the // custom composed annotation is not @Inherited. // @Inherited @ExtendWith("bar") @interface ExtendWithBar { } @AnnotationWithDefaultValue static class AnnotationWithDefaultValueClass { } @Annotation1 static class Annotation1Class { class InnerClass { class InnerInnerClass { } } static class NestedClass { } } @Annotation2 static class Annotation2Class { @Annotation1 void method() { } } @InheritedAnnotation static class InheritedAnnotationClass { } static class SubInheritedAnnotationClass extends InheritedAnnotationClass { } @ComposedAnnotation static class ComposedAnnotationClass { @ComposedAnnotation void method() { } class InnerClass { class InnerInnerClass { } } } @InheritedComposedAnnotation static class InheritedComposedAnnotationClass { @InheritedComposedAnnotation void method() { } } static class SubInheritedComposedAnnotationClass extends InheritedComposedAnnotationClass { } @NonInheritedCompositionOfInheritedAnnotation static class NonInheritedCompositionOfInheritedAnnotationClass { } static class SubNonInheritedCompositionOfInheritedAnnotationClass extends NonInheritedCompositionOfInheritedAnnotationClass { } @Annotation1 interface TestingTrait { } static class TestingTraitClass implements TestingTrait { } @ComposedAnnotation interface ComposedTestingTrait { } static class ComposedTestingTraitClass implements ComposedTestingTrait { } @Tag("a") static class SingleTaggedClass { } @Fast static class SingleComposedTaggedClass { } @Tag("a") @Tag("b") @Tag("c") static class MultiTaggedClass { } @Fast @Smoke static class MultiComposedTaggedClass { } @FastAndSmoky static class FastAndSmokyTaggedClass { } @Fast interface TaggedInterface { } static class TaggedInterfaceClass implements TaggedInterface { } @Smoke static class LocalTagOnTaggedInterfaceClass implements TaggedInterface { } @Tags({ @Tag("a"), @Tag("b"), @Tag("c") }) @Tag("d") static class ContainerTaggedClass { } @Fast @Tags({ @Tag("a"), @Tag("b"), @Tag("c") }) static class ContainerAfterComposedTaggedClass { } @ExtendWith("a") static class SingleExtensionClass { } static class SubSingleExtensionClass extends SingleExtensionClass { } @ExtendWith("a") @ExtendWith("b") @ExtendWith("c") static class MultiExtensionClass { } @ExtendWith("x") @ExtendWith("y") @ExtendWith("b") // duplicates parent @ExtendWith("z") @ExtendWith("a") // duplicates parent static class SubMultiExtensionClass extends MultiExtensionClass { } @Extensions({ @ExtendWith("a"), @ExtendWith("b"), @ExtendWith("c"), @ExtendWith("a") }) static class ContainerExtensionClass { } @ExtendWith("x") static class SubContainerExtensionClass extends ContainerExtensionClass { } @ExtendWithFoo static class SingleComposedExtensionClass { } @ExtendWithFoo(info = "A") @ExtendWithFoo(info = "B") static class MultipleFoos1 { } @FooExtensions({ @ExtendWithFoo(info = "A"), @ExtendWithFoo(info = "B") }) static class MultipleFoos2 { } static class SubSingleComposedExtensionClass extends SingleComposedExtensionClass { } @ExtendWithFoo @ExtendWithBar static class MultiComposedExtensionClass { } static class SubMultiComposedExtensionClass extends MultiComposedExtensionClass { } @ExtendWith("x") @Extensions({ @ExtendWith("y"), @ExtendWith("z") }) @ExtendWithBar static class ContainerPlusSubMultiComposedExtensionClass extends MultiComposedExtensionClass { } interface InterfaceWithAnnotatedDefaultMethod { @Annotation3 default void interfaceMethod() { } } static class SuperclassWithAnnotatedMethod { @Annotation1 void superMethod() { } } static class ClassWithAnnotatedMethods extends SuperclassWithAnnotatedMethod implements InterfaceWithAnnotatedDefaultMethod { @Annotation1 void method1() { } @Annotation2 void method2() { } @Annotation1 @Annotation2 void method3() { } } // ------------------------------------------------------------------------- interface InterfaceWithAnnotatedField { @Annotation3 String interfaceField = "interface"; } static class SuperclassWithAnnotatedField { @Annotation1 String superField = "super"; } static class ClassWithAnnotatedFields extends SuperclassWithAnnotatedField implements InterfaceWithAnnotatedField { @Annotation1 protected Object field0 = "?"; @Annotation1 protected String field1 = "foo"; @Annotation2 protected String field2 = "bar"; @Annotation1 @Annotation2 protected String field3 = "baz"; } static class ClassWithShadowedAnnotatedFields extends ClassWithAnnotatedFields { @Annotation3 String interfaceField = "interface-shadow"; @Annotation1 String superField = "super-shadow"; @Annotation1 protected String field1 = "foo-shadow"; @Annotation2 protected String field2 = "bar-shadow"; @Annotation1 @Annotation2 protected String field3 = "baz-shadow"; } // ------------------------------------------------------------------------- @Annotation1 private @Nullable Boolean privateDirectlyAnnotatedField; @Annotation1 public @Nullable String directlyAnnotatedField; @ComposedAnnotation public @Nullable Integer metaAnnotatedField; interface InterfaceWithAnnotatedFields { @Annotation1 String foo = "bar"; @Annotation1 boolean wrongType = false; } class ClassWithPublicAnnotatedFieldsFromInterface implements InterfaceWithAnnotatedFields { @Annotation1 public String bar = "baz"; @Annotation1 public boolean notAString = true; } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/util/ClassLoaderUtilsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullFor; import static org.mockito.Mockito.mock; import org.junit.jupiter.api.Test; import org.junit.platform.commons.test.TestClassLoader; /** * Unit tests for {@link ClassLoaderUtils}. * * @since 1.0 */ class ClassLoaderUtilsTests { @SuppressWarnings("DataFlowIssue") @Test void getClassLoaderPreconditions() { assertPreconditionViolationNotNullFor("Class", () -> ClassLoaderUtils.getClassLoader(null)); } @Test void getClassLoaderForPrimitive() { assertThat(int.class.getClassLoader()).isNull(); ClassLoader classLoader = ClassLoaderUtils.getClassLoader(int.class); assertThat(classLoader).isSameAs(getClass().getClassLoader()); } @Test void getClassLoaderForWrapperType() { assertThat(Byte.class.getClassLoader()).isNull(); ClassLoader classLoader = ClassLoaderUtils.getClassLoader(Byte.class); assertThat(classLoader).isSameAs(getClass().getClassLoader()); } @Test void getClassLoaderForVoidType() { assertThat(void.class.getClassLoader()).isNull(); ClassLoader classLoader = ClassLoaderUtils.getClassLoader(void.class); assertThat(classLoader).isSameAs(getClass().getClassLoader()); } @Test void getClassLoaderForTestClass() { assertThat(getClass().getClassLoader()).isNotNull(); ClassLoader classLoader = ClassLoaderUtils.getClassLoader(getClass()); assertThat(classLoader).isSameAs(getClass().getClassLoader()); } @Test void getClassLoaderForClassInDifferentClassLoader() throws Exception { try (var testClassLoader = TestClassLoader.forClasses(getClass())) { var testClass = testClassLoader.loadClass(getClass().getName()); assertThat(testClass.getClassLoader()).isSameAs(testClassLoader); var classLoader = ClassLoaderUtils.getClassLoader(testClass); assertThat(classLoader).isSameAs(testClassLoader); } } @Test void getDefaultClassLoaderWithExplicitContextClassLoader() { var original = Thread.currentThread().getContextClassLoader(); var mock = mock(ClassLoader.class); Thread.currentThread().setContextClassLoader(mock); try { assertSame(mock, ClassLoaderUtils.getDefaultClassLoader()); } finally { Thread.currentThread().setContextClassLoader(original); } } @Test void getDefaultClassLoaderWithNullContextClassLoader() { var original = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(null); try { assertSame(ClassLoader.getSystemClassLoader(), ClassLoaderUtils.getDefaultClassLoader()); } finally { Thread.currentThread().setContextClassLoader(original); } } @SuppressWarnings("DataFlowIssue") @Test void getLocationFromNullFails() { assertPreconditionViolationNotNullFor("object", () -> ClassLoaderUtils.getLocation(null)); } @Test void getLocationFromVariousObjectsArePresent() { assertTrue(ClassLoaderUtils.getLocation(getClass()).isPresent()); assertTrue(ClassLoaderUtils.getLocation(this).isPresent()); assertTrue(ClassLoaderUtils.getLocation("").isPresent()); assertTrue(ClassLoaderUtils.getLocation(0).isPresent()); assertTrue(ClassLoaderUtils.getLocation(Thread.State.RUNNABLE).isPresent()); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/util/ClassNamePatternFilterUtilsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static org.assertj.core.api.Assertions.assertThat; import java.util.List; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.commons.util.classes.AExecutionConditionClass; import org.junit.platform.commons.util.classes.ATestExecutionListenerClass; import org.junit.platform.commons.util.classes.AVanillaEmpty; import org.junit.platform.commons.util.classes.BExecutionConditionClass; import org.junit.platform.commons.util.classes.BTestExecutionListenerClass; import org.junit.platform.commons.util.classes.BVanillaEmpty; import org.junit.platform.launcher.TestExecutionListener; /** * Unit tests for {@link ClassNamePatternFilterUtils}. * * @since 1.7 */ @TestInstance(Lifecycle.PER_CLASS) class ClassNamePatternFilterUtilsTests { //@formatter:off @ValueSource(strings = { "org.junit.jupiter.*", "org.junit.platform.*.NonExistentClass", "*.NonExistentClass*", "*NonExistentClass*", "AExecutionConditionClass, BExecutionConditionClass" }) //@formatter:on @ParameterizedTest void neverExcludedConditions(String pattern) { List executionConditions = List.of(new AExecutionConditionClass(), new BExecutionConditionClass()); assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.excludeMatchingClasses(pattern)) // .hasSize(2); } //@formatter:off @ValueSource(strings = { "org.junit.platform.*", "*.platform.*", "*", "*AExecutionConditionClass, *BExecutionConditionClass", "*ExecutionConditionClass" }) //@formatter:on @ParameterizedTest void alwaysExcludedConditions(String pattern) { List executionConditions = List.of(new AExecutionConditionClass(), new BExecutionConditionClass()); assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.excludeMatchingClasses(pattern)) // .isEmpty(); } //@formatter:off @ValueSource(strings = { "org.junit.jupiter.*", "org.junit.platform.*.NonExistentClass", "*.NonExistentClass*", "*NonExistentClass*", "ATestExecutionListenerClass, BTestExecutionListenerClass" }) //@formatter:on @ParameterizedTest void neverExcludedListeners(String pattern) { List executionConditions = List.of(new ATestExecutionListenerClass(), new BTestExecutionListenerClass()); assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.excludeMatchingClasses(pattern)) // .hasSize(2); } //@formatter:off @ValueSource(strings = { "org.junit.platform.*", "*.platform.*", "*", "*ATestExecutionListenerClass, *BTestExecutionListenerClass", "*TestExecutionListenerClass" }) //@formatter:on @ParameterizedTest void alwaysExcludedListeners(String pattern) { List executionConditions = List.of(new ATestExecutionListenerClass(), new BTestExecutionListenerClass()); assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.excludeMatchingClasses(pattern)) // .isEmpty(); } //@formatter:off @ValueSource(strings = { "org.junit.jupiter.*", "org.junit.platform.*.NonExistentClass", "*.NonExistentClass*", "*NonExistentClass*", "AVanillaEmpty, BVanillaEmpty" }) //@formatter:on @ParameterizedTest void neverExcludedClass(String pattern) { var executionConditions = List.of(new AVanillaEmpty(), new BVanillaEmpty()); assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.excludeMatchingClasses(pattern)) // .hasSize(2); } //@formatter:off @ValueSource(strings = { "org.junit.platform.*", "*.platform.*", "*", "*AVanillaEmpty, *BVanillaEmpty", "*VanillaEmpty" }) //@formatter:on @ParameterizedTest void alwaysExcludedClass(String pattern) { var executionConditions = List.of(new AVanillaEmpty(), new BVanillaEmpty()); assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.excludeMatchingClasses(pattern)) // .isEmpty(); } //@formatter:off @ValueSource(strings = { "org.junit.jupiter.*", "org.junit.platform.*.NonExistentClass", "*.NonExistentClass*", "*NonExistentClass*", "AVanillaEmpty, BVanillaEmpty" }) //@formatter:on @ParameterizedTest void neverExcludedClassName(String pattern) { var executionConditions = List.of("org.junit.platform.commons.util.classes.AVanillaEmpty", "org.junit.platform.commons.util.classes.BVanillaEmpty"); assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.excludeMatchingClassNames(pattern)) // .hasSize(2); } //@formatter:off @ValueSource(strings = { "org.junit.platform.*", "*.platform.*", "*", "*AVanillaEmpty, *BVanillaEmpty", "*VanillaEmpty" }) //@formatter:on @ParameterizedTest void alwaysExcludedClassName(String pattern) { var executionConditions = List.of("org.junit.platform.commons.util.classes.AVanillaEmpty", "org.junit.platform.commons.util.classes.BVanillaEmpty"); assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.excludeMatchingClassNames(pattern)) // .isEmpty(); } //@formatter:off @ValueSource(strings = { "org.junit.jupiter.*", "org.junit.platform.*.NonExistentClass", "*.NonExistentClass*", "*NonExistentClass*", "AExecutionConditionClass, BExecutionConditionClass" }) //@formatter:on @ParameterizedTest void neverIncludedConditions(String pattern) { List executionConditions = List.of(new AExecutionConditionClass(), new BExecutionConditionClass()); assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.includeMatchingClasses(pattern)) // .isEmpty(); } //@formatter:off @ValueSource(strings = { "org.junit.platform.*", "*.platform.*", "*", "*AExecutionConditionClass, *BExecutionConditionClass", "*ExecutionConditionClass" }) //@formatter:on @ParameterizedTest void alwaysIncludedConditions(String pattern) { List executionConditions = List.of(new AExecutionConditionClass(), new BExecutionConditionClass()); assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.includeMatchingClasses(pattern)) // .hasSize(2); } //@formatter:off @ValueSource(strings = { "org.junit.jupiter.*", "org.junit.platform.*.NonExistentClass", "*.NonExistentClass*", "*NonExistentClass*", "ATestExecutionListenerClass, BTestExecutionListenerClass" }) //@formatter:on @ParameterizedTest void neverIncludedListeners(String pattern) { List executionConditions = List.of(new ATestExecutionListenerClass(), new BTestExecutionListenerClass()); assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.includeMatchingClasses(pattern)) // .isEmpty(); } //@formatter:off @ValueSource(strings = { "org.junit.platform.*", "*.platform.*", "*", "*ATestExecutionListenerClass, *BTestExecutionListenerClass", "*TestExecutionListenerClass" }) //@formatter:on @ParameterizedTest void alwaysIncludedListeners(String pattern) { List executionConditions = List.of(new ATestExecutionListenerClass(), new BTestExecutionListenerClass()); assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.includeMatchingClasses(pattern)) // .hasSize(2); } //@formatter:off @ValueSource(strings = { "org.junit.jupiter.*", "org.junit.platform.*.NonExistentClass", "*.NonExistentClass*", "*NonExistentClass*", "AVanillaEmpty, BVanillaEmpty" }) //@formatter:on @ParameterizedTest void neverIncludedClass(String pattern) { var executionConditions = List.of(new AVanillaEmpty(), new BVanillaEmpty()); assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.includeMatchingClasses(pattern)) // .isEmpty(); } //@formatter:off @ValueSource(strings = { "org.junit.platform.*", "*.platform.*", "*", "*AVanillaEmpty, *BVanillaEmpty", "*VanillaEmpty" }) //@formatter:on @ParameterizedTest void alwaysIncludedClass(String pattern) { var executionConditions = List.of(new AVanillaEmpty(), new BVanillaEmpty()); assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.includeMatchingClasses(pattern)) // .hasSize(2); } //@formatter:off @ValueSource(strings = { "org.junit.jupiter.*", "org.junit.platform.*.NonExistentClass", "*.NonExistentClass*", "*NonExistentClass*", "AVanillaEmpty, BVanillaEmpty" }) //@formatter:on @ParameterizedTest void neverIncludedClassName(String pattern) { var executionConditions = List.of("org.junit.platform.commons.util.classes.AVanillaEmpty", "org.junit.platform.commons.util.classes.BVanillaEmpty"); assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.includeMatchingClassNames(pattern)) // .isEmpty(); } //@formatter:off @ValueSource(strings = { "org.junit.platform.*", "*.platform.*", "*", "*AVanillaEmpty, *BVanillaEmpty", "*VanillaEmpty" }) //@formatter:on @ParameterizedTest void alwaysIncludedClassName(String pattern) { var executionConditions = List.of("org.junit.platform.commons.util.classes.AVanillaEmpty", "org.junit.platform.commons.util.classes.BVanillaEmpty"); assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.includeMatchingClassNames(pattern)) // .hasSize(2); } //@formatter:off @ValueSource(strings = { "org.junit.platform.*", "*.platform.*", "*", "*AVanillaEmpty, *BVanillaEmpty", "*VanillaEmpty" }) //@formatter:on @ParameterizedTest void includeAndExcludeSame(String pattern) { var executionConditions = List.of("org.junit.platform.commons.util.classes.AVanillaEmpty", "org.junit.platform.commons.util.classes.BVanillaEmpty"); assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.includeMatchingClassNames(pattern)) // .hasSize(2); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/util/ClassUtilsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.platform.commons.util.ClassUtils.nullSafeToString; import org.junit.jupiter.api.Test; /** * Unit tests for {@link ClassUtils}. * * @since 1.0 */ class ClassUtilsTests { @Test void nullSafeToStringWithDefaultMapper() { assertEquals("", nullSafeToString((Class[]) null)); assertEquals("", nullSafeToString()); assertEquals("java.lang.String", nullSafeToString(String.class)); assertEquals("java.lang.String, java.lang.Integer", nullSafeToString(String.class, Integer.class)); assertEquals("java.lang.String, null, java.lang.Integer", nullSafeToString(String.class, null, Integer.class)); } @Test void nullSafeToStringWithCustomMapper() { assertEquals("", nullSafeToString(Class::getSimpleName, (Class[]) null)); assertEquals("", nullSafeToString(Class::getSimpleName)); assertEquals("String", nullSafeToString(Class::getSimpleName, String.class)); assertEquals("String, Integer", nullSafeToString(Class::getSimpleName, String.class, Integer.class)); assertEquals("String, null, Integer", nullSafeToString(Class::getSimpleName, String.class, null, Integer.class)); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/util/CloseablePathTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.platform.commons.util.CloseablePath.JAR_URI_SCHEME; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.only; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import java.net.URI; import java.nio.file.FileSystem; import java.nio.file.FileSystemNotFoundException; import java.nio.file.FileSystems; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.platform.commons.test.ConcurrencyTestingUtils; import org.junit.platform.commons.util.CloseablePath.FileSystemProvider; import org.junit.platform.engine.support.hierarchical.OpenTest4JAwareThrowableCollector; class CloseablePathTests { URI uri; URI jarUri; List paths = new ArrayList<>(); @BeforeEach void createUris() throws Exception { uri = getClass().getResource("/jartest.jar").toURI(); jarUri = URI.create(JAR_URI_SCHEME + ':' + uri); } @AfterEach void closeAllPaths() { closeAll(paths); } @Test void parsesJarUri() throws Exception { FileSystemProvider fileSystemProvider = mock(); FileSystem fileSystem = mock(); when(fileSystemProvider.newFileSystem(any())).thenReturn(fileSystem); URI jarFileWithEntry = URI.create("jar:file:/example.jar!/com/example/Example.class"); CloseablePath.create(jarFileWithEntry, fileSystemProvider).close(); URI jarFileUri = URI.create("jar:file:/example.jar"); verify(fileSystemProvider).newFileSystem(jarFileUri); verifyNoMoreInteractions(fileSystemProvider); } @Test void parsesRecursiveJarUri() throws Exception { FileSystemProvider fileSystemProvider = mock(); FileSystem fileSystem = mock(); when(fileSystemProvider.newFileSystem(any())).thenReturn(fileSystem); URI jarNestedFileWithEntry = URI.create( "jar:nested:file:/example.jar!/BOOT-INF/classes!/com/example/Example.class"); CloseablePath.create(jarNestedFileWithEntry, fileSystemProvider).close(); URI jarNestedFile = URI.create("jar:nested:file:/example.jar!/BOOT-INF/classes"); verify(fileSystemProvider).newFileSystem(jarNestedFile); verifyNoMoreInteractions(fileSystemProvider); } @Test void createsAndClosesJarFileSystemOnceWhenCalledConcurrently() throws Exception { var numThreads = 50; FileSystemProvider fileSystemProvider = mock(); when(fileSystemProvider.newFileSystem(any())) // .thenAnswer(invocation -> FileSystems.newFileSystem((URI) invocation.getArgument(0), Map.of())); paths = ConcurrencyTestingUtils.executeConcurrently(numThreads, () -> CloseablePath.create(uri, fileSystemProvider)); verify(fileSystemProvider, only()).newFileSystem(jarUri); // Close all but the first path closeAll(paths.subList(1, numThreads)); assertDoesNotThrow(() -> FileSystems.getFileSystem(jarUri), "FileSystem should still be open"); // Close last remaining path paths.getFirst().close(); assertThrows(FileSystemNotFoundException.class, () -> FileSystems.getFileSystem(jarUri), "FileSystem should have been closed"); } @Test @SuppressWarnings("resource") void closingIsIdempotent() throws Exception { var path1 = CloseablePath.create(uri); paths.add(path1); var path2 = CloseablePath.create(uri); paths.add(path2); path1.close(); path1.close(); assertDoesNotThrow(() -> FileSystems.getFileSystem(jarUri), "FileSystem should still be open"); path2.close(); assertThrows(FileSystemNotFoundException.class, () -> FileSystems.getFileSystem(jarUri), "FileSystem should have been closed"); } private static void closeAll(List paths) { var throwableCollector = new OpenTest4JAwareThrowableCollector(); paths.forEach(closeablePath -> throwableCollector.execute(closeablePath::close)); throwableCollector.assertEmpty(); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/util/CollectionUtilsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullFor; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.Spliterator; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.DoubleStream; import java.util.stream.IntStream; import java.util.stream.LongStream; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.converter.ArgumentConversionException; import org.junit.jupiter.params.converter.ArgumentConverter; import org.junit.jupiter.params.converter.ConvertWith; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; /** * Unit tests for {@link CollectionUtils}. * * @since 1.0 */ class CollectionUtilsTests { @Nested class OnlyElement { @SuppressWarnings("DataFlowIssue") @Test void nullCollection() { assertPreconditionViolationNotNullFor("collection", () -> CollectionUtils.getOnlyElement(null)); } @Test void emptyCollection() { assertPreconditionViolationFor(() -> CollectionUtils.getOnlyElement(Set.of()))// .withMessage("collection must contain exactly one element: []"); } @Test void singleElementCollection() { var expected = new Object(); var actual = CollectionUtils.getOnlyElement(Set.of(expected)); assertSame(expected, actual); } @Test void multiElementCollection() { assertPreconditionViolationFor(() -> CollectionUtils.getOnlyElement(List.of("foo", "bar")))// .withMessage("collection must contain exactly one element: [foo, bar]"); } } @Nested class FirstElement { @SuppressWarnings("DataFlowIssue") @Test void nullCollection() { assertPreconditionViolationNotNullFor("collection", () -> CollectionUtils.getFirstElement(null)); } @Test void emptyCollection() { assertThat(CollectionUtils.getFirstElement(Set.of())).isEmpty(); } @Test void singleElementCollection() { var expected = new Object(); assertThat(CollectionUtils.getFirstElement(Set.of(expected))).containsSame(expected); } @Test void multiElementCollection() { assertThat(CollectionUtils.getFirstElement(List.of("foo", "bar"))).contains("foo"); } @Test void collectionWithNullValues() { assertThat(CollectionUtils.getFirstElement(Arrays.asList(new Object[1]))).isEmpty(); } } @Nested class StreamConversion { @ParameterizedTest @ValueSource(classes = { // Stream.class, // DoubleStream.class, // IntStream.class, // LongStream.class, // Collection.class, // Iterable.class, // Iterator.class, // IteratorProvider.class, // Object[].class, // String[].class, // int[].class, // double[].class, // char[].class // }) void isConvertibleToStreamForSupportedTypes(Class type) { assertThat(CollectionUtils.isConvertibleToStream(type)).isTrue(); } @ParameterizedTest @MethodSource("objectsConvertibleToStreams") void isConvertibleToStreamForSupportedTypesFromObjects(Object object) { assertThat(CollectionUtils.isConvertibleToStream(object.getClass())).isTrue(); } static Stream objectsConvertibleToStreams() { return Stream.of(// Stream.of("cat", "dog"), // DoubleStream.of(42.3), // IntStream.of(99), // LongStream.of(100_000_000), // Set.of(1, 2, 3), // Arguments.of((Object) new Object[] { 9, 8, 7 }), // new int[] { 5, 10, 15 }, // new IteratorProvider(1, 2, 3, 4, 5)// ); } @ParameterizedTest @ValueSource(classes = { // void.class, // Void.class, // Object.class, // Integer.class, // String.class, // UnusableIteratorProvider.class, // Spliterator.class, // int.class, // boolean.class // }) void isConvertibleToStreamForUnsupportedTypes(Class type) { assertThat(CollectionUtils.isConvertibleToStream(type)).isFalse(); } @Test void isConvertibleToStreamForNull() { assertThat(CollectionUtils.isConvertibleToStream(null)).isFalse(); } @SuppressWarnings("DataFlowIssue") @Test void toStreamWithNull() { assertPreconditionViolationNotNullFor("Object", () -> CollectionUtils.toStream(null)); } @Test void toStreamWithUnsupportedObjectType() { assertPreconditionViolationFor(() -> CollectionUtils.toStream("unknown"))// .withMessage("Cannot convert instance of java.lang.String into a Stream: unknown"); } @Test void toStreamWithExistingStream() { var input = Stream.of("foo"); var result = CollectionUtils.toStream(input); assertThat(result).isSameAs(input); } @Test @SuppressWarnings("unchecked") void toStreamWithDoubleStream() { var input = DoubleStream.of(42.23); var result = (Stream) CollectionUtils.toStream(input); assertThat(result).containsExactly(42.23); } @Test @SuppressWarnings("unchecked") void toStreamWithIntStream() { var input = IntStream.of(23, 42); var result = (Stream) CollectionUtils.toStream(input); assertThat(result).containsExactly(23, 42); } @Test @SuppressWarnings("unchecked") void toStreamWithLongStream() { var input = LongStream.of(23L, 42L); var result = (Stream) CollectionUtils.toStream(input); assertThat(result).containsExactly(23L, 42L); } @Test @SuppressWarnings({ "unchecked", "serial" }) void toStreamWithCollection() { var collectionStreamClosed = new AtomicBoolean(false); var input = new ArrayList<>(List.of("foo", "bar")) { @Override public Stream stream() { return super.stream().onClose(() -> collectionStreamClosed.set(true)); } }; try (var stream = (Stream) CollectionUtils.toStream(input)) { var result = stream.toList(); assertThat(result).containsExactly("foo", "bar"); } assertThat(collectionStreamClosed.get()).describedAs("collectionStreamClosed").isTrue(); } @Test @SuppressWarnings("unchecked") void toStreamWithIterable() { Iterable input = () -> List.of("foo", "bar").iterator(); var result = (Stream) CollectionUtils.toStream(input); assertThat(result).containsExactly("foo", "bar"); } @Test @SuppressWarnings("unchecked") void toStreamWithIterator() { var input = List.of("foo", "bar").iterator(); var result = (Stream) CollectionUtils.toStream(input); assertThat(result).containsExactly("foo", "bar"); } @Test @SuppressWarnings("unchecked") void toStreamWithIteratorProvider() { var input = new IteratorProvider("foo", "bar"); var result = (Stream) CollectionUtils.toStream(input); assertThat(result).containsExactly("foo", "bar"); } @Test void throwWhenIteratorNamedMethodDoesNotReturnAnIterator() { var o = new UnusableIteratorProvider("Test"); assertPreconditionViolationFor(() -> CollectionUtils.toStream(o))// .withMessage("Cannot convert instance of %s into a Stream: %s".formatted( UnusableIteratorProvider.class.getName(), o)); } @Test @SuppressWarnings("unchecked") void toStreamWithArray() { var result = (Stream) CollectionUtils.toStream(new String[] { "foo", "bar" }); assertThat(result).containsExactly("foo", "bar"); } @TestFactory Stream toStreamWithPrimitiveArrays() { //@formatter:off return Stream.of( dynamicTest("boolean[]", () -> toStreamWithPrimitiveArray(new boolean[] { true, false })), dynamicTest("byte[]", () -> toStreamWithPrimitiveArray(new byte[] { 0, Byte.MIN_VALUE, Byte.MAX_VALUE })), dynamicTest("char[]", () -> toStreamWithPrimitiveArray(new char[] { 0, Character.MIN_VALUE, Character.MAX_VALUE })), dynamicTest("double[]", () -> toStreamWithPrimitiveArray(new double[] { 0, Double.MIN_VALUE, Double.MAX_VALUE })), dynamicTest("float[]", () -> toStreamWithPrimitiveArray(new float[] { 0, Float.MIN_VALUE, Float.MAX_VALUE })), dynamicTest("int[]", () -> toStreamWithPrimitiveArray(new int[] { 0, Integer.MIN_VALUE, Integer.MAX_VALUE })), dynamicTest("long[]", () -> toStreamWithPrimitiveArray(new long[] { 0, Long.MIN_VALUE, Long.MAX_VALUE })), dynamicTest("short[]", () -> toStreamWithPrimitiveArray(new short[] { 0, Short.MIN_VALUE, Short.MAX_VALUE })) ); //@formatter:on } private void toStreamWithPrimitiveArray(Object primitiveArray) { assertTrue(primitiveArray.getClass().isArray()); assertTrue(primitiveArray.getClass().getComponentType().isPrimitive()); var result = CollectionUtils.toStream(primitiveArray).toArray(); for (var i = 0; i < result.length; i++) { assertEquals(Array.get(primitiveArray, i), result[i]); } } } @Nested class ReverseOrderIteration { @ParameterizedTest @CsvSource(delimiter = '|', nullValues = "N/A", textBlock = """ foo,bar,baz | baz,bar,foo foo,bar | bar,foo foo | foo N/A | N/A """) void iteratesListElementsInReverseOrder(@ConvertWith(CommaSeparator.class) List input, @ConvertWith(CommaSeparator.class) List expected) { var result = new ArrayList<>(); CollectionUtils.forEachInReverseOrder(input, result::add); assertEquals(expected, result); } private static class CommaSeparator implements ArgumentConverter { @Override public Object convert(@Nullable Object source, ParameterContext context) throws ArgumentConversionException { return source == null ? List.of() : List.of(((String) source).split(",")); } } } /** * An interface that has a method with name 'iterator', returning a java.util/Iterator as a return type */ private record IteratorProvider(Object... elements) { @SuppressWarnings("unused") Iterator iterator() { return Arrays.stream(elements).iterator(); } } /** * An interface that has a method with name 'iterator', but does not return java.util/Iterator as a return type */ private record UnusableIteratorProvider(Object... elements) { @SuppressWarnings("unused") Object iterator() { return Arrays.stream(elements).iterator(); } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/util/DefaultClasspathScannerTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assumptions.assumeFalse; import static org.junit.platform.commons.test.ConcurrencyTestingUtils.executeConcurrently; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import java.io.IOException; import java.io.InputStream; import java.lang.module.ModuleFinder; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLClassLoader; import java.nio.charset.StandardCharsets; import java.nio.file.FileSystemNotFoundException; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.Set; import java.util.function.BiFunction; import java.util.function.Predicate; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.spi.ToolProvider; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.DisabledInEclipse; import org.junit.jupiter.api.fixtures.TrackLogRecords; import org.junit.jupiter.api.io.TempDir; import org.junit.platform.commons.function.Try; import org.junit.platform.commons.io.Resource; import org.junit.platform.commons.io.ResourceFilter; import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.commons.support.scanning.ClassFilter; /** * Unit tests for {@link DefaultClasspathScanner}. * * @since 1.0 */ @TrackLogRecords class DefaultClasspathScannerTests { private static final ClassFilter allClasses = ClassFilter.of(__ -> true); private static final ResourceFilter allResources = ResourceFilter.of(__ -> true); private final List> loadedClasses = new ArrayList<>(); private final BiFunction>> trackingClassLoader = (name, classLoader) -> ReflectionUtils.tryToLoadClass(name, classLoader).ifSuccess(loadedClasses::add); private final DefaultClasspathScanner classpathScanner = new DefaultClasspathScanner( ClassLoaderUtils::getDefaultClassLoader, trackingClassLoader); @Test void scanForClassesInClasspathRootWhenMalformedClassnameInternalErrorOccursWithNullDetailedMessage( LogRecordListener listener) throws Exception { Predicate> malformedClassNameSimulationFilter = clazz -> { if (clazz.getSimpleName().equals(ClassForMalformedClassNameSimulation.class.getSimpleName())) { throw new InternalError(); } return true; }; assertClassesScannedWhenExceptionIsThrown(malformedClassNameSimulationFilter); assertDebugMessageLogged(listener, "Failed to load .+ during classpath scanning."); } @Test void scanForClassesInClasspathRootWhenMalformedClassnameInternalErrorOccurs(LogRecordListener listener) throws Exception { Predicate> malformedClassNameSimulationFilter = clazz -> { if (clazz.getSimpleName().equals(ClassForMalformedClassNameSimulation.class.getSimpleName())) { throw new InternalError("Malformed class name"); } return true; }; assertClassesScannedWhenExceptionIsThrown(malformedClassNameSimulationFilter); assertDebugMessageLogged(listener, "The java.lang.Class loaded from path .+ has a malformed class name .+"); } @Test void scanForClassesInClasspathRootWhenOtherInternalErrorOccurs(LogRecordListener listener) throws Exception { Predicate> otherInternalErrorSimulationFilter = clazz -> { if (clazz.getSimpleName().equals(ClassForOtherInternalErrorSimulation.class.getSimpleName())) { throw new InternalError("other internal error"); } return true; }; assertClassesScannedWhenExceptionIsThrown(otherInternalErrorSimulationFilter); assertDebugMessageLogged(listener, "Failed to load .+ during classpath scanning."); } @Test void scanForClassesInClasspathRootWhenGenericRuntimeExceptionOccurs(LogRecordListener listener) throws Exception { Predicate> runtimeExceptionSimulationFilter = clazz -> { if (clazz.getSimpleName().equals(ClassForGenericRuntimeExceptionSimulation.class.getSimpleName())) { throw new RuntimeException("a generic exception"); } return true; }; assertClassesScannedWhenExceptionIsThrown(runtimeExceptionSimulationFilter); assertDebugMessageLogged(listener, "Failed to load .+ during classpath scanning."); } private void assertClassesScannedWhenExceptionIsThrown(Predicate> filter) throws Exception { var classFilter = ClassFilter.of(filter); var classes = this.classpathScanner.scanForClassesInClasspathRoot(getTestClasspathRoot(), classFilter); assertThat(classes).hasSizeGreaterThanOrEqualTo(150); } @Test void scanForResourcesInClasspathRootWhenGenericRuntimeExceptionOccurs(LogRecordListener listener) { Predicate runtimeExceptionSimulationFilter = resource -> { if (resource.getName().equals("org/junit/platform/commons/other-example.resource")) { throw new RuntimeException("a generic exception"); } return true; }; assertResourcesScannedWhenExceptionIsThrown(runtimeExceptionSimulationFilter); assertDebugMessageLogged(listener, "Failed to load .+ during classpath scanning."); } private void assertResourcesScannedWhenExceptionIsThrown(Predicate filter) { var resources = this.classpathScanner.scanForResourcesInClasspathRoot(getTestClasspathResourceRoot(), ResourceFilter.of(filter)); assertThat(resources).hasSizeGreaterThanOrEqualTo(150); } private void assertDebugMessageLogged(LogRecordListener listener, String regex) { // @formatter:off assertThat(listener.stream(DefaultClasspathScanner.class, Level.FINE) .map(LogRecord::getMessage) .filter(m -> m.matches(regex)) ).hasSize(1); // @formatter:on } @Test void scanForClassesInClasspathRootWhenOutOfMemoryErrorOccurs() { Predicate> outOfMemoryErrorSimulationFilter = clazz -> { if (clazz.getSimpleName().equals(ClassForOutOfMemoryErrorSimulation.class.getSimpleName())) { throw new OutOfMemoryError(); } return true; }; var classFilter = ClassFilter.of(outOfMemoryErrorSimulationFilter); assertThrows(OutOfMemoryError.class, () -> this.classpathScanner.scanForClassesInClasspathRoot(getTestClasspathRoot(), classFilter)); } @Test void scanForClassesInClasspathRootWithinJarFile() throws Exception { scanForClassesInClasspathRootWithinJarFile("/jartest.jar"); } @Test void scanForClassesInClasspathRootWithinJarWithSpacesInPath() throws Exception { scanForClassesInClasspathRootWithinJarFile("/folder with spaces/jar test with spaces.jar"); } private void scanForClassesInClasspathRootWithinJarFile(String resourceName) throws Exception { var jarfile = requireNonNull(getClass().getResource(resourceName)); try (var classLoader = new URLClassLoader(new URL[] { jarfile }, null)) { var classpathScanner = new DefaultClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass); var classes = classpathScanner.scanForClassesInClasspathRoot(jarfile.toURI(), allClasses); assertThat(classes).extracting(Class::getName) // .containsExactlyInAnyOrder("org.junit.platform.jartest.notincluded.NotIncluded", "org.junit.platform.jartest.included.recursive.RecursivelyIncluded", "org.junit.platform.jartest.included.Included"); } } @Test void scanForResourcesInClasspathRootWithinJarFile() throws Exception { scanForResourcesInClasspathRootWithinJarFile("/jartest.jar"); } @Test void scanForResourcesInClasspathRootWithinJarWithSpacesInPath() throws Exception { scanForResourcesInClasspathRootWithinJarFile("/folder with spaces/jar test with spaces.jar"); } private void scanForResourcesInClasspathRootWithinJarFile(String resourceName) throws Exception { var jarfile = requireNonNull(getClass().getResource(resourceName)); try (var classLoader = new URLClassLoader(new URL[] { jarfile }, null)) { var classpathScanner = new DefaultClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass); var resources = classpathScanner.scanForResourcesInClasspathRoot(jarfile.toURI(), allResources); assertThat(resources).extracting(Resource::getName) // .containsExactlyInAnyOrder("org/junit/platform/jartest/notincluded/not-included.resource", "org/junit/platform/jartest/included/included.resource", "org/junit/platform/jartest/included/recursive/recursively-included.resource", "META-INF/MANIFEST.MF"); } } @Test void scanForResourcesInShadowedClassPathRoot() throws Exception { var jarFile = requireNonNull(getClass().getResource("/jartest.jar")); var shadowedJarFile = requireNonNull(getClass().getResource("/jartest-shadowed.jar")); try (var classLoader = new URLClassLoader(new URL[] { jarFile, shadowedJarFile }, null)) { var classpathScanner = new DefaultClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass); var resources = classpathScanner.scanForResourcesInClasspathRoot(shadowedJarFile.toURI(), allResources); assertThat(resources).extracting(Resource::getName).containsExactlyInAnyOrder( "org/junit/platform/jartest/included/unique.resource", // "org/junit/platform/jartest/included/included.resource", // "org/junit/platform/jartest/included/recursive/recursively-included.resource", // "META-INF/MANIFEST.MF"); assertThat(resources).extracting(Resource::getUri) // .map(DefaultClasspathScannerTests::jarFileAndEntry) // .containsExactlyInAnyOrder( // This resource only exists in the shadowed jar file "jartest-shadowed.jar!/org/junit/platform/jartest/included/unique.resource", // These resources exist in both the jar and shadowed jar file. // They must be discovered in the shadowed jar as we're searching in that classpath root. "jartest-shadowed.jar!/org/junit/platform/jartest/included/included.resource", "jartest-shadowed.jar!/org/junit/platform/jartest/included/recursive/recursively-included.resource", "jartest-shadowed.jar!/META-INF/MANIFEST.MF"); } } @Test void scanForResourcesInPackageWithDuplicateResources() throws Exception { var jarFile = requireNonNull(getClass().getResource("/jartest.jar")); var shadowedJarFile = requireNonNull(getClass().getResource("/jartest-shadowed.jar")); try (var classLoader = new URLClassLoader(new URL[] { jarFile, shadowedJarFile }, null)) { var classpathScanner = new DefaultClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass); var resources = classpathScanner.scanForResourcesInPackage("org.junit.platform.jartest.included", allResources); assertThat(resources).extracting(Resource::getUri) // .map(DefaultClasspathScannerTests::jarFileAndEntry) // .containsExactlyInAnyOrder( // This resource only exists in the shadowed jar file "jartest-shadowed.jar!/org/junit/platform/jartest/included/unique.resource", // These resources exist in both the jar and shadowed jar file. "jartest.jar!/org/junit/platform/jartest/included/included.resource", "jartest-shadowed.jar!/org/junit/platform/jartest/included/included.resource", "jartest.jar!/org/junit/platform/jartest/included/recursive/recursively-included.resource", "jartest-shadowed.jar!/org/junit/platform/jartest/included/recursive/recursively-included.resource"); } } private static String jarFileAndEntry(URI uri) { var uriString = uri.toString(); int lastJarUriSeparator = uriString.lastIndexOf("!/"); var jarUri = uriString.substring(0, lastJarUriSeparator); var jarEntry = uriString.substring(lastJarUriSeparator + 1); var fileName = jarUri.substring(jarUri.lastIndexOf("/") + 1); return fileName + "!" + jarEntry; } @Test void scanForClassesInPackage() { var classes = classpathScanner.scanForClassesInPackage("org.junit.platform.commons", allClasses); assertThat(classes).hasSizeGreaterThanOrEqualTo(20); assertTrue(classes.contains(NestedClassToBeFound.class)); assertTrue(classes.contains(MemberClassToBeFound.class)); } @Test void scanForResourcesInPackage() { var resources = classpathScanner.scanForResourcesInPackage("org.junit.platform.commons", allResources); assertThat(resources).extracting(Resource::getUri).containsExactlyInAnyOrder( uriOf("/org/junit/platform/commons/example.resource"), uriOf("/org/junit/platform/commons/other-example.resource")); } @Test // #2500 @DisabledInEclipse void scanForClassesInPackageWithinModulesSharingNamePrefix(@TempDir Path temp) throws Exception { var moduleSourcePath = Path.of(requireNonNull(getClass().getResource("/modules-2500/")).toURI()).toString(); run("javac", "--module", "foo,foo.bar", "--module-source-path", moduleSourcePath, "-d", temp.toString()); checkModules2500(ModuleFinder.of(temp)); // exploded modules var foo = temp.resolve("foo.jar"); var bar = temp.resolve("foo.bar.jar"); run("jar", "--create", "--file", foo.toString(), "-C", temp.resolve("foo").toString(), "."); run("jar", "--create", "--file", bar.toString(), "-C", temp.resolve("foo.bar").toString(), "."); checkModules2500(ModuleFinder.of(foo, bar)); // jarred modules System.gc(); // required on Windows in order to release JAR file handles } private static void run(String tool, String... args) { ToolProvider.findFirst(tool).orElseThrow().run(System.out, System.err, args); } private void checkModules2500(ModuleFinder finder) { var root = "foo.bar"; var before = ModuleFinder.of(); var boot = ModuleLayer.boot(); var configuration = boot.configuration().resolve(before, finder, Set.of(root)); var parent = ClassLoader.getPlatformClassLoader(); var layer = ModuleLayer.defineModulesWithOneLoader(configuration, List.of(boot), parent).layer(); var classpathScanner = new DefaultClasspathScanner(() -> layer.findLoader(root), ReflectionUtils::tryToLoadClass); { var classes = classpathScanner.scanForClassesInPackage("foo", allClasses); var classNames = classes.stream().map(Class::getName).toList(); assertThat(classNames).hasSize(2).contains("foo.Foo", "foo.bar.FooBar"); } { var classes = classpathScanner.scanForClassesInPackage("foo.bar", allClasses); var classNames = classes.stream().map(Class::getName).toList(); assertThat(classNames).hasSize(1).contains("foo.bar.FooBar"); } } @Test void findAllClassesInPackageWithinJarFileConcurrently() throws Exception { var jarFile = getClass().getResource("/jartest.jar"); var jarUri = URI.create("jar:" + jarFile); try (var classLoader = new URLClassLoader(new URL[] { jarFile })) { var classpathScanner = new DefaultClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass); var results = executeConcurrently(10, () -> classpathScanner.scanForClassesInPackage("org.junit.platform.jartest.included", allClasses)); assertThrows(FileSystemNotFoundException.class, () -> FileSystems.getFileSystem(jarUri), "FileSystem should be closed"); results.forEach(classes -> assertThat(classes) // .hasSize(2) // .extracting(Class::getSimpleName) // .containsExactlyInAnyOrder("Included", "RecursivelyIncluded")); } } @Test void findAllResourcesInPackageWithinJarFileConcurrently() throws Exception { var jarFile = getClass().getResource("/jartest.jar"); var jarUri = URI.create("jar:" + jarFile); try (var classLoader = new URLClassLoader(new URL[] { jarFile })) { var classpathScanner = new DefaultClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass); var results = executeConcurrently(10, () -> classpathScanner.scanForResourcesInPackage("org.junit.platform.jartest.included", allResources)); assertThrows(FileSystemNotFoundException.class, () -> FileSystems.getFileSystem(jarUri), "FileSystem should be closed"); // @formatter:off results.forEach(resources -> assertThat(resources) .hasSize(2) .extracting(Resource::getName).containsExactlyInAnyOrder( "org/junit/platform/jartest/included/included.resource", "org/junit/platform/jartest/included/recursive/recursively-included.resource" )); // @formatter:on } } @Test void scanForClassesInDefaultPackage() { var classFilter = ClassFilter.of(this::inDefaultPackage); var classes = classpathScanner.scanForClassesInPackage("", classFilter); assertThat(classes).as("number of classes found in default package").isNotEmpty(); assertTrue(classes.stream().allMatch(this::inDefaultPackage)); assertTrue(classes.stream().anyMatch(clazz -> "DefaultPackageTestCase".equals(clazz.getName()))); } @Test void scanForResourcesInDefaultPackage() { var resourceFilter = ResourceFilter.of(this::inDefaultPackage); var resources = classpathScanner.scanForResourcesInPackage("", resourceFilter); assertThat(resources).as("number of resources found in default package").isNotEmpty(); assertTrue(resources.stream().allMatch(this::inDefaultPackage)); assertTrue(resources.stream().anyMatch(resource -> "default-package.resource".equals(resource.getName()))); } @Test void scanForClassesInPackageWithFilter() { var thisClassOnly = ClassFilter.of(clazz -> clazz == DefaultClasspathScannerTests.class); var classes = classpathScanner.scanForClassesInPackage("org.junit.platform.commons", thisClassOnly); assertSame(DefaultClasspathScannerTests.class, classes.getFirst()); } @Test void scanForResourcesInPackageWithFilter() { var thisResourceOnly = ResourceFilter.of( resource -> "org/junit/platform/commons/example.resource".equals(resource.getName())); var resources = classpathScanner.scanForResourcesInPackage("org.junit.platform.commons", thisResourceOnly); assertThat(resources).extracting(Resource::getName).containsExactly( "org/junit/platform/commons/example.resource"); } @Test void resourcesCanBeRead() throws Exception { var thisResourceOnly = ResourceFilter.of( resource -> "org/junit/platform/commons/example.resource".equals(resource.getName())); var resources = classpathScanner.scanForResourcesInPackage("org.junit.platform.commons", thisResourceOnly); Resource resource = resources.getFirst(); assertThat(resource.getName()).isEqualTo("org/junit/platform/commons/example.resource"); assertThat(resource.getUri()).isEqualTo(uriOf("/org/junit/platform/commons/example.resource")); try (InputStream is = resource.getInputStream()) { String contents = new String(is.readAllBytes(), StandardCharsets.UTF_8); assertThat(contents).isEqualTo("This file was unintentionally left blank.\n"); } } @SuppressWarnings("DataFlowIssue") @Test void scanForClassesInPackageForNullBasePackage() { assertPreconditionViolationFor(() -> classpathScanner.scanForClassesInPackage(null, allClasses)); } @SuppressWarnings("DataFlowIssue") @Test void scanForResourcesInPackageForNullBasePackage() { assertPreconditionViolationFor(() -> classpathScanner.scanForResourcesInPackage(null, allResources)); } @Test void scanForClassesInPackageForWhitespaceBasePackage() { assertPreconditionViolationFor(() -> classpathScanner.scanForClassesInPackage(" ", allClasses)); } @Test void scanForResourcesInPackageForWhitespaceBasePackage() { assertPreconditionViolationFor(() -> classpathScanner.scanForResourcesInPackage(" ", allResources)); } @SuppressWarnings("DataFlowIssue") @Test void scanForClassesInPackageForNullClassFilter() { assertPreconditionViolationFor( () -> classpathScanner.scanForClassesInPackage("org.junit.platform.commons", null)); } @Test void scanForClassesInPackageWhenIOExceptionOccurs() { var scanner = new DefaultClasspathScanner(ThrowingClassLoader::new, ReflectionUtils::tryToLoadClass); var classes = scanner.scanForClassesInPackage("org.junit.platform.commons", allClasses); assertThat(classes).isEmpty(); } @Test void scanForResourcesInPackageWhenIOExceptionOccurs() { var scanner = new DefaultClasspathScanner(ThrowingClassLoader::new, ReflectionUtils::tryToLoadClass); var classes = scanner.scanForResourcesInPackage("org.junit.platform.commons", allResources); assertThat(classes).isEmpty(); } @Test void scanForClassesInPackageOnlyLoadsClassesThatAreIncludedByTheClassNameFilter() { Predicate classNameFilter = name -> DefaultClasspathScannerTests.class.getName().equals(name); var classFilter = ClassFilter.of(classNameFilter, type -> true); classpathScanner.scanForClassesInPackage("org.junit.platform.commons", classFilter); assertThat(loadedClasses).containsExactly(DefaultClasspathScannerTests.class); } @Test void findAllClassesInClasspathRoot() throws Exception { var thisClassOnly = ClassFilter.of(clazz -> clazz == DefaultClasspathScannerTests.class); var root = getTestClasspathRoot(); var classes = classpathScanner.scanForClassesInClasspathRoot(root, thisClassOnly); assertSame(DefaultClasspathScannerTests.class, classes.getFirst()); } @Test void findAllClassesInDefaultPackageInClasspathRoot() throws Exception { var classFilter = ClassFilter.of(this::inDefaultPackage); var classes = classpathScanner.scanForClassesInClasspathRoot(getTestClasspathRoot(), classFilter); assertEquals(1, classes.size(), "number of classes found in default package"); var testClass = classes.getFirst(); assertTrue(inDefaultPackage(testClass)); assertEquals("DefaultPackageTestCase", testClass.getName()); } @Test void doesNotLoopInfinitelyWithCircularSymlinks(@TempDir Path tempDir) throws Exception { // Abort if running on Microsoft Windows since we are testing symbolic links assumeFalse(System.getProperty("os.name").toLowerCase().contains("win")); var directory = Files.createDirectory(tempDir.resolve("directory")); var symlink1 = Files.createSymbolicLink(tempDir.resolve("symlink1"), directory); Files.createSymbolicLink(directory.resolve("symlink2"), symlink1); var classes = classpathScanner.scanForClassesInClasspathRoot(symlink1.toUri(), allClasses); assertThat(classes).isEmpty(); } private boolean inDefaultPackage(Class clazz) { // OpenJDK returns NULL for the default package. var pkg = clazz.getPackage(); return pkg == null || clazz.getPackage().getName().isEmpty(); } private boolean inDefaultPackage(Resource resource) { return !resource.getName().contains("/"); } @Test void findAllClassesInClasspathRootWithFilter() throws Exception { var root = getTestClasspathRoot(); var classes = classpathScanner.scanForClassesInClasspathRoot(root, allClasses); assertThat(classes).hasSizeGreaterThanOrEqualTo(20); assertTrue(classes.contains(DefaultClasspathScannerTests.class)); } @SuppressWarnings("DataFlowIssue") @Test void findAllClassesInClasspathRootForNullRoot() { assertPreconditionViolationFor(() -> classpathScanner.scanForClassesInClasspathRoot(null, allClasses)); } @Test void findAllClassesInClasspathRootForNonExistingRoot() { assertPreconditionViolationFor( () -> classpathScanner.scanForClassesInClasspathRoot(Path.of("does_not_exist").toUri(), allClasses)); } @SuppressWarnings("DataFlowIssue") @Test void findAllClassesInClasspathRootForNullClassFilter() { assertPreconditionViolationFor( () -> classpathScanner.scanForClassesInClasspathRoot(getTestClasspathRoot(), null)); } @Test void onlyLoadsClassesInClasspathRootThatAreIncludedByTheClassNameFilter() throws Exception { var classFilter = ClassFilter.of(name -> DefaultClasspathScannerTests.class.getName().equals(name), type -> true); var root = getTestClasspathRoot(); classpathScanner.scanForClassesInClasspathRoot(root, classFilter); assertThat(loadedClasses).containsExactly(DefaultClasspathScannerTests.class); } private static URI uriOf(String name) { var resource = DefaultClasspathScannerTests.class.getResource(name); try { return requireNonNull(resource).toURI(); } catch (URISyntaxException e) { throw new RuntimeException(e); } } private URI getTestClasspathRoot() throws Exception { var location = getClass().getProtectionDomain().getCodeSource().getLocation(); return location.toURI(); } private URI getTestClasspathResourceRoot() { // Gradle puts classes and resources in different roots. var defaultPackageResource = "/default-package.resource"; var resourceUri = requireNonNull(getClass().getResource(defaultPackageResource)).toString(); return URI.create(resourceUri.substring(0, resourceUri.length() - defaultPackageResource.length())); } @SuppressWarnings("InnerClassMayBeStatic") class MemberClassToBeFound { } static class NestedClassToBeFound { } static class ClassForMalformedClassNameSimulation { } static class ClassForOtherInternalErrorSimulation { } static class ClassForGenericRuntimeExceptionSimulation { } static class ClassForOutOfMemoryErrorSimulation { } private static class ThrowingClassLoader extends ClassLoader { @Override public Enumeration getResources(String name) throws IOException { throw new IOException("Demo I/O error"); } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/util/ExceptionUtilsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.junit.platform.commons.util.ExceptionUtils.findNestedThrowables; import static org.junit.platform.commons.util.ExceptionUtils.pruneStackTrace; import static org.junit.platform.commons.util.ExceptionUtils.readStackTrace; import static org.junit.platform.commons.util.ExceptionUtils.throwAsUncheckedException; import java.io.IOException; import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.commons.JUnitException; /** * Unit tests for {@link ExceptionUtils}. * * @since 1.0 */ @SuppressWarnings("ThrowableNotThrown") class ExceptionUtilsTests { @SuppressWarnings("DataFlowIssue") @Test void throwAsUncheckedExceptionWithNullException() { assertPreconditionViolationFor(() -> throwAsUncheckedException(null)); } @Test void throwAsUncheckedExceptionWithCheckedException() { assertThrows(IOException.class, () -> throwAsUncheckedException(new IOException())); } @Test void throwAsUncheckedExceptionWithUncheckedException() { assertThrows(RuntimeException.class, () -> throwAsUncheckedException(new NumberFormatException())); } @SuppressWarnings("DataFlowIssue") @Test void readStackTraceForNullThrowable() { assertPreconditionViolationFor(() -> readStackTrace(null)); } @Test void readStackTraceForLocalJUnitException() { try { throw new JUnitException("expected"); } catch (JUnitException e) { // @formatter:off assertThat(readStackTrace(e)) .startsWith(JUnitException.class.getName() + ": expected") .contains("at " + ExceptionUtilsTests.class.getName()); // @formatter:on } } @ParameterizedTest @ValueSource(strings = { "org.junit.", "jdk.internal.reflect.", "sun.reflect." }) void pruneStackTraceOfCallsFromSpecificPackage(String shouldBePruned) { JUnitException exception = new JUnitException("expected"); pruneStackTrace(exception, List.of()); assertThat(exception.getStackTrace()) // .noneMatch(element -> element.toString().contains(shouldBePruned)); } @Test void pruneStackTraceOfAllLauncherCalls() { JUnitException exception = new JUnitException("expected"); pruneStackTrace(exception, List.of()); assertThat(exception.getStackTrace()) // .noneMatch(element -> element.toString().contains("org.junit.platform.launcher.")); } @Test void pruneStackTraceOfEverythingPriorToFirstLauncherCall() { JUnitException exception = new JUnitException("expected"); StackTraceElement[] stackTrace = exception.getStackTrace(); stackTrace[stackTrace.length - 1] = new StackTraceElement("org.example.Class", "method", "file", 123); exception.setStackTrace(stackTrace); pruneStackTrace(exception, List.of()); assertThat(exception.getStackTrace()) // .noneMatch(element -> element.toString().contains("org.example.Class.method(file:123)")); } @Test void pruneStackTraceRetainsStackFramesFromJUnitStart() { // Non-test class frames from org.junit are filtered. var testClassName = "com.example.project.HelloTest"; var testFileName = "HelloTest.java"; var exception = new JUnitException("expected"); var stackTrace = exception.getStackTrace(); var extendedStacktrace = Arrays.copyOfRange(stackTrace, 0, stackTrace.length + 2); extendedStacktrace[0] = new StackTraceElement(testClassName, "stringLength", testFileName, 10); extendedStacktrace[stackTrace.length] = new StackTraceElement("org.junit.start.JUnit", "run", "JUnit.java", 3); extendedStacktrace[stackTrace.length + 1] = new StackTraceElement(testClassName, "main", testFileName, 5); exception.setStackTrace(extendedStacktrace); pruneStackTrace(exception, List.of(testClassName)); assertThat(exception.getStackTrace()) // .extracting(StackTraceElement::toString) // .containsExactly( // "com.example.project.HelloTest.stringLength(HelloTest.java:10)", // "org.junit.start.JUnit.run(JUnit.java:3)", // "com.example.project.HelloTest.main(HelloTest.java:5)"); } @Test void findSuppressedExceptionsAndCausesOfThrowable() { Throwable t1 = new Throwable("#1"); Throwable t2 = new Throwable("#2"); Throwable t3 = new Throwable("#3"); Throwable t4 = new Throwable("#4"); Throwable t5 = new Throwable("#5"); t1.initCause(t2); t2.initCause(t3); t1.addSuppressed(t4); t2.addSuppressed(t5); assertThat(findNestedThrowables(t1)) // .extracting(Throwable::getMessage) // .containsExactlyInAnyOrder("#1", "#2", "#3", "#4", "#5"); } @Test void avoidCyclesWhileSearchingForNestedThrowables() { Throwable t1 = new Throwable(); Throwable t2 = new Throwable(t1); Throwable t3 = new Throwable(t1); t1.initCause(t2); t1.addSuppressed(t3); t2.addSuppressed(t3); assertThat(findNestedThrowables(t1)).hasSize(3); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/util/FunctionUtilsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static java.util.function.Predicate.isEqual; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullFor; import org.junit.jupiter.api.Test; /** * Unit tests for {@link FunctionUtils}. * * @since 1.0 */ class FunctionUtilsTests { @SuppressWarnings("DataFlowIssue") @Test void whereWithNullFunction() { assertPreconditionViolationNotNullFor("function", () -> FunctionUtils.where(null, o -> true)); } @SuppressWarnings("DataFlowIssue") @Test void whereWithNullPredicate() { assertPreconditionViolationNotNullFor("predicate", () -> FunctionUtils.where(o -> o, null)); } @Test void whereWithChecksPredicateAgainstResultOfFunction() { var combinedPredicate = FunctionUtils.where(String::length, isEqual(3)); assertFalse(combinedPredicate.test("fo")); assertTrue(combinedPredicate.test("foo")); assertFalse(combinedPredicate.test("fooo")); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/util/LruCacheTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; import org.junit.jupiter.api.Test; /** * @since 1.6 */ class LruCacheTests { @Test void evictsEldestEntryWhenMaxSizeIsReached() { var cache = new LruCache(1); cache.put(0, 0); cache.put(1, 1); assertThat(cache) // .doesNotContain(entry(0, 0)) // .hasSize(1); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/util/PackageUtilsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotBlankFor; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullFor; import java.util.List; import java.util.function.Function; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.function.Executable; import org.opentest4j.ValueWrapper; /** * Unit tests for {@link PackageUtils}. * * @since 1.0 */ class PackageUtilsTests { @SuppressWarnings("DataFlowIssue") @Test void getAttributeWithNullType() { assertPreconditionViolationNotNullFor("type", () -> PackageUtils.getAttribute(null, p -> "any")); } @SuppressWarnings("DataFlowIssue") @Test void getAttributeWithNullFunction() { assertPreconditionViolationNotNullFor("function", () -> PackageUtils.getAttribute(getClass(), (Function) null)); } @SuppressWarnings("DataFlowIssue") @Test void getAttributeWithFunctionReturningNullIsEmpty() { assertFalse(PackageUtils.getAttribute(ValueWrapper.class, p -> null).isPresent()); } @Test void getAttributeFromDefaultPackageMemberIsEmpty() throws Exception { var classInDefaultPackage = ReflectionUtils.tryToLoadClass("DefaultPackageTestCase").getNonNull(); assertFalse(PackageUtils.getAttribute(classInDefaultPackage, Package::getSpecificationTitle).isPresent()); } @TestFactory List attributesFromValueWrapperClassArePresent() { return List.of( // dynamicTest("getName", isPresent(Package::getName)), dynamicTest("getImplementationTitle", isPresent(Package::getImplementationTitle)), dynamicTest("getImplementationVendor", isPresent(Package::getImplementationVendor)), dynamicTest("getImplementationVersion", isPresent(Package::getImplementationVersion)), dynamicTest("getSpecificationTitle", isPresent(Package::getSpecificationTitle)), dynamicTest("getSpecificationVendor", isPresent(Package::getSpecificationVendor)), dynamicTest("getSpecificationVersion", isPresent(Package::getSpecificationVersion)) // ); } private Executable isPresent(Function function) { return () -> assertTrue(PackageUtils.getAttribute(ValueWrapper.class, function).isPresent()); } @SuppressWarnings("DataFlowIssue") @Test void getAttributeWithNullTypeAndName() { assertPreconditionViolationNotNullFor("type", () -> PackageUtils.getAttribute(null, "foo")); } @SuppressWarnings("DataFlowIssue") @Test void getAttributeWithNullName() { assertPreconditionViolationNotBlankFor("name", () -> PackageUtils.getAttribute(getClass(), (String) null)); } @Test void getAttributeWithEmptyName() { assertPreconditionViolationNotBlankFor("name", () -> PackageUtils.getAttribute(getClass(), "")); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/util/PreconditionsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static java.util.Collections.singletonList; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.junit.platform.commons.util.Preconditions.condition; import static org.junit.platform.commons.util.Preconditions.containsNoBlankElements; import static org.junit.platform.commons.util.Preconditions.containsNoNullElements; import static org.junit.platform.commons.util.Preconditions.notBlank; import static org.junit.platform.commons.util.Preconditions.notEmpty; import static org.junit.platform.commons.util.Preconditions.notNull; import java.util.Collection; import java.util.List; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; /** * Unit tests for {@link Preconditions}. * * @since 1.0 */ class PreconditionsTests { @Test void notNullPassesForNonNullObject() { var object = new Object(); var nonNullObject = notNull(object, "message"); assertSame(object, nonNullObject); } @Test void notNullThrowsForNullObject() { var message = "argument is null"; assertPreconditionViolationFor(() -> notNull(null, message)).withMessage(message); } @Test void notNullThrowsForNullObjectAndMessageSupplier() { var message = "argument is null"; Object object = null; assertPreconditionViolationFor(() -> notNull(object, () -> message)).withMessage(message); } @Test void notEmptyPassesForNonEmptyArray() { var array = new String[] { "a", "b", "c" }; var nonEmptyArray = notEmpty(array, () -> "should not fail"); assertSame(array, nonEmptyArray); } @Test void notEmptyPassesForNonEmptyCollection() { Collection collection = List.of("a", "b", "c"); var nonEmptyCollection = notEmpty(collection, () -> "should not fail"); assertSame(collection, nonEmptyCollection); } @Test void notEmptyPassesForArrayWithNullElements() { notEmpty(new String[] { null }, "message"); } @Test void notEmptyPassesForCollectionWithNullElements() { notEmpty(singletonListOfNull(), "message"); } @Test void notEmptyThrowsForNullArray() { var message = "array is empty"; assertPreconditionViolationFor(() -> notEmpty((Object[]) null, message)).withMessage(message); } @Test void notEmptyThrowsForNullCollection() { var message = "collection is empty"; assertPreconditionViolationFor(() -> notEmpty((Collection) null, message)).withMessage(message); } @Test void notEmptyThrowsForEmptyArray() { var message = "array is empty"; assertPreconditionViolationFor(() -> notEmpty(new Object[0], message)).withMessage(message); } @Test void notEmptyThrowsForEmptyCollection() { var message = "collection is empty"; assertPreconditionViolationFor(() -> notEmpty(List.of(), message)).withMessage(message); } @Test void containsNoBlankElementsPassesForCollectionThatIsNullOrEmpty() { containsNoBlankElements((List) null, "collection is null"); containsNoBlankElements(List.of(), "collection is empty"); containsNoBlankElements((List) null, () -> "collection is null"); containsNoBlankElements(List.of(), () -> "collection is empty"); } @Test void containsNoBlankElementsPassesForCollectionContainingNonBlankElements() { var input = List.of("a", "b", "c"); var output = containsNoBlankElements(input, "message"); assertSame(input, output); } @Test void containsNoBlankElementsThrowsForCollectionContainingNullElements() { var message = "collection contains blank elements"; assertPreconditionViolationFor(() -> containsNoBlankElements(singletonList(""), message)) // .withMessage(message); assertPreconditionViolationFor(() -> containsNoBlankElements(singletonList((String) null), message)) // .withMessage(message); } @Test void containsNoNullElementsPassesForArrayThatIsNullOrEmpty() { containsNoNullElements((Object[]) null, "array is null"); containsNoNullElements((Object[]) null, () -> "array is null"); containsNoNullElements(new Object[0], "array is empty"); containsNoNullElements(new Object[0], () -> "array is empty"); } @Test void containsNoNullElementsPassesForCollectionThatIsNullOrEmpty() { containsNoNullElements((List) null, "collection is null"); containsNoNullElements(List.of(), "collection is empty"); containsNoNullElements((List) null, () -> "collection is null"); containsNoNullElements(List.of(), () -> "collection is empty"); } @Test void containsNoNullElementsPassesForArrayContainingNonNullElements() { var input = new String[] { "a", "b", "c" }; var output = containsNoNullElements(input, "message"); assertSame(input, output); } @Test void containsNoNullElementsPassesForCollectionContainingNonNullElements() { var input = List.of("a", "b", "c"); var output = containsNoNullElements(input, "message"); assertSame(input, output); output = containsNoNullElements(input, () -> "message"); assertSame(input, output); } @Test void containsNoNullElementsThrowsForArrayContainingNullElements() { var message = "array contains null elements"; Object[] array = { new Object(), null, new Object() }; assertPreconditionViolationFor(() -> containsNoNullElements(array, message)).withMessage(message); } @Test void containsNoNullElementsThrowsForCollectionContainingNullElements() { var message = "collection contains null elements"; assertPreconditionViolationFor(() -> containsNoNullElements(singletonListOfNull(), message)).withMessage( message); } @Test void notBlankPassesForNonBlankString() { var string = "abc"; var nonBlankString = notBlank(string, "message"); assertSame(string, nonBlankString); } @Test void notBlankThrowsForNullString() { var message = "string shouldn't be blank"; assertPreconditionViolationFor(() -> notBlank(null, message)).withMessage(message); } @Test void notBlankThrowsForNullStringWithMessageSupplier() { var message = "string shouldn't be blank"; assertPreconditionViolationFor(() -> notBlank(null, () -> message)).withMessage(message); } @Test void notBlankThrowsForEmptyString() { var message = "string shouldn't be blank"; assertPreconditionViolationFor(() -> notBlank("", message)).withMessage(message); } @Test void notBlankThrowsForEmptyStringWithMessageSupplier() { var message = "string shouldn't be blank"; assertPreconditionViolationFor(() -> notBlank("", () -> message)).withMessage(message); } @Test void notBlankThrowsForBlankString() { var message = "string shouldn't be blank"; assertPreconditionViolationFor(() -> notBlank(" ", message)).withMessage(message); } @Test void notBlankThrowsForBlankStringWithMessageSupplier() { var message = "string shouldn't be blank"; assertPreconditionViolationFor(() -> notBlank(" ", () -> message)).withMessage(message); } @Test void conditionPassesForTruePredicate() { condition(true, "error message"); } @Test void conditionPassesForTruePredicateWithMessageSupplier() { condition(true, () -> "error message"); } @Test void conditionThrowsForFalsePredicate() { var message = "condition does not hold"; assertPreconditionViolationFor(() -> condition(false, message)).withMessage(message); } @Test void conditionThrowsForFalsePredicateWithMessageSupplier() { var message = "condition does not hold"; assertPreconditionViolationFor(() -> condition(false, () -> message)).withMessage(message); } private static List<@Nullable Object> singletonListOfNull() { return singletonList(null); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static java.time.Duration.ofMillis; import static java.util.Arrays.stream; import static java.util.stream.Collectors.joining; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; 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.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullFor; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullOrBlankFor; import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP; import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.TOP_DOWN; import static org.junit.platform.commons.util.ReflectionUtils.findFields; import static org.junit.platform.commons.util.ReflectionUtils.findMethod; import static org.junit.platform.commons.util.ReflectionUtils.findMethods; import static org.junit.platform.commons.util.ReflectionUtils.invokeMethod; import static org.junit.platform.commons.util.ReflectionUtils.isWideningConversion; import static org.junit.platform.commons.util.ReflectionUtils.readFieldValues; import static org.junit.platform.commons.util.ReflectionUtils.tryToReadFieldValue; import java.io.Closeable; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.function.Predicate; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.stream.IntStream; import java.util.stream.Stream; import org.assertj.core.api.Assertions; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.fixtures.TrackLogRecords; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.util.ClearSystemProperty; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.io.Resource; import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.commons.test.TestClassLoader; import org.junit.platform.commons.util.ReflectionUtils.CycleErrorHandling; import org.junit.platform.commons.util.ReflectionUtilsTests.NestedClassTests.ClassWithNestedClasses.Nested1; import org.junit.platform.commons.util.ReflectionUtilsTests.NestedClassTests.ClassWithNestedClasses.Nested2; import org.junit.platform.commons.util.ReflectionUtilsTests.NestedClassTests.ClassWithNestedClasses.Nested3; import org.junit.platform.commons.util.ReflectionUtilsTests.NestedClassTests.Interface45.Nested5; import org.junit.platform.commons.util.ReflectionUtilsTests.NestedClassTests.InterfaceWithNestedClass; import org.junit.platform.commons.util.ReflectionUtilsTests.NestedClassTests.InterfaceWithNestedClass.Nested4; import org.junit.platform.commons.util.ReflectionUtilsTests.OuterClass.InnerClass; import org.junit.platform.commons.util.ReflectionUtilsTests.OuterClass.InnerClass.RecursiveInnerInnerClass; import org.junit.platform.commons.util.ReflectionUtilsTests.OuterClass.InnerSiblingClass; import org.junit.platform.commons.util.ReflectionUtilsTests.OuterClass.RecursiveInnerClass; import org.junit.platform.commons.util.ReflectionUtilsTests.OuterClass.StaticNestedClass; import org.junit.platform.commons.util.ReflectionUtilsTests.OuterClass.StaticNestedSiblingClass; import org.junit.platform.commons.util.ReflectionUtilsTests.OuterClassImplementingInterface.InnerClassImplementingInterface; import org.junit.platform.commons.util.classes.CustomType; import org.junit.platform.commons.util.pkg1.SuperclassWithStaticPackagePrivateBeforeMethod; import org.junit.platform.commons.util.pkg1.SuperclassWithStaticPackagePrivateTempDirField; import org.junit.platform.commons.util.pkg1.subpkg.SubclassWithNonStaticPackagePrivateBeforeMethod; import org.junit.platform.commons.util.pkg1.subpkg.SubclassWithNonStaticPackagePrivateTempDirField; /** * Unit tests for {@link ReflectionUtils}. * * @since 1.0 */ class ReflectionUtilsTests { private static final Predicate isFooMethod = method -> method.getName().equals("foo"); private static final Predicate methodContains1 = method -> method.getName().contains("1"); private static final Predicate methodContains2 = method -> method.getName().contains("2"); private static final Predicate methodContains4 = method -> method.getName().contains("4"); private static final Predicate methodContains5 = method -> method.getName().contains("5"); @Nested class MiscellaneousTests { @Test void returnsPrimitiveVoid() throws Exception { Class clazz = ClassWithVoidAndNonVoidMethods.class; assertTrue(ReflectionUtils.returnsPrimitiveVoid(clazz.getDeclaredMethod("voidMethod"))); assertFalse(ReflectionUtils.returnsPrimitiveVoid(clazz.getDeclaredMethod("methodReturningVoidReference"))); assertFalse(ReflectionUtils.returnsPrimitiveVoid(clazz.getDeclaredMethod("methodReturningObject"))); assertFalse(ReflectionUtils.returnsPrimitiveVoid(clazz.getDeclaredMethod("methodReturningPrimitive"))); } @SuppressWarnings("DataFlowIssue") @Test void getAllAssignmentCompatibleClassesWithNullClass() { assertPreconditionViolationFor(() -> ReflectionUtils.getAllAssignmentCompatibleClasses(null)); } @Test void getAllAssignmentCompatibleClasses() { var superclasses = ReflectionUtils.getAllAssignmentCompatibleClasses(B.class); assertThat(superclasses).containsExactly(B.class, InterfaceC.class, InterfaceA.class, InterfaceB.class, A.class, InterfaceD.class, Object.class); assertTrue(superclasses.stream().allMatch(clazz -> clazz.isAssignableFrom(B.class))); } @SuppressWarnings("DataFlowIssue") @Test void newInstance() { // @formatter:off assertThat(ReflectionUtils.newInstance(C.class, "one", "two")).isNotNull(); assertThat(ReflectionUtils.newInstance(C.class)).isNotNull(); assertPreconditionViolationFor(() -> ReflectionUtils.newInstance(C.class, "one", null)); assertPreconditionViolationFor(() -> ReflectionUtils.newInstance(C.class, null, "two")); assertPreconditionViolationFor(() -> ReflectionUtils.newInstance(C.class, null, null)); assertPreconditionViolationFor(() -> ReflectionUtils.newInstance(C.class, ((Object[]) null))); var exception = assertThrows(RuntimeException.class, () -> ReflectionUtils.newInstance(Exploder.class)); assertThat(exception).hasMessage("boom"); // @formatter:on } @Test void wideningConversion() { // byte assertTrue(isWideningConversion(byte.class, short.class)); assertTrue(isWideningConversion(byte.class, int.class)); assertTrue(isWideningConversion(byte.class, long.class)); assertTrue(isWideningConversion(byte.class, float.class)); assertTrue(isWideningConversion(byte.class, double.class)); // Byte assertTrue(isWideningConversion(Byte.class, short.class)); assertTrue(isWideningConversion(Byte.class, int.class)); assertTrue(isWideningConversion(Byte.class, long.class)); assertTrue(isWideningConversion(Byte.class, float.class)); assertTrue(isWideningConversion(Byte.class, double.class)); // short assertTrue(isWideningConversion(short.class, int.class)); assertTrue(isWideningConversion(short.class, long.class)); assertTrue(isWideningConversion(short.class, float.class)); assertTrue(isWideningConversion(short.class, double.class)); // Short assertTrue(isWideningConversion(Short.class, int.class)); assertTrue(isWideningConversion(Short.class, long.class)); assertTrue(isWideningConversion(Short.class, float.class)); assertTrue(isWideningConversion(Short.class, double.class)); // char assertTrue(isWideningConversion(char.class, int.class)); assertTrue(isWideningConversion(char.class, long.class)); assertTrue(isWideningConversion(char.class, float.class)); assertTrue(isWideningConversion(char.class, double.class)); // Character assertTrue(isWideningConversion(Character.class, int.class)); assertTrue(isWideningConversion(Character.class, long.class)); assertTrue(isWideningConversion(Character.class, float.class)); assertTrue(isWideningConversion(Character.class, double.class)); // int assertTrue(isWideningConversion(int.class, long.class)); assertTrue(isWideningConversion(int.class, float.class)); assertTrue(isWideningConversion(int.class, double.class)); // Integer assertTrue(isWideningConversion(Integer.class, long.class)); assertTrue(isWideningConversion(Integer.class, float.class)); assertTrue(isWideningConversion(Integer.class, double.class)); // long assertTrue(isWideningConversion(long.class, float.class)); assertTrue(isWideningConversion(long.class, double.class)); // Long assertTrue(isWideningConversion(Long.class, float.class)); assertTrue(isWideningConversion(Long.class, double.class)); // float assertTrue(isWideningConversion(float.class, double.class)); // Float assertTrue(isWideningConversion(Float.class, double.class)); // double and Double --> nothing to test // Unsupported assertFalse(isWideningConversion(int.class, byte.class)); // narrowing assertFalse(isWideningConversion(float.class, int.class)); // narrowing assertFalse(isWideningConversion(int.class, int.class)); // direct match assertFalse(isWideningConversion(String.class, int.class)); // neither a primitive nor a wrapper } @Test void getWrapperType() { assertEquals(Boolean.class, ReflectionUtils.getWrapperType(boolean.class)); assertEquals(Byte.class, ReflectionUtils.getWrapperType(byte.class)); assertEquals(Character.class, ReflectionUtils.getWrapperType(char.class)); assertEquals(Short.class, ReflectionUtils.getWrapperType(short.class)); assertEquals(Integer.class, ReflectionUtils.getWrapperType(int.class)); assertEquals(Long.class, ReflectionUtils.getWrapperType(long.class)); assertEquals(Float.class, ReflectionUtils.getWrapperType(float.class)); assertEquals(Double.class, ReflectionUtils.getWrapperType(double.class)); assertNull(ReflectionUtils.getWrapperType(Object.class)); } @Test @ClearSystemProperty(key = "java.class.path") void getAllClasspathRootDirectories(@TempDir Path tempDirectory) throws Exception { var root1 = tempDirectory.resolve("root1").toAbsolutePath(); var root2 = tempDirectory.resolve("root2").toAbsolutePath(); var testClassPath = root1 + File.pathSeparator + root2; System.setProperty("java.class.path", testClassPath); createDirectories(root1, root2); assertThat(ReflectionUtils.getAllClasspathRootDirectories()).containsOnly(root1, root2); } private static void createDirectories(Path... paths) throws IOException { for (var path : paths) { Files.createDirectory(path); } } @SuppressWarnings("DataFlowIssue") @Test void getDeclaredConstructorPreconditions() { // @formatter:off assertPreconditionViolationFor(() -> ReflectionUtils.getDeclaredConstructor(null)); assertPreconditionViolationFor(() -> ReflectionUtils.getDeclaredConstructor(ClassWithTwoConstructors.class)); // @formatter:on } @Test void getDeclaredConstructor() { Constructor constructor = ReflectionUtils.getDeclaredConstructor(getClass()); assertNotNull(constructor); assertEquals(getClass(), constructor.getDeclaringClass()); constructor = ReflectionUtils.getDeclaredConstructor(ClassWithOneCustomConstructor.class); assertNotNull(constructor); assertEquals(ClassWithOneCustomConstructor.class, constructor.getDeclaringClass()); assertEquals(String.class, constructor.getParameterTypes()[0]); } @Test void isGeneric() { for (var method : Generic.class.getMethods()) { assertTrue(ReflectionUtils.isGeneric(method)); } for (var method : NonGenericClass.class.getMethods()) { assertFalse(ReflectionUtils.isGeneric(method)); } } /** * @see GitHub - Issue #3684 */ @Test void getInterfaceMethodIfPossible() throws Exception { // "anonymous" because it's implemented as an anonymous class. InputStream anonymousInputStream = InputStream.nullInputStream(); Class targetType = anonymousInputStream.getClass(); assertThat(targetType.isAnonymousClass()).isTrue(); Method method = targetType.getMethod("close"); assertThat(method).isNotNull(); assertThat(method.getDeclaringClass()).isEqualTo(targetType); Method interfaceMethod = ReflectionUtils.getInterfaceMethodIfPossible(method, targetType); assertThat(interfaceMethod).isNotNull().isNotEqualTo(method); // InputStream implements Closeable directly, so we find the `close` method // in Closeable instead of AutoCloseable. assertThat(interfaceMethod.getDeclaringClass()).isEqualTo(Closeable.class); } @Test void isRecordObject() { assertTrue(ReflectionUtils.isRecordObject(new SomeRecord(1))); assertFalse(ReflectionUtils.isRecordObject(new ClassWithOneCustomConstructor(""))); assertFalse(ReflectionUtils.isRecordObject(null)); } @Test void isRecordClass() { assertTrue(ReflectionUtils.isRecordClass(SomeRecord.class)); assertFalse(ReflectionUtils.isRecordClass(ClassWithOneCustomConstructor.class)); assertFalse(ReflectionUtils.isRecordClass(Object.class)); } record SomeRecord(int n) { } static class ClassWithVoidAndNonVoidMethods { void voidMethod() { } Void methodReturningVoidReference() { return null; } String methodReturningObject() { return ""; } int methodReturningPrimitive() { return 0; } } static class Exploder { Exploder() { throw new RuntimeException("boom"); } } interface InterfaceA { } interface InterfaceB { } interface InterfaceC extends InterfaceA, InterfaceB { } interface InterfaceD { } static class A implements InterfaceA, InterfaceD { } static class B extends A implements InterfaceC { } static class C { C() { } C(String a, String b) { } } @SuppressWarnings("unused") private static class ClassWithOneCustomConstructor { ClassWithOneCustomConstructor(String str) { } } @SuppressWarnings("unused") private static class ClassWithTwoConstructors { ClassWithTwoConstructors() { } ClassWithTwoConstructors(String str) { } } public class NonGenericClass { public void publicMethod() { } } } @Nested class ModifierTests { @Test void isPublic() throws Exception { assertTrue(ReflectionUtils.isPublic(PublicClass.class)); assertTrue(ReflectionUtils.isPublic(PublicClass.class.getMethod("publicMethod"))); assertFalse(ReflectionUtils.isPublic(PrivateClass.class)); assertFalse(ReflectionUtils.isPublic(PrivateClass.class.getDeclaredMethod("privateMethod"))); assertFalse(ReflectionUtils.isPublic(ProtectedClass.class)); assertFalse(ReflectionUtils.isPublic(ProtectedClass.class.getDeclaredMethod("protectedMethod"))); assertFalse(ReflectionUtils.isPublic(PackageVisibleClass.class)); assertFalse(ReflectionUtils.isPublic(PackageVisibleClass.class.getDeclaredMethod("packageVisibleMethod"))); } @Test void isPrivate() throws Exception { assertTrue(ReflectionUtils.isPrivate(PrivateClass.class)); assertTrue(ReflectionUtils.isPrivate(PrivateClass.class.getDeclaredMethod("privateMethod"))); assertFalse(ReflectionUtils.isPrivate(PublicClass.class)); assertFalse(ReflectionUtils.isPrivate(PublicClass.class.getMethod("publicMethod"))); assertFalse(ReflectionUtils.isPrivate(ProtectedClass.class)); assertFalse(ReflectionUtils.isPrivate(ProtectedClass.class.getDeclaredMethod("protectedMethod"))); assertFalse(ReflectionUtils.isPrivate(PackageVisibleClass.class)); assertFalse(ReflectionUtils.isPrivate(PackageVisibleClass.class.getDeclaredMethod("packageVisibleMethod"))); } @Test void isNotPrivate() throws Exception { assertTrue(ReflectionUtils.isNotPrivate(PublicClass.class)); assertTrue(ReflectionUtils.isNotPrivate(PublicClass.class.getDeclaredMethod("publicMethod"))); assertTrue(ReflectionUtils.isNotPrivate(ProtectedClass.class)); assertTrue(ReflectionUtils.isNotPrivate(ProtectedClass.class.getDeclaredMethod("protectedMethod"))); assertTrue(ReflectionUtils.isNotPrivate(PackageVisibleClass.class)); assertTrue( ReflectionUtils.isNotPrivate(PackageVisibleClass.class.getDeclaredMethod("packageVisibleMethod"))); assertFalse(ReflectionUtils.isNotPrivate(PrivateClass.class.getDeclaredMethod("privateMethod"))); } @Test void isAbstract() throws Exception { assertTrue(ReflectionUtils.isAbstract(AbstractClass.class)); assertTrue(ReflectionUtils.isAbstract(AbstractClass.class.getDeclaredMethod("abstractMethod"))); assertFalse(ReflectionUtils.isAbstract(PublicClass.class)); assertFalse(ReflectionUtils.isAbstract(PublicClass.class.getDeclaredMethod("publicMethod"))); } @Test void isStatic() throws Exception { assertTrue(ReflectionUtils.isStatic(StaticClass.class)); assertTrue(ReflectionUtils.isStatic(StaticClass.class.getDeclaredMethod("staticMethod"))); assertFalse(ReflectionUtils.isStatic(PublicClass.class)); assertFalse(ReflectionUtils.isStatic(PublicClass.class.getDeclaredMethod("publicMethod"))); } @Test void isNotStatic() throws Exception { assertTrue(ReflectionUtils.isNotStatic(PublicClass.class)); assertTrue(ReflectionUtils.isNotStatic(PublicClass.class.getDeclaredMethod("publicMethod"))); assertFalse(ReflectionUtils.isNotStatic(StaticClass.class)); assertFalse(ReflectionUtils.isNotStatic(StaticClass.class.getDeclaredMethod("staticMethod"))); } @Test void isFinal() throws Exception { assertTrue(ReflectionUtils.isFinal(FinalClass.class)); assertTrue(ReflectionUtils.isFinal(FinalClass.class.getDeclaredMethod("finalMethod"))); assertFalse(ReflectionUtils.isFinal(PublicClass.class)); assertFalse(ReflectionUtils.isFinal(PublicClass.class.getDeclaredMethod("publicMethod"))); } @Test void isNotFinal() throws Exception { assertTrue(ReflectionUtils.isNotFinal(PublicClass.class)); assertTrue(ReflectionUtils.isNotFinal(PublicClass.class.getDeclaredMethod("publicMethod"))); assertFalse(ReflectionUtils.isNotFinal(FinalClass.class)); assertFalse(ReflectionUtils.isNotFinal(FinalClass.class.getDeclaredMethod("finalMethod"))); } // Intentionally non-static public class PublicClass { public void publicMethod() { } public void method(String str, Integer num) { } public void method(String[] strings, Integer[] nums) { } public void method(boolean b, char c) { } public void method(char[] characters, int[] nums) { } } private class PrivateClass { @SuppressWarnings("unused") private void privateMethod() { } } protected class ProtectedClass { @SuppressWarnings("unused") protected void protectedMethod() { } } class PackageVisibleClass { @SuppressWarnings("unused") void packageVisibleMethod() { } } final class FinalClass { @SuppressWarnings({ "unused", "FinalMethodInFinalClass", "RedundantModifier" }) final void finalMethod() { } } abstract static class AbstractClass { abstract void abstractMethod(); } static class StaticClass { static void staticMethod() { } } } @Nested class IsClassAssignableToClassTests { @SuppressWarnings("DataFlowIssue") @Test void isAssignableToForNullSourceType() { assertPreconditionViolationNotNullFor("source type", () -> ReflectionUtils.isAssignableTo(null, getClass())); } @Test void isAssignableToForPrimitiveSourceType() { assertPreconditionViolationFor(() -> ReflectionUtils.isAssignableTo(int.class, Integer.class))// .withMessage("source type must not be a primitive type"); } @SuppressWarnings("DataFlowIssue") @Test void isAssignableToForNullTargetType() { assertPreconditionViolationNotNullFor("target type", () -> ReflectionUtils.isAssignableTo(getClass(), null)); } @Test void isAssignableTo() { // Reference Types assertTrue(ReflectionUtils.isAssignableTo(String.class, Object.class)); assertTrue(ReflectionUtils.isAssignableTo(String.class, CharSequence.class)); assertTrue(ReflectionUtils.isAssignableTo(String.class, String.class)); assertTrue(ReflectionUtils.isAssignableTo(Integer.class, Number.class)); assertTrue(ReflectionUtils.isAssignableTo(Integer.class, Integer.class)); assertFalse(ReflectionUtils.isAssignableTo(Object.class, String.class)); assertFalse(ReflectionUtils.isAssignableTo(CharSequence.class, String.class)); assertFalse(ReflectionUtils.isAssignableTo(Number.class, Integer.class)); // Arrays assertTrue(ReflectionUtils.isAssignableTo(int[].class, int[].class)); assertTrue(ReflectionUtils.isAssignableTo(double[].class, double[].class)); assertTrue(ReflectionUtils.isAssignableTo(double[].class, Object.class)); assertTrue(ReflectionUtils.isAssignableTo(String[].class, Object.class)); assertTrue(ReflectionUtils.isAssignableTo(String[].class, Object[].class)); assertTrue(ReflectionUtils.isAssignableTo(String[].class, String[].class)); // Wrappers to Primitives assertTrue(ReflectionUtils.isAssignableTo(Integer.class, int.class)); assertTrue(ReflectionUtils.isAssignableTo(Boolean.class, boolean.class)); // Void to void assertFalse(ReflectionUtils.isAssignableTo(Void.class, void.class)); // Widening Conversions from Wrappers to Primitives assertTrue(ReflectionUtils.isAssignableTo(Integer.class, long.class)); assertTrue(ReflectionUtils.isAssignableTo(Float.class, double.class)); assertTrue(ReflectionUtils.isAssignableTo(Byte.class, double.class)); // Widening Conversions from Wrappers to Wrappers (not supported by Java) assertFalse(ReflectionUtils.isAssignableTo(Integer.class, Long.class)); assertFalse(ReflectionUtils.isAssignableTo(Float.class, Double.class)); assertFalse(ReflectionUtils.isAssignableTo(Byte.class, Double.class)); // Narrowing Conversions assertFalse(ReflectionUtils.isAssignableTo(Integer.class, char.class)); assertFalse(ReflectionUtils.isAssignableTo(Long.class, byte.class)); assertFalse(ReflectionUtils.isAssignableTo(Long.class, int.class)); } } @Nested class IsObjectAssignableToClassTests { @SuppressWarnings("DataFlowIssue") @Test void isAssignableToForNullClass() { assertPreconditionViolationFor(() -> ReflectionUtils.isAssignableTo(new Object(), null)); } @Test void isAssignableTo() { // Reference Types assertTrue(ReflectionUtils.isAssignableTo("string", String.class)); assertTrue(ReflectionUtils.isAssignableTo("string", CharSequence.class)); assertTrue(ReflectionUtils.isAssignableTo("string", Object.class)); assertFalse(ReflectionUtils.isAssignableTo(new Object(), String.class)); assertFalse(ReflectionUtils.isAssignableTo(Integer.valueOf("1"), StringBuilder.class)); assertFalse(ReflectionUtils.isAssignableTo(new StringBuilder(), String.class)); // Arrays assertTrue(ReflectionUtils.isAssignableTo(new int[0], int[].class)); assertTrue(ReflectionUtils.isAssignableTo(new double[0], Object.class)); assertTrue(ReflectionUtils.isAssignableTo(new String[0], String[].class)); assertTrue(ReflectionUtils.isAssignableTo(new String[0], Object.class)); // Primitive Types assertTrue(ReflectionUtils.isAssignableTo(1, int.class)); assertTrue(ReflectionUtils.isAssignableTo(Long.valueOf("1"), long.class)); assertTrue(ReflectionUtils.isAssignableTo(Boolean.TRUE, boolean.class)); // Widening Conversions to Primitives assertTrue(ReflectionUtils.isAssignableTo(1, long.class)); assertTrue(ReflectionUtils.isAssignableTo(1f, double.class)); assertTrue(ReflectionUtils.isAssignableTo((byte) 1, double.class)); // Widening Conversions to Wrappers (not supported by Java) assertFalse(ReflectionUtils.isAssignableTo(1, Long.class)); assertFalse(ReflectionUtils.isAssignableTo(1f, Double.class)); assertFalse(ReflectionUtils.isAssignableTo((byte) 1, Double.class)); // Narrowing Conversions assertFalse(ReflectionUtils.isAssignableTo(1, char.class)); assertFalse(ReflectionUtils.isAssignableTo(1L, byte.class)); assertFalse(ReflectionUtils.isAssignableTo(1L, int.class)); } @Test void isAssignableToForNullObject() { assertTrue(ReflectionUtils.isAssignableTo((Object) null, Object.class)); assertTrue(ReflectionUtils.isAssignableTo((Object) null, String.class)); assertTrue(ReflectionUtils.isAssignableTo((Object) null, Long.class)); assertTrue(ReflectionUtils.isAssignableTo((Object) null, Character[].class)); } @Test void isAssignableToForNullObjectAndPrimitive() { assertFalse(ReflectionUtils.isAssignableTo((Object) null, byte.class)); assertFalse(ReflectionUtils.isAssignableTo((Object) null, int.class)); assertFalse(ReflectionUtils.isAssignableTo((Object) null, long.class)); assertFalse(ReflectionUtils.isAssignableTo((Object) null, boolean.class)); } } @Nested class MethodInvocationTests { @SuppressWarnings("DataFlowIssue") @Test void invokeMethodPreconditions() { // @formatter:off assertPreconditionViolationFor(() -> invokeMethod(null, new Object())); assertPreconditionViolationFor(() -> invokeMethod(Object.class.getMethod("hashCode"), null)); // @formatter:on } @Test void invokePublicMethod() throws Exception { var tracker = new InvocationTracker(); invokeMethod(InvocationTracker.class.getDeclaredMethod("publicMethod"), tracker); assertTrue(tracker.publicMethodInvoked); } @Test void invokePrivateMethod() throws Exception { var tracker = new InvocationTracker(); invokeMethod(InvocationTracker.class.getDeclaredMethod("privateMethod"), tracker); assertTrue(tracker.privateMethodInvoked); } @Test void invokePublicStaticMethod() throws Exception { invokeMethod(InvocationTracker.class.getDeclaredMethod("publicStaticMethod"), null); assertTrue(InvocationTracker.publicStaticMethodInvoked); } @Test void invokePrivateStaticMethod() throws Exception { invokeMethod(InvocationTracker.class.getDeclaredMethod("privateStaticMethod"), null); assertTrue(InvocationTracker.privateStaticMethodInvoked); } static class InvocationTracker { static boolean publicStaticMethodInvoked; static boolean privateStaticMethodInvoked; boolean publicMethodInvoked; boolean privateMethodInvoked; public static void publicStaticMethod() { publicStaticMethodInvoked = true; } @SuppressWarnings("unused") private static void privateStaticMethod() { privateStaticMethodInvoked = true; } public void publicMethod() { publicMethodInvoked = true; } @SuppressWarnings("unused") private void privateMethod() { privateMethodInvoked = true; } } } @Nested class ResourceLoadingTests { @SuppressWarnings("DataFlowIssue") @Test void tryToGetResourcePreconditions() { assertPreconditionViolationFor(() -> ReflectionUtils.tryToGetResources("")); assertPreconditionViolationFor(() -> ReflectionUtils.tryToGetResources(" ")); assertPreconditionViolationFor(() -> ReflectionUtils.tryToGetResources(null)); assertPreconditionViolationFor( () -> ReflectionUtils.tryToGetResources("org/junit/platform/commons/example.resource", null)); } @Test void tryToGetResource() { var tryToGetResource = ReflectionUtils.tryToGetResources("org/junit/platform/commons/example.resource"); var resource = assertDoesNotThrow(tryToGetResource::get); assertAll( // () -> assertThat(resource).hasSize(1), // () -> assertThat(resource).extracting(Resource::getName) // .containsExactly("org/junit/platform/commons/example.resource")); } @Test void tryToGetResourceWithPrefixedSlash() { var tryToGetResource = ReflectionUtils.tryToGetResources("/org/junit/platform/commons/example.resource"); var resource = assertDoesNotThrow(tryToGetResource::get); assertAll( // () -> assertThat(resource).hasSize(1), // () -> assertThat(resource).extracting(Resource::getName) // .containsExactly("org/junit/platform/commons/example.resource")); } @Test void tryToGetResourceWhenResourceNotFound() { var tryToGetResource = ReflectionUtils.tryToGetResources("org/junit/platform/commons/no-such.resource"); var resource = assertDoesNotThrow(tryToGetResource::get); assertThat(resource).isEmpty(); } } @Nested class ClassLoadingTests { @SuppressWarnings("DataFlowIssue") @Test void tryToLoadClassPreconditions() { assertPreconditionViolationFor(() -> ReflectionUtils.tryToLoadClass(null)); assertPreconditionViolationFor(() -> ReflectionUtils.tryToLoadClass("")); assertPreconditionViolationFor(() -> ReflectionUtils.tryToLoadClass(" ")); assertPreconditionViolationFor(() -> ReflectionUtils.tryToLoadClass(null, null)); assertPreconditionViolationFor(() -> ReflectionUtils.tryToLoadClass(getClass().getName(), null)); } @Test void tryToLoadClassWhenClassNotFoundException() { assertThrows(ClassNotFoundException.class, () -> ReflectionUtils.tryToLoadClass("foo.bar.EnigmaClassThatDoesNotExist").get()); } @Test void tryToLoadClassFailsWithinReasonableTimeForInsanelyLargeAndInvalidMultidimensionalPrimitiveArrayName() { // Create a class name of the form int[][][]...[][][]X String className = IntStream.rangeClosed(1, 20_000)// .mapToObj(i -> "[]")// .collect(joining("", "int", "X")); // The following should ideally fail in less than 50ms. So we just make // sure it fails in less than 500ms in order to (hopefully) allow the // test to pass on CI servers with limited resources. assertTimeoutPreemptively(ofMillis(500), () -> assertThrows(ClassNotFoundException.class, () -> ReflectionUtils.tryToLoadClass(className).get())); } @Test void tryToLoadClass() { assertTryToLoadClass(getClass().getName(), getClass()); assertTryToLoadClass(Integer.class.getName(), Integer.class); assertTryToLoadClass(Void.class.getName(), Void.class); } @Test void tryToLoadClassTrimsClassName() { assertTryToLoadClass(" " + Integer.class.getName() + "\t", Integer.class); } @Test void tryToLoadClassForVoidPseudoPrimitiveType() { assertTryToLoadClass("void", void.class); } @Test void tryToLoadClassForPrimitiveType() { assertTryToLoadClass("boolean", boolean.class); assertTryToLoadClass("char", char.class); assertTryToLoadClass("byte", byte.class); assertTryToLoadClass("short", short.class); assertTryToLoadClass("int", int.class); assertTryToLoadClass("long", long.class); assertTryToLoadClass("float", float.class); assertTryToLoadClass("double", double.class); } @Test void tryToLoadClassForBinaryPrimitiveArrayName() { assertTryToLoadClass("[Z", boolean[].class); assertTryToLoadClass("[C", char[].class); assertTryToLoadClass("[B", byte[].class); assertTryToLoadClass("[S", short[].class); assertTryToLoadClass("[I", int[].class); assertTryToLoadClass("[J", long[].class); assertTryToLoadClass("[F", float[].class); assertTryToLoadClass("[D", double[].class); } @Test void tryToLoadClassForCanonicalPrimitiveArrayName() { assertTryToLoadClass("boolean[]", boolean[].class); assertTryToLoadClass("char[]", char[].class); assertTryToLoadClass("byte[]", byte[].class); assertTryToLoadClass("short[]", short[].class); assertTryToLoadClass("int[]", int[].class); assertTryToLoadClass("long[]", long[].class); assertTryToLoadClass("float[]", float[].class); assertTryToLoadClass("double[]", double[].class); } @Test void tryToLoadClassForBinaryObjectArrayName() { assertTryToLoadClass(String[].class.getName(), String[].class); } @Test void tryToLoadClassForCanonicalObjectArrayName() { assertTryToLoadClass("java.lang.String[]", String[].class); } @Test void tryToLoadClassForBinaryTwoDimensionalPrimitiveArrayName() { assertTryToLoadClass("[[Z", boolean[][].class); assertTryToLoadClass("[[C", char[][].class); assertTryToLoadClass("[[B", byte[][].class); assertTryToLoadClass("[[S", short[][].class); assertTryToLoadClass("[[I", int[][].class); assertTryToLoadClass("[[J", long[][].class); assertTryToLoadClass("[[F", float[][].class); assertTryToLoadClass("[[D", double[][].class); } @Test void tryToLoadClassForCanonicalTwoDimensionalPrimitiveArrayName() { assertTryToLoadClass("boolean[][]", boolean[][].class); assertTryToLoadClass("char[][]", char[][].class); assertTryToLoadClass("byte[][]", byte[][].class); assertTryToLoadClass("short[][]", short[][].class); assertTryToLoadClass("int[][]", int[][].class); assertTryToLoadClass("long[][]", long[][].class); assertTryToLoadClass("float[][]", float[][].class); assertTryToLoadClass("double[][]", double[][].class); } @Test void tryToLoadClassForBinaryMultidimensionalPrimitiveArrayName() { assertTryToLoadClass("[[[[[Z", boolean[][][][][].class); assertTryToLoadClass("[[[[[C", char[][][][][].class); assertTryToLoadClass("[[[[[B", byte[][][][][].class); assertTryToLoadClass("[[[[[S", short[][][][][].class); assertTryToLoadClass("[[[[[I", int[][][][][].class); assertTryToLoadClass("[[[[[J", long[][][][][].class); assertTryToLoadClass("[[[[[F", float[][][][][].class); assertTryToLoadClass("[[[[[D", double[][][][][].class); } @Test void tryToLoadClassForCanonicalMultidimensionalPrimitiveArrayName() { assertTryToLoadClass("boolean[][][][][]", boolean[][][][][].class); assertTryToLoadClass("char[][][][][]", char[][][][][].class); assertTryToLoadClass("byte[][][][][]", byte[][][][][].class); assertTryToLoadClass("short[][][][][]", short[][][][][].class); assertTryToLoadClass("int[][][][][]", int[][][][][].class); assertTryToLoadClass("long[][][][][]", long[][][][][].class); assertTryToLoadClass("float[][][][][]", float[][][][][].class); assertTryToLoadClass("double[][][][][]", double[][][][][].class); } @Test void tryToLoadClassForBinaryMultidimensionalObjectArrayName() { assertTryToLoadClass(String[][][][][].class.getName(), String[][][][][].class); } @Test void tryToLoadClassForCanonicalMultidimensionalObjectArrayName() { assertTryToLoadClass("java.lang.String[][][][][]", String[][][][][].class); } private static void assertTryToLoadClass(String name, Class type) { try { assertThat(ReflectionUtils.tryToLoadClass(name).get()).as(name).isEqualTo(type); } catch (Exception ex) { ExceptionUtils.throwAsUncheckedException(ex); } } } @Nested class FullyQualifiedMethodNameTests { @SuppressWarnings("DataFlowIssue") @Test void getFullyQualifiedMethodNamePreconditions() { // @formatter:off assertPreconditionViolationFor(() -> ReflectionUtils.getFullyQualifiedMethodName(null, null)); assertPreconditionViolationFor(() -> ReflectionUtils.getFullyQualifiedMethodName(null, "testMethod")); assertPreconditionViolationFor(() -> ReflectionUtils.getFullyQualifiedMethodName(Object.class, null)); // @formatter:on } @Test void getFullyQualifiedMethodNameForMethodWithoutParameters() { assertThat(ReflectionUtils.getFullyQualifiedMethodName(Object.class, "toString"))// .isEqualTo("java.lang.Object#toString()"); } @Test void getFullyQualifiedMethodNameForMethodWithNullParameters() { assertThat(ReflectionUtils.getFullyQualifiedMethodName(Object.class, "toString", (Class[]) null))// .isEqualTo("java.lang.Object#toString()"); } @Test void getFullyQualifiedMethodNameForMethodWithSingleParameter() { assertThat(ReflectionUtils.getFullyQualifiedMethodName(Object.class, "equals", Object.class))// .isEqualTo("java.lang.Object#equals(java.lang.Object)"); } @Test void getFullyQualifiedMethodNameForMethodWithMultipleParameters() { // @formatter:off assertThat(ReflectionUtils.getFullyQualifiedMethodName(Object.class, "testMethod", int.class, Object.class))// .isEqualTo("java.lang.Object#testMethod(int, java.lang.Object)"); // @formatter:on } @SuppressWarnings("DataFlowIssue") @Test void parseFullyQualifiedMethodNamePreconditions() { // @formatter:off assertPreconditionViolationFor(() -> ReflectionUtils.parseFullyQualifiedMethodName(null)); assertPreconditionViolationFor(() -> ReflectionUtils.parseFullyQualifiedMethodName("")); assertPreconditionViolationFor(() -> ReflectionUtils.parseFullyQualifiedMethodName(" ")); assertPreconditionViolationFor(() -> ReflectionUtils.parseFullyQualifiedMethodName("java.lang.Object#")); assertPreconditionViolationFor(() -> ReflectionUtils.parseFullyQualifiedMethodName("#equals")); assertPreconditionViolationFor(() -> ReflectionUtils.parseFullyQualifiedMethodName("#")); assertPreconditionViolationFor(() -> ReflectionUtils.parseFullyQualifiedMethodName("java.lang.Object")); assertPreconditionViolationFor(() -> ReflectionUtils.parseFullyQualifiedMethodName("equals")); assertPreconditionViolationFor(() -> ReflectionUtils.parseFullyQualifiedMethodName("()")); assertPreconditionViolationFor(() -> ReflectionUtils.parseFullyQualifiedMethodName("(int, java.lang.Object)")); // @formatter:on } @Test void parseFullyQualifiedMethodNameForMethodWithoutParameters() { assertThat(ReflectionUtils.parseFullyQualifiedMethodName("com.example.Test#method()"))// .containsExactly("com.example.Test", "method", ""); } @Test void parseFullyQualifiedMethodNameForMethodWithSingleParameter() { assertThat(ReflectionUtils.parseFullyQualifiedMethodName("com.example.Test#method(java.lang.Object)"))// .containsExactly("com.example.Test", "method", "java.lang.Object"); } @Test void parseFullyQualifiedMethodNameForMethodWithMultipleParameters() { assertThat(ReflectionUtils.parseFullyQualifiedMethodName("com.example.Test#method(int, java.lang.Object)"))// .containsExactly("com.example.Test", "method", "int, java.lang.Object"); } } @Nested class NestedClassTests { @SuppressWarnings("DataFlowIssue") @Test void findNestedClassesPreconditions() { // @formatter:off assertPreconditionViolationFor(() -> ReflectionUtils.findNestedClasses(null, null)); assertPreconditionViolationFor(() -> ReflectionUtils.findNestedClasses(null, clazz -> true)); assertPreconditionViolationFor(() -> ReflectionUtils.findNestedClasses(getClass(), null)); // @formatter:on } @SuppressWarnings("DataFlowIssue") @Test void isNestedClassPresentPreconditions() { // @formatter:off assertPreconditionViolationFor(() -> ReflectionUtils.isNestedClassPresent(null, null, null)); assertPreconditionViolationFor(() -> ReflectionUtils.isNestedClassPresent(null, clazz -> true, CycleErrorHandling.THROW_EXCEPTION)); assertPreconditionViolationFor(() -> ReflectionUtils.isNestedClassPresent(getClass(), null, CycleErrorHandling.ABORT_VISIT)); assertPreconditionViolationFor(() -> ReflectionUtils.isNestedClassPresent(getClass(), clazz -> true, null)); // @formatter:on } @Test void findNestedClasses() { // @formatter:off assertThat(findNestedClasses(Object.class)).isEmpty(); assertThat(isNestedClassPresent(Object.class)).isFalse(); assertThat(findNestedClasses(ClassWithNestedClasses.class)) .containsOnly(Nested1.class, Nested2.class, Nested3.class); assertThat(isNestedClassPresent(ClassWithNestedClasses.class)) .isTrue(); assertThat(ReflectionUtils.findNestedClasses(ClassWithNestedClasses.class, clazz -> clazz.getName().contains("1"))) .containsExactly(Nested1.class); assertThat(ReflectionUtils.isNestedClassPresent(ClassWithNestedClasses.class, clazz -> clazz.getName().contains("1"), CycleErrorHandling.THROW_EXCEPTION)) .isTrue(); assertThat(ReflectionUtils.findNestedClasses(ClassWithNestedClasses.class, ReflectionUtils::isStatic)) .containsExactly(Nested3.class); assertThat(ReflectionUtils.isNestedClassPresent(ClassWithNestedClasses.class, ReflectionUtils::isStatic, CycleErrorHandling.THROW_EXCEPTION)) .isTrue(); assertThat(findNestedClasses(ClassExtendingClassWithNestedClasses.class)) .containsOnly(Nested1.class, Nested2.class, Nested3.class, Nested4.class, Nested5.class); assertThat(isNestedClassPresent(ClassExtendingClassWithNestedClasses.class)) .isTrue(); assertThat(findNestedClasses(ClassWithNestedClasses.Nested1.class)).isEmpty(); assertThat(isNestedClassPresent(ClassWithNestedClasses.Nested1.class)).isFalse(); // @formatter:on } /** * @since 1.6 */ @Test void findNestedClassesWithSeeminglyRecursiveHierarchies() { assertThat(findNestedClasses(AbstractOuterClass.class))// .containsExactly(AbstractOuterClass.InnerClass.class); assertThat(isNestedClassPresent(AbstractOuterClass.class))// .isTrue(); // OuterClass contains recursive hierarchies, but the non-matching // predicate should prevent cycle detection. // See https://github.com/junit-team/junit-framework/issues/2249 assertThat(ReflectionUtils.findNestedClasses(OuterClass.class, clazz -> false)).isEmpty(); assertThat(ReflectionUtils.isNestedClassPresent(OuterClass.class, clazz -> false, CycleErrorHandling.THROW_EXCEPTION)).isFalse(); // RecursiveInnerInnerClass is part of a recursive hierarchy, but the non-matching // predicate should prevent cycle detection. assertThat(ReflectionUtils.findNestedClasses(RecursiveInnerInnerClass.class, clazz -> false)).isEmpty(); assertThat(ReflectionUtils.isNestedClassPresent(RecursiveInnerInnerClass.class, clazz -> false, CycleErrorHandling.THROW_EXCEPTION)).isFalse(); // Sibling types don't actually result in cycles. assertThat(findNestedClasses(StaticNestedSiblingClass.class))// .containsExactly(AbstractOuterClass.InnerClass.class); assertThat(isNestedClassPresent(StaticNestedSiblingClass.class))// .isTrue(); assertThat(findNestedClasses(InnerSiblingClass.class))// .containsExactly(AbstractOuterClass.InnerClass.class); assertThat(isNestedClassPresent(InnerSiblingClass.class))// .isTrue(); // Interfaces with static nested classes assertThat(findNestedClasses(OuterClassImplementingInterface.class))// .containsExactly(InnerClassImplementingInterface.class, Nested4.class); assertThat(isNestedClassPresent(OuterClassImplementingInterface.class))// .isTrue(); assertThat(findNestedClasses(InnerClassImplementingInterface.class))// .containsExactly(Nested4.class); assertThat(isNestedClassPresent(InnerClassImplementingInterface.class))// .isTrue(); } /** * @since 1.6 */ @Test void findNestedClassesWithRecursiveHierarchies() { Runnable runnable1 = () -> assertNestedCycle(OuterClass.class, InnerClass.class, OuterClass.class); Runnable runnable2 = () -> assertNestedCycle(StaticNestedClass.class, InnerClass.class, OuterClass.class); Runnable runnable3 = () -> assertNestedCycle(RecursiveInnerClass.class, OuterClass.class); Runnable runnable4 = () -> assertNestedCycle(RecursiveInnerInnerClass.class, OuterClass.class); Runnable runnable5 = () -> assertNestedCycle(InnerClass.class, RecursiveInnerInnerClass.class, OuterClass.class); Stream.of(runnable1, runnable1, runnable1, runnable2, runnable2, runnable2, runnable3, runnable3, runnable3, runnable4, runnable4, runnable4, runnable5, runnable5, runnable5).parallel().forEach(Runnable::run); } @Test void findNestedClassesWithMultipleNestedClasses() { var nestedClasses = findNestedClasses(OuterClassWithMultipleNestedClasses.class); assertThat(nestedClasses) // .map(Class::getSimpleName) // .containsExactly("Beta", "Zeta", "Eta", "Alpha", "Delta", "Gamma", "Theta", "Epsilon"); } private static List> findNestedClasses(Class clazz) { return ReflectionUtils.findNestedClasses(clazz, c -> true); } private static boolean isNestedClassPresent(Class clazz) { return ReflectionUtils.isNestedClassPresent(clazz, c -> true, CycleErrorHandling.THROW_EXCEPTION); } private void assertNestedCycle(Class from, Class to) { assertNestedCycle(from, from, to); } private void assertNestedCycle(Class start, Class from, Class to) { assertThatExceptionOfType(JUnitException.class)// .as("expected cycle from %s to %s", from.getSimpleName(), to.getSimpleName())// .isThrownBy(() -> findNestedClasses(start))// .withMessageMatching("Detected cycle in inner class hierarchy between .+%s and .+%s".formatted( from.getSimpleName(), to.getSimpleName())); } /** * @since 1.3 */ @Test void findNestedClassesWithInvalidNestedClassFile(@TrackLogRecords LogRecordListener listener) throws Exception { var jarUrl = getClass().getResource("/gh-1436-invalid-nested-class-file.jar"); try (var classLoader = new URLClassLoader(new URL[] { jarUrl })) { var fqcn = "tests.NestedInterfaceGroovyTests"; var classWithInvalidNestedClassFile = classLoader.loadClass(fqcn); assertEquals(fqcn, classWithInvalidNestedClassFile.getName()); var noClassDefFoundError = assertThrows(NoClassDefFoundError.class, classWithInvalidNestedClassFile::getDeclaredClasses); assertThat(noClassDefFoundError) // .hasMessageMatching("tests[./]NestedInterfaceGroovyTests\\$NestedInterface\\$1"); assertThat(findNestedClasses(classWithInvalidNestedClassFile)).isEmpty(); // @formatter:off var logMessage = listener.stream(ReflectionUtils.class, Level.FINE) .findFirst() .map(LogRecord::getMessage) .orElse("didn't find log record"); // @formatter:on assertThat(logMessage).isEqualTo("Failed to retrieve declared classes for " + fqcn); } } static class ClassWithNestedClasses { class Nested1 { } class Nested2 { } static class Nested3 { } } interface InterfaceWithNestedClass { class Nested4 { } } interface Interface45 extends InterfaceWithNestedClass { class Nested5 { } } static class ClassExtendingClassWithNestedClasses extends ClassWithNestedClasses implements Interface45 { } } @Nested class MethodUtilitiesTests { @SuppressWarnings("DataFlowIssue") @Test void tryToGetMethodPreconditions() { assertPreconditionViolationFor(() -> ReflectionUtils.tryToGetMethod(null, null)); assertPreconditionViolationFor(() -> ReflectionUtils.tryToGetMethod(String.class, null)); assertPreconditionViolationFor(() -> ReflectionUtils.tryToGetMethod(null, "hashCode")); } @Test void tryToGetMethod() throws Exception { assertThat(ReflectionUtils.tryToGetMethod(Object.class, "hashCode").get())// .isEqualTo(Object.class.getMethod("hashCode")); assertThat(ReflectionUtils.tryToGetMethod(String.class, "charAt", int.class).get())// .isEqualTo(String.class.getMethod("charAt", int.class)); assertThat(ReflectionUtils.tryToGetMethod(Path.class, "subpath", int.class, int.class).get())// .isEqualTo(Path.class.getMethod("subpath", int.class, int.class)); assertThat(ReflectionUtils.tryToGetMethod(String.class, "chars").get())// .isEqualTo(String.class.getMethod("chars")); assertThat(ReflectionUtils.tryToGetMethod(String.class, "noSuchMethod").toOptional()).isEmpty(); assertThat(ReflectionUtils.tryToGetMethod(Object.class, "clone", int.class).toOptional()).isEmpty(); } @SuppressWarnings("DataFlowIssue") @Test void isMethodPresentPreconditions() { assertPreconditionViolationFor(() -> ReflectionUtils.isMethodPresent(null, m -> true)); assertPreconditionViolationFor(() -> ReflectionUtils.isMethodPresent(getClass(), null)); } @Test void isMethodPresent() { Predicate isMethod1 = method -> (method.getName().equals("method1") && method.getParameterTypes().length == 1 && method.getParameterTypes()[0] == String.class); assertThat(ReflectionUtils.isMethodPresent(MethodShadowingChild.class, isMethod1)).isTrue(); assertThat(ReflectionUtils.isMethodPresent(getClass(), isMethod1)).isFalse(); } } @Nested class FindMethodTests { @SuppressWarnings("DataFlowIssue") @Test void findMethodByParameterTypesPreconditions() { // @formatter:off assertPreconditionViolationFor(() -> findMethod(null, null)); assertPreconditionViolationFor(() -> findMethod(null, "method")); assertPreconditionViolationNotNullOrBlankFor("Method name", () -> findMethod(String.class, null)); assertPreconditionViolationNotNullOrBlankFor("Method name", () -> findMethod(String.class, " ")); assertPreconditionViolationNotNullFor("Parameter types array", () -> findMethod(Files.class, "copy", (Class[]) null)); assertPreconditionViolationNotNullFor("Individual parameter types", () -> findMethod(Files.class, "copy", (Class) null)); assertPreconditionViolationNotNullFor("Individual parameter types", () -> findMethod(Files.class, "copy", new Class[] { Path.class, null })); // @formatter:on } @Test void findMethodByParameterTypes() throws Exception { assertThat(findMethod(Object.class, "noSuchMethod")).isEmpty(); assertThat(findMethod(String.class, "noSuchMethod")).isEmpty(); assertThat(findMethod(String.class, "chars")).contains(String.class.getMethod("chars")); assertThat(findMethod(Files.class, "copy", Path.class, OutputStream.class))// .contains(Files.class.getMethod("copy", Path.class, OutputStream.class)); assertThat(findMethod(MethodShadowingChild.class, "method1", String.class))// .contains(MethodShadowingChild.class.getMethod("method1", String.class)); } @Test void findMethodByParameterTypesInGenericInterface() { Class ifc = InterfaceWithGenericDefaultMethod.class; var method = findMethod(ifc, "foo", Number.class); assertThat(method).isNotEmpty(); assertThat(method.get().getName()).isEqualTo("foo"); } /** * @see #findMethodByParameterTypesWithOverloadedMethodNextToGenericDefaultMethod() */ @Test void findMethodByParameterTypesInGenericInterfaceViaParameterizedSubclass() { Class clazz = InterfaceWithGenericDefaultMethodImpl.class; var method = findMethod(clazz, "foo", Long.class); assertThat(method).isNotEmpty(); assertThat(method.get().getName()).isEqualTo("foo"); // One might expect or desire that the signature for the generic foo(N) // default method would be "foo(java.lang.Long)" when looked up via the // concrete parameterized class, but it apparently is only _visible_ as // "foo(java.lang.Number)" via reflection. Hence the following assertion // checks for java.lang.Number instead of java.lang.Long. assertThat(method.get().getParameterTypes()[0]).isEqualTo(Number.class); } /** * This test is identical to * {@link #findMethodByParameterTypesInGenericInterfaceViaParameterizedSubclass()}, * except that this test attempts to find the overloaded * {@link InterfaceWithGenericDefaultMethodImpl#foo(Double)} method instead of * the {@link InterfaceWithGenericDefaultMethod#foo(Number)} default method. */ @Test void findMethodByParameterTypesWithOverloadedMethodNextToGenericDefaultMethod() { Class clazz = InterfaceWithGenericDefaultMethodImpl.class; Class parameterType = Double.class; var method = findMethod(clazz, "foo", parameterType); assertThat(method).isNotEmpty(); assertThat(method.get().getName()).isEqualTo("foo"); assertThat(method.get().getParameterTypes()[0]).isEqualTo(parameterType); } @Test void findMethodByParameterNamesWithPrimitiveArrayParameter() throws Exception { assertFindMethodByParameterNames("methodWithPrimitiveArray", int[].class); } @Test void findMethodByParameterNamesWithTwoDimensionalPrimitiveArrayParameter() throws Exception { assertFindMethodByParameterNames("methodWithTwoDimensionalPrimitiveArray", int[][].class); } @Test void findMethodByParameterNamesWithMultidimensionalPrimitiveArrayParameter() throws Exception { assertFindMethodByParameterNames("methodWithMultidimensionalPrimitiveArray", int[][][][][].class); } @Test void findMethodByParameterNamesWithObjectArrayParameter() throws Exception { assertFindMethodByParameterNames("methodWithObjectArray", String[].class); } @Test void findMethodByParameterNamesWithMultidimensionalObjectArrayParameter() throws Exception { assertFindMethodByParameterNames("methodWithMultidimensionalObjectArray", Double[][][][][].class); } /** * @since 5.10 */ @Test void findMethodByParameterNamesWithWithCustomTypeFromDifferentClassLoader() throws Exception { var methodName = "customMethod"; var customTypeName = CustomType.class.getName(); var nestedTypeName = CustomType.NestedType.class.getName(); try (var testClassLoader = TestClassLoader.forClassNamePrefix(customTypeName)) { var customType = testClassLoader.loadClass(customTypeName); assertThat(customType.getClassLoader()).isSameAs(testClassLoader); var optional = findMethod(customType, methodName, nestedTypeName); assertThat(optional).get().satisfies(method -> { assertThat(method.getName()).isEqualTo(methodName); var declaringClass = method.getDeclaringClass(); assertThat(declaringClass.getClassLoader()).isSameAs(testClassLoader); assertThat(declaringClass.getName()).isEqualTo(customTypeName); assertThat(declaringClass).isNotEqualTo(CustomType.class); var parameterTypes = method.getParameterTypes(); assertThat(parameterTypes).extracting(Class::getName).containsExactly(nestedTypeName); Class parameterType = parameterTypes[0]; assertThat(parameterType).isNotEqualTo(CustomType.NestedType.class); assertThat(parameterType.getClassLoader()).isSameAs(testClassLoader); }); } } @Test void findMethodByParameterNamesWithParameterizedMapParameter() throws Exception { var methodName = "methodWithParameterizedMap"; // standard, supported use case assertFindMethodByParameterNames(methodName, Map.class); // generic type info in parameter list var clazz = getClass(); var method = clazz.getDeclaredMethod(methodName, Map.class); var genericParameterTypeName = method.getGenericParameterTypes()[0].getTypeName(); var exception = assertThrows(JUnitException.class, () -> findMethod(clazz, methodName, genericParameterTypeName)); assertThat(exception).hasMessageStartingWith( "Failed to load parameter type [java.util.Map parameterType) throws NoSuchMethodException { var clazz = getClass(); var method = clazz.getDeclaredMethod(methodName, parameterType); var optional = findMethod(clazz, methodName, parameterType.getName()); assertThat(optional).contains(method); } void methodWithPrimitiveArray(int[] nums) { } void methodWithTwoDimensionalPrimitiveArray(int[][] grid) { } void methodWithMultidimensionalPrimitiveArray(int[][][][][] grid) { } void methodWithObjectArray(String[] info) { } void methodWithTwoDimensionalObjectArray(String[][] info) { } void methodWithMultidimensionalObjectArray(Double[][][][][] data) { } void methodWithParameterizedMap(Map map) { } } @Nested class FindMethodsTests { @SuppressWarnings("DataFlowIssue") @Test void findMethodsPreconditions() { // @formatter:off assertPreconditionViolationFor(() -> findMethods(null, null)); assertPreconditionViolationFor(() -> findMethods(null, clazz -> true)); assertPreconditionViolationFor(() -> findMethods(String.class, null)); assertPreconditionViolationFor(() -> findMethods(null, null, null)); assertPreconditionViolationFor(() -> findMethods(null, clazz -> true, BOTTOM_UP)); assertPreconditionViolationFor(() -> findMethods(String.class, null, BOTTOM_UP)); assertPreconditionViolationFor(() -> findMethods(String.class, clazz -> true, null)); // @formatter:on } @Test void findMethodsInInterface() { assertOneFooMethodIn(InterfaceWithOneDeclaredMethod.class); assertOneFooMethodIn(InterfaceWithDefaultMethod.class); assertOneFooMethodIn(InterfaceWithDefaultMethodImpl.class); assertOneFooMethodIn(InterfaceWithStaticMethod.class); assertOneFooMethodIn(InterfaceWithStaticMethodImpl.class); } private static void assertOneFooMethodIn(Class clazz) { assertThat(findMethods(clazz, isFooMethod)).hasSize(1); } /** * @since 1.9.1 * @see GitHub - Issue #2993 */ @Test void findMethodsFindsDistinctMethodsDeclaredInMultipleInterfaces() { Predicate isStringsMethod = method -> method.getName().equals("strings"); assertThat(findMethods(DoubleInheritedInterfaceMethodTestCase.class, isStringsMethod)).hasSize(1); } @Test void findMethodsInObject() { var methods = findMethods(Object.class, method -> true); assertNotNull(methods); assertTrue(methods.size() > 10); } @Test void findMethodsInVoid() { assertThat(findMethods(void.class, method -> true)).isEmpty(); assertThat(findMethods(Void.class, method -> true)).isEmpty(); } @Test void findMethodsInPrimitive() { assertThat(findMethods(int.class, method -> true)).isEmpty(); } @Test void findMethodsInArrays() { assertThat(findMethods(int[].class, method -> true)).isEmpty(); assertThat(findMethods(Integer[].class, method -> true)).isEmpty(); } @Test void findMethodsIgnoresSyntheticMethods() { assertTrue(stream(ClassWithSyntheticMethod.class.getDeclaredMethods()).anyMatch(Method::isSynthetic), "ClassWithSyntheticMethod must actually contain at least one synthetic method."); var methods = findMethods(ClassWithSyntheticMethod.class, method -> true); assertThat(methods).isEmpty(); } @Test void findMethodsUsingHierarchyUpMode() throws Exception { assertThat(findMethods(ChildClass.class, method -> method.getName().contains("method"), BOTTOM_UP))// .containsExactly(ChildClass.class.getMethod("method4"), ParentClass.class.getMethod("method3"), GrandparentInterface.class.getMethod("method2"), GrandparentClass.class.getMethod("method1")); assertThat(findMethods(ChildClass.class, method -> method.getName().contains("other"), BOTTOM_UP))// .containsExactly(ChildClass.class.getMethod("otherMethod3"), ParentClass.class.getMethod("otherMethod2"), GrandparentClass.class.getMethod("otherMethod1")); assertThat(findMethods(ChildClass.class, method -> method.getName().equals("method2"), BOTTOM_UP))// .containsExactly(ParentClass.class.getMethod("method2")); assertThat(findMethods(ChildClass.class, method -> method.getName().equals("wrongName"), BOTTOM_UP))// .isEmpty(); assertThat(findMethods(ParentClass.class, method -> method.getName().contains("method"), BOTTOM_UP))// .containsExactly(ParentClass.class.getMethod("method3"), GrandparentInterface.class.getMethod("method2"), GrandparentClass.class.getMethod("method1")); } @Test void findMethodsUsingHierarchyDownMode() throws Exception { assertThat(findMethods(ChildClass.class, method -> method.getName().contains("method"), TOP_DOWN))// .containsExactly(GrandparentClass.class.getMethod("method1"), GrandparentInterface.class.getMethod("method2"), ParentClass.class.getMethod("method3"), ChildClass.class.getMethod("method4")); assertThat(findMethods(ChildClass.class, method -> method.getName().contains("other"), TOP_DOWN))// .containsExactly(GrandparentClass.class.getMethod("otherMethod1"), ParentClass.class.getMethod("otherMethod2"), ChildClass.class.getMethod("otherMethod3")); assertThat(findMethods(ChildClass.class, method -> method.getName().equals("method2"), TOP_DOWN))// .containsExactly(ParentClass.class.getMethod("method2")); assertThat(findMethods(ChildClass.class, method -> method.getName().equals("wrongName"), TOP_DOWN))// .isEmpty(); assertThat(findMethods(ParentClass.class, method -> method.getName().contains("method"), TOP_DOWN))// .containsExactly(GrandparentClass.class.getMethod("method1"), GrandparentInterface.class.getMethod("method2"), ParentClass.class.getMethod("method3")); } @Test void findMethodsWithShadowingUsingHierarchyUpMode() throws Exception { assertThat(findMethods(MethodShadowingChild.class, methodContains1, BOTTOM_UP))// .containsExactly(MethodShadowingChild.class.getMethod("method1", String.class)); assertThat(findMethods(MethodShadowingChild.class, methodContains2, BOTTOM_UP))// .containsExactly(MethodShadowingParent.class.getMethod("method2", int.class, int.class, int.class), MethodShadowingInterface.class.getMethod("method2", int.class, int.class)); assertThat(findMethods(MethodShadowingChild.class, methodContains4, BOTTOM_UP))// .containsExactly(MethodShadowingChild.class.getMethod("method4", boolean.class)); assertThat(findMethods(MethodShadowingChild.class, methodContains5, BOTTOM_UP))// .containsExactly(MethodShadowingChild.class.getMethod("method5", Long.class), MethodShadowingParent.class.getMethod("method5", String.class)); var methods = findMethods(MethodShadowingChild.class, method -> true, BOTTOM_UP); assertEquals(6, methods.size()); assertThat(methods.subList(0, 3)).containsOnly( MethodShadowingChild.class.getMethod("method4", boolean.class), MethodShadowingChild.class.getMethod("method1", String.class), MethodShadowingChild.class.getMethod("method5", Long.class)); assertThat(methods.subList(3, 5)).containsOnly( MethodShadowingParent.class.getMethod("method2", int.class, int.class, int.class), MethodShadowingParent.class.getMethod("method5", String.class)); assertEquals(MethodShadowingInterface.class.getMethod("method2", int.class, int.class), methods.get(5)); } @Test void findMethodsWithShadowingUsingHierarchyDownMode() throws Exception { assertThat(findMethods(MethodShadowingChild.class, methodContains1, TOP_DOWN))// .containsExactly(MethodShadowingChild.class.getMethod("method1", String.class)); assertThat(findMethods(MethodShadowingChild.class, methodContains2, TOP_DOWN))// .containsExactly(MethodShadowingInterface.class.getMethod("method2", int.class, int.class), MethodShadowingParent.class.getMethod("method2", int.class, int.class, int.class)); assertThat(findMethods(MethodShadowingChild.class, methodContains4, TOP_DOWN))// .containsExactly(MethodShadowingChild.class.getMethod("method4", boolean.class)); assertThat(findMethods(MethodShadowingChild.class, methodContains5, TOP_DOWN))// .containsExactly(MethodShadowingParent.class.getMethod("method5", String.class), MethodShadowingChild.class.getMethod("method5", Long.class)); var methods = findMethods(MethodShadowingChild.class, method -> true, TOP_DOWN); assertEquals(6, methods.size()); assertEquals(MethodShadowingInterface.class.getMethod("method2", int.class, int.class), methods.getFirst()); assertThat(methods.subList(1, 3)).containsOnly( MethodShadowingParent.class.getMethod("method2", int.class, int.class, int.class), MethodShadowingParent.class.getMethod("method5", String.class)); assertThat(methods.subList(3, 6)).containsOnly( MethodShadowingChild.class.getMethod("method4", boolean.class), MethodShadowingChild.class.getMethod("method1", String.class), MethodShadowingChild.class.getMethod("method5", Long.class)); } /** * In non-legacy mode, "static hiding" does not occur. */ @Test void findMethodsWithoutStaticHidingUsingHierarchyUpMode() throws Exception { Class ifc = StaticMethodHidingInterface.class; Class parent = StaticMethodHidingParent.class; Class child = StaticMethodHidingChild.class; var ifcMethod1 = ifc.getDeclaredMethod("method1", String.class); var ifcMethod2 = ifc.getDeclaredMethod("method2", int.class, int.class); var childMethod1 = child.getDeclaredMethod("method1", String.class); var childMethod4 = child.getDeclaredMethod("method4", boolean.class); var childMethod5 = child.getDeclaredMethod("method5", Long.class); var parentMethod1 = parent.getDeclaredMethod("method1", String.class); var parentMethod2 = parent.getDeclaredMethod("method2", int.class, int.class, int.class); var parentMethod4 = parent.getDeclaredMethod("method4", boolean.class); var parentMethod5 = parent.getDeclaredMethod("method5", String.class); assertThat(findMethods(child, methodContains1, BOTTOM_UP)).containsExactly(childMethod1, parentMethod1, ifcMethod1); assertThat(findMethods(child, methodContains2, BOTTOM_UP)).containsExactly(parentMethod2, ifcMethod2); assertThat(findMethods(child, methodContains4, BOTTOM_UP)).containsExactly(childMethod4, parentMethod4); assertThat(findMethods(child, methodContains5, BOTTOM_UP)).containsExactly(childMethod5, parentMethod5); var methods = findMethods(child, method -> true, BOTTOM_UP); assertEquals(9, methods.size()); assertThat(methods.subList(0, 3)).containsOnly(childMethod1, childMethod4, childMethod5); assertThat(methods.subList(3, 7)).containsOnly(parentMethod1, parentMethod2, parentMethod4, parentMethod5); assertThat(methods.subList(7, 9)).containsOnly(ifcMethod1, ifcMethod2); } /** * In non-legacy mode, "static hiding" does not occur. */ @Test void findMethodsWithoutStaticHidingUsingHierarchyDownMode() throws Exception { Class ifc = StaticMethodHidingInterface.class; Class parent = StaticMethodHidingParent.class; Class child = StaticMethodHidingChild.class; var ifcMethod1 = ifc.getDeclaredMethod("method1", String.class); var ifcMethod2 = ifc.getDeclaredMethod("method2", int.class, int.class); var childMethod1 = child.getDeclaredMethod("method1", String.class); var childMethod4 = child.getDeclaredMethod("method4", boolean.class); var childMethod5 = child.getDeclaredMethod("method5", Long.class); var parentMethod1 = parent.getDeclaredMethod("method1", String.class); var parentMethod2 = parent.getDeclaredMethod("method2", int.class, int.class, int.class); var parentMethod4 = parent.getDeclaredMethod("method4", boolean.class); var parentMethod5 = parent.getDeclaredMethod("method5", String.class); assertThat(findMethods(child, methodContains1, TOP_DOWN)).containsExactly(ifcMethod1, parentMethod1, childMethod1); assertThat(findMethods(child, methodContains2, TOP_DOWN)).containsExactly(ifcMethod2, parentMethod2); assertThat(findMethods(child, methodContains4, TOP_DOWN)).containsExactly(parentMethod4, childMethod4); assertThat(findMethods(child, methodContains5, TOP_DOWN)).containsExactly(parentMethod5, childMethod5); var methods = findMethods(child, method -> true, TOP_DOWN); assertEquals(9, methods.size()); assertThat(methods.subList(0, 2)).containsOnly(ifcMethod1, ifcMethod2); assertThat(methods.subList(2, 6)).containsOnly(parentMethod1, parentMethod2, parentMethod4, parentMethod5); assertThat(methods.subList(6, 9)).containsOnly(childMethod1, childMethod4, childMethod5); } @Test void findMethodsDoesNotReturnOverriddenMethods() { Predicate isSpecial = method -> method.isAnnotationPresent(Special.class); // Search for all @Special methods. var methods = findMethods(SuperclassWithInstanceMethods.class, isSpecial); assertThat(signaturesOf(methods))// .containsExactlyInAnyOrder("specialFoo()", "specialFoo(int)", "specialFoo(char)", "specialBar()", "specialBaz()"); // Search for all @Special methods. methods = findMethods(SubclassWithOverriddenInstanceMethods.class, isSpecial); assertThat(signaturesOf(methods))// .containsExactlyInAnyOrder("foo()", "specialFoo()", "specialFoo(int)", "specialBar()"); } @Test void findMethodsReturnsAllOverloadedMethodsInGenericTypeHierarchy() { Class clazz = InterfaceWithGenericDefaultMethodImpl.class; // Search for all foo(*) methods. var methods = findMethods(clazz, isFooMethod); // One might expect or desire that the signature for the generic foo(N) // default method would be "foo(java.lang.Long)" when looked up via the // concrete parameterized class, but it apparently is only _visible_ as // "foo(java.lang.Number)" via reflection. assertThat(signaturesOf(methods)).containsExactly("foo(java.lang.Number)", "foo(java.lang.Double)"); } @Test void findMethodsDoesNotReturnOverriddenDefaultMethods() { Class clazz = InterfaceWithOverriddenGenericDefaultMethodImpl.class; // Search for all foo(*) methods. var methods = findMethods(clazz, isFooMethod); var signatures = signaturesOf(methods); // Although the subsequent assertion covers this case as well, this // assertion is in place to provide a more informative failure message. assertThat(signatures).as("overridden default method should not be in results").doesNotContain( "foo(java.lang.Number)"); assertThat(signatures).containsExactly("foo(java.lang.Long)", "foo(java.lang.Double)"); } private static List signaturesOf(List methods) { // @formatter:off return methods.stream() .map(m -> "%s(%s)".formatted(m.getName(), ClassUtils.nullSafeToString(m.getParameterTypes()))) .toList(); // @formatter:on } @Test void findMethodsIgnoresBridgeMethods() throws Exception { assertFalse(Modifier.isPublic(PublicChildClass.class.getSuperclass().getModifiers())); assertTrue(Modifier.isPublic(PublicChildClass.class.getModifiers())); assertTrue(PublicChildClass.class.getDeclaredMethod("method1").isBridge()); assertTrue(PublicChildClass.class.getDeclaredMethod("method3").isBridge()); var methods = findMethods(PublicChildClass.class, method -> true); var signatures = signaturesOf(methods); assertThat(signatures).containsOnly("method1()", "method2()", "method3()", "otherMethod1()", "otherMethod2()"); assertEquals(0, methods.stream().filter(Method::isBridge).count()); } /** * @see GitHub - Issue #3553 */ @Test void findMethodsDoesNotAllowInstanceMethodToHideStaticMethod() throws Exception { final String BEFORE = "before"; Class superclass = SuperclassWithStaticPackagePrivateBeforeMethod.class; Method staticMethod = superclass.getDeclaredMethod(BEFORE); Class subclass = SubclassWithNonStaticPackagePrivateBeforeMethod.class; Method nonStaticMethod = subclass.getDeclaredMethod(BEFORE); // Prerequisite var methods = findMethods(superclass, ReflectionUtils::isStatic); assertThat(methods).containsExactly(staticMethod); // Actual use cases for this test methods = findMethods(subclass, ReflectionUtils::isStatic); assertThat(methods).containsExactly(staticMethod); methods = findMethods(subclass, ReflectionUtils::isNotStatic); assertThat(methods).containsExactly(nonStaticMethod); } interface StringsInterface1 { static Stream strings() { return Stream.of("abc", "def"); } } interface StringsInterface2 extends StringsInterface1 { } /** * Inherits strings() from interfaces StringsInterface1 and StringsInterface2. */ static class DoubleInheritedInterfaceMethodTestCase implements StringsInterface1, StringsInterface2 { } @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @interface Special { } static class SuperclassWithInstanceMethods { void foo() { } void bar() { } void baz() { } @Special void specialFoo() { } @Special void specialFoo(int i) { } @Special void specialFoo(char ch) { } @Special int specialBar() { return 99; } @Special String specialBaz() { return "42"; } } static class SubclassWithOverriddenInstanceMethods extends SuperclassWithInstanceMethods { // foo() is now special. @Special @Override void foo() { } // No longer special. // Simulates overriding a @Test method without redeclaring @Test. @Override void specialFoo(char ch) { } // No longer special. // Simulates overriding a @TestFactory method without redeclaring @TestFactory. @Override String specialBaz() { return super.specialBaz(); } } } @Nested class ReadFieldTests { @Test void tryToReadFieldValueOfNonexistentStaticField() { assertThrows(NoSuchFieldException.class, () -> tryToReadFieldValue(MyClass.class, "doesNotExist", null).get()); assertThrows(NoSuchFieldException.class, () -> tryToReadFieldValue(MySubClass.class, "staticField", null).get()); } @Test void tryToReadFieldValueOfNonexistentInstanceField() { assertThrows(NoSuchFieldException.class, () -> tryToReadFieldValue(MyClass.class, "doesNotExist", new MyClass(42)).get()); assertThrows(NoSuchFieldException.class, () -> tryToReadFieldValue(MyClass.class, "doesNotExist", new MySubClass(42)).get()); } @SuppressWarnings("DataFlowIssue") @Test void tryToReadFieldValueOfExistingStaticField() throws Exception { assertThat(tryToReadFieldValue(MyClass.class, "staticField", null).get()).isEqualTo(42); var field = MyClass.class.getDeclaredField("staticField"); assertThat(tryToReadFieldValue(field).get()).isEqualTo(42); assertThat(tryToReadFieldValue(field, null).get()).isEqualTo(42); } @Test void tryToReadFieldValueOfExistingInstanceField() throws Exception { var instance = new MyClass(42); assertThat(tryToReadFieldValue(MyClass.class, "instanceField", instance).get()).isEqualTo(42); var field = MyClass.class.getDeclaredField("instanceField"); assertThat(tryToReadFieldValue(field, instance).getNonNull()).isEqualTo(42); assertPreconditionViolationFor(() -> tryToReadFieldValue(field, null).get())// .withMessageStartingWith("Cannot read non-static field")// .withMessageEndingWith("on a null instance."); } } @Nested class FindAndReadFieldsTests { /** * @see GitHub - Issue #3553 */ @Test void findFieldsDoesNotAllowInstanceFieldToHideStaticField() throws Exception { final String TEMP_DIR = "tempDir"; Class superclass = SuperclassWithStaticPackagePrivateTempDirField.class; Field staticField = superclass.getDeclaredField(TEMP_DIR); Class subclass = SubclassWithNonStaticPackagePrivateTempDirField.class; Field nonStaticField = subclass.getDeclaredField(TEMP_DIR); // Prerequisite var fields = findFields(superclass, ReflectionUtils::isStatic, TOP_DOWN); assertThat(fields).containsExactly(staticField); // Actual use cases for this test fields = findFields(subclass, ReflectionUtils::isStatic, TOP_DOWN); assertThat(fields).containsExactly(staticField); fields = findFields(subclass, ReflectionUtils::isNotStatic, TOP_DOWN); assertThat(fields).containsExactly(nonStaticField); } @SuppressWarnings("DataFlowIssue") @Test void readFieldValuesPreconditions() { List fields = new ArrayList<>(); assertPreconditionViolationFor(() -> readFieldValues(null, new Object())); assertPreconditionViolationFor(() -> readFieldValues(fields, null, null)); assertPreconditionViolationFor(() -> readFieldValues(fields, new Object(), null)); } @Test void readFieldValuesFromInstance() { var fields = findFields(ClassWithFields.class, f -> true, TOP_DOWN); var values = readFieldValues(fields, new ClassWithFields()); Assertions. assertThat(values).containsExactly("enigma", 3.14, "text", 2.5, null, 42, "constant", 99); } @Test void readFieldValuesFromClass() { var fields = findFields(ClassWithFields.class, ReflectionUtils::isStatic, TOP_DOWN); var values = readFieldValues(fields, null); Assertions. assertThat(values).containsExactly(2.5, "constant", 99); } /** * @see GitHub - Issue #3646 * @since 1.11 */ @Test void readFieldValuesFromInterfacesAndClassesInTypeHierarchy() { var fields = findFields(InterfaceWithField.class, ReflectionUtils::isStatic, TOP_DOWN); var values = readFieldValues(fields, null); Assertions. assertThat(values).containsOnly("ifc"); fields = findFields(SuperclassWithFieldAndFieldFromInterface.class, ReflectionUtils::isStatic, TOP_DOWN); values = readFieldValues(fields, null); Assertions. assertThat(values).containsExactly("ifc", "super"); fields = findFields(SubclassWithFieldAndFieldFromInterface.class, ReflectionUtils::isStatic, TOP_DOWN); values = readFieldValues(fields, null); Assertions. assertThat(values).containsExactly("ifc", "super", "sub"); } @Test void readFieldValuesFromInstanceWithTypeFilterForString() { var fields = findFields(ClassWithFields.class, isA(String.class), TOP_DOWN); var values = readFieldValues(fields, new ClassWithFields(), isA(String.class)); Assertions. assertThat(values).containsExactly("enigma", "text", null, "constant"); } @Test void readFieldValuesFromClassWithTypeFilterForString() { var fields = findFields(ClassWithFields.class, isA(String.class).and(ReflectionUtils::isStatic), TOP_DOWN); var values = readFieldValues(fields, null, isA(String.class)); Assertions. assertThat(values).containsExactly("constant"); } @Test void readFieldValuesFromInstanceWithTypeFilterForInteger() { var fields = findFields(ClassWithFields.class, isA(int.class), TOP_DOWN); var values = readFieldValues(fields, new ClassWithFields(), isA(int.class)); Assertions. assertThat(values).containsExactly(42); } @Test void readFieldValuesFromClassWithTypeFilterForInteger() { var fields = findFields(ClassWithFields.class, isA(Integer.class).and(ReflectionUtils::isStatic), TOP_DOWN); var values = readFieldValues(fields, null, isA(Integer.class)); Assertions. assertThat(values).containsExactly(99); } @Test void readFieldValuesFromInstanceWithTypeFilterForDouble() { var fields = findFields(ClassWithFields.class, isA(double.class), TOP_DOWN); var values = readFieldValues(fields, new ClassWithFields(), isA(double.class)); Assertions. assertThat(values).containsExactly(3.14); } @Test void readFieldValuesFromClassWithTypeFilterForDouble() { var fields = findFields(ClassWithFields.class, isA(Double.class).and(ReflectionUtils::isStatic), TOP_DOWN); var values = readFieldValues(fields, null, isA(Double.class)); Assertions. assertThat(values).containsExactly(2.5); } private static Predicate isA(Class type) { return f -> f.getType().isAssignableFrom(type); } public static class ClassWithFields { public static final String CONST = "constant"; public static final Integer CONST_INTEGER = 99; public static final Double CONST_DOUBLE = 2.5; public final String stringField = "text"; @SuppressWarnings("unused") private final String privateStringField = "enigma"; @Nullable final String nullStringField = null; public final int integerField = 42; public final double doubleField = 3.14; } interface InterfaceWithField { String interfacePath = "ifc"; } static class SuperclassWithFieldAndFieldFromInterface implements InterfaceWithField { static final String superPath = "super"; } static class SubclassWithFieldAndFieldFromInterface extends SuperclassWithFieldAndFieldFromInterface implements InterfaceWithField { static final String subPath = "sub"; } } // ------------------------------------------------------------------------- interface Generic { X foo(); Y foo(X x, Y y); Z foo(Z[][] zees); int foo(T t); T foo(int i); } class ClassWithSyntheticMethod { // The following lambda expression results in a synthetic method in the // compiled byte code. Comparable synthetic = number -> 0; } interface InterfaceWithOneDeclaredMethod { void foo(); } interface InterfaceWithDefaultMethod { default void foo() { } } static class InterfaceWithDefaultMethodImpl implements InterfaceWithDefaultMethod { } interface InterfaceWithGenericDefaultMethod { default void foo(N number) { } } static class InterfaceWithGenericDefaultMethodImpl implements InterfaceWithGenericDefaultMethod { void foo(Double number) { } } static class InterfaceWithOverriddenGenericDefaultMethodImpl implements InterfaceWithGenericDefaultMethod { @Override public void foo(Long number) { } void foo(Double number) { } } interface InterfaceWithStaticMethod { static void foo() { } } static class InterfaceWithStaticMethodImpl implements InterfaceWithStaticMethod { } static class MyClass { static final int staticField = 42; final int instanceField; MyClass(int value) { this.instanceField = value; } } static class MySubClass extends MyClass { MySubClass(int value) { super(value); } } static class GrandparentClass { public void method1() { } public void otherMethod1() { } } interface GrandparentInterface { default void method2() { } } static class ParentClass extends GrandparentClass implements GrandparentInterface { public void method3() { } public void otherMethod2() { } } static class ChildClass extends ParentClass { public void method4() { } public void otherMethod3() { } } // "public" modifier is necessary here, so that the compiler creates a bridge method. public static class PublicChildClass extends ParentClass { @Override public void otherMethod1() { } @Override public void otherMethod2() { } } interface MethodShadowingInterface { default void method1(String string) { } default void method2(int i, int j) { } } static class MethodShadowingParent implements MethodShadowingInterface { @Override public void method1(String string) { } public void method2(int i, int j, int k) { } public void method4(boolean flag) { } public void method5(String string) { } } static class MethodShadowingChild extends MethodShadowingParent { @Override public void method1(String string) { } @Override public void method4(boolean flag) { } public void method5(Long i) { } } interface StaticMethodHidingInterface { static void method1(String string) { } static void method2(int i, int j) { } } static class StaticMethodHidingParent implements StaticMethodHidingInterface { static void method1(String string) { } static void method2(int i, int j, int k) { } static void method4(boolean flag) { } static void method5(String string) { } } static class StaticMethodHidingChild extends StaticMethodHidingParent { static void method1(String string) { } static void method4(boolean flag) { } static void method5(Long i) { } } abstract static class AbstractOuterClass { class InnerClass { } } static class OuterClass extends AbstractOuterClass { // sibling of OuterClass due to common super type static class StaticNestedSiblingClass extends AbstractOuterClass { } // sibling of OuterClass due to common super type class InnerSiblingClass extends AbstractOuterClass { } static class StaticNestedClass extends OuterClass { } class RecursiveInnerClass extends OuterClass { } class InnerClass { class RecursiveInnerInnerClass extends OuterClass { } } } static class OuterClassImplementingInterface implements InterfaceWithNestedClass { class InnerClassImplementingInterface implements InterfaceWithNestedClass { } } static class OuterClassWithMultipleNestedClasses { class Alpha { } class Beta { } class Gamma { } class Delta { } class Epsilon { } class Zeta { } class Eta { } class Theta { } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsWithGenericTypeHierarchiesTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.platform.commons.util.ReflectionUtils.findMethod; import java.lang.reflect.Method; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @SuppressWarnings("TypeParameterExplicitlyExtendsObject") class ReflectionUtilsWithGenericTypeHierarchiesTests { @Test @Disabled("Describes a new case that does not yet yield the expected result.") void findsMethodsIndependentlyFromOrderOfImplementationsOfInterfaces() { class AB implements InterfaceDouble, InterfaceGenericNumber { } class BA implements InterfaceGenericNumber, InterfaceDouble { } var methodAB = findMethod(AB.class, "foo", Double.class).orElseThrow(); var methodBA = findMethod(BA.class, "foo", Double.class).orElseThrow(); assertEquals(methodAB, methodBA); } @Test void findMoreSpecificMethodFromAbstractImplementationOverDefaultInterfaceMethod() { class A implements InterfaceGenericNumber { @Override public void foo(Long parameter) { } } var foo = findMethod(A.class, "foo", Long.class).orElseThrow(); assertEquals(A.class, foo.getDeclaringClass()); } @Test @Disabled("Describes a new case that does not yet yield the expected result.") void findMoreSpecificMethodFromOverriddenImplementationOfGenericInterfaceMethod() { class A implements InterfaceGenericNumber { @Override public void foo(Number parameter) { } } var foo = findMethod(A.class, "foo", Long.class).orElseThrow(); assertEquals(A.class, foo.getDeclaringClass()); } @Test @Disabled("Describes a new case that does not yet yield the expected result.") void findMoreSpecificMethodFromImplementationOverDefaultInterfaceMethodAndGenericClassExtension() { class AParent { @SuppressWarnings("unused") public void foo(Number parameter) { } } class A extends AParent implements InterfaceGenericNumber { @Override public void foo(Number parameter) { } } var foo = findMethod(A.class, "foo", Long.class).orElseThrow(); assertEquals(A.class, foo.getDeclaringClass()); } @Test @Disabled("Expected behaviour is not clear yet.") void unclearPrecedenceOfImplementationsInParentClassAndInterfaceDefault() { class AParent { public void foo(@SuppressWarnings("unused") Number parameter) { } } class A extends AParent implements InterfaceGenericNumber { } var foo = findMethod(A.class, "foo", Long.class).orElseThrow(); // ???????? assertEquals(A.class, foo.getDeclaringClass()); assertEquals(AParent.class, foo.getDeclaringClass()); assertEquals(InterfaceGenericNumber.class, foo.getDeclaringClass()); } @Test @Disabled("Describes cases where current implementation returns unexpected value") public void findMethodWithMostSpecificParameterTypeInHierarchy() { // Searched Parameter Type is more specific assertSpecificFooMethodFound(ClassImplementingInterfaceWithInvertedHierarchy.class, InterfaceWithGenericNumberParameter.class, Double.class); assertSpecificFooMethodFound(ClassImplementingGenericInterfaceWithMoreSpecificMethod.class, ClassImplementingGenericInterfaceWithMoreSpecificMethod.class, Double.class); assertSpecificFooMethodFound(ClassImplementingGenericAndMoreSpecificInterface.class, InterfaceWithGenericNumberParameter.class, Double.class); assertSpecificFooMethodFound(ClassOverridingDefaultMethodAndImplementingMoreSpecificInterface.class, ClassOverridingDefaultMethodAndImplementingMoreSpecificInterface.class, Double.class); // Exact Type Match assertSpecificFooMethodFound(ClassImplementingGenericInterfaceWithMoreSpecificMethod.class, ClassImplementingGenericInterfaceWithMoreSpecificMethod.class, Number.class); } private void assertSpecificFooMethodFound(Class classToSearchIn, Class classWithMostSpecificMethod, Class parameterType) { var foo = findMethod(classToSearchIn, "foo", parameterType).orElseThrow(); assertDeclaringClass(foo, classWithMostSpecificMethod); } private void assertDeclaringClass(Method method, Class expectedClass) { assertEquals(expectedClass, method.getDeclaringClass()); } interface InterfaceDouble { default void foo(@SuppressWarnings("unused") Double parameter) { } } interface InterfaceGenericNumber { default void foo(@SuppressWarnings("unused") T parameter) { } } public interface InterfaceWithGenericObjectParameter { default void foo(@SuppressWarnings("unused") T a) { } } public interface InterfaceWithGenericNumberParameter { default void foo(@SuppressWarnings("unused") T a) { } } public interface InterfaceExtendingNumberInterfaceWithGenericObjectMethod extends InterfaceWithGenericNumberParameter { default void foo(@SuppressWarnings("unused") T a) { } } public static class ClassImplementingGenericInterfaceWithMoreSpecificMethod implements InterfaceWithGenericObjectParameter { public void foo(@SuppressWarnings("unused") Number a) { } } public static class ClassImplementingGenericAndMoreSpecificInterface implements InterfaceWithGenericObjectParameter, InterfaceWithGenericNumberParameter { } public static class ClassOverridingDefaultMethodAndImplementingMoreSpecificInterface implements InterfaceWithGenericObjectParameter, InterfaceWithGenericNumberParameter { @Override public void foo(@SuppressWarnings("unused") T a) { } } public static class ClassImplementingInterfaceWithInvertedHierarchy implements InterfaceExtendingNumberInterfaceWithGenericObjectMethod { } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/util/RuntimeUtilsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; /** * Unit tests for {@link RuntimeUtils}. * * @since 1.6 */ class RuntimeUtilsTests { @Test void jmxIsAvailableAndInputArgumentsAreReturned() { var optionalArguments = RuntimeUtils.getInputArguments(); assertTrue(optionalArguments.isPresent(), "JMX not available or something else happened..."); var arguments = optionalArguments.get(); assertNotNull(arguments); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/util/SerializationUtils.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class SerializationUtils { public static Object deserialize(byte[] bytes) throws Exception { try (var in = new ObjectInputStream(new ByteArrayInputStream(bytes))) { return in.readObject(); } } public static byte[] serialize(Object object) throws Exception { try (var byteArrayOutputStream = new ByteArrayOutputStream(); var objectOutputStream = new ObjectOutputStream(byteArrayOutputStream)) { objectOutputStream.writeObject(object); objectOutputStream.flush(); return byteArrayOutputStream.toByteArray(); } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/util/StringUtilsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static org.assertj.core.api.Assertions.assertThat; 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.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.junit.platform.commons.util.StringUtils.containsIsoControlCharacter; import static org.junit.platform.commons.util.StringUtils.containsWhitespace; import static org.junit.platform.commons.util.StringUtils.doesNotContainIsoControlCharacter; import static org.junit.platform.commons.util.StringUtils.doesNotContainWhitespace; import static org.junit.platform.commons.util.StringUtils.isBlank; import static org.junit.platform.commons.util.StringUtils.isNotBlank; import static org.junit.platform.commons.util.StringUtils.nullSafeToString; import static org.junit.platform.commons.util.StringUtils.replaceIsoControlCharacters; import static org.junit.platform.commons.util.StringUtils.replaceWhitespaceCharacters; import org.jspecify.annotations.NullUnmarked; import org.junit.jupiter.api.Test; /** * Unit tests for {@link StringUtils}. * * @since 1.0 */ class StringUtilsTests { @Test void blankness() { // @formatter:off assertAll("Blankness", () -> assertTrue(isBlank(null)), () -> assertTrue(isBlank("")), () -> assertTrue(isBlank(" \t\n\r")), () -> assertTrue(isNotBlank(".")) ); // @formatter:on } @SuppressWarnings("DataFlowIssue") @Test void whitespace() { // @formatter:off assertAll("Whitespace", () -> shouldContainWhitespace(" "), () -> shouldContainWhitespace("\u005Ct"), // horizontal tab () -> shouldContainWhitespace("\t"), () -> shouldContainWhitespace("\u005Cn"), // line feed () -> shouldContainWhitespace("\n"), () -> shouldContainWhitespace("\u005Cf"), // form feed () -> shouldContainWhitespace("\f"), () -> shouldContainWhitespace("\u005Cr"), // carriage return () -> shouldContainWhitespace("\r"), () -> shouldContainWhitespace("hello world"), () -> shouldNotContainWhitespace(null), () -> shouldNotContainWhitespace(""), () -> shouldNotContainWhitespace("hello-world"), () -> shouldNotContainWhitespace("0123456789"), () -> shouldNotContainWhitespace("$-_=+!@.,") ); // @formatter:on } @SuppressWarnings("DataFlowIssue") @Test void controlCharacters() { // @formatter:off assertAll("ISO Control Characters", () -> shouldContainIsoControlCharacter("\u005Ct"), // horizontal tab () -> shouldContainIsoControlCharacter("\t"), () -> shouldContainIsoControlCharacter("\u005Cn"), // line feed () -> shouldContainIsoControlCharacter("\n"), () -> shouldContainIsoControlCharacter("\u005Cf"), // form feed () -> shouldContainIsoControlCharacter("\f"), () -> shouldContainIsoControlCharacter("\u005Cr"), // carriage return () -> shouldContainIsoControlCharacter("\r"), () -> shouldNotContainIsoControlCharacter(null), () -> shouldNotContainIsoControlCharacter(""), () -> shouldNotContainIsoControlCharacter("hello-world"), () -> shouldNotContainIsoControlCharacter("0123456789"), () -> shouldNotContainIsoControlCharacter("$-_=+!@.,"), () -> shouldNotContainIsoControlCharacter(" "), () -> shouldNotContainIsoControlCharacter("hello world") ); // @formatter:on } @SuppressWarnings("DataFlowIssue") @Test void replaceControlCharacters() { assertNull(replaceIsoControlCharacters(null, "")); assertEquals("", replaceIsoControlCharacters("", ".")); assertEquals("", replaceIsoControlCharacters("\t\n\r", "")); assertEquals("...", replaceIsoControlCharacters("\t\n\r", ".")); assertEquals("...", replaceIsoControlCharacters("\u005Ct\u005Cn\u005Cr", ".")); assertEquals("abc", replaceIsoControlCharacters("abc", "?")); assertEquals("...", replaceIsoControlCharacters("...", "?")); assertPreconditionViolationFor(() -> replaceIsoControlCharacters("", null)); } @SuppressWarnings("DataFlowIssue") @Test void replaceWhitespaces() { assertNull(replaceWhitespaceCharacters(null, "")); assertEquals("", replaceWhitespaceCharacters("", ".")); assertEquals("", replaceWhitespaceCharacters("\t\n\r", "")); assertEquals("...", replaceWhitespaceCharacters("\t\n\r", ".")); assertEquals("...", replaceWhitespaceCharacters("\u005Ct\u005Cn\u005Cr", ".")); assertEquals("abc", replaceWhitespaceCharacters("abc", "?")); assertEquals("...", replaceWhitespaceCharacters("...", "?")); assertEquals(" ", replaceWhitespaceCharacters(" ", " ")); assertEquals(" ", replaceWhitespaceCharacters("\u000B", " ")); assertEquals(" ", replaceWhitespaceCharacters("\f", " ")); assertPreconditionViolationFor(() -> replaceWhitespaceCharacters("", null)); } @Test void nullSafeToStringChecks() { assertEquals("null", nullSafeToString(null)); assertEquals("", nullSafeToString("")); assertEquals("\t", nullSafeToString("\t")); assertEquals("foo", nullSafeToString("foo")); assertEquals("3.14", nullSafeToString(Double.valueOf("3.14"))); assertEquals("[1, 2, 3]", nullSafeToString(new int[] { 1, 2, 3 })); assertEquals("[a, b, c]", nullSafeToString(new char[] { 'a', 'b', 'c' })); assertEquals("[foo, bar]", nullSafeToString(new String[] { "foo", "bar" })); assertEquals("[34, 42]", nullSafeToString(new Integer[] { 34, 42 })); assertEquals("[[2, 4], [3, 9]]", nullSafeToString(new Integer[][] { { 2, 4 }, { 3, 9 } })); } @Test void nullSafeToStringForObjectWhoseToStringImplementationReturnsNull() { assertEquals("null", nullSafeToString(new ToStringReturnsNull())); } @Test void nullSafeToStringForObjectWhoseToStringImplementationThrowsAnException() { assertThat(nullSafeToString(new ToStringThrowsException()))// .startsWith(ToStringThrowsException.class.getName() + "@"); } private void shouldContainWhitespace(String str) { assertTrue(containsWhitespace(str), () -> "'%s' should contain whitespace".formatted(str)); assertFalse(doesNotContainWhitespace(str), () -> "'%s' should contain whitespace".formatted(str)); } private void shouldNotContainWhitespace(String str) { assertTrue(doesNotContainWhitespace(str), () -> "'%s' should not contain whitespace".formatted(str)); assertFalse(containsWhitespace(str), () -> "'%s' should not contain whitespace".formatted(str)); } private void shouldContainIsoControlCharacter(String str) { assertTrue(containsIsoControlCharacter(str), () -> "'%s' should contain ISO control character".formatted(str)); assertFalse(doesNotContainIsoControlCharacter(str), () -> "'%s' should contain ISO control character".formatted(str)); } private void shouldNotContainIsoControlCharacter(String str) { assertTrue(doesNotContainIsoControlCharacter(str), () -> "'%s' should not contain ISO control character".formatted(str)); assertFalse(containsIsoControlCharacter(str), () -> "'%s' should not contain ISO control character".formatted(str)); } @NullUnmarked private static class ToStringReturnsNull { @Override public String toString() { return null; } } private static class ToStringThrowsException { @Override public String toString() { throw new RuntimeException("Boom!"); } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/util/ToStringBuilderTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import java.util.LinkedHashMap; import java.util.Map; import org.jspecify.annotations.NullUnmarked; import org.junit.jupiter.api.Test; /** * Unit tests for {@link ToStringBuilder}. * * @since 1.0 */ class ToStringBuilderTests { @SuppressWarnings("DataFlowIssue") @Test void withNullObject() { assertPreconditionViolationFor(() -> new ToStringBuilder((Object) null)); } @SuppressWarnings("DataFlowIssue") @Test void withNullClass() { assertPreconditionViolationFor(() -> new ToStringBuilder((Class) null)); } @SuppressWarnings("DataFlowIssue") @Test void appendWithIllegalName() { var builder = new ToStringBuilder(""); assertPreconditionViolationFor(() -> builder.append(null, "")); assertPreconditionViolationFor(() -> builder.append("", "")); assertPreconditionViolationFor(() -> builder.append(" ", "")); } @Test void withZeroFields() { assertEquals("RoleModel []", new ToStringBuilder(new RoleModel()).toString()); assertEquals("RoleModel []", new ToStringBuilder(RoleModel.class).toString()); } @Test void withOneField() { assertEquals("RoleModel [name = 'Dilbert']", new ToStringBuilder(new RoleModel()).append("name", "Dilbert").toString()); } @Test void withNullField() { assertEquals("RoleModel [name = null]", new ToStringBuilder(new RoleModel()).append("name", null).toString()); } @Test void withTwoFields() { assertEquals("RoleModel [name = 'Dilbert', age = 42]", new ToStringBuilder(new RoleModel()).append("name", "Dilbert").append("age", 42).toString()); } @Test void withIntegerArrayField() { assertEquals("RoleModel [magic numbers = [1, 42, 99]]", new ToStringBuilder(new RoleModel()).append("magic numbers", new Integer[] { 1, 42, 99 }).toString()); } @Test void withIntArrayField() { assertEquals("RoleModel [magic numbers = [1, 42, 23]]", new ToStringBuilder(new RoleModel()).append("magic numbers", new int[] { 1, 42, 23 }).toString()); } @Test void withCharArrayField() { assertEquals("RoleModel [magic characters = [a, b]]", new ToStringBuilder(new RoleModel()).append("magic characters", new char[] { 'a', 'b' }).toString()); } @Test void withPrimitiveBooleanArrayField() { assertEquals("RoleModel [booleans = [true, false, true]]", new ToStringBuilder(new RoleModel()).append("booleans", new boolean[] { true, false, true }).toString()); } @Test void withShortArrayField() { assertEquals("RoleModel [values = [23, 42]]", new ToStringBuilder(new RoleModel()).append("values", new short[] { 23, 42 }).toString()); } @Test void withByteArrayField() { assertEquals("RoleModel [values = [23, 42]]", new ToStringBuilder(new RoleModel()).append("values", new byte[] { 23, 42 }).toString()); } @Test void withPrimitiveLongArrayField() { assertEquals("RoleModel [values = [23, 42]]", new ToStringBuilder(new RoleModel()).append("values", new long[] { 23, 42 }).toString()); } @Test void withPrimitiveFloatArrayField() { assertEquals("RoleModel [values = [23.45, 17.13]]", new ToStringBuilder(new RoleModel()).append("values", new float[] { 23.45f, 17.13f }).toString()); } @Test void withPrimitiveDoubleArrayField() { assertEquals("RoleModel [values = [23.45, 17.13]]", new ToStringBuilder(new RoleModel()).append("values", new double[] { 23.45d, 17.13d }).toString()); } @Test @SuppressWarnings("serial") void withMapField() { // @formatter:off Map map = new LinkedHashMap<>() {{ put("foo", 42); put("bar", "enigma"); }}; // @formatter:on assertEquals("RoleModel [mystery map = {foo=42, bar=enigma}]", new ToStringBuilder(new RoleModel()).append("mystery map", map).toString()); } @Test void withDemoImplementation() { var roleModel = new RoleModel("Dilbert", 42); assertEquals("RoleModel [name = 'Dilbert', age = 42]", roleModel.toString()); } @NullUnmarked static class RoleModel { String name; int age; RoleModel() { } RoleModel(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { // @formatter:off return new ToStringBuilder(this) .append("name", this.name) .append("age", this.age) .toString(); // @formatter:on } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/util/classes/AExecutionConditionClass.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util.classes; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.ExtensionContext; /** * @since 5.7 */ public class AExecutionConditionClass implements ExecutionCondition { @Override public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { return ConditionEvaluationResult.enabled("for Class Name Filter Tests"); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/util/classes/ATestExecutionListenerClass.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util.classes; import org.junit.platform.launcher.TestExecutionListener; /** * @since 5.7 */ public class ATestExecutionListenerClass implements TestExecutionListener { } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/util/classes/AVanillaEmpty.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util.classes; /** * @since 5.7 */ public class AVanillaEmpty { } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/util/classes/BExecutionConditionClass.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util.classes; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.ExtensionContext; /** * @since 5.7 */ public class BExecutionConditionClass implements ExecutionCondition { @Override public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { return ConditionEvaluationResult.enabled("for Class Name Filter Tests"); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/util/classes/BTestExecutionListenerClass.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util.classes; import org.junit.platform.launcher.TestExecutionListener; /** * @since 5.7 */ public class BTestExecutionListenerClass implements TestExecutionListener { } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/util/classes/BVanillaEmpty.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util.classes; /** * @since 5.7 */ public class BVanillaEmpty { } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/util/classes/CustomType.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util.classes; public class CustomType { void customMethod(NestedType arg) { } public static class NestedType { } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/ClassLevelDir.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util.pkg1; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Mimics {@code @TempDir}. */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface ClassLevelDir { } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/InstanceLevelDir.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util.pkg1; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Mimics {@code @TempDir}. */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface InstanceLevelDir { } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/SuperclassWithStaticPackagePrivateBeforeMethod.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util.pkg1; import org.junit.jupiter.api.BeforeAll; /** * @see GitHub - Issue #3553 */ public class SuperclassWithStaticPackagePrivateBeforeMethod { @BeforeAll static void before() { // no-op } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/SuperclassWithStaticPackagePrivateTempDirField.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util.pkg1; import java.nio.file.Path; /** * @see GitHub - Issue #3553 */ public class SuperclassWithStaticPackagePrivateTempDirField { @ClassLevelDir static Path tempDir; } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/subpkg/SubclassWithNonStaticPackagePrivateBeforeMethod.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util.pkg1.subpkg; import org.junit.jupiter.api.BeforeEach; import org.junit.platform.commons.util.pkg1.SuperclassWithStaticPackagePrivateBeforeMethod; /** * See GitHub -Issue #3553 */ public class SubclassWithNonStaticPackagePrivateBeforeMethod extends SuperclassWithStaticPackagePrivateBeforeMethod { @BeforeEach void before() { // no-op } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/subpkg/SubclassWithNonStaticPackagePrivateTempDirField.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util.pkg1.subpkg; import java.nio.file.Path; import org.junit.platform.commons.util.pkg1.InstanceLevelDir; import org.junit.platform.commons.util.pkg1.SuperclassWithStaticPackagePrivateTempDirField; /** * @see Github - Issue #3553 */ public class SubclassWithNonStaticPackagePrivateTempDirField extends SuperclassWithStaticPackagePrivateTempDirField { @InstanceLevelDir Path tempDir; } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/console/ConsoleDetailsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.Assertions.assertLinesMatch; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.junit.platform.commons.support.HierarchyTraversalMode.TOP_DOWN; import static org.junit.platform.commons.support.ReflectionSupport.findMethods; import static org.junit.platform.commons.util.ReflectionUtils.getFullyQualifiedMethodName; import java.io.File; import java.net.URI; import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.EnumMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DynamicNode; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.TestReporter; import org.junit.jupiter.api.function.Executable; import org.junit.platform.console.options.Details; import org.junit.platform.console.output.Theme; import org.opentest4j.TestAbortedException; /** * @since 1.0 */ class ConsoleDetailsTests { @TestFactory @DisplayName("Basic tests and annotations usage") List basic() { return scanContainerClassAndCreateDynamicTests(BasicTestCase.class); } @TestFactory @DisplayName("Skipped and disabled tests") List skipped() { return scanContainerClassAndCreateDynamicTests(SkipTestCase.class); } @TestFactory @DisplayName("Failed tests") List failed() { return scanContainerClassAndCreateDynamicTests(FailTestCase.class); } @TestFactory @DisplayName("Tests publishing report entries") List reports() { return scanContainerClassAndCreateDynamicTests(ReportTestCase.class); } private List scanContainerClassAndCreateDynamicTests(Class containerClass) { var containerName = containerClass.getSimpleName().replace("TestCase", ""); // String containerName = containerClass.getSimpleName(); List nodes = new ArrayList<>(); Map> map = new EnumMap<>(Details.class); for (var method : findMethods(containerClass, m -> m.isAnnotationPresent(Test.class), TOP_DOWN)) { var methodName = method.getName(); var types = method.getParameterTypes(); for (var details : Details.values()) { var tests = map.computeIfAbsent(details, key -> new ArrayList<>()); for (var theme : Theme.values()) { var caption = containerName + "-" + methodName + "-" + details + "-" + theme; String[] args = { // "execute", // "--include-engine", "junit-jupiter", // "--details", details.name(), // "--details-theme", theme.name(), // "--disable-ansi-colors", // "--disable-banner", // "--include-classname", containerClass.getCanonicalName(), // "--select-method", getFullyQualifiedMethodName(containerClass, methodName, types) // }; var displayName = methodName + "() " + theme.name(); var dirName = "console/details/" + containerName.toLowerCase(); var outName = caption + ".out.txt"; var runner = new Runner(dirName, outName, args); var source = toUri(dirName, outName).orElse(null); tests.add(dynamicTest(displayName, source, runner)); } } } var source = new File("src/test/resources/console/details").toURI(); map.forEach((details, tests) -> nodes.add(dynamicContainer(details.name(), source, tests.stream()))); return nodes; } @DisplayName("Basic") static class BasicTestCase { @Test void empty() { } @Test @DisplayName(".oO fancy display name Oo.") void changeDisplayName() { } } @DisplayName("Skip") static class SkipTestCase { @Test @Disabled("single line skip reason") void skipWithSingleLineReason() { } @Test @Disabled("multi\nline\nfail\nmessage") void skipWithMultiLineMessage() { } } @DisplayName("Fail") static class FailTestCase { @Test void failWithSingleLineMessage() { fail("single line fail message"); } @Test void failWithMultiLineMessage() { fail("multi\nline\nfail\nmessage"); } } @DisplayName("Report") static class ReportTestCase { @Test void reportSingleMessage(TestReporter reporter) { reporter.publishEntry("foo"); } @Test void reportMultipleMessages(TestReporter reporter) { reporter.publishEntry("foo"); reporter.publishEntry("bar"); } @Test void reportSingleEntryWithSingleMapping(TestReporter reporter) { reporter.publishEntry("foo", "bar"); } @Test void reportMultiEntriesWithSingleMapping(TestReporter reporter) { reporter.publishEntry("foo", "bar"); reporter.publishEntry("far", "boo"); } @Test void reportMultiEntriesWithMultiMappings(TestReporter reporter) { Map values = new LinkedHashMap<>(); values.put("user name", "dk38"); values.put("award year", "1974"); reporter.publishEntry(values); reporter.publishEntry("single", "mapping"); Map more = new LinkedHashMap<>(); more.put("user name", "st77"); more.put("award year", "1977"); more.put("last seen", "2001"); reporter.publishEntry(more); } } private record Runner(String dirName, String outName, String... args) implements Executable { @Override public void execute() throws Throwable { var wrapper = new ConsoleLauncherWrapper(); var result = wrapper.execute(Optional.empty(), args); var optionalUri = toUri(dirName, outName); if (optionalUri.isEmpty()) { if (Boolean.getBoolean("org.junit.platform.console.ConsoleDetailsTests.writeResultOut")) { // do not use Files.createTempDirectory(prefix) as we want one folder for one container var temp = Path.of(System.getProperty("java.io.tmpdir"), dirName.replace('/', '-')); Files.createDirectories(temp); var path = Files.writeString(temp.resolve(outName), result.out); throw new TestAbortedException( "resource `%s` not found\nwrote console stdout to: %s/%s".formatted(dirName, outName, path)); } fail("could not load resource named `" + dirName + "/" + outName + "`"); } var path = Path.of(optionalUri.get()); assumeTrue(Files.exists(path), "path does not exist: " + path); assumeTrue(Files.isReadable(path), "can not read: " + path); var expectedLines = Files.readAllLines(path, UTF_8); var actualLines = List.of(result.out.split("\\R")); assertLinesMatch(expectedLines, actualLines); } } static Optional toUri(String dirName, String outName) { var resourceName = dirName + "/" + outName; var url = ConsoleDetailsTests.class.getClassLoader().getResource(resourceName); if (url == null) { return Optional.empty(); } try { return Optional.of(url.toURI()); } catch (URISyntaxException e) { return Optional.empty(); } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.params.provider.Arguments.arguments; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.FieldSource; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.console.command.StdStreamTestCase; import org.junit.platform.console.subpackage.FailingTestCase; /** * @since 1.0 */ class ConsoleLauncherIntegrationTests { @Test void executeWithoutSubcommandFailsAndPrintsHelpInformation() { var result = new ConsoleLauncherWrapper().execute(-1); assertAll("empty args array results in display of help information and an exception stacktrace", // () -> assertThat(result.err).contains("help information"), // () -> assertThat(result.err).contains("Missing required subcommand") // ); } @Test void executeWithoutExcludeClassnameOptionDoesNotExcludeClassesAndMustIncludeAllClassesMatchingTheStandardClassnamePattern() { String[] args = { "execute", "-e", "junit-jupiter", "-p", "org.junit.platform.console.subpackage" }; assertEquals(9, new ConsoleLauncherWrapper().execute(args).getTestsFoundCount()); } @Test void executeWithExcludeClassnameOptionExcludesClasses() { String[] args = { "execute", "-e", "junit-jupiter", "-p", "org.junit.platform.console.subpackage", "--exclude-classname", "^org\\.junit\\.platform\\.console\\.subpackage\\..*" }; var result = new ConsoleLauncherWrapper().execute(args); assertAll("all subpackage test classes are excluded by the class name filter", // () -> assertArrayEquals(args, result.args), // () -> assertEquals(0, result.code), // () -> assertEquals(0, result.getTestsFoundCount()) // ); } @Test void executeWithExcludeMethodNameOptionExcludesMethods() { var line = "execute -e junit-jupiter -p org.junit.platform.console.subpackage --exclude-methodname" + " ^org\\.junit\\.platform\\.console\\.subpackage\\..+#test"; var args = line.split(" "); var result = new ConsoleLauncherWrapper().execute(args); assertAll("all subpackage test methods are excluded by the method name filter", // () -> assertArrayEquals(args, result.args), // () -> assertEquals(0, result.code), // () -> assertEquals(0, result.getTestsFoundCount()) // ); } @ParameterizedTest @ValueSource(strings = { // "execute -e junit-jupiter -o java.base", // "execute -e junit-jupiter --select-module java.base" // }) void executeSelectingModuleNames(String line) { var args = line.split(" "); assertEquals(0, new ConsoleLauncherWrapper().execute(args).getTestsFoundCount()); } @Test void executeScanModules() { String[] args = { "execute", "-e", "junit-jupiter", "--scan-modules" }; assertEquals(0, new ConsoleLauncherWrapper().execute(args).getTestsFoundCount()); } @ParameterizedTest @FieldSource("redirectStreamArguments") void executeWithRedirectedStdStream(String redirectedStream, int outputFileSize, @TempDir Path tempDir) throws Exception { var outputFile = tempDir.resolve("output.txt"); var line = "execute -e junit-jupiter --select-class %s %s %s".formatted(StdStreamTestCase.class.getName(), redirectedStream, outputFile); var args = line.split(" "); new ConsoleLauncherWrapper().execute(args); assertTrue(Files.exists(outputFile), "File does not exist."); assertEquals(outputFileSize, Files.size(outputFile), "Invalid file size."); } static List redirectStreamArguments = List.of( arguments("--redirect-stdout", StdStreamTestCase.getStdoutOutputFileSize()), arguments("--redirect-stderr", StdStreamTestCase.getStderrOutputFileSize())); @Test void executeWithRedirectedStdStreamsToSameFile(@TempDir Path tempDir) throws Exception { var outputFile = tempDir.resolve("output.txt"); var line = "execute -e junit-jupiter --select-class %s --redirect-stdout %s --redirect-stderr %s".formatted( StdStreamTestCase.class.getName(), outputFile, outputFile); var args = line.split(" "); new ConsoleLauncherWrapper().execute(args); assertTrue(Files.exists(outputFile), "File does not exist."); assertEquals(StdStreamTestCase.getStdoutOutputFileSize() + StdStreamTestCase.getStderrOutputFileSize(), Files.size(outputFile), "Invalid file size."); } @Test void stopsAfterFirstFailingTest() { var result = new ConsoleLauncherWrapper().execute(1, "execute", "-e", "junit-jupiter", "--select-class", FailingTestCase.class.getName(), "--fail-fast", "--disable-ansi-colors"); assertThat(result.getTestsStartedCount()).isEqualTo(1); assertThat(result.getTestsFailedCount()).isEqualTo(1); assertThat(result.getTestsSkippedCount()).isEqualTo(1); assertThat(result.out).endsWith("%nTest execution was cancelled due to --fail-fast mode.%n%n".formatted()); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.params.provider.Arguments.arguments; import java.io.PrintWriter; import java.io.StringWriter; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.EmptySource; import org.junit.jupiter.params.provider.MethodSource; /** * @since 1.0 */ class ConsoleLauncherTests { private final StringWriter stringWriter = new StringWriter(); private final PrintWriter printSink = new PrintWriter(stringWriter); @ParameterizedTest(name = "cmd={0}") @EmptySource @MethodSource("commandsWithEmptyOptionExitCodes") void displayHelp(String command) { var exitCode = ConsoleLauncher.run(printSink, printSink, command, "--help").getExitCode(); assertEquals(0, exitCode); assertThat(output()).contains("--help"); } @ParameterizedTest(name = "cmd={0}") @EmptySource @MethodSource("commandsWithEmptyOptionExitCodes") void displayVersion(String command) { var exitCode = ConsoleLauncher.run(printSink, printSink, command, "--version").getExitCode(); assertEquals(0, exitCode); assertThat(output()).contains("JUnit Platform Console Launcher"); } @ParameterizedTest(name = "{0}") @MethodSource("commandsWithEmptyOptionExitCodes") void displayBanner(String command) { ConsoleLauncher.run(printSink, printSink, command); assertThat(output()).contains("Thanks for using JUnit!"); } @ParameterizedTest(name = "{0}") @MethodSource("commandsWithEmptyOptionExitCodes") void disableBanner(String command, int expectedExitCode) { var exitCode = ConsoleLauncher.run(printSink, printSink, command, "--disable-banner").getExitCode(); assertEquals(expectedExitCode, exitCode); assertThat(output()).doesNotContain("Thanks for using JUnit!"); } @ParameterizedTest(name = "{0}") @MethodSource("commandsWithEmptyOptionExitCodes") void executeWithUnknownCommandLineOption(String command) { var exitCode = ConsoleLauncher.run(printSink, printSink, command, "--all").getExitCode(); assertEquals(-1, exitCode); assertThat(output()).contains("Unknown option: '--all'").contains("Usage:"); } private String output() { return stringWriter.toString(); } @ParameterizedTest(name = "{0}") @MethodSource("commandsWithEmptyOptionExitCodes") void executeWithoutCommandLineOptions(String command, int expectedExitCode) { var actualExitCode = ConsoleLauncher.run(printSink, printSink, command).getExitCode(); assertEquals(expectedExitCode, actualExitCode); } static Stream commandsWithEmptyOptionExitCodes() { return Stream.of( // arguments("execute", -1), // arguments("discover", -1), // arguments("engines", 0) // ); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapper.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Optional; import org.junit.platform.console.command.CommandFacade; import org.junit.platform.console.command.ConsoleTestExecutor; /** * @since 1.0 */ class ConsoleLauncherWrapper { private final StringWriter out = new StringWriter(); private final StringWriter err = new StringWriter(); private final ConsoleTestExecutor.Factory consoleTestExecutorFactory; ConsoleLauncherWrapper() { this(ConsoleTestExecutor::new); } private ConsoleLauncherWrapper(ConsoleTestExecutor.Factory consoleTestExecutorFactory) { this.consoleTestExecutorFactory = consoleTestExecutorFactory; } public ConsoleLauncherWrapperResult execute(String... args) { return execute(0, args); } public ConsoleLauncherWrapperResult execute(int expectedExitCode, String... args) { return execute(Optional.of(expectedExitCode), args); } public ConsoleLauncherWrapperResult execute(Optional expectedCode, String... args) { var outWriter = new PrintWriter(out, false); var errWriter = new PrintWriter(err, false); var result = new CommandFacade(consoleTestExecutorFactory).run(args, outWriter, errWriter); var code = result.getExitCode(); var outText = out.toString(); var errText = err.toString(); if (expectedCode.isPresent()) { int expectedValue = expectedCode.get(); assertEquals(expectedValue, code, "ConsoleLauncher execute code mismatch!"); if (expectedValue != 0 && expectedValue != 1) { assertThat(errText).isNotBlank(); } } return new ConsoleLauncherWrapperResult(args, outText, errText, result); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapperResult.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console; import static java.util.Objects.requireNonNull; import java.io.PrintWriter; import java.util.List; import org.jspecify.annotations.Nullable; import org.junit.platform.console.command.CommandResult; import org.junit.platform.launcher.listeners.TestExecutionSummary; /** * @since 1.0 */ class ConsoleLauncherWrapperResult implements TestExecutionSummary { final String[] args; final String out; final String err; final int code; private final @Nullable TestExecutionSummary summary; ConsoleLauncherWrapperResult(String[] args, String out, String err, CommandResult result) { this.args = args; this.out = out; this.err = err; this.code = result.getExitCode(); this.summary = (TestExecutionSummary) result.getValue() // .filter(TestExecutionSummary.class::isInstance) // .orElse(null); } private void checkTestExecutionSummaryState() { if (summary == null) { throw new IllegalStateException("TestExecutionSummary not assigned. Exit code is: " + code); } } @Override public long getTimeStarted() { checkTestExecutionSummaryState(); return requiredSummary().getTimeStarted(); } @Override public long getTimeFinished() { checkTestExecutionSummaryState(); return requiredSummary().getTimeFinished(); } @Override public long getTotalFailureCount() { checkTestExecutionSummaryState(); return requiredSummary().getTotalFailureCount(); } @Override public long getContainersFoundCount() { checkTestExecutionSummaryState(); return requiredSummary().getContainersFoundCount(); } @Override public long getContainersStartedCount() { checkTestExecutionSummaryState(); return requiredSummary().getContainersStartedCount(); } @Override public long getContainersSkippedCount() { checkTestExecutionSummaryState(); return requiredSummary().getContainersSkippedCount(); } @Override public long getContainersAbortedCount() { checkTestExecutionSummaryState(); return requiredSummary().getContainersAbortedCount(); } @Override public long getContainersSucceededCount() { checkTestExecutionSummaryState(); return requiredSummary().getContainersSucceededCount(); } @Override public long getContainersFailedCount() { checkTestExecutionSummaryState(); return requiredSummary().getContainersFailedCount(); } @Override public long getTestsFoundCount() { checkTestExecutionSummaryState(); return requiredSummary().getTestsFoundCount(); } @Override public long getTestsStartedCount() { checkTestExecutionSummaryState(); return requiredSummary().getTestsStartedCount(); } @Override public long getTestsSkippedCount() { checkTestExecutionSummaryState(); return requiredSummary().getTestsSkippedCount(); } @Override public long getTestsAbortedCount() { checkTestExecutionSummaryState(); return requiredSummary().getTestsAbortedCount(); } @Override public long getTestsSucceededCount() { checkTestExecutionSummaryState(); return requiredSummary().getTestsSucceededCount(); } @Override public long getTestsFailedCount() { checkTestExecutionSummaryState(); return requiredSummary().getTestsFailedCount(); } @Override public void printTo(PrintWriter writer) { checkTestExecutionSummaryState(); requiredSummary().printTo(writer); } @Override public void printFailuresTo(PrintWriter writer) { checkTestExecutionSummaryState(); requiredSummary().printFailuresTo(writer); } @Override public void printFailuresTo(PrintWriter writer, int maxStackTraceLines) { checkTestExecutionSummaryState(); requiredSummary().printFailuresTo(writer, maxStackTraceLines); } @Override public List getFailures() { checkTestExecutionSummaryState(); return requiredSummary().getFailures(); } private TestExecutionSummary requiredSummary() { return requireNonNull(summary); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/console/command/CommandLineOptionsParsingTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.command; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; 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.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.engine.discovery.ClassNameFilter.STANDARD_INCLUDE_PATTERN; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathResource; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectDirectory; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectFile; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectIteration; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectModule; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUri; import static org.mockito.Mockito.mock; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.Map; import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledOnOs; import org.junit.jupiter.api.condition.OS; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.junit.platform.console.options.Details; import org.junit.platform.console.options.TestConsoleOutputOptions; import org.junit.platform.console.options.TestDiscoveryOptions; import org.junit.platform.console.output.Theme; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.discovery.DiscoverySelectors; import org.junit.platform.engine.discovery.FilePosition; /** * @since 1.10 */ class CommandLineOptionsParsingTests { @Test void parseNoArguments() { String[] noArguments = {}; Result options = parse(noArguments); // @formatter:off assertAll( () -> assertFalse(options.output.isAnsiColorOutputDisabled()), () -> assertNull(options.output.getStdoutPath()), () -> assertNull(options.output.getStderrPath()), () -> assertEquals(TestConsoleOutputOptions.DEFAULT_DETAILS, options.output.getDetails()), () -> assertFalse(options.discovery.isScanClasspath()), () -> assertEquals(List.of(STANDARD_INCLUDE_PATTERN), options.discovery.getIncludedClassNamePatterns()), () -> assertEquals(List.of(), options.discovery.getExcludedClassNamePatterns()), () -> assertEquals(List.of(), options.discovery.getIncludedPackages()), () -> assertEquals(List.of(), options.discovery.getExcludedPackages()), () -> assertEquals(List.of(), options.discovery.getIncludedMethodNamePatterns()), () -> assertEquals(List.of(), options.discovery.getExcludedMethodNamePatterns()), () -> assertEquals(List.of(), options.discovery.getIncludedTagExpressions()), () -> assertEquals(List.of(), options.discovery.getExcludedTagExpressions()), () -> assertEquals(List.of(), options.discovery.getAdditionalClasspathEntries()), () -> assertEquals(List.of(), options.discovery.getSelectedUris()), () -> assertEquals(List.of(), options.discovery.getSelectedFiles()), () -> assertEquals(List.of(), options.discovery.getSelectedDirectories()), () -> assertEquals(List.of(), options.discovery.getSelectedModules()), () -> assertEquals(List.of(), options.discovery.getSelectedPackages()), () -> assertEquals(List.of(), options.discovery.getSelectedMethods()), () -> assertEquals(List.of(), options.discovery.getSelectedClasspathEntries()), () -> assertEquals(Map.of(), options.discovery.getConfigurationParameters()) ); // @formatter:on } @Test void parseSwitches() { // @formatter:off assertAll( () -> assertTrue(parse("--disable-ansi-colors").output.isAnsiColorOutputDisabled(), "disable ansi"), () -> assertTrue(parse("--scan-class-path").discovery.isScanClasspath(), "scan class path") ); // @formatter:on } @ParameterizedTest @EnumSource void parseValidDetails(ArgsType type) { // @formatter:off assertAll( () -> assertEquals(Details.VERBOSE, type.parseArgLine("--details verbose").output.getDetails()), () -> assertEquals(Details.TREE, type.parseArgLine("--details tree").output.getDetails()), () -> assertEquals(Details.FLAT, type.parseArgLine("--details flat").output.getDetails()), () -> assertEquals(Details.NONE, type.parseArgLine("--details NONE").output.getDetails()), () -> assertEquals(Details.NONE, type.parseArgLine("--details none").output.getDetails()), () -> assertEquals(Details.NONE, type.parseArgLine("--details None").output.getDetails()) ); // @formatter:on } @Test void parseInvalidDetails() { assertOptionWithMissingRequiredArgumentThrowsException("--details"); } @ParameterizedTest @EnumSource void parseValidDetailsTheme(ArgsType type) { // @formatter:off assertAll( () -> assertEquals(Theme.ASCII, type.parseArgLine("--details-theme ascii").output.getTheme()), () -> assertEquals(Theme.ASCII, type.parseArgLine("--details-theme ASCII").output.getTheme()), () -> assertEquals(Theme.UNICODE, type.parseArgLine("--details-theme unicode").output.getTheme()), () -> assertEquals(Theme.UNICODE, type.parseArgLine("--details-theme UNICODE").output.getTheme()), () -> assertEquals(Theme.UNICODE, type.parseArgLine("--details-theme uniCode").output.getTheme()) ); // @formatter:on } @Test void parseInvalidDetailsTheme() { assertOptionWithMissingRequiredArgumentThrowsException("--details-theme"); } @ParameterizedTest @EnumSource void parseValidIncludeClassNamePatterns(ArgsType type) { // @formatter:off assertAll( () -> assertEquals(List.of(".*Test"), type.parseArgLine("-n .*Test").discovery.getIncludedClassNamePatterns()), () -> assertEquals(List.of(".*Test", ".*Tests"), type.parseArgLine("--include-classname .*Test --include-classname .*Tests").discovery.getIncludedClassNamePatterns()), () -> assertEquals(List.of(".*Test"), type.parseArgLine("--include-classname=.*Test").discovery.getIncludedClassNamePatterns()) ); // @formatter:on } @ParameterizedTest @EnumSource void parseValidExcludeClassNamePatterns(ArgsType type) { // @formatter:off assertAll( () -> assertEquals(List.of(".*Test"), type.parseArgLine("-N .*Test").discovery.getExcludedClassNamePatterns()), () -> assertEquals(List.of(".*Test", ".*Tests"), type.parseArgLine("--exclude-classname .*Test --exclude-classname .*Tests").discovery.getExcludedClassNamePatterns()), () -> assertEquals(List.of(".*Test"), type.parseArgLine("--exclude-classname=.*Test").discovery.getExcludedClassNamePatterns()) ); // @formatter:on } @Test void usesDefaultClassNamePatternWithoutExplicitArgument() throws Exception { assertEquals(List.of(STANDARD_INCLUDE_PATTERN), ArgsType.args.parseArgLine("").discovery.getIncludedClassNamePatterns()); } @Test void parseInvalidIncludeClassNamePatterns() { assertOptionWithMissingRequiredArgumentThrowsException("-n", "--include-classname"); } @Test void parseInvalidExcludeClassNamePatterns() { assertOptionWithMissingRequiredArgumentThrowsException("-N", "--exclude-classname"); } @ParameterizedTest @EnumSource void parseValidIncludedPackages(ArgsType type) { // @formatter:off assertAll( () -> assertEquals(List.of("org.junit.included"), type.parseArgLine("--include-package org.junit.included").discovery.getIncludedPackages()), () -> assertEquals(List.of("org.junit.included"), type.parseArgLine("--include-package=org.junit.included").discovery.getIncludedPackages()), () -> assertEquals(List.of("org.junit.included1", "org.junit.included2"), type.parseArgLine("--include-package org.junit.included1 --include-package org.junit.included2").discovery.getIncludedPackages()) ); // @formatter:on } @ParameterizedTest @EnumSource void parseValidExcludedPackages(ArgsType type) { // @formatter:off assertAll( () -> assertEquals(List.of("org.junit.excluded"), type.parseArgLine("--exclude-package org.junit.excluded").discovery.getExcludedPackages()), () -> assertEquals(List.of("org.junit.excluded"), type.parseArgLine("--exclude-package=org.junit.excluded").discovery.getExcludedPackages()), () -> assertEquals(List.of("org.junit.excluded1", "org.junit.excluded2"), type.parseArgLine("--exclude-package org.junit.excluded1 --exclude-package org.junit.excluded2").discovery.getExcludedPackages()) ); // @formatter:on } @ParameterizedTest @EnumSource void parseValidIncludeMethodNamePatterns(ArgsType type) { // @formatter:off assertAll( () -> assertEquals(List.of(".+#method.*"), type.parseArgLine("--include-methodname .+#method.*").discovery.getIncludedMethodNamePatterns()), () -> assertEquals(List.of(".+#methodA.*", ".+#methodB.*"), type.parseArgLine("--include-methodname .+#methodA.* --include-methodname .+#methodB.*").discovery.getIncludedMethodNamePatterns()), () -> assertEquals(List.of(".+#method.*"), type.parseArgLine("--include-methodname=.+#method.*").discovery.getIncludedMethodNamePatterns()) ); // @formatter:on } @ParameterizedTest @EnumSource void parseValidExcludeMethodNamePatterns(ArgsType type) { // @formatter:off assertAll( () -> assertEquals(List.of(".+#method.*"), type.parseArgLine("--exclude-methodname .+#method.*").discovery.getExcludedMethodNamePatterns()), () -> assertEquals(List.of(".+#methodA.*", ".+#methodB.*"), type.parseArgLine("--exclude-methodname .+#methodA.* --exclude-methodname .+#methodB.*").discovery.getExcludedMethodNamePatterns()), () -> assertEquals(List.of(".+#method.*"), type.parseArgLine("--exclude-methodname=.+#method.*").discovery.getExcludedMethodNamePatterns()) ); // @formatter:on } @Test void parseInvalidIncludeMethodNamePatterns() { assertOptionWithMissingRequiredArgumentThrowsException("--include-methodname"); } @Test void parseInvalidExcludeMethodNamePatterns() { assertOptionWithMissingRequiredArgumentThrowsException("--exclude-methodname"); } @ParameterizedTest @EnumSource void parseValidIncludedTags(ArgsType type) { // @formatter:off assertAll( () -> assertEquals(List.of("fast"), type.parseArgLine("-t fast").discovery.getIncludedTagExpressions()), () -> assertEquals(List.of("fast"), type.parseArgLine("--include-tag fast").discovery.getIncludedTagExpressions()), () -> assertEquals(List.of("fast"), type.parseArgLine("--include-tag=fast").discovery.getIncludedTagExpressions()), () -> assertEquals(List.of("fast", "slow"), type.parseArgLine("-t fast -t slow").discovery.getIncludedTagExpressions()) ); // @formatter:on } @Test void parseInvalidIncludedTags() { assertOptionWithMissingRequiredArgumentThrowsException("-t", "--include-tag"); } @ParameterizedTest @EnumSource void parseValidExcludedTags(ArgsType type) { // @formatter:off assertAll( () -> assertEquals(List.of("fast"), type.parseArgLine("-T fast").discovery.getExcludedTagExpressions()), () -> assertEquals(List.of("fast"), type.parseArgLine("--exclude-tag fast").discovery.getExcludedTagExpressions()), () -> assertEquals(List.of("fast"), type.parseArgLine("--exclude-tag=fast").discovery.getExcludedTagExpressions()), () -> assertEquals(List.of("fast", "slow"), type.parseArgLine("-T fast -T slow").discovery.getExcludedTagExpressions()) ); // @formatter:on } @Test void parseInvalidExcludedTags() { assertOptionWithMissingRequiredArgumentThrowsException("-T", "--exclude-tag"); } @ParameterizedTest @EnumSource void parseValidIncludedEngines(ArgsType type) { // @formatter:off assertAll( () -> assertEquals(List.of("junit-jupiter"), type.parseArgLine("-e junit-jupiter").discovery.getIncludedEngines()), () -> assertEquals(List.of("junit-vintage"), type.parseArgLine("--include-engine junit-vintage").discovery.getIncludedEngines()), () -> assertEquals(List.of(), type.parseArgLine("").discovery.getIncludedEngines()) ); // @formatter:on } @Test void parseInvalidIncludedEngines() { assertOptionWithMissingRequiredArgumentThrowsException("-e", "--include-engine"); } @ParameterizedTest @EnumSource void parseValidExcludedEngines(ArgsType type) { // @formatter:off assertAll( () -> assertEquals(List.of("junit-jupiter"), type.parseArgLine("-E junit-jupiter").discovery.getExcludedEngines()), () -> assertEquals(List.of("junit-vintage"), type.parseArgLine("--exclude-engine junit-vintage").discovery.getExcludedEngines()), () -> assertEquals(List.of(), type.parseArgLine("").discovery.getExcludedEngines()) ); // @formatter:on } @Test void parseInvalidExcludedEngines() { assertOptionWithMissingRequiredArgumentThrowsException("-E", "--exclude-engine"); } @ParameterizedTest @EnumSource void parseValidAdditionalClasspathEntries(ArgsType type) { var dir = Path.of("."); // @formatter:off assertAll( () -> assertEquals(List.of(dir), type.parseArgLine("-cp .").discovery.getAdditionalClasspathEntries()), () -> assertEquals(List.of(dir), type.parseArgLine("--classpath .").discovery.getAdditionalClasspathEntries()), () -> assertEquals(List.of(dir), type.parseArgLine("--classpath=.").discovery.getAdditionalClasspathEntries()), () -> assertEquals(List.of(dir), type.parseArgLine("--class-path .").discovery.getAdditionalClasspathEntries()), () -> assertEquals(List.of(dir), type.parseArgLine("--class-path=.").discovery.getAdditionalClasspathEntries()), () -> assertEquals(List.of(dir, Path.of("lib/some.jar")), type.parseArgLine("-cp . -cp lib/some.jar").discovery.getAdditionalClasspathEntries()), () -> assertEquals(List.of(dir, Path.of("lib/some.jar")), type.parseArgLine("-cp ." + File.pathSeparator + "lib/some.jar").discovery.getAdditionalClasspathEntries()) ); // @formatter:on } @Test @EnabledOnOs(OS.WINDOWS) void parseValidAndAbsoluteAdditionalClasspathEntries() throws Exception { ArgsType type = ArgsType.args; assertEquals(List.of(Path.of("C:\\a.jar")), type.parseArgLine("-cp C:\\a.jar").discovery.getAdditionalClasspathEntries()); assertEquals(List.of(Path.of("C:\\foo.jar"), Path.of("D:\\bar.jar")), type.parseArgLine("-cp C:\\foo.jar;D:\\bar.jar").discovery.getAdditionalClasspathEntries()); } @Test void parseInvalidAdditionalClasspathEntries() { assertOptionWithMissingRequiredArgumentThrowsException("-cp", "--classpath", "--class-path"); } @Test void parseInvalidXmlReportsDirs() { assertOptionWithMissingRequiredArgumentThrowsException("--reports-dir"); } @ParameterizedTest @EnumSource void parseValidUriSelectors(ArgsType type) { // @formatter:off assertAll( () -> assertEquals(List.of(selectUri("file:///foo.txt")), type.parseArgLine("-u file:///foo.txt").discovery.getSelectedUris()), () -> assertEquals(List.of(selectUri("file:///foo.txt")), type.parseArgLine("--select-uri file:///foo.txt").discovery.getSelectedUris()), () -> assertEquals(List.of(selectUri("file:///foo.txt")), type.parseArgLine("--select-uri=file:///foo.txt").discovery.getSelectedUris()), () -> assertEquals(List.of(selectUri("file:///foo.txt"), selectUri("https://example")), type.parseArgLine("-u file:///foo.txt -u https://example").discovery.getSelectedUris()), () -> assertEquals(List.of(selectUri("file:///foo.txt"), selectUri("https://example")), type.parseArgLine("-u file:///foo.txt https://example").discovery.getSelectedUris()) ); // @formatter:on } @Test void parseInvalidUriSelectors() { assertOptionWithMissingRequiredArgumentThrowsException("-u", "--select-uri", "-u unknown-scheme:"); } @ParameterizedTest @EnumSource void parseValidFileSelectors(ArgsType type) { // @formatter:off assertAll( () -> assertEquals(List.of(selectFile("foo.txt")), type.parseArgLine("-f foo.txt").discovery.getSelectedFiles()), () -> assertEquals(List.of(selectFile("foo.txt")), type.parseArgLine("--select-file foo.txt").discovery.getSelectedFiles()), () -> assertEquals(List.of(selectFile("foo.txt")), type.parseArgLine("--select-file=foo.txt").discovery.getSelectedFiles()), () -> assertEquals(List.of(selectFile("foo.txt"), selectFile("bar.csv")), type.parseArgLine("-f foo.txt -f bar.csv").discovery.getSelectedFiles()), () -> assertEquals(List.of(selectFile("foo.txt"), selectFile("bar.csv")), type.parseArgLine("-f foo.txt bar.csv").discovery.getSelectedFiles()), () -> assertEquals(List.of(selectFile("foo.txt", FilePosition.from(5))), type.parseArgLine("-f foo.txt?line=5").discovery.getSelectedFiles()), () -> assertEquals(List.of(selectFile("foo.txt", FilePosition.from(12, 34))), type.parseArgLine("-f foo.txt?line=12&column=34").discovery.getSelectedFiles()) ); // @formatter:on } @Test void parseInvalidFileSelectors() { assertOptionWithMissingRequiredArgumentThrowsException("-f", "--select-file"); } @ParameterizedTest @EnumSource void parseValidDirectorySelectors(ArgsType type) { // @formatter:off assertAll( () -> assertEquals(List.of(selectDirectory("foo/bar")), type.parseArgLine("-d foo/bar").discovery.getSelectedDirectories()), () -> assertEquals(List.of(selectDirectory("foo/bar")), type.parseArgLine("--select-directory foo/bar").discovery.getSelectedDirectories()), () -> assertEquals(List.of(selectDirectory("foo/bar")), type.parseArgLine("--select-directory=foo/bar").discovery.getSelectedDirectories()), () -> assertEquals(List.of(selectDirectory("foo/bar"), selectDirectory("bar/qux")), type.parseArgLine("-d foo/bar -d bar/qux").discovery.getSelectedDirectories()), () -> assertEquals(List.of(selectDirectory("foo/bar"), selectDirectory("bar/qux")), type.parseArgLine("-d foo/bar bar/qux").discovery.getSelectedDirectories()) ); // @formatter:on } @Test void parseInvalidDirectorySelectors() { assertOptionWithMissingRequiredArgumentThrowsException("-d", "--select-directory"); } @ParameterizedTest @EnumSource void parseValidModuleSelectors(ArgsType type) { // @formatter:off assertAll( () -> assertEquals(List.of(selectModule("com.acme.foo")), type.parseArgLine("-o com.acme.foo").discovery.getSelectedModules()), () -> assertEquals(List.of(selectModule("com.acme.foo")), type.parseArgLine("--select-module com.acme.foo").discovery.getSelectedModules()), () -> assertEquals(List.of(selectModule("com.acme.foo")), type.parseArgLine("--select-module=com.acme.foo").discovery.getSelectedModules()), () -> assertEquals(List.of(selectModule("com.acme.foo"), selectModule("com.example.bar")), type.parseArgLine("-o com.acme.foo -o com.example.bar").discovery.getSelectedModules()), () -> assertEquals(List.of(selectModule("com.acme.foo"), selectModule("com.example.bar")), type.parseArgLine("-o com.acme.foo com.example.bar").discovery.getSelectedModules()) ); // @formatter:on } @Test void parseInvalidModuleSelectors() { assertOptionWithMissingRequiredArgumentThrowsException("-o", "--select-module"); } @ParameterizedTest @EnumSource void parseValidPackageSelectors(ArgsType type) { // @formatter:off assertAll( () -> assertEquals(List.of(selectPackage("com.acme.foo")), type.parseArgLine("-p com.acme.foo").discovery.getSelectedPackages()), () -> assertEquals(List.of(selectPackage("com.acme.foo")), type.parseArgLine("--select-package com.acme.foo").discovery.getSelectedPackages()), () -> assertEquals(List.of(selectPackage("com.acme.foo")), type.parseArgLine("--select-package=com.acme.foo").discovery.getSelectedPackages()), () -> assertEquals(List.of(selectPackage("com.acme.foo"), selectPackage("com.example.bar")), type.parseArgLine("-p com.acme.foo -p com.example.bar").discovery.getSelectedPackages()), () -> assertEquals(List.of(selectPackage("com.acme.foo"), selectPackage("com.example.bar")), type.parseArgLine("-p com.acme.foo com.example.bar").discovery.getSelectedPackages()) ); // @formatter:on } @Test void parseInvalidPackageSelectors() { assertOptionWithMissingRequiredArgumentThrowsException("-p", "--select-package"); } @ParameterizedTest @EnumSource void parseValidClassSelectors(ArgsType type) { // @formatter:off assertAll( () -> assertEquals(List.of(selectClass("com.acme.Foo")), type.parseArgLine("-c com.acme.Foo").discovery.getSelectedClasses()), () -> assertEquals(List.of(selectClass("com.acme.Foo")), type.parseArgLine("--select-class com.acme.Foo").discovery.getSelectedClasses()), () -> assertEquals(List.of(selectClass("com.acme.Foo")), type.parseArgLine("--select-class=com.acme.Foo").discovery.getSelectedClasses()), () -> assertEquals(List.of(selectClass("com.acme.Foo"), selectClass("com.example.Bar")), type.parseArgLine("-c com.acme.Foo -c com.example.Bar").discovery.getSelectedClasses()), () -> assertEquals(List.of(selectClass("com.acme.Foo"), selectClass("com.example.Bar")), type.parseArgLine("-c com.acme.Foo com.example.Bar").discovery.getSelectedClasses()) ); // @formatter:on } @Test void parseInvalidClassSelectors() { assertOptionWithMissingRequiredArgumentThrowsException("-c", "--select-class"); } @ParameterizedTest @EnumSource void parseValidMethodSelectors(ArgsType type) { // @formatter:off assertAll( () -> assertEquals(List.of(selectMethod("com.acme.Foo#m()")), type.parseArgLine("-m com.acme.Foo#m()").discovery.getSelectedMethods()), () -> assertEquals(List.of(selectMethod("com.acme.Foo#m()")), type.parseArgLine("--select-method com.acme.Foo#m()").discovery.getSelectedMethods()), () -> assertEquals(List.of(selectMethod("com.acme.Foo#m()")), type.parseArgLine("--select-method=com.acme.Foo#m()").discovery.getSelectedMethods()), () -> assertEquals(List.of(selectMethod("com.acme.Foo#m()"), selectMethod("com.example.Bar#method(java.lang.Object)")), type.parseArgLine("-m com.acme.Foo#m() -m com.example.Bar#method(java.lang.Object)").discovery.getSelectedMethods()), () -> assertEquals(List.of(selectMethod("com.acme.Foo#m()"), selectMethod("com.example.Bar#method(java.lang.Object)")), type.parseArgLine("-m com.acme.Foo#m() com.example.Bar#method(java.lang.Object)").discovery.getSelectedMethods()) ); // @formatter:on } @Test void parseInvalidMethodSelectors() { assertOptionWithMissingRequiredArgumentThrowsException("-m", "--select-method"); } @ParameterizedTest @EnumSource void parseValidClasspathResourceSelectors(ArgsType type) { // @formatter:off assertAll( () -> assertEquals(List.of(selectClasspathResource("/foo.csv")), type.parseArgLine("-r /foo.csv").discovery.getSelectedClasspathResources()), () -> assertEquals(List.of(selectClasspathResource("/foo.csv")), type.parseArgLine("--select-resource /foo.csv").discovery.getSelectedClasspathResources()), () -> assertEquals(List.of(selectClasspathResource("/foo.csv")), type.parseArgLine("--select-resource=/foo.csv").discovery.getSelectedClasspathResources()), () -> assertEquals(List.of(selectClasspathResource("/foo.csv"), selectClasspathResource("bar.json")), type.parseArgLine("-r /foo.csv -r bar.json").discovery.getSelectedClasspathResources()), () -> assertEquals(List.of(selectClasspathResource("/foo.csv"), selectClasspathResource("bar.json")), type.parseArgLine("-r /foo.csv bar.json").discovery.getSelectedClasspathResources()), () -> assertEquals(List.of(selectClasspathResource("/foo.csv", FilePosition.from(5))), type.parseArgLine("-r /foo.csv?line=5").discovery.getSelectedClasspathResources()), () -> assertEquals(List.of(selectClasspathResource("/foo.csv", FilePosition.from(12, 34))), type.parseArgLine("-r /foo.csv?line=12&column=34").discovery.getSelectedClasspathResources()) ); // @formatter:on } @Test void parseInvalidClasspathResourceSelectors() { assertOptionWithMissingRequiredArgumentThrowsException("-r", "--select-resource"); } @ParameterizedTest @EnumSource void parseValidIterationSelectors(ArgsType type) { // @formatter:off assertAll( () -> assertEquals(List.of(selectIteration(selectClasspathResource("/foo.csv"), 0)), type.parseArgLine("-i resource:/foo.csv[0]").discovery.getSelectedIterations()), () -> assertEquals(List.of(selectIteration(selectMethod("com.acme.Foo#m()"), 1, 2)), type.parseArgLine("-i method:com.acme.Foo#m()[1..2]").discovery.getSelectedIterations()), () -> assertEquals(List.of(selectIteration(selectClass("com.acme.Foo"), 0, 2)), type.parseArgLine("--select-iteration class:com.acme.Foo[0,2]").discovery.getSelectedIterations()), () -> assertEquals(List.of(selectIteration(selectPackage("com.acme.foo"), 3)), type.parseArgLine("--select-iteration=package:com.acme.foo[3]").discovery.getSelectedIterations()), () -> assertEquals(List.of(selectIteration(selectModule("com.acme.foo"), 0, 1, 2, 4, 5, 6)), type.parseArgLine("--select-iteration module:com.acme.foo[0..2,4..6]").discovery.getSelectedIterations()), () -> assertEquals(List.of(selectIteration(selectDirectory("foo/bar"), 1, 5)), type.parseArgLine("--select-iteration=directory:foo/bar[1,5]").discovery.getSelectedIterations()), () -> assertEquals(List.of(selectIteration(selectFile("foo.txt"), 6), selectIteration(selectUri("file:///foo.txt"), 7)), type.parseArgLine("-i file:foo.txt[6] -i uri:file:///foo.txt[7]").discovery.getSelectedIterations()), () -> assertEquals(List.of(selectIteration(selectFile("foo.txt"), 6), selectIteration(selectUri("file:///foo.txt"), 7)), type.parseArgLine("-i file:foo.txt[6] uri:file:///foo.txt[7]").discovery.getSelectedIterations()) ); // @formatter:on } @Test void parseInvalidIterationSelectors() { assertOptionWithMissingRequiredArgumentThrowsException("-i", "--select-iteration"); } @ParameterizedTest @EnumSource void parseValidUniqueIdSelectors(ArgsType type) { // @formatter:off assertAll( () -> assertEquals(List.of(selectUniqueId("[engine:junit-jupiter]/[class:MyClass]/[method:myMethod]")), type.parseArgLine("--uid [engine:junit-jupiter]/[class:MyClass]/[method:myMethod]").discovery.getSelectedUniqueIds()), () -> assertEquals(List.of(selectUniqueId("[engine:junit-jupiter]/[class:MyClass]/[method:myMethod]")), type.parseArgLine("--select-unique-id [engine:junit-jupiter]/[class:MyClass]/[method:myMethod]").discovery.getSelectedUniqueIds()), () -> assertEquals(List.of(selectUniqueId("[engine:junit-jupiter]/[class:MyClass1]"), selectUniqueId("[engine:junit-jupiter]/[class:MyClass2]")), type.parseArgLine("--uid [engine:junit-jupiter]/[class:MyClass1] --uid [engine:junit-jupiter]/[class:MyClass2]").discovery.getSelectedUniqueIds()), () -> assertEquals(List.of(selectUniqueId("[engine:junit-jupiter]/[class:MyClass1]"), selectUniqueId("[engine:junit-jupiter]/[class:MyClass2]")), type.parseArgLine("--uid [engine:junit-jupiter]/[class:MyClass1] [engine:junit-jupiter]/[class:MyClass2]").discovery.getSelectedUniqueIds()) ); // @formatter:on } @Test void parseInvalidUniqueIdSelectors() { assertOptionWithMissingRequiredArgumentThrowsException("--uid", "--select-unique-id"); } @ParameterizedTest @EnumSource void parseClasspathScanningEntries(ArgsType type) { var dir = Path.of("."); // @formatter:off assertAll( () -> assertTrue(type.parseArgLine("--scan-class-path").discovery.isScanClasspath()), () -> assertEquals(List.of(), type.parseArgLine("--scan-class-path").discovery.getSelectedClasspathEntries()), () -> assertTrue(type.parseArgLine("--scan-classpath").discovery.isScanClasspath()), () -> assertEquals(List.of(), type.parseArgLine("--scan-classpath").discovery.getSelectedClasspathEntries()), () -> assertTrue(type.parseArgLine("--scan-class-path .").discovery.isScanClasspath()), () -> assertEquals(List.of(dir), type.parseArgLine("--scan-class-path .").discovery.getSelectedClasspathEntries()), () -> assertEquals(List.of(dir), type.parseArgLine("--scan-class-path=.").discovery.getSelectedClasspathEntries()), () -> assertEquals(List.of(dir, Path.of("src/test/java")), type.parseArgLine("--scan-class-path . --scan-class-path src/test/java").discovery.getSelectedClasspathEntries()), () -> assertEquals(List.of(dir, Path.of("src/test/java")), type.parseArgLine("--scan-class-path ." + File.pathSeparator + "src/test/java").discovery.getSelectedClasspathEntries()) ); // @formatter:on } @ParameterizedTest @EnumSource void parseValidConfigurationParameters(ArgsType type) { // @formatter:off assertAll( () -> assertThat(type.parseArgLine("--config foo=bar").discovery.getConfigurationParameters()) .containsOnly(entry("foo", "bar")), () -> assertThat(type.parseArgLine("--config=foo=bar").discovery.getConfigurationParameters()) .containsOnly(entry("foo", "bar")), () -> assertThat(type.parseArgLine("--config foo=bar --config baz=qux").discovery.getConfigurationParameters()) .containsExactly(entry("foo", "bar"), entry("baz", "qux")) ); // @formatter:on } @ParameterizedTest @EnumSource void parseValidConfigurationParametersResource(ArgsType type) { // @formatter:off assertAll( () -> assertThat(type.parseArgLine("--config-resource foo.properties").discovery.getConfigurationParametersResources()) .containsOnly("foo.properties"), () -> assertThat(type.parseArgLine("--config-resource foo.properties --config-resource bar.properties").discovery.getConfigurationParametersResources()) .containsExactly("foo.properties", "bar.properties") ); // @formatter:on } @Test void parseInvalidConfigurationParameters() { assertOptionWithMissingRequiredArgumentThrowsException("--config"); } @ParameterizedTest @EnumSource void parseValidStdoutRedirectionFile(ArgsType type) { var file = Path.of("foo.txt"); // @formatter:off assertAll( () -> assertNull(type.parseArgLine("").output.getStdoutPath()), () -> assertEquals(file, type.parseArgLine("--redirect-stdout=foo.txt").output.getStdoutPath()), () -> assertEquals(file, type.parseArgLine("--redirect-stdout foo.txt").output.getStdoutPath()), () -> assertEquals(file, type.parseArgLine("--redirect-stdout bar.txt --redirect-stdout foo.txt").output.getStdoutPath()) ); // @formatter:on } @ParameterizedTest @EnumSource void parseValidStderrRedirectionFile(ArgsType type) { var file = Path.of("foo.txt"); // @formatter:off assertAll( () -> assertNull(type.parseArgLine("").output.getStderrPath()), () -> assertEquals(file, type.parseArgLine("--redirect-stderr=foo.txt").output.getStderrPath()), () -> assertEquals(file, type.parseArgLine("--redirect-stderr foo.txt").output.getStderrPath()), () -> assertEquals(file, type.parseArgLine("--redirect-stderr bar.txt --redirect-stderr foo.txt").output.getStderrPath()) ); // @formatter:on } @Test void parseInvalidStdoutRedirectionFile() { assertOptionWithMissingRequiredArgumentThrowsException("--redirect-stdout"); } @Test void parseInvalidStderrRedirectionFile() { assertOptionWithMissingRequiredArgumentThrowsException("--redirect-stderr"); } @Test void parseInvalidConfigurationParametersResource() { assertOptionWithMissingRequiredArgumentThrowsException("--config-resource"); } @ParameterizedTest @EnumSource void parseInvalidConfigurationParametersWithDuplicateKey(ArgsType type) { Exception e = assertThrows(Exception.class, () -> type.parseArgLine("--config foo=bar --config foo=baz")); assertThat(e.getMessage()).isEqualTo("Duplicate key 'foo' for values 'bar' and 'baz'."); } @ParameterizedTest @EnumSource void parseValidSelectorIdentifier(ArgsType type) { // @formatter:off assertAll( () -> assertEquals(List.of(selectClasspathResource("/foo.csv")), parseIdentifiers(type,"--select resource:/foo.csv")), () -> assertEquals(List.of(selectMethod("com.acme.Foo#m()")), parseIdentifiers(type,"--select method:com.acme.Foo#m()")), () -> assertEquals(List.of(selectClass("com.acme.Foo")), parseIdentifiers(type,"--select class:com.acme.Foo")), () -> assertEquals(List.of(selectPackage("com.acme.foo")), parseIdentifiers(type,"--select package:com.acme.foo")), () -> assertEquals(List.of(selectModule("com.acme.foo")), parseIdentifiers(type,"--select module:com.acme.foo")), () -> assertEquals(List.of(selectDirectory("foo/bar")), parseIdentifiers(type,"--select directory:foo/bar")), () -> assertEquals(List.of(selectFile("foo.txt"), selectUri("file:///foo.txt")), parseIdentifiers(type,"--select file:foo.txt --select uri:file:///foo.txt")), () -> assertEquals(List.of(selectUniqueId("[engine:junit-jupiter]/[class:MyClass]/[method:myMethod]")), parseIdentifiers(type,"--select uid:[engine:junit-jupiter]/[class:MyClass]/[method:myMethod]")) ); // @formatter:on } private static List parseIdentifiers(ArgsType type, String argLine) throws IOException { return DiscoverySelectors.parseAll(type.parseArgLine(argLine).discovery.getSelectorIdentifiers()).toList(); } @Test void parseInvalidSelectorIdentifier() { assertOptionWithMissingRequiredArgumentThrowsException("--select"); } private void assertOptionWithMissingRequiredArgumentThrowsException(String... options) { assertAll( Stream.of(options).map(opt -> () -> assertThrows(Exception.class, () -> ArgsType.args.parseArgLine(opt)))); } enum ArgsType { args { @Override Result parseArgLine(String argLine) { return parse(split(argLine)); } }, atFile { @Override Result parseArgLine(String argLine) throws IOException { var atFile = Files.createTempFile("junit-launcher-args", ".txt"); try { Files.write(atFile, List.of(split(argLine))); return parse("@" + atFile); } finally { Files.deleteIfExists(atFile); } } }; abstract Result parseArgLine(String argLine) throws IOException; private static String[] split(String argLine) { return argLine.isEmpty() ? new String[0] : argLine.split("\\s+"); } } private static Result parse(String... args) { ExecuteTestsCommand command = new ExecuteTestsCommand((__, ___) -> mock()); command.parseArgs(args); return new Result(command.toTestDiscoveryOptions(), command.toTestConsoleOutputOptions()); } record Result(TestDiscoveryOptions discovery, TestConsoleOutputOptions output) { } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/console/command/ConsoleTestExecutorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.command; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.platform.commons.util.ClassLoaderUtils.getDefaultClassLoader; import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncher; import java.io.PrintWriter; import java.io.StringWriter; import java.nio.file.Path; import java.util.List; import java.util.Optional; import org.junit.jupiter.api.Test; import org.junit.platform.console.options.Details; import org.junit.platform.console.options.TestConsoleOutputOptions; import org.junit.platform.console.options.TestDiscoveryOptions; import org.junit.platform.engine.support.hierarchical.DemoHierarchicalTestEngine; /** * @since 1.0 */ class ConsoleTestExecutorTests { private static final Runnable FAILING_BLOCK = () -> fail("should fail"); private static final Runnable SUCCEEDING_TEST = () -> { }; private final StringWriter stringWriter = new StringWriter(); private final TestDiscoveryOptions discoveryOptions = new TestDiscoveryOptions(); private final TestConsoleOutputOptions outputOptions = new TestConsoleOutputOptions(); private final DemoHierarchicalTestEngine dummyTestEngine = new DemoHierarchicalTestEngine(); { discoveryOptions.setScanClasspath(true); } @Test void printsSummary() { dummyTestEngine.addTest("succeedingTest", SUCCEEDING_TEST); dummyTestEngine.addTest("failingTest", FAILING_BLOCK); var task = new ConsoleTestExecutor(discoveryOptions, outputOptions, () -> createLauncher(dummyTestEngine)); task.execute(new PrintWriter(stringWriter), Optional.empty(), false); assertThat(stringWriter.toString()).contains("Test run finished after", "2 tests found", "0 tests skipped", "2 tests started", "0 tests aborted", "1 tests successful", "1 tests failed"); } @Test void printsDetailsIfTheyAreNotHidden() { outputOptions.setDetails(Details.FLAT); dummyTestEngine.addTest("failingTest", FAILING_BLOCK); var task = new ConsoleTestExecutor(discoveryOptions, outputOptions, () -> createLauncher(dummyTestEngine)); task.execute(new PrintWriter(stringWriter), Optional.empty(), false); assertThat(stringWriter.toString()).contains("Test execution started."); } @Test void printsNoDetailsIfTheyAreHidden() { outputOptions.setDetails(Details.NONE); dummyTestEngine.addTest("failingTest", FAILING_BLOCK); var task = new ConsoleTestExecutor(discoveryOptions, outputOptions, () -> createLauncher(dummyTestEngine)); task.execute(new PrintWriter(stringWriter), Optional.empty(), false); assertThat(stringWriter.toString()).doesNotContain("Test execution started."); } @Test void printsFailuresEvenIfDetailsAreHidden() { outputOptions.setDetails(Details.NONE); dummyTestEngine.addTest("failingTest", FAILING_BLOCK); dummyTestEngine.addContainer("failingContainer", FAILING_BLOCK); var task = new ConsoleTestExecutor(discoveryOptions, outputOptions, () -> createLauncher(dummyTestEngine)); task.execute(new PrintWriter(stringWriter), Optional.empty(), false); assertThat(stringWriter.toString()).contains("Failures (2)", "failingTest", "failingContainer"); } @Test void usesCustomClassLoaderIfAdditionalClassPathEntriesArePresent() { discoveryOptions.setAdditionalClasspathEntries(List.of(Path.of("."))); var oldClassLoader = getDefaultClassLoader(); dummyTestEngine.addTest("failingTest", () -> assertSame(oldClassLoader, getDefaultClassLoader(), "should fail")); var task = new ConsoleTestExecutor(discoveryOptions, outputOptions, () -> createLauncher(dummyTestEngine)); task.execute(new PrintWriter(stringWriter), Optional.empty(), false); assertThat(stringWriter.toString()).contains("failingTest", "should fail", "1 tests failed"); } @Test void usesSameClassLoaderIfNoAdditionalClassPathEntriesArePresent() { discoveryOptions.setAdditionalClasspathEntries(List.of()); var oldClassLoader = getDefaultClassLoader(); dummyTestEngine.addTest("failingTest", () -> assertNotSame(oldClassLoader, getDefaultClassLoader(), "should fail")); var task = new ConsoleTestExecutor(discoveryOptions, outputOptions, () -> createLauncher(dummyTestEngine)); task.execute(new PrintWriter(stringWriter), Optional.empty(), false); assertThat(stringWriter.toString()).contains("failingTest", "should fail", "1 tests failed"); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/console/command/ConsoleUtilsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.command; import static org.junit.jupiter.api.Assertions.assertEquals; import java.nio.charset.Charset; import org.junit.jupiter.api.Test; import org.junit.platform.console.options.ConsoleUtils; class ConsoleUtilsTests { @Test void consoleCharsetReportedByConsoleUtilsIsEitherNativeCharsetOrDefaultCharset() { var console = System.console(); var expected = console != null ? console.charset() : Charset.defaultCharset(); assertEquals(expected, ConsoleUtils.charset()); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/console/command/CustomContextClassLoaderExecutorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.command; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.jupiter.api.Test; /** * @since 1.0 */ class CustomContextClassLoaderExecutorTests { @Test void invokeWithoutCustomClassLoaderDoesNotSetClassLoader() { var originalClassLoader = Thread.currentThread().getContextClassLoader(); var executor = new CustomContextClassLoaderExecutor(Optional.empty()); int result = executor.invoke(() -> { assertSame(originalClassLoader, Thread.currentThread().getContextClassLoader()); return 42; }); assertEquals(42, result); assertSame(originalClassLoader, Thread.currentThread().getContextClassLoader()); } @Test void invokeWithCustomClassLoaderSetsCustomAndResetsToOriginal() { var originalClassLoader = Thread.currentThread().getContextClassLoader(); ClassLoader customClassLoader = URLClassLoader.newInstance(new URL[0]); var executor = new CustomContextClassLoaderExecutor(Optional.of(customClassLoader)); int result = executor.invoke(() -> { assertSame(customClassLoader, Thread.currentThread().getContextClassLoader()); return 23; }); assertEquals(23, result); assertSame(originalClassLoader, Thread.currentThread().getContextClassLoader()); } @Test void invokeWithCustomClassLoaderAndEnsureItIsClosedAfterUsage() { var closed = new AtomicBoolean(false); ClassLoader localClassLoader = new URLClassLoader(new URL[0]) { @Override public void close() throws IOException { closed.set(true); super.close(); } }; var executor = new CustomContextClassLoaderExecutor(Optional.of(localClassLoader)); int result = executor.invoke(() -> 4711); assertEquals(4711, result); assertTrue(closed.get()); } @Test void invokeWithCustomClassLoaderAndKeepItOpenAfterUsage() { var closed = new AtomicBoolean(false); ClassLoader localClassLoader = new URLClassLoader(new URL[0]) { @Override public void close() throws IOException { closed.set(true); super.close(); } }; var executor = new CustomContextClassLoaderExecutor(Optional.of(localClassLoader), CustomClassLoaderCloseStrategy.KEEP_OPEN); int result = executor.invoke(() -> 4711); assertEquals(4711, result); assertFalse(closed.get()); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/console/command/DiscoveryRequestCreatorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.command; import static java.util.stream.Collectors.toMap; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.junit.platform.engine.discovery.ClassNameFilter.STANDARD_INCLUDE_PATTERN; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathResource; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectDirectory; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectFile; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectIteration; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUri; import java.io.File; import java.net.URI; import java.nio.file.Path; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.logging.LogRecord; import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.fixtures.TrackLogRecords; import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.console.options.TestDiscoveryOptions; import org.junit.platform.engine.Filter; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.discovery.ClassNameFilter; import org.junit.platform.engine.discovery.ClassSelector; import org.junit.platform.engine.discovery.ClasspathResourceSelector; import org.junit.platform.engine.discovery.ClasspathRootSelector; import org.junit.platform.engine.discovery.DirectorySelector; import org.junit.platform.engine.discovery.FileSelector; import org.junit.platform.engine.discovery.IterationSelector; import org.junit.platform.engine.discovery.MethodSelector; import org.junit.platform.engine.discovery.PackageNameFilter; import org.junit.platform.engine.discovery.PackageSelector; import org.junit.platform.engine.discovery.UniqueIdSelector; import org.junit.platform.engine.discovery.UriSelector; import org.junit.platform.launcher.LauncherDiscoveryRequest; /** * @since 1.0 */ class DiscoveryRequestCreatorTests { private final TestDiscoveryOptions options = new TestDiscoveryOptions(); @Test void convertsScanClasspathOptionWithoutExplicitRootDirectories() { options.setScanClasspath(true); var request = convert(); var classpathRootSelectors = request.getSelectorsByType(ClasspathRootSelector.class); // @formatter:off assertThat(classpathRootSelectors).extracting(ClasspathRootSelector::getClasspathRoot) .hasAtLeastOneElementOfType(URI.class) .doesNotContainNull(); // @formatter:on } @Test void convertsScanClasspathOptionWithExplicitRootDirectories() { options.setScanClasspath(true); options.setSelectedClasspathEntries(List.of(Path.of("."), Path.of(".."))); var request = convert(); var classpathRootSelectors = request.getSelectorsByType(ClasspathRootSelector.class); // @formatter:off assertThat(classpathRootSelectors).extracting(ClasspathRootSelector::getClasspathRoot) .containsExactly(new File(".").toURI(), new File("..").toURI()); // @formatter:on } @Test void convertsScanClasspathOptionWithAdditionalClasspathEntries() { options.setScanClasspath(true); options.setAdditionalClasspathEntries(List.of(Path.of("."), Path.of(".."))); var request = convert(); var classpathRootSelectors = request.getSelectorsByType(ClasspathRootSelector.class); // @formatter:off assertThat(classpathRootSelectors).extracting(ClasspathRootSelector::getClasspathRoot) .contains(new File(".").toURI(), new File("..").toURI()); // @formatter:on } @Test void doesNotSupportScanClasspathAndExplicitSelectors() { options.setScanClasspath(true); options.setSelectedClasses(List.of(selectClass("SomeTest"))); assertPreconditionViolationFor(this::convert).withMessageContaining("not supported"); } @Test void convertsDefaultIncludeClassNamePatternOption() { options.setScanClasspath(true); var request = convert(); var filters = request.getFiltersByType(ClassNameFilter.class); assertThat(filters).hasSize(1); assertExcludes(filters.getFirst(), STANDARD_INCLUDE_PATTERN); } @Test void convertsExplicitIncludeClassNamePatternOption() { options.setScanClasspath(true); options.setIncludedClassNamePatterns(List.of("Foo.*Bar", "Bar.*Foo")); var request = convert(); var filters = request.getFiltersByType(ClassNameFilter.class); assertThat(filters).hasSize(1); assertIncludes(filters.getFirst(), "Foo.*Bar"); assertIncludes(filters.getFirst(), "Bar.*Foo"); } @Test void includeSelectedClassesAndMethodsRegardlessOfClassNamePatterns() { options.setSelectedClasses(List.of(selectClass("SomeTest"))); options.setSelectedMethods(List.of(selectMethod("com.acme.Foo#m()"))); options.setSelectedIterations(List.of(selectIteration(selectMethod("com.acme.Bar#n()"), 0))); options.setIncludedClassNamePatterns(List.of("Foo.*Bar")); var request = convert(); var filters = request.getFiltersByType(ClassNameFilter.class); assertThat(filters).hasSize(1); assertIncludes(filters.getFirst(), "SomeTest"); assertIncludes(filters.getFirst(), "com.acme.Foo"); assertIncludes(filters.getFirst(), "com.acme.Bar"); assertIncludes(filters.getFirst(), "Foo.*Bar"); } @Test void convertsExcludeClassNamePatternOption() { options.setScanClasspath(true); options.setExcludedClassNamePatterns(List.of("Foo.*Bar", "Bar.*Foo")); var request = convert(); var filters = request.getFiltersByType(ClassNameFilter.class); assertThat(filters).hasSize(2); assertExcludes(filters.get(1), "Foo.*Bar"); assertExcludes(filters.get(1), "Bar.*Foo"); } @Test void convertsPackageOptions() { options.setScanClasspath(true); options.setIncludedPackages(List.of("org.junit.included1", "org.junit.included2", "org.junit.included3")); options.setExcludedPackages(List.of("org.junit.excluded1")); var request = convert(); var packageNameFilters = request.getFiltersByType(PackageNameFilter.class); assertThat(packageNameFilters).hasSize(2); assertIncludes(packageNameFilters.getFirst(), "org.junit.included1"); assertIncludes(packageNameFilters.get(0), "org.junit.included2"); assertIncludes(packageNameFilters.get(0), "org.junit.included3"); assertExcludes(packageNameFilters.get(1), "org.junit.excluded1"); } @Test void convertsMethodNamePatternOptions() { options.setScanClasspath(true); options.setIncludedMethodNamePatterns(List.of(".+#foo.*Bar", ".+#toString", ".+#method.*")); options.setExcludedMethodNamePatterns(List.of(".+#bar.*Foo")); var request = convert(); var methodNameFilters = request.getPostDiscoveryFilters(); assertThat(methodNameFilters).hasSize(2); assertThat(methodNameFilters.get(0).toString()).contains(".+#foo.*Bar", ".+#toString", ".+#method.*"); assertThat(methodNameFilters.get(1).toString()).contains(".+#bar.*Foo"); } @Test void convertsTagOptions() { options.setScanClasspath(true); options.setIncludedTagExpressions(List.of("fast", "medium", "slow")); options.setExcludedTagExpressions(List.of("slow")); var request = convert(); var postDiscoveryFilters = request.getPostDiscoveryFilters(); assertThat(postDiscoveryFilters).hasSize(2); assertThat(postDiscoveryFilters.get(0).toString()).contains("TagFilter"); assertThat(postDiscoveryFilters.get(1).toString()).contains("TagFilter"); } @Test void convertsEngineOptions() { options.setScanClasspath(true); options.setIncludedEngines(List.of("engine1", "engine2", "engine3")); options.setExcludedEngines(List.of("engine2")); var request = convert(); var engineFilters = request.getEngineFilters(); assertThat(engineFilters).hasSize(2); assertThat(engineFilters.get(0).toString()).contains("includes", "[engine1, engine2, engine3]"); assertThat(engineFilters.get(1).toString()).contains("excludes", "[engine2]"); } @Test void propagatesUniqueIdSelectors() { options.setSelectedUniqueId(List.of(selectUniqueId("[engine:a]/[1:1]"), selectUniqueId("[engine:b]/[2:2]"))); var request = convert(); var uriSelectors = request.getSelectorsByType(UniqueIdSelector.class); assertThat(uriSelectors) // .extracting(UniqueIdSelector::getUniqueId) // .containsExactly( // UniqueId.parse("[engine:a]/[1:1]"), //, // UniqueId.parse("[engine:b]/[2:2]") // ); } @Test void propagatesUriSelectors() { options.setSelectedUris(List.of(selectUri("a"), selectUri("b"))); var request = convert(); var uriSelectors = request.getSelectorsByType(UriSelector.class); assertThat(uriSelectors).extracting(UriSelector::getUri).containsExactly(URI.create("a"), URI.create("b")); } @Test void propagatesFileSelectors() { options.setSelectedFiles(List.of(selectFile("foo.txt"), selectFile("bar.csv"))); var request = convert(); var fileSelectors = request.getSelectorsByType(FileSelector.class); assertThat(fileSelectors).extracting(FileSelector::getRawPath).containsExactly("foo.txt", "bar.csv"); } @Test void propagatesDirectorySelectors() { options.setSelectedDirectories(List.of(selectDirectory("foo/bar"), selectDirectory("bar/qux"))); var request = convert(); var directorySelectors = request.getSelectorsByType(DirectorySelector.class); assertThat(directorySelectors).extracting(DirectorySelector::getRawPath).containsExactly("foo/bar", "bar/qux"); } @Test void propagatesPackageSelectors() { options.setSelectedPackages(List.of(selectPackage("com.acme.foo"), selectPackage("com.example.bar"))); var request = convert(); var packageSelectors = request.getSelectorsByType(PackageSelector.class); assertThat(packageSelectors).extracting(PackageSelector::getPackageName).containsExactly("com.acme.foo", "com.example.bar"); } @Test void propagatesClassSelectors() { options.setSelectedClasses(List.of(selectClass("com.acme.Foo"), selectClass("com.example.Bar"))); var request = convert(); var classSelectors = request.getSelectorsByType(ClassSelector.class); assertThat(classSelectors).extracting(ClassSelector::getClassName).containsExactly("com.acme.Foo", "com.example.Bar"); } @Test void propagatesMethodSelectors() { options.setSelectedMethods( List.of(selectMethod("com.acme.Foo#m()"), selectMethod("com.example.Bar#method(java.lang.Object)"))); var request = convert(); var methodSelectors = request.getSelectorsByType(MethodSelector.class); assertThat(methodSelectors).hasSize(2); assertThat(methodSelectors.getFirst().getClassName()).isEqualTo("com.acme.Foo"); assertThat(methodSelectors.get(0).getMethodName()).isEqualTo("m"); assertThat(methodSelectors.get(0).getParameterTypeNames()).isEmpty(); assertThat(methodSelectors.get(1).getClassName()).isEqualTo("com.example.Bar"); assertThat(methodSelectors.get(1).getMethodName()).isEqualTo("method"); assertThat(methodSelectors.get(1).getParameterTypeNames()).isEqualTo("java.lang.Object"); } @Test void propagatesClasspathResourceSelectors() { options.setSelectedClasspathResources( List.of(selectClasspathResource("foo.csv"), selectClasspathResource("com/example/bar.json"))); var request = convert(); var classpathResourceSelectors = request.getSelectorsByType(ClasspathResourceSelector.class); assertThat(classpathResourceSelectors).extracting( ClasspathResourceSelector::getClasspathResourceName).containsExactly("foo.csv", "com/example/bar.json"); } @Test void propagatesIterationSelectors() { var methodSelector = selectMethod("com.acme.Foo#m()"); var classSelector = selectClass("com.example.Bar"); options.setSelectedIterations(List.of(selectIteration(methodSelector, 1), selectIteration(classSelector, 2))); var request = convert(); var iterationSelectors = request.getSelectorsByType(IterationSelector.class); assertThat(iterationSelectors).hasSize(2); assertThat(iterationSelectors.get(0).getParentSelector()).isEqualTo(methodSelector); assertThat(iterationSelectors.get(0).getIterationIndices()).containsExactly(1); assertThat(iterationSelectors.get(1).getParentSelector()).isEqualTo(classSelector); assertThat(iterationSelectors.get(1).getIterationIndices()).containsExactly(2); } @Test void propagatesSelectorIdentifiers() { var methodSelector = selectMethod("com.acme.Foo#m()"); var classSelector = selectClass("com.example.Bar"); options.setSelectorIdentifiers( List.of(methodSelector.toIdentifier().orElseThrow(), classSelector.toIdentifier().orElseThrow())); var request = convert(); assertThat(request.getSelectorsByType(MethodSelector.class)).containsExactly(methodSelector); assertThat(request.getSelectorsByType(ClassSelector.class)).containsExactly(classSelector); } @Test void convertsConfigurationParameters() { options.setScanClasspath(true); options.setConfigurationParameters(mapOf(entry("foo", "bar"), entry("baz", "true"))); var request = convert(); var configurationParameters = request.getConfigurationParameters(); assertThat(configurationParameters.get("foo")).contains("bar"); assertThat(configurationParameters.getBoolean("baz")).contains(true); } @Test void convertsConfigurationParametersResources() { options.setScanClasspath(true); options.setConfigurationParameters(mapOf(entry("foo", "bar"), entry("com.example.prop.first", "baz"))); options.setConfigurationParametersResources(List.of("config-test.properties")); var request = convert(); var configurationParameters = request.getConfigurationParameters(); assertThat(configurationParameters.get("foo")).contains("bar"); assertThat(configurationParameters.get("com.example.prop.first")).contains("baz"); assertThat(configurationParameters.get("com.example.prop.second")).contains("second value"); } @Test void logsInvalidSearchPathRoots(@TrackLogRecords LogRecordListener listener) { var opts = new TestDiscoveryOptions(); opts.setScanClasspath(true); var missingPath = Path.of("/does/not/exist"); opts.setSelectedClasspathEntries(List.of(missingPath)); DiscoveryRequestCreator.toDiscoveryRequestBuilder(opts); assertThat(listener.stream(DiscoveryRequestCreator.class)) // .map(LogRecord::getMessage) // .filteredOn(message -> message.contains(missingPath.toString())) // .hasSize(1); } @Test void logsInvalidAdditionalClasspathRoots(@TrackLogRecords LogRecordListener listener) { var opts = new TestDiscoveryOptions(); opts.setScanClasspath(true); var missingPath = Path.of("/also/does/not/exist"); opts.setAdditionalClasspathEntries(List.of(missingPath)); DiscoveryRequestCreator.toDiscoveryRequestBuilder(opts); assertThat(listener.stream(DiscoveryRequestCreator.class)) // .map(LogRecord::getMessage) // .filteredOn(message -> message.contains(missingPath.toString())) // .hasSize(1); } private LauncherDiscoveryRequest convert() { return DiscoveryRequestCreator.toDiscoveryRequestBuilder(options).build(); } private void assertIncludes(Filter filter, String included) { assertThat(filter.apply(included).included()).isTrue(); } private void assertExcludes(Filter filter, String excluded) { assertThat(filter.apply(excluded).excluded()).isTrue(); } @SafeVarargs @SuppressWarnings("varargs") private static Map mapOf(Entry... entries) { return Stream.of(entries).collect(toMap(Entry::getKey, Entry::getValue)); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/console/command/ExecuteTestsCommandTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.command; import static org.assertj.core.api.Assertions.assertThat; 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.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.nio.file.Path; import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.platform.launcher.listeners.TestExecutionSummary; /** * @since 1.10 */ class ExecuteTestsCommandTests { private final TestExecutionSummary summary = mock(); private final ConsoleTestExecutor consoleTestExecutor = mock(); private final ExecuteTestsCommand command = new ExecuteTestsCommand((__, ___) -> consoleTestExecutor); @BeforeEach void setUp() { when(consoleTestExecutor.execute(any(), any(), anyBoolean())).thenReturn(summary); } @Test void hasStatusCode0ForNoTotalFailures() { when(summary.getTotalFailureCount()).thenReturn(0L); command.execute(); assertThat(command.getExitCode()).isEqualTo(0); } @Test void hasStatusCode1ForForAnyFailure() { when(summary.getTotalFailureCount()).thenReturn(1L); command.execute(); assertThat(command.getExitCode()).isEqualTo(1); } /** * @since 1.3 */ @Test void hasStatusCode2ForNoTestsAndHasOptionFailIfNoTestsFound() { when(summary.getTestsFoundCount()).thenReturn(0L); command.execute("--fail-if-no-tests"); assertThat(command.getExitCode()).isEqualTo(2); } /** * @since 1.3 */ @Test void hasStatusCode0ForTestsAndHasOptionFailIfNoTestsFound() { when(summary.getTestsFoundCount()).thenReturn(1L); when(summary.getTotalFailureCount()).thenReturn(0L); command.execute("--fail-if-no-tests"); assertThat(command.getExitCode()).isEqualTo(0); } /** * @since 1.3 */ @Test void hasStatusCode0ForNoTestsAndNotFailIfNoTestsFound() { when(summary.getTestsFoundCount()).thenReturn(0L); command.execute(); assertThat(command.getExitCode()).isEqualTo(0); } @Test void parseValidXmlReportsDirs() { var dir = Path.of("build", "test-results"); // @formatter:off assertAll( () -> assertEquals(Optional.empty(), parseArgs().getReportsDir()), () -> assertEquals(Optional.of(dir), parseArgs("--reports-dir", "build/test-results").getReportsDir()), () -> assertEquals(Optional.of(dir), parseArgs("--reports-dir=build/test-results").getReportsDir()) ); // @formatter:on } @Test void parseValidFailFast() { // @formatter:off assertAll( () -> assertFalse(parseArgs().isFailFast()), () -> assertTrue(parseArgs("--fail-fast").isFailFast()) ); // @formatter:on } private ExecuteTestsCommand parseArgs(String... args) { command.parseArgs(args); return command; } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/console/command/StdStreamTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.command; import org.junit.jupiter.api.Test; public class StdStreamTestCase { private static final String STDOUT_DATA = "Writing to STDOUT..."; private static final String STDERR_DATA = "Writing to STDERR..."; public static int getStdoutOutputFileSize() { return STDOUT_DATA.length(); } public static int getStderrOutputFileSize() { return STDERR_DATA.length(); } @Test void printTest() { System.out.print(STDOUT_DATA); System.err.print(STDERR_DATA); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/console/command/ThemeTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.command; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; import org.junit.platform.console.output.Theme; class ThemeTests { @Test void givenUtf8ShouldReturnUnicode() { assertEquals(Theme.UNICODE, Theme.valueOf(StandardCharsets.UTF_8)); } @Test void givenAnythingElseShouldReturnAscii() { assertAll("All character sets that are not UTF-8 should return Theme.ASCII", () -> { assertEquals(Theme.ASCII, Theme.valueOf(StandardCharsets.ISO_8859_1)); assertEquals(Theme.ASCII, Theme.valueOf(StandardCharsets.US_ASCII)); assertEquals(Theme.ASCII, Theme.valueOf(StandardCharsets.UTF_16)); }); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/console/output/ColorPaletteTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.output; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.platform.launcher.core.OutputDirectoryCreators.dummyOutputDirectoryCreator; import static org.mockito.Mockito.mock; import java.io.PrintWriter; import java.io.StringReader; import java.io.StringWriter; import java.util.List; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.fakes.TestDescriptorStub; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; /** * Unit tests for {@link ColorPalette}. * * @since 1.9 */ class ColorPaletteTests { @Nested class LoadFromPropertiesTests { @Test void singleOverride() { String properties = """ SUCCESSFUL = 35;1 """; ColorPalette colorPalette = new ColorPalette(new StringReader(properties)); String actual = colorPalette.paint(Style.SUCCESSFUL, "text"); assertEquals("\u001B[35;1mtext\u001B[0m", actual); } @Test void keysAreCaseInsensitive() { String properties = """ suCcESSfuL = 35;1 """; ColorPalette colorPalette = new ColorPalette(new StringReader(properties)); String actual = colorPalette.paint(Style.SUCCESSFUL, "text"); assertEquals("\u001B[35;1mtext\u001B[0m", actual); } @Test void junkKeysAreIgnored() { String properties = """ SUCCESSFUL = 35;1 JUNK = 1;31;40 """; ColorPalette colorPalette = new ColorPalette(new StringReader(properties)); String actual = colorPalette.paint(Style.SUCCESSFUL, "text"); assertEquals("\u001B[35;1mtext\u001B[0m", actual); } @Test void multipleOverrides() { String properties = """ SUCCESSFUL = 35;1 FAILED = 33;4 """; ColorPalette colorPalette = new ColorPalette(new StringReader(properties)); String successful = colorPalette.paint(Style.SUCCESSFUL, "text"); String failed = colorPalette.paint(Style.FAILED, "text"); assertEquals("\u001B[35;1mtext\u001B[0m", successful); assertEquals("\u001B[33;4mtext\u001B[0m", failed); } @Test void unspecifiedStylesAreDefault() { String properties = """ SUCCESSFUL = 35;1 """; ColorPalette colorPalette = new ColorPalette(new StringReader(properties)); String actual = colorPalette.paint(Style.FAILED, "text"); assertEquals("\u001B[31mtext\u001B[0m", actual); } @Test void cannotOverrideNone() { String properties = """ NONE = 35;1 """; StringReader reader = new StringReader(properties); assertThrows(IllegalArgumentException.class, () -> new ColorPalette(reader)); } } @Nested class DemonstratePalettesTests { private static final String ESC = "\u001B["; private static final String RESET = ESC + "0m"; private static final String NEW_LINE = System.lineSeparator(); @Test void verbose_default() { StringWriter stringWriter = new StringWriter(); PrintWriter out = new PrintWriter(stringWriter, true); TestExecutionListener listener = new VerboseTreePrintingListener(out, ColorPalette.DEFAULT, 16, Theme.ASCII); demoTestRun(listener); String output = stringWriter.toString(); // @formatter:off assertThat(output).contains( withAnsi("{green}[OK] SUCCESSFUL{NL}{reset}"), withAnsi("{red}[X] FAILED{NL}{reset}"), withAnsi("{yellow}[A] ABORTED{NL}{reset}"), withAnsi("{magenta}[S] SKIPPED") ); // @formatter:on } @Test void verbose_single_color() { StringWriter stringWriter = new StringWriter(); PrintWriter out = new PrintWriter(stringWriter, true); TestExecutionListener listener = new VerboseTreePrintingListener(out, ColorPalette.SINGLE_COLOR, 16, Theme.ASCII); demoTestRun(listener); String output = stringWriter.toString(); // @formatter:off assertThat(output).contains( withAnsi("{bold}[OK] SUCCESSFUL{NL}{reset}"), withAnsi("{reverse}[X] FAILED{NL}{reset}"), withAnsi("{underline}[A] ABORTED{NL}{reset}"), withAnsi("{strikethrough}[S] SKIPPED") ); // @formatter:on } @Test void simple_default() { StringWriter stringWriter = new StringWriter(); PrintWriter out = new PrintWriter(stringWriter, true); TestExecutionListener listener = new TreePrintingListener(out, ColorPalette.DEFAULT, Theme.ASCII); demoTestRun(listener); String output = stringWriter.toString(); // @formatter:off assertThat(output).contains( withAnsi("{blue}My Test{reset} {green}[OK]{reset}"), withAnsi("{red}My Test{reset} {red}[X]{reset}"), withAnsi("{yellow}My Test{reset} {yellow}[A]{reset}"), withAnsi("{magenta}My Test{reset} {magenta}[S]{reset}") ); // @formatter:on } @Test void simple_single_color() { StringWriter stringWriter = new StringWriter(); PrintWriter out = new PrintWriter(stringWriter, true); TestExecutionListener listener = new TreePrintingListener(out, ColorPalette.SINGLE_COLOR, Theme.ASCII); demoTestRun(listener); String output = stringWriter.toString(); // @formatter:off assertThat(output).contains( withAnsi("{bold}[OK]{reset}"), withAnsi("{reverse}[X]{reset}"), withAnsi("{underline}[A]{reset}"), withAnsi("{strikethrough}[S]{reset}") ); // @formatter:on } @Test void flat_default() { StringWriter stringWriter = new StringWriter(); PrintWriter out = new PrintWriter(stringWriter, true); TestExecutionListener listener = new FlatPrintingListener(out, ColorPalette.DEFAULT); demoTestRun(listener); String output = stringWriter.toString(); // @formatter:off assertThat(output).contains( withAnsi("{green}Finished: My Test ([engine:demo-engine]){reset}"), withAnsi("{red}Finished: My Test ([engine:demo-engine]){reset}"), withAnsi("{yellow}Finished: My Test ([engine:demo-engine]){reset}"), withAnsi("{magenta}Skipped: My Test ([engine:demo-engine]){reset}") ); // @formatter:on } @Test void flat_single_color() { StringWriter stringWriter = new StringWriter(); PrintWriter out = new PrintWriter(stringWriter, true); TestExecutionListener listener = new FlatPrintingListener(out, ColorPalette.SINGLE_COLOR); demoTestRun(listener); String output = stringWriter.toString(); // @formatter:off assertThat(output).contains( withAnsi("{bold}Finished: My Test ([engine:demo-engine]){reset}"), withAnsi("{reverse}Finished: My Test ([engine:demo-engine]){reset}"), withAnsi("{underline}Finished: My Test ([engine:demo-engine]){reset}"), withAnsi("{strikethrough}Skipped: My Test ([engine:demo-engine]){reset}") ); // @formatter:on } private static String withAnsi(String template) { // @formatter:off return template .replace("{blue}", ESC + "34m") .replace("{green}", ESC + "32m") .replace("{red}", ESC + "31m") .replace("{yellow}", ESC + "33m") .replace("{magenta}", ESC + "35m") .replace("{bold}", ESC + "1m") .replace("{reverse}", ESC + "7m") .replace("{underline}", ESC + "4m") .replace("{strikethrough}", ESC + "9m") .replace("{reset}", RESET) .replace("{NL}", NEW_LINE); // @formatter:on } private void demoTestRun(TestExecutionListener listener) { TestDescriptor testDescriptor = new TestDescriptorStub(UniqueId.forEngine("demo-engine"), "My Test"); TestPlan testPlan = TestPlan.from(true, List.of(testDescriptor), mock(), dummyOutputDirectoryCreator()); listener.testPlanExecutionStarted(testPlan); listener.executionStarted(TestIdentifier.from(testDescriptor)); listener.executionFinished(TestIdentifier.from(testDescriptor), TestExecutionResult.successful()); listener.dynamicTestRegistered(TestIdentifier.from(testDescriptor)); listener.executionStarted(TestIdentifier.from(testDescriptor)); listener.executionFinished(TestIdentifier.from(testDescriptor), TestExecutionResult.failed(new Exception())); listener.executionStarted(TestIdentifier.from(testDescriptor)); listener.executionFinished(TestIdentifier.from(testDescriptor), TestExecutionResult.aborted(new Exception())); listener.reportingEntryPublished(TestIdentifier.from(testDescriptor), ReportEntry.from("Key", "Value")); listener.executionSkipped(TestIdentifier.from(testDescriptor), "to demonstrate skipping"); listener.testPlanExecutionFinished(testPlan); } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/console/output/FlatPrintingListenerTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.output; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.console.output.FlatPrintingListener.INDENTATION; import static org.junit.platform.engine.TestExecutionResult.failed; import java.io.PrintWriter; import java.io.StringWriter; import java.nio.file.Path; import org.assertj.core.util.Maps; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.fakes.TestDescriptorStub; import org.junit.platform.launcher.TestIdentifier; /** * @since 1.0 */ class FlatPrintingListenerTests { private static final String EOL = System.lineSeparator(); @Test void executionSkipped() { var stringWriter = new StringWriter(); listener(stringWriter).executionSkipped(newTestIdentifier(), "Test" + EOL + "disabled"); var lines = lines(stringWriter); assertEquals(3, lines.length); assertAll("lines in the output", // () -> assertEquals("Skipped: demo-test ([engine:demo-engine])", lines[0]), // () -> assertEquals(INDENTATION + "=> Reason: Test", lines[1]), // () -> assertEquals(INDENTATION + "disabled", lines[2])); } @Test void reportingEntryPublished() { var stringWriter = new StringWriter(); listener(stringWriter).reportingEntryPublished(newTestIdentifier(), ReportEntry.from("foo", "bar")); var lines = lines(stringWriter); assertEquals(2, lines.length); assertAll("lines in the output", // () -> assertEquals("Reported: demo-test ([engine:demo-engine])", lines[0]), // () -> assertTrue(lines[1].startsWith(INDENTATION + "=> Reported values: ReportEntry [timestamp =")), // () -> assertTrue(lines[1].endsWith(", foo = 'bar']"))); } @Test void fileEntryPublished() { var stringWriter = new StringWriter(); listener(stringWriter).fileEntryPublished(newTestIdentifier(), FileEntry.from(Path.of("test.txt"), "text/plain")); var lines = lines(stringWriter); assertEquals(2, lines.length); assertAll("lines in the output", // () -> assertEquals("Reported: demo-test ([engine:demo-engine])", lines[0]), // () -> assertTrue(lines[1].startsWith(INDENTATION + "=> Reported file: FileEntry [timestamp =")), // () -> assertTrue(lines[1].endsWith(", path = test.txt, mediaType = 'text/plain']"))); } @Test void executionFinishedWithFailure() { var stringWriter = new StringWriter(); listener(stringWriter).executionFinished(newTestIdentifier(), failed(new AssertionError("Boom!"))); var lines = lines(stringWriter); assertAll("lines in the output", // () -> assertEquals("Finished: demo-test ([engine:demo-engine])", lines[0]), // () -> assertEquals(INDENTATION + "=> Exception: java.lang.AssertionError: Boom!", lines[1])); } @Nested class ColorPaletteTests { @Nested class DefaultColorPaletteTests { @Test void executionSkipped() { var stringWriter = new StringWriter(); new FlatPrintingListener(new PrintWriter(stringWriter), ColorPalette.DEFAULT).executionSkipped( newTestIdentifier(), "Test" + EOL + "disabled"); var lines = lines(stringWriter); assertEquals(3, lines.length); assertAll("lines in the output", // () -> assertEquals("\u001B[35mSkipped: demo-test ([engine:demo-engine])\u001B[0m", lines[0]), // () -> assertEquals("\u001B[35m" + INDENTATION + "=> Reason: Test", lines[1]), // () -> assertEquals(INDENTATION + "disabled\u001B[0m", lines[2])); } @Test void reportingEntryPublished() { var stringWriter = new StringWriter(); new FlatPrintingListener(new PrintWriter(stringWriter), ColorPalette.DEFAULT).reportingEntryPublished( newTestIdentifier(), ReportEntry.from("foo", "bar")); var lines = lines(stringWriter); assertEquals(2, lines.length); assertAll("lines in the output", // () -> assertEquals("\u001B[37mReported: demo-test ([engine:demo-engine])\u001B[0m", lines[0]), // () -> assertTrue(lines[1].startsWith( "\u001B[37m" + INDENTATION + "=> Reported values: ReportEntry [timestamp =")), // () -> assertTrue(lines[1].endsWith(", foo = 'bar']\u001B[0m"))); } @Test void executionFinishedWithFailure() { var stringWriter = new StringWriter(); new FlatPrintingListener(new PrintWriter(stringWriter), ColorPalette.DEFAULT).executionFinished( newTestIdentifier(), failed(new AssertionError("Boom!"))); var lines = lines(stringWriter); assertAll("lines in the output", // () -> assertEquals("\u001B[31mFinished: demo-test ([engine:demo-engine])\u001B[0m", lines[0]), // () -> assertEquals("\u001B[31m" + INDENTATION + "=> Exception: java.lang.AssertionError: Boom!", lines[1]), () -> assertTrue(lines[lines.length - 1].endsWith("\u001B[0m"))); } } @Nested class ColorPaletteOverrideTests { @Test void overridingSkipped() { var stringWriter = new StringWriter(); ColorPalette colorPalette = new ColorPalette(Maps.newHashMap(Style.SKIPPED, "36;7")); new FlatPrintingListener(new PrintWriter(stringWriter), colorPalette).executionSkipped( newTestIdentifier(), "Test" + EOL + "disabled"); var lines = lines(stringWriter); assertEquals(3, lines.length); assertAll("lines in the output", // () -> assertEquals("\u001B[36;7mSkipped: demo-test ([engine:demo-engine])\u001B[0m", lines[0]), // () -> assertEquals("\u001B[36;7m" + INDENTATION + "=> Reason: Test", lines[1]), // () -> assertEquals(INDENTATION + "disabled\u001B[0m", lines[2])); } @Test void overridingUnusedStyle() { var stringWriter = new StringWriter(); ColorPalette colorPalette = new ColorPalette(Maps.newHashMap(Style.FAILED, "36;7")); new FlatPrintingListener(new PrintWriter(stringWriter), colorPalette).executionSkipped( newTestIdentifier(), "Test" + EOL + "disabled"); var lines = lines(stringWriter); assertEquals(3, lines.length); assertAll("lines in the output", // () -> assertEquals("\u001B[35mSkipped: demo-test ([engine:demo-engine])\u001B[0m", lines[0]), // () -> assertEquals("\u001B[35m" + INDENTATION + "=> Reason: Test", lines[1]), // () -> assertEquals(INDENTATION + "disabled\u001B[0m", lines[2])); } } } private FlatPrintingListener listener(StringWriter stringWriter) { return new FlatPrintingListener(new PrintWriter(stringWriter), ColorPalette.NONE); } private static TestIdentifier newTestIdentifier() { var testDescriptor = new TestDescriptorStub(UniqueId.forEngine("demo-engine"), "demo-test"); return TestIdentifier.from(testDescriptor); } private String[] lines(StringWriter stringWriter) { return stringWriter.toString().split(EOL); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/console/output/TestFeedPrintingListenerTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.output; import static org.junit.jupiter.api.Assertions.assertLinesMatch; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import static org.junit.platform.launcher.core.OutputDirectoryCreators.dummyOutputDirectoryCreator; import static org.mockito.Mockito.mock; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Set; import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.EngineDescriptor; import org.junit.platform.fakes.TestDescriptorStub; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; import org.opentest4j.TestAbortedException; class TestFeedPrintingListenerTests { TestPlan testPlan; TestIdentifier testIdentifier; StringWriter stringWriter = new StringWriter(); TestFeedPrintingListener listener = new TestFeedPrintingListener(new PrintWriter(stringWriter), ColorPalette.NONE); @BeforeEach void prepareListener() { var engineDescriptor = new EngineDescriptor(UniqueId.forEngine("demo-engine"), "Demo Engine"); var testDescriptor = new TestDescriptorStub(engineDescriptor.getUniqueId().append("type", "test"), "%c ool test"); engineDescriptor.addChild(testDescriptor); testPlan = TestPlan.from(true, Set.of(engineDescriptor), mock(), dummyOutputDirectoryCreator()); testIdentifier = testPlan.getTestIdentifier(testDescriptor.getUniqueId()); listener.testPlanExecutionStarted(testPlan); } @Test public void testExecutionSkipped() { listener.executionSkipped(testIdentifier, "Test disabled"); assertLinesMatch( // """ Demo Engine > %c ool test :: SKIPPED \tReason: Test disabled """.lines(), // actualLines() // ); } @Test public void testExecutionFailed() { listener.executionFinished(testIdentifier, TestExecutionResult.failed(new AssertionError("Boom!"))); assertLinesMatch( // """ Demo Engine > %c ool test :: FAILED \tjava.lang.AssertionError: Boom! >>>> """.lines(), // actualLines() // ); } @Test public void testExecutionAborted() { listener.executionFinished(testIdentifier, TestExecutionResult.aborted(new TestAbortedException("Boom!"))); assertLinesMatch( // """ Demo Engine > %c ool test :: ABORTED \torg.opentest4j.TestAbortedException: Boom! >>>> """.lines(), // actualLines() // ); } @Test public void testExecutionSucceeded() { listener.executionFinished(testIdentifier, TestExecutionResult.successful()); assertLinesMatch(Stream.of("Demo Engine > %c ool test :: SUCCESSFUL"), actualLines()); } @Test public void testExecutionFailedOfContainer() { var engineIdentifier = getOnlyElement(testPlan.getRoots()); listener.executionFinished(engineIdentifier, TestExecutionResult.failed(new AssertionError("Boom!"))); assertLinesMatch( // """ Demo Engine :: FAILED \tjava.lang.AssertionError: Boom! >>>> """.lines(), // actualLines() // ); } private Stream actualLines() { return stringWriter.toString().lines(); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/console/output/TreeNodeTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.output; import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadPoolExecutor; import org.junit.jupiter.api.Test; import org.junit.platform.engine.reporting.ReportEntry; class TreeNodeTests { private static final int NUM_THREADS = 2; private static final int ITEMS_PER_THREAD = 1000; @Test void caption() { assertEquals("", TreeNode.createCaption("")); assertEquals("[@@]", TreeNode.createCaption("[@@]")); assertEquals("[@ @]", TreeNode.createCaption("[@ @]")); assertEquals("[@ @]", TreeNode.createCaption("[@\u000B@]")); assertEquals("[@ @]", TreeNode.createCaption("[@\t@]")); assertEquals("[@ @]", TreeNode.createCaption("[@\t\n@]")); assertEquals("[@ @]", TreeNode.createCaption("[@\t\n\r@]")); assertEquals("[@ @]", TreeNode.createCaption("[@\t\n\r\f@]")); assertEquals("@".repeat(80) + "...", TreeNode.createCaption("@".repeat(1000) + "!")); } @Test void childrenCanBeAddedConcurrently() throws Exception { var treeNode = new TreeNode("root"); runConcurrently(() -> { for (long i = 0; i < ITEMS_PER_THREAD; i++) { treeNode.addChild(new TreeNode(String.valueOf(i))); } }); assertThat(treeNode.children).hasSize(NUM_THREADS * ITEMS_PER_THREAD); } @Test void reportEntriesCanBeAddedConcurrently() throws Exception { var treeNode = new TreeNode("root"); runConcurrently(() -> { for (long i = 0; i < ITEMS_PER_THREAD; i++) { treeNode.addReportEntry(ReportEntry.from("index", String.valueOf(i))); } }); assertThat(treeNode.reports).hasSize(NUM_THREADS * ITEMS_PER_THREAD); } @SuppressWarnings("resource") private void runConcurrently(Runnable action) throws InterruptedException { ExecutorService executor = new ThreadPoolExecutor(NUM_THREADS, NUM_THREADS, 10, SECONDS, new ArrayBlockingQueue<>(NUM_THREADS)); try { var barrier = new CyclicBarrier(NUM_THREADS); for (long i = 0; i < NUM_THREADS; i++) { executor.submit(() -> { await(barrier); action.run(); }); } } finally { executor.shutdown(); var terminated = executor.awaitTermination(10, SECONDS); assertTrue(terminated, "Executor was not terminated"); } } private void await(CyclicBarrier barrier) { try { barrier.await(); } catch (Exception e) { throw new RuntimeException(e); } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/console/output/TreePrinterTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.output; import static org.junit.jupiter.api.Assertions.assertIterableEquals; import static org.junit.jupiter.api.Assertions.assertLinesMatch; import static org.junit.platform.engine.TestExecutionResult.aborted; import static org.junit.platform.engine.TestExecutionResult.failed; import static org.junit.platform.engine.TestExecutionResult.successful; import java.io.ByteArrayOutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.List; import org.junit.jupiter.api.Test; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.fakes.TestDescriptorStub; import org.junit.platform.launcher.TestIdentifier; class TreePrinterTests { private final Charset charset = StandardCharsets.UTF_8; private final ByteArrayOutputStream stream = new ByteArrayOutputStream(1000); private final PrintWriter out = new PrintWriter(new OutputStreamWriter(stream, charset)); private List actual() { out.flush(); return List.of(stream.toString(charset).split("\\R")); } @Test void emptyTree() { new TreePrinter(out, Theme.UNICODE, ColorPalette.NONE).print(new TreeNode("")); assertIterableEquals(List.of("╷"), actual()); } @Test void emptyEngines() { var root = new TreeNode(""); root.addChild(new TreeNode(identifier("e-0", "engine zero"), "none")); root.addChild(new TreeNode(identifier("e-1", "engine one")).setResult(successful())); root.addChild(new TreeNode(identifier("e-2", "engine two")).setResult(failed(null))); root.addChild(new TreeNode(identifier("e-3", "engine three")).setResult(aborted(null))); new TreePrinter(out, Theme.UNICODE, ColorPalette.NONE).print(root); assertIterableEquals( // List.of( // "╷", // "├─ engine zero ↷ none", // "├─ engine one ✔", // "├─ engine two ✘", // "└─ engine three ■"), // actual()); } @Test // https://github.com/junit-team/junit-framework/issues/786 void printNodeHandlesNullMessageThrowableGracefully() { var result = TestExecutionResult.failed(new NullPointerException()); var node = new TreeNode(identifier("NPE", "test()")).setResult(result); new TreePrinter(out, Theme.ASCII, ColorPalette.NONE).print(node); assertLinesMatch(List.of(".", "+-- test() [X] java.lang.NullPointerException"), actual()); } @Test // https://github.com/junit-team/junit-framework/issues/1531 void reportsAreTabbedCorrectly() { var root = new TreeNode(""); var e1 = new TreeNode(identifier("e-1", "engine one")).setResult(successful()); e1.addReportEntry(ReportEntry.from("key", "e-1")); root.addChild(e1); var c1 = new TreeNode(identifier("c-1", "class one")).setResult(successful()); c1.addReportEntry(ReportEntry.from("key", "c-1")); e1.addChild(c1); var m1 = new TreeNode(identifier("m-1", "method one")).setResult(successful()); m1.addReportEntry(ReportEntry.from("key", "m-1")); c1.addChild(m1); var m2 = new TreeNode(identifier("m-2", "method two")).setResult(successful()); m2.addFileEntry(FileEntry.from(Path.of("test.txt"), "text/plain")); c1.addChild(m2); new TreePrinter(out, Theme.UNICODE, ColorPalette.NONE).print(root); assertLinesMatch(List.of( // "╷", // "└─ engine one ✔", // " │ ....-..-..T..:...* key = `e-1`", // " └─ class one ✔", // " │ ....-..-..T..:...* key = `c-1`", // " ├─ method one ✔", // " │ ....-..-..T..:...* key = `m-1`", // " └─ method two ✔", // " ....-..-..T..:...* file:.*" // ), // actual()); } private static TestIdentifier identifier(String id, String displayName) { var descriptor = new TestDescriptorStub(UniqueId.forEngine(id), displayName); return TestIdentifier.from(descriptor); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/console/output/VerboseTreePrintingListenerTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.output; import static org.junit.jupiter.api.Assertions.assertLinesMatch; import static org.junit.platform.engine.TestExecutionResult.failed; import java.io.PrintWriter; import java.io.StringWriter; import java.nio.file.Path; import java.util.List; import org.junit.jupiter.api.Test; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.fakes.TestDescriptorStub; import org.junit.platform.launcher.TestIdentifier; /** * @since 1.3.2 */ class VerboseTreePrintingListenerTests { private static final String EOL = System.lineSeparator(); @Test void executionSkipped() { var stringWriter = new StringWriter(); listener(stringWriter).executionSkipped(newTestIdentifier(), "Test" + EOL + "disabled"); var lines = lines(stringWriter); assertLinesMatch(List.of( // "+-- %c ool test", // " tags: []", // " uniqueId: [engine:demo-engine]", // " parent: []", // " reason: Test", // " disabled", // " status: [S] SKIPPED"), List.of(lines)); } @Test void reportingEntryPublished() { var stringWriter = new StringWriter(); listener(stringWriter).reportingEntryPublished(newTestIdentifier(), ReportEntry.from("foo", "bar")); var lines = lines(stringWriter); assertLinesMatch(List.of(" reports: ReportEntry \\[timestamp = .+, foo = 'bar'\\]"), List.of(lines)); } @Test void fileEntryPublished() { var stringWriter = new StringWriter(); listener(stringWriter).fileEntryPublished(newTestIdentifier(), FileEntry.from(Path.of("test.txt"), "text/plain")); var lines = lines(stringWriter); assertLinesMatch( List.of(" reports: FileEntry \\[timestamp = .+, path = test.txt, mediaType = 'text/plain'\\]"), List.of(lines)); } @Test void executionFinishedWithFailure() { var stringWriter = new StringWriter(); listener(stringWriter).executionFinished(newTestIdentifier(), failed(new AssertionError("Boom!"))); var lines = lines(stringWriter); assertLinesMatch(List.of(" caught: java.lang.AssertionError: Boom!", // ">> STACKTRACE >>", // " duration: \\d+ ms", // " status: [X] FAILED"), List.of(lines)); } @Test void failureMessageWithFormatSpecifier() { var stringWriter = new StringWriter(); listener(stringWriter).executionFinished(newTestIdentifier(), failed(new AssertionError("%crash"))); var lines = lines(stringWriter); assertLinesMatch(List.of(" caught: java.lang.AssertionError: %crash", // ">> STACKTRACE >>", // " duration: \\d+ ms", // " status: [X] FAILED"), List.of(lines)); } private VerboseTreePrintingListener listener(StringWriter stringWriter) { return new VerboseTreePrintingListener(new PrintWriter(stringWriter), ColorPalette.NONE, 16, Theme.ASCII); } private static TestIdentifier newTestIdentifier() { var testDescriptor = new TestDescriptorStub(UniqueId.forEngine("demo-engine"), "%c ool test"); return TestIdentifier.from(testDescriptor); } private String[] lines(StringWriter stringWriter) { return stringWriter.toString().split(EOL); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForInnerTest.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.subpackage; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; // Even though our "example test classes" should be named *TestCase // according to team policy, this class must be named *Test in order // for ConsoleLauncherIntegrationTests to pass, but that's not a // problem since the test method here can never fail. class ContainerForInnerTest { @Nested class NestedTest { @Test void test() { } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForInnerTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.subpackage; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; // Even though our "example test classes" should be named *TestCase // according to team policy, this class must be named *Tests in order // for ConsoleLauncherIntegrationTests to pass, but that's not a // problem since the test method here can never fail. class ContainerForInnerTests { @Nested class NestedTests { @Test void test() { } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForStaticNestedTest.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.subpackage; import org.junit.jupiter.api.Test; // Even though our "example test classes" should be named *TestCase // according to team policy, this class must be named *Test in order // for ConsoleLauncherIntegrationTests to pass, but that's not a // problem since the test method here can never fail. class ContainerForStaticNestedTest { static class NestedTest { @Test void test() { } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForStaticNestedTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.subpackage; import org.junit.jupiter.api.Test; // Even though our "example test classes" should be named *TestCase // according to team policy, this class must be named *Tests in order // for ConsoleLauncherIntegrationTests to pass, but that's not a // problem since the test method here can never fail. class ContainerForStaticNestedTests { static class NestedTests { @Test void test() { } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/console/subpackage/FailingTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.subpackage; import static org.junit.jupiter.api.Assertions.fail; import org.junit.jupiter.api.Test; public class FailingTestCase { @Test void first() { fail(); } @Test void second() { fail(); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/console/subpackage/SecondTest.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.subpackage; import org.junit.jupiter.api.Test; // Even though our "example test classes" should be named *TestCase // according to team policy, this class must be named *Test in order // for ConsoleLauncherIntegrationTests to pass, but that's not a // problem since the test method here can never fail. class SecondTest { @Test void test() { } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/console/subpackage/Test.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.subpackage; // Even though our "example test classes" should be named *TestCase // according to team policy, this class must be named Test in order // for ConsoleLauncherIntegrationTests to pass, but that's not a // problem since the test method here can never fail. class Test { @org.junit.jupiter.api.Test void test() { } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/console/subpackage/Test1.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.subpackage; import org.junit.jupiter.api.Test; // Even though our "example test classes" should be named *TestCase // according to team policy, this class must be named Test* in order // for ConsoleLauncherIntegrationTests to pass, but that's not a // problem since the test method here can never fail. class Test1 { @Test void test() { } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/console/subpackage/Tests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.subpackage; import org.junit.jupiter.api.Test; // Even though our "example test classes" should be named *TestCase // according to team policy, this class must be named Tests in order // for ConsoleLauncherIntegrationTests to pass, but that's not a // problem since the test method here can never fail. class Tests { @Test void test() { } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/console/subpackage/ThirdTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.console.subpackage; import org.junit.jupiter.api.Test; // Even though our "example test classes" should be named *TestCase // according to team policy, this class must be named *Tests in order // for ConsoleLauncherIntegrationTests to pass, but that's not a // problem since the test method here can never fail. class ThirdTests { @Test void test() { } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/CompositeTestDescriptorVisitorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import org.junit.jupiter.api.Test; import org.junit.platform.engine.TestDescriptor.Visitor; import org.mockito.InOrder; class CompositeTestDescriptorVisitorTests { @SuppressWarnings("DataFlowIssue") @Test void checksPreconditions() { assertPreconditionViolationFor(Visitor::composite); assertPreconditionViolationFor(() -> Visitor.composite((Visitor[]) null)); assertPreconditionViolationFor(() -> Visitor.composite((Visitor) null)); } @Test void optimizesForSingleVisitor() { Visitor visitor = mock(); assertSame(visitor, Visitor.composite(visitor)); } @Test void callsAllVisitorsInOrder() { Visitor visitor1 = mock("visitor1"); Visitor visitor2 = mock("visitor2"); TestDescriptor testDescriptor = mock(); var composite = Visitor.composite(visitor1, visitor2); composite.visit(testDescriptor); InOrder inOrder = inOrder(visitor1, visitor2); inOrder.verify(visitor1).visit(testDescriptor); inOrder.verify(visitor2).visit(testDescriptor); inOrder.verifyNoMoreInteractions(); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/DiscoveryIssueTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import static org.mockito.Mockito.mock; import java.util.Optional; import java.util.function.UnaryOperator; import org.junit.jupiter.api.Test; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.support.descriptor.ClassSource; class DiscoveryIssueTests { @Test void create() { var issue = DiscoveryIssue.create(Severity.ERROR, "message"); assertThat(issue.severity()).isEqualTo(Severity.ERROR); assertThat(issue.message()).isEqualTo("message"); assertThat(issue.source()).isEmpty(); assertThat(issue.cause()).isEmpty(); } @Test void builder() { var source = mock(TestSource.class); var cause = new RuntimeException("boom"); var issue = DiscoveryIssue.builder(Severity.WARNING, "message") // .source(source) // .cause(cause) // .build(); assertThat(issue.severity()).isEqualTo(Severity.WARNING); assertThat(issue.message()).isEqualTo("message"); assertThat(issue.source()).containsSame(source); assertThat(issue.cause()).containsSame(cause); } @Test void equalsAndHashCode() { assertEqualsAndHashCode( // DiscoveryIssue.create(Severity.ERROR, "message"), // DiscoveryIssue.builder(Severity.ERROR, "message").build(), // DiscoveryIssue.create(Severity.WARNING, "message") // ); assertEqualsAndHashCode( // DiscoveryIssue.create(Severity.ERROR, "message"), // DiscoveryIssue.builder(Severity.ERROR, "message").build(), // DiscoveryIssue.create(Severity.ERROR, "anotherMessage") // ); assertEqualsAndHashCode( // DiscoveryIssue.builder(Severity.ERROR, "message") // .source(ClassSource.from(DiscoveryIssue.class)).build(), // DiscoveryIssue.builder(Severity.ERROR, "message") // .source(Optional.of(ClassSource.from(DiscoveryIssue.class))).build(), // DiscoveryIssue.builder(Severity.ERROR, "message") // .source(ClassSource.from(DefaultDiscoveryIssue.class)).build() // ); var cause = new RuntimeException("boom"); assertEqualsAndHashCode( // DiscoveryIssue.builder(Severity.ERROR, "message").cause(cause).build(), // DiscoveryIssue.builder(Severity.ERROR, "message").cause(Optional.of(cause)).build(), // DiscoveryIssue.builder(Severity.ERROR, "message").cause(new RuntimeException("boom")).build() // ); } @Test void stringRepresentationWithoutAttributes() { var issue = DiscoveryIssue.create(Severity.WARNING, "message"); assertThat(issue.toString()) // .isEqualTo("DiscoveryIssue [severity = WARNING, message = 'message']"); } @Test void stringRepresentationWithOptionalAttributes() { var issue = DiscoveryIssue.builder(Severity.WARNING, "message") // .source(ClassSource.from(DiscoveryIssue.class)) // .cause(new RuntimeException("boom")) // .build(); assertThat(issue.toString()) // .isEqualTo( "DiscoveryIssue [severity = WARNING, message = 'message', source = ClassSource [className = 'org.junit.platform.engine.DiscoveryIssue', filePosition = null], cause = java.lang.RuntimeException: boom]"); } @Test void withNewMessage() { var issue = DiscoveryIssue.builder(Severity.WARNING, "message") // .source(ClassSource.from(DiscoveryIssue.class)) // .cause(new RuntimeException("boom")) // .build(); var newIssue = issue.withMessage(__ -> "new message"); assertThat(newIssue.severity()).isEqualTo(Severity.WARNING); assertThat(newIssue.message()).isEqualTo("new message"); assertThat(newIssue.source()).containsSame(issue.source().orElseThrow()); assertThat(newIssue.cause()).containsSame(issue.cause().orElseThrow()); } @Test void withSameMessage() { var issue = DiscoveryIssue.builder(Severity.WARNING, "message") // .source(ClassSource.from(DiscoveryIssue.class)) // .cause(new RuntimeException("boom")) // .build(); var newIssue = issue.withMessage(UnaryOperator.identity()); assertThat(newIssue).isSameAs(issue); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/FilterCompositionTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.engine.FilterResult.excluded; import static org.junit.platform.engine.FilterResult.included; import org.junit.jupiter.api.Test; import org.junit.platform.engine.discovery.ClassNameFilter; import org.junit.platform.launcher.FilterStub; /** * Unit tests for {@link Filter#composeFilters}. * * {@link Filter#composeFilters} will delegate to {@linkplain CompositeFilter} under the hood. * * @since 1.0 */ class FilterCompositionTests { @Test void composingNoFiltersCreatesFilterThatIncludesEverything() { var composedFilter = Filter.composeFilters(); assertTrue(composedFilter.apply(String.class).included()); assertTrue(composedFilter.toPredicate().test(String.class)); assertTrue(composedFilter.apply(Object.class).included()); assertTrue(composedFilter.toPredicate().test(Object.class)); } @Test void composingSingleFilterWillReturnTheOriginalOne() { Filter singleFilter = ClassNameFilter.includeClassNamePatterns(".*ring.*"); var composed = Filter.composeFilters(singleFilter); assertSame(singleFilter, composed); } @Test void composingMultipleFiltersIsAConjunctionOfFilters() { Filter firstFilter = ClassNameFilter.includeClassNamePatterns(".*ring.*"); Filter secondFilter = ClassNameFilter.includeClassNamePatterns(".*Join.*"); var composed = Filter.composeFilters(firstFilter, secondFilter); assertFalse(composed.apply("java.lang.String").included()); assertFalse(composed.toPredicate().test("java.lang.String")); assertTrue(composed.apply("java.util.StringJoiner").included()); assertTrue(composed.toPredicate().test("java.util.StringJoiner")); } @Test void aFilterComposedOfMultipleFiltersHasReadableDescription() { Filter firstFilter = new FilterStub<>(o -> excluded("wrong"), () -> "1st"); Filter secondFilter = new FilterStub<>(o -> included("right"), () -> "2nd"); var composed = Filter.composeFilters(firstFilter, secondFilter); assertFalse(composed.apply(String.class).included()); assertEquals("(1st) and (2nd)", composed.toString()); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/TestDescriptorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; import org.junit.platform.fakes.TestDescriptorStub; /** * @since 1.0 */ class TestDescriptorTests { @Test void isRootWithoutParent() { TestDescriptor root = new TestDescriptorStub(UniqueId.root("root", "id"), "id"); assertTrue(root.isRoot()); } @Test void isRootWithParent() { TestDescriptor child = new TestDescriptorStub(UniqueId.root("child", "child"), "child"); child.setParent(new TestDescriptorStub(UniqueId.root("root", "root"), "root")); assertFalse(child.isRoot()); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/TestTagTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine; 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.assertTrue; import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import org.jspecify.annotations.NullMarked; import org.junit.jupiter.api.Test; /** * Unit tests for {@link TestTag}. * * @since 1.0 */ @NullMarked class TestTagTests { @SuppressWarnings("DataFlowIssue") @Test void validSyntax() { // @formatter:off assertAll("Valid Tag Syntax", () -> yep("fast"), () -> yep("super_fast"), () -> yep("unit-test"), () -> yep("integration.test"), () -> yep("org.example.CustomTagClass"), () -> yep(" surrounded-by-whitespace\t\n"), () -> nope(null), () -> nope(""), () -> nope(" "), () -> nope("\t"), () -> nope("\f"), () -> nope("\r"), () -> nope("\n"), () -> nope("custom tag"), // internal space () -> nope(","), // comma () -> nope("("), // opening parenthesis () -> nope(")"), // closing parenthesis () -> nope("&"), // boolean AND () -> nope("|"), // boolean OR () -> nope("!") // boolean NOT ); // @formatter:on } @Test void factory() { assertEquals("foo", TestTag.create("foo").getName()); assertEquals("foo.tag", TestTag.create("foo.tag").getName()); assertEquals("foo-tag", TestTag.create("foo-tag").getName()); assertEquals("foo-tag", TestTag.create(" foo-tag ").getName()); assertEquals("foo-tag", TestTag.create("\t foo-tag \n").getName()); } @SuppressWarnings({ "DataFlowIssue", "UnnecessaryUnicodeEscape" }) @Test void factoryPreconditions() { assertSyntaxViolation(null); assertSyntaxViolation(""); assertSyntaxViolation(" "); assertSyntaxViolation("X\tX"); assertSyntaxViolation("X\nX"); assertSyntaxViolation("XXX\u005CtXXX"); } @Test void tagEqualsOtherTagWithSameName() { assertEqualsAndHashCode(TestTag.create("fast"), TestTag.create("fast"), TestTag.create("slow")); } @Test void toStringPrintsName() { assertEquals("fast", TestTag.create("fast").toString()); } private static void yep(String tag) { assertTrue(TestTag.isValid(tag), () -> "'%s' should be a valid tag".formatted(tag)); } private static void nope(String tag) { assertFalse(TestTag.isValid(tag), () -> "'%s' should not be a valid tag".formatted(tag)); } private void assertSyntaxViolation(String tag) { assertPreconditionViolationFor(() -> TestTag.create(tag))// .withMessageStartingWith("Tag name")// .withMessageEndingWith("must be syntactically valid"); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/UniqueIdFormatTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.platform.commons.JUnitException; import org.junit.platform.engine.UniqueId.Segment; /** * @since 1.0 */ class UniqueIdFormatTests { @Nested class Formatting { private final UniqueId engineId = UniqueId.root("engine", "junit-jupiter"); private final UniqueIdFormat format = UniqueIdFormat.getDefault(); @Test void engineIdOnly() { assertEquals("[engine:junit-jupiter]", engineId.toString()); assertEquals(format.format(engineId), engineId.toString()); } @Test void withTwoSegments() { var classId = engineId.append("class", "org.junit.MyClass"); assertEquals("[engine:junit-jupiter]/[class:org.junit.MyClass]", classId.toString()); assertEquals(format.format(classId), classId.toString()); } @Test void withManySegments() { var uniqueId = engineId.append("t1", "v1").append("t2", "v2").append("t3", "v3"); assertEquals("[engine:junit-jupiter]/[t1:v1]/[t2:v2]/[t3:v3]", uniqueId.toString()); assertEquals(format.format(uniqueId), uniqueId.toString()); } } @Nested class ParsingWithDefaultFormat implements ParsingTestTrait { private final UniqueIdFormat format = UniqueIdFormat.getDefault(); @Override public UniqueIdFormat getFormat() { return this.format; } @Override public String getEngineUid() { return "[engine:junit-jupiter]"; } @Override public String getDefaultEngineUid() { return getEngineUid(); } @Override public String getMethodUid() { return "[engine:junit-jupiter]/[class:MyClass]/[method:myMethod]"; } @Override public String getDefaultMethodUid() { return getMethodUid(); } } @Nested class ParsingWithCustomFormat implements ParsingTestTrait { private final UniqueIdFormat format = new UniqueIdFormat('{', '=', '}', ','); @Override public UniqueIdFormat getFormat() { return this.format; } @Override public String getEngineUid() { return "{engine=junit-jupiter}"; } @Override public String getDefaultEngineUid() { return "[engine:junit-jupiter]"; } @Override public String getMethodUid() { return "{engine=junit-jupiter},{class=MyClass},{method=myMethod}"; } @Override public String getDefaultMethodUid() { return "[engine:junit-jupiter]/[class:MyClass]/[method:myMethod]"; } } // ------------------------------------------------------------------------- private static void assertSegment(Segment segment, String expectedType, String expectedValue) { assertEquals(expectedType, segment.getType(), "segment type"); assertEquals(expectedValue, segment.getValue(), "segment value"); } interface ParsingTestTrait { UniqueIdFormat getFormat(); String getEngineUid(); String getDefaultEngineUid(); String getMethodUid(); String getDefaultMethodUid(); @Test default void parseMalformedUid() { Throwable throwable = assertThrows(JUnitException.class, () -> getFormat().parse("malformed UID")); assertThat(throwable).hasMessageContaining("malformed UID"); } @Test default void parseEngineUid() { var parsedId = getFormat().parse(getEngineUid()); assertSegment(parsedId.getSegments().getFirst(), "engine", "junit-jupiter"); assertEquals(getEngineUid(), getFormat().format(parsedId)); assertEquals(getDefaultEngineUid(), parsedId.toString()); } @Test default void parseMethodUid() { var parsedId = getFormat().parse(getMethodUid()); assertSegment(parsedId.getSegments().get(0), "engine", "junit-jupiter"); assertSegment(parsedId.getSegments().get(1), "class", "MyClass"); assertSegment(parsedId.getSegments().get(2), "method", "myMethod"); assertEquals(getMethodUid(), getFormat().format(parsedId)); assertEquals(getDefaultMethodUid(), parsedId.toString()); } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/UniqueIdTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.Base64; import java.util.Optional; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.engine.UniqueId.Segment; /** * Unit tests for {@link UniqueId}. * * @since 1.0 * @see org.junit.jupiter.engine.execution.UniqueIdParsingForArrayParameterIntegrationTests */ class UniqueIdTests { private static final String ENGINE_ID = "junit-jupiter"; private static final String SUITE_ENGINE_ID = "junit-platform-suite"; @Nested class Creation { @Test void uniqueIdCanBeCreatedFromEngineId() { var uniqueId = UniqueId.forEngine(ENGINE_ID); assertEquals("[engine:junit-jupiter]", uniqueId.toString()); assertSegment(uniqueId.getSegments().getFirst(), "engine", "junit-jupiter"); } @Test void engineIdCanBeAppended() { var suiteEngineId = UniqueId.forEngine(SUITE_ENGINE_ID); var uniqueId = suiteEngineId.appendEngine(ENGINE_ID); assertSegment(uniqueId.getSegments().get(1), "engine", "junit-jupiter"); } @Test void retrievingOptionalEngineId() { var uniqueIdWithEngine = UniqueId.forEngine(ENGINE_ID); assertThat(uniqueIdWithEngine.getEngineId()).contains("junit-jupiter"); var uniqueIdWithoutEngine = UniqueId.root("root", "aValue"); assertEquals(Optional.empty(), uniqueIdWithoutEngine.getEngineId()); } @Test void uniqueIdCanBeCreatedFromTypeAndValue() { var uniqueId = UniqueId.root("aType", "aValue"); assertEquals("[aType:aValue]", uniqueId.toString()); assertSegment(uniqueId.getSegments().getFirst(), "aType", "aValue"); } @Test void rootSegmentCanBeRetrieved() { var uniqueId = UniqueId.root("aType", "aValue"); assertThat(uniqueId.getRoot()).contains(new Segment("aType", "aValue")); } @Test void appendingOneSegment() { var engineId = UniqueId.root("engine", ENGINE_ID); var classId = engineId.append("class", "org.junit.MyClass"); assertThat(classId.getSegments()).hasSize(2); assertSegment(classId.getSegments().get(0), "engine", ENGINE_ID); assertSegment(classId.getSegments().get(1), "class", "org.junit.MyClass"); } @Test void appendingSegmentLeavesOriginalUnchanged() { var uniqueId = UniqueId.root("engine", ENGINE_ID); uniqueId.append("class", "org.junit.MyClass"); assertThat(uniqueId.getSegments()).hasSize(1); assertSegment(uniqueId.getSegments().getFirst(), "engine", ENGINE_ID); } @Test void appendingSeveralSegments() { var engineId = UniqueId.root("engine", ENGINE_ID); var uniqueId = engineId.append("t1", "v1").append("t2", "v2").append("t3", "v3"); assertThat(uniqueId.getSegments()).hasSize(4); assertSegment(uniqueId.getSegments().get(0), "engine", ENGINE_ID); assertSegment(uniqueId.getSegments().get(1), "t1", "v1"); assertSegment(uniqueId.getSegments().get(2), "t2", "v2"); assertSegment(uniqueId.getSegments().get(3), "t3", "v3"); } @Test void appendingSegmentInstance() { var uniqueId = UniqueId.forEngine(ENGINE_ID).append("t1", "v1"); uniqueId = uniqueId.append(new Segment("t2", "v2")); assertThat(uniqueId.getSegments()).hasSize(3); assertSegment(uniqueId.getSegments().get(0), "engine", ENGINE_ID); assertSegment(uniqueId.getSegments().get(1), "t1", "v1"); assertSegment(uniqueId.getSegments().get(2), "t2", "v2"); } @SuppressWarnings("DataFlowIssue") @Test void appendingNullIsNotAllowed() { var uniqueId = UniqueId.forEngine(ENGINE_ID); assertPreconditionViolationFor(() -> uniqueId.append(null)); assertPreconditionViolationFor(() -> uniqueId.append(null, "foo")); assertPreconditionViolationFor(() -> uniqueId.append("foo", null)); } } @Nested class ParsingAndFormatting { @Test void ensureDefaultUniqueIdFormatIsUsedForParsing() { var uniqueIdString = "[engine:junit-jupiter]/[class:MyClass]/[method:myMethod]"; var parsedDirectly = UniqueId.parse(uniqueIdString); var parsedViaFormat = UniqueIdFormat.getDefault().parse(uniqueIdString); assertEquals(parsedViaFormat, parsedDirectly); } @Test void ensureDefaultUniqueIdFormatIsUsedForFormatting() { var parsedDirectly = UniqueId.parse("[engine:junit-jupiter]/[class:MyClass]/[method:myMethod]"); assertEquals("[engine:junit-jupiter]/[class:MyClass]/[method:myMethod]", parsedDirectly.toString()); } @Test void ensureDefaultUniqueIdFormatDecodingEncodesSegmentParts() { var segment = UniqueId.parse("[%5B+%25+%5D):(%3A+%2B+%2F]").getSegments().getFirst(); assertEquals("[ % ])", segment.getType()); assertEquals("(: + /", segment.getValue()); } @Test void ensureDefaultUniqueIdFormatCanHandleAllCharacters() { for (char c = 0; c < Character.MAX_VALUE; c++) { var value = "foo " + c + " bar"; var uniqueId = UniqueId.parse(UniqueId.root("type", value).toString()); var segment = uniqueId.getSegments().getFirst(); assertEquals(value, segment.getValue()); } } @ParameterizedTest @ValueSource(strings = { "[a:b]", "[a:b]/[a:b]", "[a$b:b()]", "[a:b(%5BI)]", "[%5B%5D:%3A%2F]" }) void ensureDefaultToStringAndParsingIsIdempotent(String expected) { assertEquals(expected, UniqueId.parse(expected).toString()); } } @Nested class EqualsContract { @Test void sameEnginesAreEqual() { var id1 = UniqueId.root("engine", "junit-jupiter"); var id2 = UniqueId.root("engine", "junit-jupiter"); assertEquals(id2, id1); assertEquals(id1, id2); assertEquals(id1.hashCode(), id2.hashCode()); } @Test void differentEnginesAreNotEqual() { var id1 = UniqueId.root("engine", "junit-vintage"); var id2 = UniqueId.root("engine", "junit-jupiter"); assertNotEquals(id2, id1); assertNotEquals(id1, id2); } @Test void uniqueIdWithSameSegmentsAreEqual() { var id1 = UniqueId.root("engine", "junit-jupiter").append("t1", "v1").append("t2", "v2"); var id2 = UniqueId.root("engine", "junit-jupiter").append("t1", "v1").append("t2", "v2"); assertEquals(id2, id1); assertEquals(id1, id2); assertEquals(id1.hashCode(), id2.hashCode()); } @Test void differentOrderOfSegmentsAreNotEqual() { var id1 = UniqueId.root("engine", "junit-jupiter").append("t2", "v2").append("t1", "v1"); var id2 = UniqueId.root("engine", "junit-jupiter").append("t1", "v1").append("t2", "v2"); assertNotEquals(id2, id1); assertNotEquals(id1, id2); } @Test void additionalSegmentMakesItNotEqual() { var id1 = UniqueId.root("engine", "junit-jupiter").append("t1", "v1"); var id2 = id1.append("t2", "v2"); assertNotEquals(id2, id1); assertNotEquals(id1, id2); } } @Nested class Prefixing { @SuppressWarnings("DataFlowIssue") @Test void nullIsNotAPrefix() { var id = UniqueId.forEngine(ENGINE_ID); assertPreconditionViolationFor(() -> id.hasPrefix(null)); } @Test void uniqueIdIsPrefixForItself() { var id = UniqueId.forEngine(ENGINE_ID).append("t1", "v1").append("t2", "v2"); assertTrue(id.hasPrefix(id)); } @Test void uniqueIdIsPrefixForUniqueIdWithAdditionalSegments() { var id1 = UniqueId.forEngine(ENGINE_ID); var id2 = id1.append("t1", "v1"); var id3 = id2.append("t2", "v2"); assertFalse(id1.hasPrefix(id2)); assertFalse(id1.hasPrefix(id3)); assertTrue(id2.hasPrefix(id1)); assertFalse(id2.hasPrefix(id3)); assertTrue(id3.hasPrefix(id1)); assertTrue(id3.hasPrefix(id2)); } @Test void completelyUnrelatedUniqueIdsAreNotPrefixesForEachOther() { var id1 = UniqueId.forEngine("foo"); var id2 = UniqueId.forEngine("bar"); assertFalse(id1.hasPrefix(id2)); assertFalse(id2.hasPrefix(id1)); } } @Nested class LastSegment { @Test void returnsLastSegment() { var uniqueId = UniqueId.forEngine("foo"); assertSame(uniqueId.getSegments().get(0), uniqueId.getLastSegment()); uniqueId = UniqueId.forEngine("foo").append("type", "bar"); assertSame(uniqueId.getSegments().get(1), uniqueId.getLastSegment()); } @Test void removesLastSegment() { var uniqueId = UniqueId.forEngine("foo"); assertPreconditionViolationFor(uniqueId::removeLastSegment); var newUniqueId = uniqueId.append("type", "bar").removeLastSegment(); assertEquals(uniqueId, newUniqueId); } } @Nested class Serialization { final UniqueId uniqueId = UniqueId.root("engine", "junit-jupiter"); @Test void roundTrip() throws IOException, ClassNotFoundException { var bytesOut = new ByteArrayOutputStream(); var out = new ObjectOutputStream(bytesOut); out.writeObject(uniqueId); var bytesIn = new ByteArrayInputStream(bytesOut.toByteArray()); var in = new ObjectInputStream(bytesIn); var actual = in.readObject(); assertEquals(uniqueId, actual); assertEquals(uniqueId.toString(), actual.toString()); } @Test void deserializeFromJunit60() throws IOException, ClassNotFoundException { /* Serialized representation of: new UniqueId( new UniqueIdFormat('[', ':', ']', '/'), List.of(new Segment("engine", "junit-jupiter")) ); */ var uniqueIdFromJunit60 = Base64.getMimeDecoder().decode(""" rO0ABXNyACJvcmcuanVuaXQucGxhdGZvcm0uZW5naW5lLlVuaXF1ZUlkAAAAAAAAAAECAAJMAAhzZWdtZW50c3QAEExqYXZhL3V0 aWwvTGlzdDtMAA51bmlxdWVJZEZvcm1hdHQAKkxvcmcvanVuaXQvcGxhdGZvcm0vZW5naW5lL1VuaXF1ZUlkRm9ybWF0O3hwc3IA EWphdmEudXRpbC5Db2xsU2VyV46rtjobqBEDAAFJAAN0YWd4cAAAAAF3BAAAAAFzcgAqb3JnLmp1bml0LnBsYXRmb3JtLmVuZ2lu ZS5VbmlxdWVJZCRTZWdtZW50AAAAAAAAAAECAAJMAAR0eXBldAASTGphdmEvbGFuZy9TdHJpbmc7TAAFdmFsdWVxAH4AB3hwdAAG ZW5naW5ldAANanVuaXQtanVwaXRlcnhzcgAob3JnLmp1bml0LnBsYXRmb3JtLmVuZ2luZS5VbmlxdWVJZEZvcm1hdAAAAAAAAAAB AgAGQwAMY2xvc2VTZWdtZW50QwALb3BlblNlZ21lbnRDABBzZWdtZW50RGVsaW1pdGVyQwASdHlwZVZhbHVlU2VwYXJhdG9yTAAT ZW5jb2RlZENoYXJhY3Rlck1hcHQAE0xqYXZhL3V0aWwvSGFzaE1hcDtMAA5zZWdtZW50UGF0dGVybnQAGUxqYXZhL3V0aWwvcmVn ZXgvUGF0dGVybjt4cABdAFsALwA6c3IAEWphdmEudXRpbC5IYXNoTWFwBQfawcMWYNEDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNo b2xkeHA/QAAAAAAADHcIAAAAEAAAAAZzcgATamF2YS5sYW5nLkNoYXJhY3RlcjSLR9lrGiZ4AgABQwAFdmFsdWV4cAAldAADJTI1 c3EAfgARADp0AAMlM0FzcQB+ABEAW3QAAyU1QnNxAH4AEQArdAADJTJCc3EAfgARAF10AAMlNURzcQB+ABEAL3QAAyUyRnhzcgAX amF2YS51dGlsLnJlZ2V4LlBhdHRlcm5GZ9VrbkkCDQIAAkkABWZsYWdzTAAHcGF0dGVybnEAfgAHeHAAAAAgdAAXXFFbXEUoLisp XFE6XEUoLispXFFdXEU="""); var bytesIn = new ByteArrayInputStream(uniqueIdFromJunit60); var in = new ObjectInputStream(bytesIn); var actual = in.readObject(); assertEquals(uniqueId, actual); assertEquals(uniqueId.toString(), actual.toString()); } @Test void deserializeFromJunit60IgnoresCustomFormat() throws IOException, ClassNotFoundException { /* Serialized representation of: new UniqueId( new UniqueIdFormat('{', '=', '}', ','), List.of(new Segment("engine", "junit-jupiter")) ); */ var uniqueIdWithCustomFormatFromJunit60 = Base64.getMimeDecoder().decode(""" rO0ABXNyACJvcmcuanVuaXQucGxhdGZvcm0uZW5naW5lLlVuaXF1ZUlkAAAAAAAAAAECAAJMAAhzZWdtZW50c3QAEExqYXZhL3V0 aWwvTGlzdDtMAA51bmlxdWVJZEZvcm1hdHQAKkxvcmcvanVuaXQvcGxhdGZvcm0vZW5naW5lL1VuaXF1ZUlkRm9ybWF0O3hwc3IA EWphdmEudXRpbC5Db2xsU2VyV46rtjobqBEDAAFJAAN0YWd4cAAAAAF3BAAAAAFzcgAqb3JnLmp1bml0LnBsYXRmb3JtLmVuZ2lu ZS5VbmlxdWVJZCRTZWdtZW50AAAAAAAAAAECAAJMAAR0eXBldAASTGphdmEvbGFuZy9TdHJpbmc7TAAFdmFsdWVxAH4AB3hwdAAG ZW5naW5ldAANanVuaXQtanVwaXRlcnhzcgAob3JnLmp1bml0LnBsYXRmb3JtLmVuZ2luZS5VbmlxdWVJZEZvcm1hdAAAAAAAAAAB AgAGQwAMY2xvc2VTZWdtZW50QwALb3BlblNlZ21lbnRDABBzZWdtZW50RGVsaW1pdGVyQwASdHlwZVZhbHVlU2VwYXJhdG9yTAAT ZW5jb2RlZENoYXJhY3Rlck1hcHQAE0xqYXZhL3V0aWwvSGFzaE1hcDtMAA5zZWdtZW50UGF0dGVybnQAGUxqYXZhL3V0aWwvcmVn ZXgvUGF0dGVybjt4cAB9AHsALAA9c3IAEWphdmEudXRpbC5IYXNoTWFwBQfawcMWYNEDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNo b2xkeHA/QAAAAAAADHcIAAAAEAAAAAZzcgATamF2YS5sYW5nLkNoYXJhY3RlcjSLR9lrGiZ4AgABQwAFdmFsdWV4cAAldAADJTI1 c3EAfgARAHt0AAMlN0JzcQB+ABEAK3QAAyUyQnNxAH4AEQAsdAADJTJDc3EAfgARAH10AAMlN0RzcQB+ABEAPXQAAyUzRHhzcgAX amF2YS51dGlsLnJlZ2V4LlBhdHRlcm5GZ9VrbkkCDQIAAkkABWZsYWdzTAAHcGF0dGVybnEAfgAHeHAAAAAgdAAXXFF7XEUoLisp XFE9XEUoLispXFF9XEU="""); var bytesIn = new ByteArrayInputStream(uniqueIdWithCustomFormatFromJunit60); var in = new ObjectInputStream(bytesIn); var actual = in.readObject(); assertEquals(uniqueId, actual); assertEquals(uniqueId.toString(), actual.toString()); } } private static void assertSegment(Segment segment, String expectedType, String expectedValue) { assertEquals(expectedType, segment.getType(), "segment type"); assertEquals(expectedValue, segment.getValue(), "segment value"); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/discovery/ClassNameFilterTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationContainsNoNullElementsFor; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullOrEmptyFor; import org.junit.jupiter.api.Test; /** * @since 1.0 */ class ClassNameFilterTests { @SuppressWarnings("DataFlowIssue") @Test void includeClassNamePatternsChecksPreconditions() { assertPreconditionViolationNotNullOrEmptyFor("patterns array", () -> ClassNameFilter.includeClassNamePatterns((String[]) null)); assertPreconditionViolationNotNullOrEmptyFor("patterns array", () -> ClassNameFilter.includeClassNamePatterns(new String[0])); assertPreconditionViolationContainsNoNullElementsFor("patterns array", () -> ClassNameFilter.includeClassNamePatterns(new String[] { null })); } @Test void includeClassNamePatternsWithSinglePattern() { var regex = "^java\\.lang\\..*"; var filter = ClassNameFilter.includeClassNamePatterns(regex); assertThat(filter).hasToString( "IncludeClassNameFilter that includes class names that match one of the following regular expressions: '" + regex + "'"); assertTrue(filter.apply("java.lang.String").included()); assertTrue(filter.toPredicate().test("java.lang.String")); assertThat(filter.apply("java.lang.String").getReason()).contains( "Class name [java.lang.String] matches included pattern: '" + regex + "'"); assertFalse(filter.apply("java.time.Instant").included()); assertFalse(filter.toPredicate().test("java.time.Instant")); assertThat(filter.apply("java.time.Instant").getReason()).contains( "Class name [java.time.Instant] does not match any included pattern: '" + regex + "'"); } @Test void includeClassNamePatternsWithMultiplePatterns() { var firstRegex = "^java\\.lang\\..*"; var secondRegex = "^java\\.util\\..*"; var filter = ClassNameFilter.includeClassNamePatterns(firstRegex, secondRegex); assertThat(filter).hasToString( "IncludeClassNameFilter that includes class names that match one of the following regular expressions: '" + firstRegex + "' OR '" + secondRegex + "'"); assertTrue(filter.apply("java.lang.String").included()); assertTrue(filter.toPredicate().test("java.lang.String")); assertThat(filter.apply("java.lang.String").getReason()).contains( "Class name [java.lang.String] matches included pattern: '" + firstRegex + "'"); assertTrue(filter.apply("java.util.Collection").included()); assertTrue(filter.toPredicate().test("java.util.Collection")); assertThat(filter.apply("java.util.Collection").getReason()).contains( "Class name [java.util.Collection] matches included pattern: '" + secondRegex + "'"); assertFalse(filter.apply("java.time.Instant").included()); assertFalse(filter.toPredicate().test("java.time.Instant")); assertThat(filter.apply("java.time.Instant").getReason()).contains( "Class name [java.time.Instant] does not match any included pattern: '" + firstRegex + "' OR '" + secondRegex + "'"); } @SuppressWarnings("DataFlowIssue") @Test void excludeClassNamePatternsChecksPreconditions() { assertPreconditionViolationNotNullOrEmptyFor("patterns array", () -> ClassNameFilter.excludeClassNamePatterns((String[]) null)); assertPreconditionViolationNotNullOrEmptyFor("patterns array", () -> ClassNameFilter.excludeClassNamePatterns(new String[0])); assertPreconditionViolationContainsNoNullElementsFor("patterns array", () -> ClassNameFilter.excludeClassNamePatterns(new String[] { null })); } @Test void excludeClassNamePatternsWithSinglePattern() { var regex = "^java\\.lang\\..*"; var filter = ClassNameFilter.excludeClassNamePatterns(regex); assertThat(filter).hasToString( "ExcludeClassNameFilter that excludes class names that match one of the following regular expressions: '" + regex + "'"); assertTrue(filter.apply("java.lang.String").excluded()); assertFalse(filter.toPredicate().test("java.lang.String")); assertThat(filter.apply("java.lang.String").getReason()).contains( "Class name [java.lang.String] matches excluded pattern: '" + regex + "'"); assertTrue(filter.apply("java.time.Instant").included()); assertTrue(filter.toPredicate().test("java.time.Instant")); assertThat(filter.apply("java.time.Instant").getReason()).contains( "Class name [java.time.Instant] does not match any excluded pattern: '" + regex + "'"); } @Test void excludeClassNamePatternsWithMultiplePatterns() { var firstRegex = "^java\\.lang\\..*"; var secondRegex = "^java\\.util\\..*"; var filter = ClassNameFilter.excludeClassNamePatterns(firstRegex, secondRegex); assertThat(filter).hasToString( "ExcludeClassNameFilter that excludes class names that match one of the following regular expressions: '" + firstRegex + "' OR '" + secondRegex + "'"); assertTrue(filter.apply("java.lang.String").excluded()); assertFalse(filter.toPredicate().test("java.lang.String")); assertThat(filter.apply("java.lang.String").getReason()).contains( "Class name [java.lang.String] matches excluded pattern: '" + firstRegex + "'"); assertTrue(filter.apply("java.util.Collection").excluded()); assertFalse(filter.toPredicate().test("java.util.Collection")); assertThat(filter.apply("java.util.Collection").getReason()).contains( "Class name [java.util.Collection] matches excluded pattern: '" + secondRegex + "'"); assertFalse(filter.apply("java.time.Instant").excluded()); assertTrue(filter.toPredicate().test("java.time.Instant")); assertThat(filter.apply("java.time.Instant").getReason()).contains( "Class name [java.time.Instant] does not match any excluded pattern: '" + firstRegex + "' OR '" + secondRegex + "'"); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/discovery/ClassSelectorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import org.junit.jupiter.api.Test; /** * Unit tests for {@link ClassSelector}. * * @since 1.3 * @see DiscoverySelectorsTests */ class ClassSelectorTests { @Test void equalsAndHashCode() { var selector1 = new ClassSelector(null, "org.example.TestClass"); var selector2 = new ClassSelector(null, "org.example.TestClass"); var selector3 = new ClassSelector(null, "org.example.X"); assertEqualsAndHashCode(selector1, selector2, selector3); } @Test void preservesOriginalExceptionWhenTryingToLoadClass() { var selector = new ClassSelector(null, "org.example.TestClass"); assertPreconditionViolationFor(selector::getJavaClass)// .withMessage("Could not load class with name: org.example.TestClass")// .withCauseInstanceOf(ClassNotFoundException.class); } @Test void usesClassClassLoader() { var selector = new ClassSelector(getClass()); assertThat(selector.getClassLoader()).isNotNull().isSameAs(getClass().getClassLoader()); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/discovery/ClasspathResourceSelectorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import org.junit.jupiter.api.Test; /** * Unit tests for {@link ClasspathResourceSelector}. * * @since 1.3 * @see DiscoverySelectorsTests */ class ClasspathResourceSelectorTests { @Test void equalsAndHashCode() { var selector1 = new ClasspathResourceSelector("/foo/bar.txt", null); var selector2 = new ClasspathResourceSelector("/foo/bar.txt", null); var selector3 = new ClasspathResourceSelector("/foo/X.txt", null); assertEqualsAndHashCode(selector1, selector2, selector3); } @Test void equalsAndHashCodeWithFilePosition() { var selector1 = new ClasspathResourceSelector("/foo/bar.txt", FilePosition.from(1)); var selector2 = new ClasspathResourceSelector("/foo/bar.txt", FilePosition.from(1)); var selector3 = new ClasspathResourceSelector("/foo/bar.txt", FilePosition.from(2)); assertEqualsAndHashCode(selector1, selector2, selector3); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/discovery/ClasspathRootSelectorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import java.net.URI; import org.junit.jupiter.api.Test; /** * Unit tests for {@link ClasspathRootSelector}. * * @since 1.3 * @see DiscoverySelectorsTests */ class ClasspathRootSelectorTests { @Test void equalsAndHashCode() throws Exception { var selector1 = new ClasspathRootSelector(new URI("file://example/path")); var selector2 = new ClasspathRootSelector(new URI("file://example/path")); var selector3 = new ClasspathRootSelector(new URI("file://example/foo")); assertEqualsAndHashCode(selector1, selector2, selector3); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/discovery/DirectorySelectorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import org.junit.jupiter.api.Test; /** * Unit tests for {@link DirectorySelector}. * * @since 1.3 * @see DiscoverySelectorsTests */ class DirectorySelectorTests { @Test void equalsAndHashCode() { var selector1 = new DirectorySelector("/example/foo"); var selector2 = new DirectorySelector("/example/foo"); var selector3 = new DirectorySelector("/example/bar"); assertEqualsAndHashCode(selector1, selector2, selector3); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/discovery/DiscoverySelectorsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static java.lang.String.join; import static java.util.Collections.singletonList; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.InstanceOfAssertFactories.type; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.uniqueIdForMethod; import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasses; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClassesByName; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathResource; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathResourceByName; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathResources; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathRoots; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectDirectory; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectFile; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectModule; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectModules; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectNestedClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectNestedMethod; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUri; import java.io.File; import java.lang.reflect.Method; import java.net.URI; import java.nio.file.Path; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.stream.Stream; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.junit.platform.commons.io.Resource; import org.junit.platform.commons.test.TestClassLoader; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.DiscoverySelectorIdentifier; /** * Unit tests for {@link DiscoverySelectors}. * * @since 1.0 */ class DiscoverySelectorsTests { @Nested class SelectUriTests { @SuppressWarnings("DataFlowIssue") @Test void selectUriByName() { assertPreconditionViolationFor(() -> selectUri((String) null)); assertPreconditionViolationFor(() -> selectUri(" ")); assertPreconditionViolationFor(() -> selectUri("foo:")); var uri = "https://junit.org"; var selector = selectUri(uri); assertEquals(uri, selector.getUri().toString()); } @SuppressWarnings("DataFlowIssue") @Test void selectUriByURI() { assertPreconditionViolationFor(() -> selectUri((URI) null)); assertPreconditionViolationFor(() -> selectUri(" ")); var uri = URI.create("https://junit.org"); var selector = selectUri(uri); assertEquals(uri, selector.getUri()); } @Test void parseUriSelector() { var selector = parseIdentifier(selectUri("https://junit.org")); assertThat(selector) // .asInstanceOf(type(UriSelector.class)) // .extracting(UriSelector::getUri) // .isEqualTo(URI.create("https://junit.org")); } } @Nested class SelectFileTests { @SuppressWarnings("DataFlowIssue") @Test void selectFileByName() { assertPreconditionViolationFor(() -> selectFile((String) null)); assertPreconditionViolationFor(() -> selectFile(" ")); var path = "src/test/resources/do_not_delete_me.txt"; var selector = selectFile(path); assertEquals(path, selector.getRawPath()); assertEquals(new File(path), selector.getFile()); assertEquals(Path.of(path), selector.getPath()); } @SuppressWarnings("DataFlowIssue") @Test void selectFileByNameAndPosition() { var filePosition = FilePosition.from(12, 34); assertPreconditionViolationFor(() -> selectFile((String) null, filePosition)); assertPreconditionViolationFor(() -> selectFile(" ", filePosition)); var path = "src/test/resources/do_not_delete_me.txt"; var selector = selectFile(path, filePosition); assertEquals(path, selector.getRawPath()); assertEquals(new File(path), selector.getFile()); assertEquals(Path.of(path), selector.getPath()); assertEquals(filePosition, selector.getPosition().orElseThrow()); } @SuppressWarnings("DataFlowIssue") @Test void selectFileByFileReference() throws Exception { assertPreconditionViolationFor(() -> selectFile((File) null)); assertPreconditionViolationFor(() -> selectFile(new File("bogus/nonexistent.txt"))); var currentDir = new File(".").getCanonicalFile(); var relativeDir = new File("..", currentDir.getName()); var file = new File(relativeDir, "src/test/resources/do_not_delete_me.txt"); var path = file.getCanonicalFile().getPath(); var selector = selectFile(file); assertEquals(path, selector.getRawPath()); assertEquals(file.getCanonicalFile(), selector.getFile()); assertEquals(Path.of(path), selector.getPath()); } @SuppressWarnings("DataFlowIssue") @Test void selectFileByFileReferenceAndPosition() throws Exception { var filePosition = FilePosition.from(12, 34); assertPreconditionViolationFor(() -> selectFile((File) null, filePosition)); assertPreconditionViolationFor(() -> selectFile(new File("bogus/nonexistent.txt"), filePosition)); var currentDir = new File(".").getCanonicalFile(); var relativeDir = new File("..", currentDir.getName()); var file = new File(relativeDir, "src/test/resources/do_not_delete_me.txt"); var path = file.getCanonicalFile().getPath(); var selector = selectFile(file, filePosition); assertEquals(path, selector.getRawPath()); assertEquals(file.getCanonicalFile(), selector.getFile()); assertEquals(Path.of(path), selector.getPath()); assertEquals(FilePosition.from(12, 34), selector.getPosition().orElseThrow()); } @Test void parseFileSelectorWithRelativePath() { var path = "src/test/resources/do_not_delete_me.txt"; var selector = parseIdentifier(selectFile(path)); assertThat(selector) // .asInstanceOf(type(FileSelector.class)) // .extracting(FileSelector::getRawPath, FileSelector::getFile, FileSelector::getPath, FileSelector::getPosition) // .containsExactly(path, new File(path), Path.of(path), Optional.empty()); } @Test void parseFileSelectorWithAbsolutePath() { var path = "src/test/resources/do_not_delete_me.txt"; var absolutePath = new File(path).getAbsolutePath(); var selector = parseIdentifier(selectFile(absolutePath)); assertThat(selector) // .asInstanceOf(type(FileSelector.class)) // .extracting(FileSelector::getRawPath, FileSelector::getFile, FileSelector::getPath, FileSelector::getPosition) // .containsExactly(absolutePath, new File(absolutePath), Path.of(absolutePath), Optional.empty()); } @Test void parseFileSelectorWithRelativePathAndFilePosition() { var path = "src/test/resources/do_not_delete_me.txt"; var filePosition = FilePosition.from(12, 34); var selector = parseIdentifier(selectFile(path, filePosition)); assertThat(selector) // .asInstanceOf(type(FileSelector.class)) // .extracting(FileSelector::getRawPath, FileSelector::getFile, FileSelector::getPath, FileSelector::getPosition) // .containsExactly(path, new File(path), Path.of(path), Optional.of(filePosition)); } @Test void parseFileSelectorWithAbsolutePathAndFilePosition() { var path = "src/test/resources/do_not_delete_me.txt"; var absolutePath = new File(path).getAbsolutePath(); var filePosition = FilePosition.from(12, 34); var selector = parseIdentifier(selectFile(absolutePath, filePosition)); assertThat(selector) // .asInstanceOf(type(FileSelector.class)) // .extracting(FileSelector::getRawPath, FileSelector::getFile, FileSelector::getPath, FileSelector::getPosition) // .containsExactly(absolutePath, new File(absolutePath), Path.of(absolutePath), Optional.of(filePosition)); } } @Nested class SelectDirectoryTests { @SuppressWarnings("DataFlowIssue") @Test void selectDirectoryByName() { assertPreconditionViolationFor(() -> selectDirectory((String) null)); assertPreconditionViolationFor(() -> selectDirectory(" ")); var path = "src/test/resources"; var selector = selectDirectory(path); assertEquals(path, selector.getRawPath()); assertEquals(new File(path), selector.getDirectory()); assertEquals(Path.of(path), selector.getPath()); } @SuppressWarnings("DataFlowIssue") @Test void selectDirectoryByFileReference() throws Exception { assertPreconditionViolationFor(() -> selectDirectory((File) null)); assertPreconditionViolationFor(() -> selectDirectory(new File("bogus/nonexistent"))); var currentDir = new File(".").getCanonicalFile(); var relativeDir = new File("..", currentDir.getName()); var directory = new File(relativeDir, "src/test/resources"); var path = directory.getCanonicalFile().getPath(); var selector = selectDirectory(directory); assertEquals(path, selector.getRawPath()); assertEquals(directory.getCanonicalFile(), selector.getDirectory()); assertEquals(Path.of(path), selector.getPath()); } @Test void parseDirectorySelectorWithRelativePath() { var path = "src/test/resources"; var selector = parseIdentifier(selectDirectory(path)); assertThat(selector) // .asInstanceOf(type(DirectorySelector.class)) // .extracting(DirectorySelector::getRawPath, DirectorySelector::getDirectory, DirectorySelector::getPath) // .containsExactly(path, new File(path), Path.of(path)); } @Test void parseDirectorySelectorWithAbsolutePath() { var path = new File("src/test/resources").getAbsolutePath(); var selector = parseIdentifier(selectDirectory(path)); assertThat(selector) // .asInstanceOf(type(DirectorySelector.class)) // .extracting(DirectorySelector::getRawPath, DirectorySelector::getDirectory, DirectorySelector::getPath) // .containsExactly(path, new File(path), Path.of(path)); } } @Nested class SelectClasspathResourceTests { @SuppressWarnings("DataFlowIssue") @Test void selectClasspathResourcePreconditions() { // @formatter:off assertPreconditionViolationFor(() -> selectClasspathResource((String) null)); assertPreconditionViolationFor(() -> selectClasspathResource("")); assertPreconditionViolationFor(() -> selectClasspathResource("/")); assertPreconditionViolationFor(() -> selectClasspathResource(" ")); assertPreconditionViolationFor(() -> selectClasspathResource("/ ")); assertPreconditionViolationFor(() -> selectClasspathResource("\t")); assertPreconditionViolationFor(() -> selectClasspathResource("/\t")); assertPreconditionViolationFor(() -> selectClasspathResourceByName(null)); assertPreconditionViolationFor(() -> selectClasspathResourceByName(Collections.emptySet())); assertPreconditionViolationFor(() -> selectClasspathResourceByName(Collections.singleton(null))); assertPreconditionViolationFor(() -> selectClasspathResourceByName(Set.of(new StubResource(null)))); assertPreconditionViolationFor(() -> selectClasspathResourceByName(Set.of(new StubResource("")))); assertPreconditionViolationFor(() -> selectClasspathResourceByName(Set.of(new StubResource("a"), new StubResource("b")))); // @formatter:on } @Test void selectIndividualClasspathResource() { // with unnecessary "/" prefix var selector = selectClasspathResource("/foo/bar/spec.xml"); assertEquals("foo/bar/spec.xml", selector.getClasspathResourceName()); // standard use case selector = selectClasspathResource("A/B/C/spec.json"); assertEquals("A/B/C/spec.json", selector.getClasspathResourceName()); } @Test void getSelectedClasspathResource() { var selector = selectClasspathResource("org/junit/platform/commons/example.resource"); var classpathResources = selector.getResources(); assertAll(() -> assertThat(classpathResources).hasSize(1), // () -> assertThat(classpathResources) // .extracting(Resource::getName) // .containsExactly("org/junit/platform/commons/example.resource") // ); } @Test void getMissingClasspathResource() { var selector = selectClasspathResource("org/junit/platform/commons/no-such-example.resource"); assertPreconditionViolationFor(selector::getResources); } @SuppressWarnings("DataFlowIssue") @Test void selectClasspathResourcesWithFilePosition() { var filePosition = FilePosition.from(12, 34); assertPreconditionViolationFor(() -> selectClasspathResource(null, filePosition)); assertPreconditionViolationFor(() -> selectClasspathResource("", filePosition)); assertPreconditionViolationFor(() -> selectClasspathResource(" ", filePosition)); assertPreconditionViolationFor(() -> selectClasspathResource("\t", filePosition)); // with unnecessary "/" prefix var selector = selectClasspathResource("/foo/bar/spec.xml", filePosition); assertEquals("foo/bar/spec.xml", selector.getClasspathResourceName()); assertEquals(FilePosition.from(12, 34), selector.getPosition().orElseThrow()); // standard use case selector = selectClasspathResource("A/B/C/spec.json", filePosition); assertEquals("A/B/C/spec.json", selector.getClasspathResourceName()); assertEquals(filePosition, selector.getPosition().orElseThrow()); } @Test void parseClasspathResources() { // with unnecessary "/" prefix var selector = parseIdentifier(selectClasspathResource("/foo/bar/spec.xml")); assertThat(selector) // .asInstanceOf(type(ClasspathResourceSelector.class)) // .extracting(ClasspathResourceSelector::getClasspathResourceName, ClasspathResourceSelector::getPosition) // .containsExactly("foo/bar/spec.xml", Optional.empty()); // standard use case selector = parseIdentifier(selectClasspathResource("A/B/C/spec.json")); assertThat(selector) // .asInstanceOf(type(ClasspathResourceSelector.class)) // .extracting(ClasspathResourceSelector::getClasspathResourceName, ClasspathResourceSelector::getPosition) // .containsExactly("A/B/C/spec.json", Optional.empty()); } @Test void parseClasspathResourcesWithFilePosition() { var filePosition = FilePosition.from(12, 34); // with unnecessary "/" prefix var selector = parseIdentifier(selectClasspathResource("/foo/bar/spec.xml", FilePosition.from(12, 34))); assertThat(selector) // .asInstanceOf(type(ClasspathResourceSelector.class)) // .extracting(ClasspathResourceSelector::getClasspathResourceName, ClasspathResourceSelector::getPosition) // .containsExactly("foo/bar/spec.xml", Optional.of(filePosition)); // standard use case selector = parseIdentifier(selectClasspathResource("A/B/C/spec.json", FilePosition.from(12, 34))); assertThat(selector) // .asInstanceOf(type(ClasspathResourceSelector.class)) // .extracting(ClasspathResourceSelector::getClasspathResourceName, ClasspathResourceSelector::getPosition) // .containsExactly("A/B/C/spec.json", Optional.of(filePosition)); } private record StubResource(String name) implements Resource { @Override public String getName() { return name(); } @Override public URI getUri() { throw new UnsupportedOperationException(); } } } /** * @since 6.1 */ @Nested class SelectClasspathResourcesTests { @SuppressWarnings("DataFlowIssue") @Test void selectClasspathResourcesPreconditions() { assertPreconditionViolationFor(() -> selectClasspathResources((String) null)); assertPreconditionViolationFor(() -> selectClasspathResources((String[]) null)); assertPreconditionViolationFor(() -> selectClasspathResources("")); assertPreconditionViolationFor(() -> selectClasspathResources(singletonList(null))); assertPreconditionViolationFor(() -> selectClasspathResources(singletonList(""))); } @Test void selectMultipleClasspathResources() { var selectors = selectClasspathResources( // "org/junit/platform/example-a.resource", // "org/junit/platform/example-b.resource" // ); assertThat(selectors).extracting(ClasspathResourceSelector::getClasspathResourceName) // .containsExactly( // "org/junit/platform/example-a.resource", // "org/junit/platform/example-b.resource" // ); } @Test void selectDistinctClasspathResources() { var selectors = selectClasspathResources( // "org/junit/platform/example-a.resource", // "org/junit/platform/example-b.resource", // "org/junit/platform/example-a.resource", // "org/junit/platform/example-c.resource" // ); assertThat(selectors).extracting(ClasspathResourceSelector::getClasspathResourceName) // .containsExactly( // "org/junit/platform/example-a.resource", // "org/junit/platform/example-b.resource", // "org/junit/platform/example-c.resource" // ); } } @Nested class SelectModuleTests { @SuppressWarnings("DataFlowIssue") @Test void selectModuleByNamePreconditions() { assertPreconditionViolationFor(() -> selectModule((String) null)); assertPreconditionViolationFor(() -> selectModule("")); assertPreconditionViolationFor(() -> selectModule(" ")); } @Test void selectModuleByName() { var selector = selectModule("java.base"); assertEquals("java.base", selector.getModuleName()); } @SuppressWarnings("DataFlowIssue") @Test void selectModuleByInstancePreconditions() { assertPreconditionViolationFor(() -> selectModule((Module) null)); assertPreconditionViolationFor(() -> selectModule(getClass().getClassLoader().getUnnamedModule())); } @Test void selectModuleByInstance() { var module = Object.class.getModule(); var selector = selectModule(module); assertEquals("java.base", selector.getModuleName()); assertSame(module, selector.getModule().orElseThrow()); } @SuppressWarnings("DataFlowIssue") @Test void selectModulesByNamesPreconditions() { assertPreconditionViolationFor(() -> selectModules(null)); assertPreconditionViolationFor(() -> selectModules(Set.of("a", " "))); } @Test void selectModulesByNames() { var selectors = selectModules(Set.of("a", "b")); var names = selectors.stream().map(ModuleSelector::getModuleName).toList(); assertThat(names).containsExactlyInAnyOrder("b", "a"); } @Test void parseModuleSelector() { var selector = parseIdentifier(selectModule("java.base")); assertThat(selector) // .asInstanceOf(type(ModuleSelector.class)) // .extracting(ModuleSelector::getModuleName) // .isEqualTo("java.base"); } } @Nested class SelectPackageTests { @Test void selectPackageByName() { var selector = selectPackage(getClass().getPackage().getName()); assertEquals(getClass().getPackage().getName(), selector.getPackageName()); } @Test void parsePackageSelector() { var selector = parseIdentifier(selectPackage(getClass().getPackage().getName())); assertThat(selector) // .asInstanceOf(type(PackageSelector.class)) // .extracting(PackageSelector::getPackageName) // .isEqualTo(getClass().getPackage().getName()); } } @Nested class SelectClasspathRootsTests { @Test void selectClasspathRootsWithNonExistingDirectory() { var selectors = selectClasspathRoots(Set.of(Path.of("some", "local", "path"))); assertThat(selectors).isEmpty(); } @Test void selectClasspathRootsWithNonExistingJarFile() { var selectors = selectClasspathRoots(Set.of(Path.of("some.jar"))); assertThat(selectors).isEmpty(); } @Test void selectClasspathRootsWithExistingDirectory(@TempDir Path tempDir) { var selectors = selectClasspathRoots(Set.of(tempDir)); assertThat(selectors).extracting(ClasspathRootSelector::getClasspathRoot).containsExactly(tempDir.toUri()); } @Test void selectClasspathRootsWithExistingJarFile() throws Exception { var jarUri = requireNonNull(getClass().getResource("/jartest.jar")).toURI(); var jarFile = Path.of(jarUri); var selectors = selectClasspathRoots(Set.of(jarFile)); assertThat(selectors).extracting(ClasspathRootSelector::getClasspathRoot).containsExactly(jarUri); } @Test void parseClasspathRootSelectorWithNonExistingDirectory() { var selectorStream = parseIdentifiers(selectClasspathRoots(Set.of(Path.of("some/local/path")))); assertThat(selectorStream).isEmpty(); } @Test void parseClasspathRootSelectorWithNonExistingJarFile() { var selectorStream = parseIdentifiers(selectClasspathRoots(Set.of(Path.of("some.jar")))); assertThat(selectorStream).isEmpty(); } @Test void parseClasspathRootSelectorWithExistingDirectory(@TempDir Path tempDir) { var selectorStream = parseIdentifiers(selectClasspathRoots(Set.of(tempDir))); var selector = selectorStream.findAny().orElseThrow(); assertThat(selector) // .asInstanceOf(type(ClasspathRootSelector.class)) // .extracting(ClasspathRootSelector::getClasspathRoot) // .isEqualTo(tempDir.toUri()); } @Test void parseClasspathRootSelectorWithExistingJarFile() throws Exception { var jarUri = requireNonNull(getClass().getResource("/jartest.jar")).toURI(); var jarPath = Path.of(jarUri); var selectorStream = parseIdentifiers(selectClasspathRoots(Set.of(jarPath))); var selector = selectorStream.findAny().orElseThrow(); assertThat(selector) // .asInstanceOf(type(ClasspathRootSelector.class)) // .extracting(ClasspathRootSelector::getClasspathRoot) // .isEqualTo(jarUri); } } @Nested class SelectClassTests { @Test void selectClassByReference() { var selector = selectClass(getClass()); assertEquals(getClass(), selector.getJavaClass()); } @Test void selectClassByName() { var selector = selectClass(getClass().getName()); assertEquals(getClass(), selector.getJavaClass()); } @Test void selectClassByNameWithExplicitClassLoader() throws Exception { try (var testClassLoader = TestClassLoader.forClasses(getClass())) { var selector = selectClass(testClassLoader, getClass().getName()); assertThat(selector.getJavaClass().getName()).isEqualTo(getClass().getName()); assertThat(selector.getJavaClass()).isNotEqualTo(getClass()); assertThat(selector.getClassLoader()).isSameAs(testClassLoader); assertThat(selector.getJavaClass().getClassLoader()).isSameAs(testClassLoader); } } @Test void parseClassSelector() { var selector = parseIdentifier(selectClass(getClass())); assertThat(selector) // .asInstanceOf(type(ClassSelector.class)) // .extracting(ClassSelector::getJavaClass) // .isEqualTo(getClass()); } } /** * @since 6.0 */ @Nested class SelectClassesTests { @Test void selectClassesByReferenceViaVarargs() { assertSelectClassesByReferenceResults(selectClasses(String.class, Integer.class)); } @Test void selectClassesByReferenceViaList() { assertSelectClassesByReferenceResults(selectClasses(List.of(String.class, Integer.class))); } private static void assertSelectClassesByReferenceResults(List selectors) { Class class1 = String.class; Class class2 = Integer.class; assertThat(selectors).satisfiesExactly(// selector1 -> { assertThat(selector1.getJavaClass()).isEqualTo(class1); assertThat(selector1.getClassName()).isEqualTo(class1.getName()); }, // selector2 -> { assertThat(selector2.getJavaClass()).isEqualTo(class2); assertThat(selector2.getClassName()).isEqualTo(class2.getName()); }); } @Test void selectClassesByNameViaVarargsWithExplicitClassLoader() throws Exception { Class class1 = Foo.class; Class class2 = Bar.class; try (var testClassLoader = TestClassLoader.forClasses(class1, class2)) { assertThat(selectClassesByName(testClassLoader, class1.getName(), class2.getName())).satisfiesExactly( selector1 -> checkClassSelector(testClassLoader, selector1, class1), // selector2 -> checkClassSelector(testClassLoader, selector2, class2)); // } } @Test void selectClassesByNameViaListWithExplicitClassLoader() throws Exception { Class class1 = Foo.class; Class class2 = Bar.class; try (var testClassLoader = TestClassLoader.forClasses(class1, class2)) { assertThat(selectClassesByName(testClassLoader, List.of(class1.getName(), class2.getName())))// .satisfiesExactly(// selector1 -> checkClassSelector(testClassLoader, selector1, class1), // selector2 -> checkClassSelector(testClassLoader, selector2, class2)); // } } private static void checkClassSelector(TestClassLoader testClassLoader, ClassSelector selector, Class clazz) { assertThat(selector.getJavaClass().getName()).isEqualTo(clazz.getName()); assertThat(selector.getJavaClass()).isNotEqualTo(clazz); assertThat(selector.getJavaClass().getClassLoader()).isSameAs(testClassLoader); assertThat(selector.getClassLoader()).isSameAs(testClassLoader); } class Foo { } class Bar { } } @Nested class SelectMethodTests { @SuppressWarnings("DataFlowIssue") @Test @DisplayName("Preconditions: selectMethod(className, methodName)") void selectMethodByClassNameAndMethodNamePreconditions() { assertPreconditionViolationFor(() -> selectMethod("TestClass", null)); assertPreconditionViolationFor(() -> selectMethod("TestClass", "")); assertPreconditionViolationFor(() -> selectMethod("TestClass", " ")); assertPreconditionViolationFor(() -> selectMethod((String) null, "method")); assertPreconditionViolationFor(() -> selectMethod("", "method")); assertPreconditionViolationFor(() -> selectMethod(" ", "method")); } @SuppressWarnings("DataFlowIssue") @Test @DisplayName("Preconditions: selectMethod(className, methodName, parameterTypeNames)") void selectMethodByClassNameMethodNameAndParameterTypeNamesPreconditions() { assertPreconditionViolationFor(() -> selectMethod("TestClass", null, "int")); assertPreconditionViolationFor(() -> selectMethod("TestClass", "", "int")); assertPreconditionViolationFor(() -> selectMethod("TestClass", " ", "int")); assertPreconditionViolationFor(() -> selectMethod((String) null, "method", "int")); assertPreconditionViolationFor(() -> selectMethod("", "method", "int")); assertPreconditionViolationFor(() -> selectMethod(" ", "method", "int")); assertPreconditionViolationFor(() -> selectMethod("TestClass", "method", (String) null)); } @SuppressWarnings("DataFlowIssue") @Test @DisplayName("Preconditions: selectMethod(className, methodName, parameterTypes)") void selectMethodByClassNameMethodNameAndParameterTypesPreconditions() { // @formatter:off assertPreconditionViolationFor(() -> selectMethod("TestClass", null, int.class)); assertPreconditionViolationFor(() -> selectMethod("TestClass", "", int.class)); assertPreconditionViolationFor(() -> selectMethod("TestClass", " ", int.class)); assertPreconditionViolationFor(() -> selectMethod((String) null, "method", int.class)); assertPreconditionViolationFor(() -> selectMethod("", "method", int.class)); assertPreconditionViolationFor(() -> selectMethod(" ", "method", int.class)); assertPreconditionViolationFor(() -> selectMethod("TestClass", "method", (Class) null)); assertPreconditionViolationFor(() -> selectMethod("TestClass", "method", new Class[] { int.class, null })); // @formatter:on } @SuppressWarnings("DataFlowIssue") @Test @DisplayName("Preconditions: selectMethod(class, methodName)") void selectMethodByClassAndMethodNamePreconditions() { assertPreconditionViolationFor(() -> selectMethod(testClass(), (String) null)); assertPreconditionViolationFor(() -> selectMethod(testClass(), "")); assertPreconditionViolationFor(() -> selectMethod(testClass(), " ")); assertPreconditionViolationFor(() -> selectMethod((Class) null, "method")); } @SuppressWarnings("DataFlowIssue") @Test @DisplayName("Preconditions: selectMethod(class, methodName, parameterTypeNames)") void selectMethodByClassMethodNameAndParameterTypeNamesPreconditions() { assertPreconditionViolationFor(() -> selectMethod((Class) null, "method", "int")); assertPreconditionViolationFor(() -> selectMethod(testClass(), null, "int")); assertPreconditionViolationFor(() -> selectMethod(testClass(), "", "int")); assertPreconditionViolationFor(() -> selectMethod(testClass(), " ", "int")); assertPreconditionViolationFor(() -> selectMethod(testClass(), "method", (String) null)); } @SuppressWarnings("DataFlowIssue") @Test @DisplayName("Preconditions: selectMethod(class, method)") void selectMethodByClassAndMethodPreconditions() { var method = getClass().getDeclaredMethods()[0]; assertPreconditionViolationFor(() -> selectMethod(null, method)); assertPreconditionViolationFor(() -> selectMethod(testClass(), (Method) null)); } @ParameterizedTest(name = "FQMN: ''{0}''") @MethodSource("invalidFullyQualifiedMethodNames") @DisplayName("Preconditions: selectMethod(FQMN)") void selectMethodByFullyQualifiedNamePreconditions(String fqmn, String message) { assertPreconditionViolationFor(() -> selectMethod(fqmn)).withMessageContaining(message); } static Stream invalidFullyQualifiedMethodNames() { // @formatter:off return Stream.of( arguments(null, "must not be null or blank"), arguments("", "must not be null or blank"), arguments(" ", "must not be null or blank"), arguments("com.example", "not a valid fully qualified method name"), arguments("com.example.Foo", "not a valid fully qualified method name"), arguments("method", "not a valid fully qualified method name"), arguments("#method", "not a valid fully qualified method name"), arguments("#method()", "not a valid fully qualified method name"), arguments("#method(int)", "not a valid fully qualified method name"), arguments("java.lang.String#", "not a valid fully qualified method name") ); // @formatter:on } @Test void selectMethodByFullyQualifiedName() throws Exception { Class clazz = testClass(); var method = clazz.getDeclaredMethod("myTest"); var selector = assertSelectMethodByFullyQualifiedName(clazz, method); assertThat(parseIdentifier(selector)).isEqualTo(selector); } @Test void selectMethodByFullyQualifiedNameWithExplicitClassLoader() throws Exception { try (var testClassLoader = TestClassLoader.forClasses(testClass())) { var clazz = testClassLoader.loadClass(testClass().getName()); assertThat(clazz).isNotEqualTo(testClass()); var method = clazz.getDeclaredMethod("myTest"); var selector = selectMethod(testClassLoader, testClass().getName(), "myTest"); assertThat(selector.getJavaMethod()).isEqualTo(method); assertThat(selector.getJavaClass()).isEqualTo(clazz); assertThat(selector.getClassName()).isEqualTo(clazz.getName()); assertThat(selector.getMethodName()).isEqualTo(method.getName()); assertThat(selector.getParameterTypeNames()).isEmpty(); } } @Test void selectMethodByFullyQualifiedNameForDefaultMethodInInterface() throws Exception { Class clazz = TestCaseWithDefaultMethod.class; var method = clazz.getMethod("myTest"); var selector = assertSelectMethodByFullyQualifiedName(clazz, method); assertThat(parseIdentifier(selector)).isEqualTo(selector); } @Test void selectMethodByFullyQualifiedNameWithPrimitiveParameter() throws Exception { var method = testClass().getDeclaredMethod("myTest", int.class); var selector = assertSelectMethodByFullyQualifiedName(testClass(), method, int.class, "int"); assertThat(parseIdentifier(selector)).isEqualTo(selector); } @Test void selectMethodByFullyQualifiedNameWithPrimitiveParameterUsingSourceCodeSyntax() throws Exception { var method = testClass().getDeclaredMethod("myTest", int.class); var selector = assertSelectMethodByFullyQualifiedName(testClass(), method, "int", "int"); assertThat(parseIdentifier(selector)).isEqualTo(selector); } @Test void selectMethodByFullyQualifiedNameWithObjectParameter() throws Exception { var method = testClass().getDeclaredMethod("myTest", String.class); var selector = assertSelectMethodByFullyQualifiedName(testClass(), method, String.class, String.class.getName()); assertThat(parseIdentifier(selector)).isEqualTo(selector); } @Test void selectMethodByFullyQualifiedNameWithObjectParameterUsingSourceCodeSyntax() throws Exception { var method = testClass().getDeclaredMethod("myTest", String.class); var selector = assertSelectMethodByFullyQualifiedName(testClass(), method, "java.lang.String", String.class.getName()); assertThat(parseIdentifier(selector)).isEqualTo(selector); } @Test void selectMethodByFullyQualifiedNameWithPrimitiveArrayParameter() throws Exception { var method = testClass().getDeclaredMethod("myTest", int[].class); var selector = assertSelectMethodByFullyQualifiedName(testClass(), method, int[].class, int[].class.getName()); assertThat(parseIdentifier(selector)).isEqualTo(selector); } @Test void selectMethodByFullyQualifiedNameWithPrimitiveArrayParameterUsingSourceCodeSyntax() throws Exception { var method = testClass().getDeclaredMethod("myTest", int[].class); var selector = assertSelectMethodByFullyQualifiedName(testClass(), method, "int[]", "int[]"); assertThat(parseIdentifier(selector)).isEqualTo(selector); } @Test void selectMethodByFullyQualifiedNameWithObjectArrayParameter() throws Exception { var method = testClass().getDeclaredMethod("myTest", String[].class); var selector = assertSelectMethodByFullyQualifiedName(testClass(), method, String[].class, String[].class.getName()); assertThat(parseIdentifier(selector)).isEqualTo(selector); } @Test void selectMethodByFullyQualifiedNameWithObjectArrayParameterUsingSourceCodeSyntax() throws Exception { var method = testClass().getDeclaredMethod("myTest", String[].class); var selector = assertSelectMethodByFullyQualifiedName(testClass(), method, "java.lang.String[]", "java.lang.String[]"); assertThat(parseIdentifier(selector)).isEqualTo(selector); } @Test void selectMethodByFullyQualifiedNameWithTwoDimensionalPrimitiveArrayParameter() throws Exception { var method = testClass().getDeclaredMethod("myTest", int[][].class); var selector = assertSelectMethodByFullyQualifiedName(testClass(), method, int[][].class, int[][].class.getName()); assertThat(parseIdentifier(selector)).isEqualTo(selector); } @Test void selectMethodByFullyQualifiedNameWithTwoDimensionalPrimitiveArrayParameterUsingSourceCodeSyntax() throws Exception { var method = testClass().getDeclaredMethod("myTest", int[][].class); var selector = assertSelectMethodByFullyQualifiedName(testClass(), method, "int[][]", "int[][]"); assertThat(parseIdentifier(selector)).isEqualTo(selector); } @Test void selectMethodByFullyQualifiedNameWithTwoDimensionalObjectArrayParameter() throws Exception { var method = testClass().getDeclaredMethod("myTest", String[][].class); var selector = assertSelectMethodByFullyQualifiedName(testClass(), method, String[][].class, String[][].class.getName()); assertThat(parseIdentifier(selector)).isEqualTo(selector); } @Test void selectMethodByFullyQualifiedNameWithTwoDimensionalObjectArrayParameterUsingSourceCodeSyntax() throws Exception { var method = testClass().getDeclaredMethod("myTest", String[][].class); var selector = assertSelectMethodByFullyQualifiedName(testClass(), method, "java.lang.String[][]", "java.lang.String[][]"); assertThat(parseIdentifier(selector)).isEqualTo(selector); } @Test void selectMethodByFullyQualifiedNameWithMultidimensionalPrimitiveArrayParameter() throws Exception { var method = testClass().getDeclaredMethod("myTest", int[][][][][].class); var selector = assertSelectMethodByFullyQualifiedName(testClass(), method, int[][][][][].class, int[][][][][].class.getName()); assertThat(parseIdentifier(selector)).isEqualTo(selector); } @Test void selectMethodByFullyQualifiedNameWithMultidimensionalPrimitiveArrayParameterUsingSourceCodeSyntax() throws Exception { var method = testClass().getDeclaredMethod("myTest", int[][][][][].class); var selector = assertSelectMethodByFullyQualifiedName(testClass(), method, "int[][][][][]", "int[][][][][]"); assertThat(parseIdentifier(selector)).isEqualTo(selector); } @Test void selectMethodByFullyQualifiedNameWithMultidimensionalObjectArrayParameter() throws Exception { var method = testClass().getDeclaredMethod("myTest", Double[][][][][].class); var selector = assertSelectMethodByFullyQualifiedName(testClass(), method, Double[][][][][].class, Double[][][][][].class.getName()); assertThat(parseIdentifier(selector)).isEqualTo(selector); } @Test void selectMethodByFullyQualifiedNameWithMultidimensionalObjectArrayParameterUsingSourceCodeSyntax() throws Exception { var method = testClass().getDeclaredMethod("myTest", Double[][][][][].class); var selector = assertSelectMethodByFullyQualifiedName(testClass(), method, "java.lang.Double[][][][][]", "java.lang.Double[][][][][]"); assertThat(parseIdentifier(selector)).isEqualTo(selector); } @Test void selectMethodByFullyQualifiedNameEndingInOpeningParenthesis() { var className = "org.example.MyClass"; // The following bizarre method name is not permissible in Java source // code; however, it'selector permitted by the JVM -- for example, in Groovy // or Kotlin source code using back ticks. var methodName = ")--("; var fqmn = className + "#" + methodName; var selector = selectMethod(fqmn); assertEquals(className, selector.getClassName()); assertEquals(methodName, selector.getMethodName()); assertEquals("", selector.getParameterTypeNames()); assertThat(parseIdentifier(selector)).isEqualTo(selector); } /** * Inspired by Spock specifications. */ @Test void selectMethodByFullyQualifiedNameContainingHashtags() { var className = "org.example.CalculatorSpec"; var methodName = "#a plus #b equals #c"; var fqmn = className + "#" + methodName; var selector = selectMethod(fqmn); assertEquals(className, selector.getClassName()); assertEquals(methodName, selector.getMethodName()); assertEquals("", selector.getParameterTypeNames()); assertThat(parseIdentifier(selector)).isEqualTo(selector); } /** * Inspired by Spock specifications. */ @Test void selectMethodByFullyQualifiedNameContainingHashtagsAndWithParameterList() { var className = "org.example.CalculatorSpec"; var methodName = "#a plus #b equals #c"; var methodParameters = "int, int, int"; var fqmn = "%s#%s(%s)".formatted(className, methodName, methodParameters); var selector = selectMethod(fqmn); assertEquals(className, selector.getClassName()); assertEquals(methodName, selector.getMethodName()); assertEquals(methodParameters, selector.getParameterTypeNames()); assertThat(parseIdentifier(selector)).isEqualTo(selector); } /** * Inspired by Kotlin tests. */ @Test void selectMethodByFullyQualifiedNameContainingParentheses() { var className = "org.example.KotlinTestCase"; var methodName = "🦆 ~|~test with a really, (really) terrible name & that needs to be changed!~|~"; var fqmn = className + "#" + methodName; var selector = selectMethod(fqmn); assertEquals(className, selector.getClassName()); assertEquals(methodName, selector.getMethodName()); assertEquals("", selector.getParameterTypeNames()); assertThat(parseIdentifier(selector)).isEqualTo(selector); } /** * Inspired by Kotlin tests. */ @Test void selectMethodByFullyQualifiedNameEndingWithParentheses() { var className = "org.example.KotlinTestCase"; var methodName = "test name ends with parentheses()"; var fqmn = className + "#" + methodName + "()"; var selector = selectMethod(fqmn); assertEquals(className, selector.getClassName()); assertEquals(methodName, selector.getMethodName()); assertEquals("", selector.getParameterTypeNames()); assertThat(parseIdentifier(selector)).isEqualTo(selector); } /** * Inspired by Kotlin tests. */ @Test void selectMethodByFullyQualifiedNameEndingWithParenthesesAndWithParameterList() { var className = "org.example.KotlinTestCase"; var methodName = "test name ends with parentheses()"; var methodParameters = "int, int, int"; var fqmn = "%s#%s(%s)".formatted(className, methodName, methodParameters); var selector = selectMethod(fqmn); assertEquals(className, selector.getClassName()); assertEquals(methodName, selector.getMethodName()); assertEquals(methodParameters, selector.getParameterTypeNames()); assertThat(parseIdentifier(selector)).isEqualTo(selector); } private MethodSelector assertSelectMethodByFullyQualifiedName(Class clazz, Method method) { var selector = selectMethod(fqmn(clazz, method.getName())); assertEquals(method, selector.getJavaMethod()); assertEquals(clazz, selector.getJavaClass()); assertEquals(clazz.getName(), selector.getClassName()); assertEquals(method.getName(), selector.getMethodName()); assertEquals("", selector.getParameterTypeNames()); return selector; } private MethodSelector assertSelectMethodByFullyQualifiedName(Class clazz, Method method, Class parameterType, String expectedParameterTypes) { var selector = selectMethod(fqmn(parameterType)); assertEquals(method, selector.getJavaMethod()); assertEquals(clazz, selector.getJavaClass()); assertEquals(clazz.getName(), selector.getClassName()); assertEquals(method.getName(), selector.getMethodName()); assertEquals(expectedParameterTypes, selector.getParameterTypeNames()); return selector; } private MethodSelector assertSelectMethodByFullyQualifiedName(Class clazz, Method method, String parameterName, String expectedParameterTypes) { var selector = selectMethod(fqmnWithParamNames(parameterName)); assertEquals(method, selector.getJavaMethod()); assertEquals(clazz, selector.getJavaClass()); assertEquals(clazz.getName(), selector.getClassName()); assertEquals(method.getName(), selector.getMethodName()); assertEquals(expectedParameterTypes, selector.getParameterTypeNames()); return selector; } @Test void selectMethodByClassAndMethodName() throws Exception { var method = testClass().getDeclaredMethod("myTest"); var selector = selectMethod(testClass(), "myTest"); assertEquals(testClass(), selector.getJavaClass()); assertEquals(testClass().getName(), selector.getClassName()); assertEquals(method, selector.getJavaMethod()); assertEquals("myTest", selector.getMethodName()); assertEquals("", selector.getParameterTypeNames()); assertThat(parseIdentifier(selector)).isEqualTo(selector); } @Test void selectMethodByClassMethodNameAndParameterTypeNames() throws Exception { var testClass = testClass(); var method = testClass.getDeclaredMethod("myTest", String.class, boolean[].class); var selector = selectMethod(testClass, "myTest", "java.lang.String, boolean[]"); assertThat(selector.getClassName()).isEqualTo(testClass.getName()); assertThat(selector.getJavaClass()).isEqualTo(testClass); assertThat(selector.getMethodName()).isEqualTo("myTest"); assertThat(selector.getJavaMethod()).isEqualTo(method); assertThat(selector.getParameterTypeNames()).isEqualTo("java.lang.String, boolean[]"); assertThat(selector.getParameterTypes()).containsExactly(String.class, boolean[].class); assertThat(parseIdentifier(selector)).isEqualTo(selector); } @Test void selectMethodByClassNameMethodNameAndParameterTypes() throws Exception { var testClass = testClass(); var method = testClass.getDeclaredMethod("myTest", String.class, boolean[].class); var selector = selectMethod(testClass.getName(), "myTest", String.class, boolean[].class); assertThat(selector.getClassName()).isEqualTo(testClass.getName()); assertThat(selector.getJavaClass()).isEqualTo(testClass); assertThat(selector.getMethodName()).isEqualTo("myTest"); assertThat(selector.getJavaMethod()).isEqualTo(method); assertThat(selector.getParameterTypeNames()).isEqualTo("java.lang.String, boolean[]"); assertThat(selector.getParameterTypes()).containsExactly(String.class, boolean[].class); assertThat(parseIdentifier(selector)).isEqualTo(selector); } @Test void selectMethodByClassNameMethodNameAndParameterTypeNamesWithExplicitClassLoader() throws Exception { var testClass = testClass(); try (var testClassLoader = TestClassLoader.forClasses(testClass)) { var clazz = testClassLoader.loadClass(testClass.getName()); assertThat(clazz).isNotEqualTo(testClass); var method = clazz.getDeclaredMethod("myTest", String.class, boolean[].class); var selector = selectMethod(testClassLoader, testClass.getName(), "myTest", "java.lang.String, boolean[]"); assertThat(selector.getClassName()).isEqualTo(clazz.getName()); assertThat(selector.getJavaClass()).isEqualTo(clazz); assertThat(selector.getMethodName()).isEqualTo(method.getName()); assertThat(selector.getJavaMethod()).isEqualTo(method); assertThat(selector.getParameterTypeNames()).isEqualTo("java.lang.String, boolean[]"); assertThat(selector.getParameterTypes()).containsExactly(String.class, boolean[].class); assertThat(parseIdentifier(selector)).isEqualTo(selector); } } @Test void selectMethodByClassMethodNameAndParameterTypes() throws Exception { var testClass = testClass(); var method = testClass.getDeclaredMethod("myTest", String.class, boolean[].class); var selector = selectMethod(testClass, "myTest", String.class, boolean[].class); assertThat(selector.getClassName()).isEqualTo(testClass.getName()); assertThat(selector.getJavaClass()).isEqualTo(testClass); assertThat(selector.getMethodName()).isEqualTo("myTest"); assertThat(selector.getJavaMethod()).isEqualTo(method); assertThat(selector.getParameterTypeNames()).isEqualTo("java.lang.String, boolean[]"); assertThat(selector.getParameterTypes()).containsExactly(String.class, boolean[].class); assertThat(parseIdentifier(selector)).isEqualTo(selector); } @Test void selectMethodWithParametersByMethodReference() throws Exception { var testClass = testClass(); var method = testClass.getDeclaredMethod("myTest", String.class, boolean[].class); var selector = selectMethod(testClass, method); assertThat(selector.getClassName()).isEqualTo(testClass.getName()); assertThat(selector.getJavaClass()).isEqualTo(testClass); assertThat(selector.getMethodName()).isEqualTo("myTest"); assertThat(selector.getJavaMethod()).isEqualTo(method); assertThat(selector.getParameterTypeNames()).isEqualTo("java.lang.String, boolean[]"); assertThat(selector.getParameterTypes()).containsExactly(String.class, boolean[].class); assertThat(parseIdentifier(selector)).isEqualTo(selector); } @Test void selectClassByNameForSpockSpec() { var className = "org.example.CalculatorSpec"; var selector = selectClass(className); assertEquals(className, selector.getClassName()); } @Test void selectMethodByClassAndNameForSpockSpec() { var className = "org.example.CalculatorSpec"; var methodName = "#a plus #b equals #c"; var selector = selectMethod(className, methodName); assertEquals(className, selector.getClassName()); assertEquals(methodName, selector.getMethodName()); assertEquals("", selector.getParameterTypeNames()); assertThat(parseIdentifier(selector)).isEqualTo(selector); } private static Class testClass() { return DiscoverySelectorsTests.class; } } @Nested class SelectNestedClassAndSelectNestedMethodTests { private final String enclosingClassName = getClass().getName() + "$ClassWithNestedInnerClass"; private final String nestedClassName = getClass().getName() + "$ClassWithNestedInnerClass$NestedClass"; private final String doubleNestedClassName = nestedClassName + "$DoubleNestedClass"; private final String methodName = "nestedTest"; @Test void selectNestedClassByClassNames() { var selector = selectNestedClass(List.of(enclosingClassName), nestedClassName); assertThat(selector.getEnclosingClasses()).containsOnly(ClassWithNestedInnerClass.class); assertThat(selector.getNestedClass()).isEqualTo(ClassWithNestedInnerClass.NestedClass.class); assertThat(selector.getEnclosingClassNames()).containsOnly(enclosingClassName); assertThat(selector.getNestedClassName()).isEqualTo(nestedClassName); assertThat(parseIdentifier(selector)).isEqualTo(selector); } @Test void selectNestedClassByClassNamesWithExplicitClassLoader() throws Exception { var testClasses = List.of(ClassWithNestedInnerClass.class, ClassWithNestedInnerClass.NestedClass.class); try (var testClassLoader = TestClassLoader.forClasses(testClasses)) { var selector = selectNestedClass(testClassLoader, List.of(enclosingClassName), nestedClassName); assertThat(selector.getEnclosingClasses()).doesNotContain(ClassWithNestedInnerClass.class); assertThat(selector.getEnclosingClasses()).extracting(Class::getName).containsOnly(enclosingClassName); assertThat(selector.getNestedClass()).isNotEqualTo(ClassWithNestedInnerClass.NestedClass.class); assertThat(selector.getNestedClass().getName()).isEqualTo(nestedClassName); assertThat(selector.getClassLoader()).isSameAs(testClassLoader); assertThat(selector.getEnclosingClasses()).extracting(Class::getClassLoader).containsOnly( testClassLoader); assertThat(selector.getNestedClass().getClassLoader()).isSameAs(testClassLoader); assertThat(parseIdentifier(selector)).isEqualTo(selector); } } @Test void selectDoubleNestedClassByClassNames() { var selector = selectNestedClass(List.of(enclosingClassName, nestedClassName), doubleNestedClassName); assertThat(selector.getEnclosingClasses()).containsExactly(ClassWithNestedInnerClass.class, ClassWithNestedInnerClass.NestedClass.class); assertThat(selector.getNestedClass()).isEqualTo( ClassWithNestedInnerClass.NestedClass.DoubleNestedClass.class); assertThat(selector.getEnclosingClassNames()).containsExactly(enclosingClassName, nestedClassName); assertThat(selector.getNestedClassName()).isEqualTo(doubleNestedClassName); assertThat(parseIdentifier(selector)).isEqualTo(selector); } @SuppressWarnings("DataFlowIssue") @Test void selectNestedClassPreconditions() { assertPreconditionViolationFor(() -> selectNestedClass(null, "ClassName")); assertPreconditionViolationFor(() -> selectNestedClass(List.of(), "ClassName")); assertPreconditionViolationFor(() -> selectNestedClass(List.of("ClassName"), null)); assertPreconditionViolationFor(() -> selectNestedClass(List.of("ClassName"), "")); assertPreconditionViolationFor(() -> selectNestedClass(List.of("ClassName"), " ")); } @Test void selectNestedMethodByEnclosingClassNamesAndMethodName() throws Exception { var selector = selectNestedMethod(List.of(enclosingClassName), nestedClassName, methodName); assertThat(selector.getEnclosingClasses()).containsOnly(ClassWithNestedInnerClass.class); assertThat(selector.getNestedClass()).isEqualTo(ClassWithNestedInnerClass.NestedClass.class); assertThat(selector.getMethod()).isEqualTo(selector.getNestedClass().getDeclaredMethod(methodName)); assertThat(selector.getEnclosingClassNames()).containsOnly(enclosingClassName); assertThat(selector.getNestedClassName()).isEqualTo(nestedClassName); assertThat(selector.getMethodName()).isEqualTo(methodName); assertThat(parseIdentifier(selector)).isEqualTo(selector); } @Test void selectNestedMethodByEnclosingClassNamesAndMethodNameWithExplicitClassLoader() throws Exception { var testClasses = List.of(ClassWithNestedInnerClass.class, ClassWithNestedInnerClass.NestedClass.class); try (var testClassLoader = TestClassLoader.forClasses(testClasses)) { var selector = selectNestedMethod(testClassLoader, List.of(enclosingClassName), nestedClassName, methodName); assertThat(selector.getEnclosingClasses()).doesNotContain(ClassWithNestedInnerClass.class); assertThat(selector.getEnclosingClasses()).extracting(Class::getName).containsOnly(enclosingClassName); assertThat(selector.getNestedClass()).isNotEqualTo(ClassWithNestedInnerClass.NestedClass.class); assertThat(selector.getNestedClass().getName()).isEqualTo(nestedClassName); assertThat(selector.getClassLoader()).isSameAs(testClassLoader); assertThat(selector.getEnclosingClasses()).extracting(Class::getClassLoader).containsOnly( testClassLoader); assertThat(selector.getNestedClass().getClassLoader()).isSameAs(testClassLoader); assertThat(selector.getMethodName()).isEqualTo(methodName); assertThat(parseIdentifier(selector)).isEqualTo(selector); } } @Test void selectNestedMethodByEnclosingClassesAndMethodName() throws Exception { var selector = selectNestedMethod(List.of(ClassWithNestedInnerClass.class), ClassWithNestedInnerClass.NestedClass.class, methodName); assertThat(selector.getEnclosingClasses()).containsOnly(ClassWithNestedInnerClass.class); assertThat(selector.getNestedClass()).isEqualTo(ClassWithNestedInnerClass.NestedClass.class); assertThat(selector.getMethod()).isEqualTo(selector.getNestedClass().getDeclaredMethod(methodName)); assertThat(selector.getEnclosingClassNames()).containsOnly(enclosingClassName); assertThat(selector.getNestedClassName()).isEqualTo(nestedClassName); assertThat(selector.getMethodName()).isEqualTo(methodName); assertThat(parseIdentifier(selector)).isEqualTo(selector); } @Test void selectNestedMethodByEnclosingClassNamesMethodNameAndParameterTypeNames() throws Exception { var selector = selectNestedMethod(List.of(enclosingClassName), nestedClassName, methodName, String.class.getName()); assertThat(selector.getEnclosingClasses()).containsOnly(ClassWithNestedInnerClass.class); assertThat(selector.getNestedClass()).isEqualTo(ClassWithNestedInnerClass.NestedClass.class); assertThat(selector.getMethod()).isEqualTo( selector.getNestedClass().getDeclaredMethod(methodName, String.class)); assertThat(selector.getEnclosingClassNames()).containsOnly(enclosingClassName); assertThat(selector.getNestedClassName()).isEqualTo(nestedClassName); assertThat(selector.getMethodName()).isEqualTo(methodName); assertThat(parseIdentifier(selector)).isEqualTo(selector); } /** * @since 1.0 */ @Test void selectNestedMethodByEnclosingClassNamesMethodNameAndParameterTypes() throws Exception { var selector = selectNestedMethod(List.of(enclosingClassName), nestedClassName, methodName, String.class); assertThat(selector.getEnclosingClasses()).containsOnly(ClassWithNestedInnerClass.class); assertThat(selector.getNestedClass()).isEqualTo(ClassWithNestedInnerClass.NestedClass.class); assertThat(selector.getMethod()).isEqualTo( selector.getNestedClass().getDeclaredMethod(methodName, String.class)); assertThat(selector.getParameterTypes()).containsExactly(String.class); assertThat(selector.getEnclosingClassNames()).containsOnly(enclosingClassName); assertThat(selector.getNestedClassName()).isEqualTo(nestedClassName); assertThat(selector.getMethodName()).isEqualTo(methodName); assertThat(selector.getParameterTypeNames()).isEqualTo("java.lang.String"); assertThat(parseIdentifier(selector)).isEqualTo(selector); } /** * @since 1.10 */ @Test void selectNestedMethodByEnclosingClassNamesMethodNameAndParameterTypeNamesWithExplicitClassLoader() throws Exception { var enclosingClass = ClassWithNestedInnerClass.class; var nestedClass = ClassWithNestedInnerClass.NestedClass.class; try (var testClassLoader = TestClassLoader.forClasses(enclosingClass, nestedClass)) { var selector = selectNestedMethod(testClassLoader, List.of(enclosingClassName), nestedClassName, methodName, String.class.getName()); assertThat(selector.getEnclosingClasses()).doesNotContain(enclosingClass); assertThat(selector.getEnclosingClasses()).extracting(Class::getName).containsOnly(enclosingClassName); assertThat(selector.getNestedClass()).isNotEqualTo(nestedClass); assertThat(selector.getNestedClass().getName()).isEqualTo(nestedClassName); assertThat(selector.getClassLoader()).isSameAs(testClassLoader); assertThat(selector.getEnclosingClasses()).extracting(Class::getClassLoader).containsOnly( testClassLoader); assertThat(selector.getNestedClass().getClassLoader()).isSameAs(testClassLoader); assertThat(selector.getMethodName()).isEqualTo(methodName); assertThat(selector.getParameterTypeNames()).isEqualTo(String.class.getName()); assertThat(parseIdentifier(selector)).isEqualTo(selector); } } /** * @since 1.10 */ @Test void selectNestedMethodByEnclosingClassesMethodNameAndParameterTypes() throws Exception { var selector = selectNestedMethod(List.of(ClassWithNestedInnerClass.class), ClassWithNestedInnerClass.NestedClass.class, methodName, String.class); assertThat(selector.getEnclosingClasses()).containsOnly(ClassWithNestedInnerClass.class); assertThat(selector.getNestedClass()).isEqualTo(ClassWithNestedInnerClass.NestedClass.class); assertThat(selector.getMethod()).isEqualTo( selector.getNestedClass().getDeclaredMethod(methodName, String.class)); assertThat(selector.getParameterTypes()).containsExactly(String.class); assertThat(selector.getEnclosingClassNames()).containsOnly(enclosingClassName); assertThat(selector.getNestedClassName()).isEqualTo(nestedClassName); assertThat(selector.getMethodName()).isEqualTo(methodName); assertThat(selector.getParameterTypeNames()).isEqualTo("java.lang.String"); assertThat(parseIdentifier(selector)).isEqualTo(selector); } @Test void selectDoubleNestedMethodByEnclosingClassNamesAndMethodName() throws Exception { var doubleNestedMethodName = "doubleNestedTest"; var selector = selectNestedMethod(List.of(enclosingClassName, nestedClassName), doubleNestedClassName, doubleNestedMethodName); assertThat(selector.getEnclosingClasses()).containsExactly(ClassWithNestedInnerClass.class, ClassWithNestedInnerClass.NestedClass.class); assertThat(selector.getNestedClass()).isEqualTo( ClassWithNestedInnerClass.NestedClass.DoubleNestedClass.class); assertThat(selector.getMethod()).isEqualTo( selector.getNestedClass().getDeclaredMethod(doubleNestedMethodName)); assertThat(selector.getEnclosingClassNames()).containsExactly(enclosingClassName, nestedClassName); assertThat(selector.getNestedClassName()).isEqualTo(doubleNestedClassName); assertThat(selector.getMethodName()).isEqualTo(doubleNestedMethodName); assertThat(parseIdentifier(selector)).isEqualTo(selector); } @SuppressWarnings("DataFlowIssue") @Test @DisplayName("Preconditions: selectNestedMethod(enclosingClassNames, nestedClassName, methodName)") void selectNestedMethodByEnclosingClassNamesAndMethodNamePreconditions() { assertPreconditionViolationFor(() -> selectNestedMethod(null, "ClassName", "methodName")); assertPreconditionViolationFor(() -> selectNestedMethod(List.of(), "ClassName", "methodName")); assertPreconditionViolationFor(() -> selectNestedMethod(List.of("ClassName"), null, "methodName")); assertPreconditionViolationFor(() -> selectNestedMethod(List.of("ClassName"), " ", "methodName")); assertPreconditionViolationFor(() -> selectNestedMethod(List.of("ClassName"), "ClassName", null)); assertPreconditionViolationFor(() -> selectNestedMethod(List.of("ClassName"), "ClassName", " ")); } @SuppressWarnings("DataFlowIssue") @Test @DisplayName("Preconditions: selectNestedMethod(enclosingClassNames, nestedClassName, methodName, parameterTypeNames)") void selectNestedMethodByEnclosingClassNamesMethodNameAndParameterTypeNamesPreconditions() { // @formatter:off assertPreconditionViolationFor(() -> selectNestedMethod(null, "ClassName", "methodName", "int")); assertPreconditionViolationFor(() -> selectNestedMethod(List.of(), "ClassName", "methodName", "int")); assertPreconditionViolationFor(() -> selectNestedMethod(List.of("ClassName"), null, "methodName", "int")); assertPreconditionViolationFor(() -> selectNestedMethod(List.of("ClassName"), " ", "methodName", "int")); assertPreconditionViolationFor(() -> selectNestedMethod(List.of("ClassName"), "ClassName", null, "int")); assertPreconditionViolationFor(() -> selectNestedMethod(List.of("ClassName"), "ClassName", " ", "int")); assertPreconditionViolationFor(() -> selectNestedMethod(List.of("ClassName"), "ClassName", "methodName", (String) null)); // @formatter:on } /** * @since 1.10 */ @SuppressWarnings("DataFlowIssue") @Test @DisplayName("Preconditions: selectNestedMethod(enclosingClassNames, nestedClassName, methodName, parameterTypes)") void selectNestedMethodByEnclosingClassNamesMethodNameAndParameterTypesPreconditions() { // @formatter:off assertPreconditionViolationFor(() -> selectNestedMethod(null, "ClassName", "methodName", int.class)); assertPreconditionViolationFor(() -> selectNestedMethod(List.of(), "ClassName", "methodName", int.class)); assertPreconditionViolationFor(() -> selectNestedMethod(List.of("ClassName"), null, "methodName", int.class)); assertPreconditionViolationFor(() -> selectNestedMethod(List.of("ClassName"), " ", "methodName", int.class)); assertPreconditionViolationFor(() -> selectNestedMethod(List.of("ClassName"), "ClassName", null, int.class)); assertPreconditionViolationFor(() -> selectNestedMethod(List.of("ClassName"), "ClassName", " ", int.class)); assertPreconditionViolationFor(() -> selectNestedMethod(List.of("ClassName"), "ClassName", "methodName", (Class) null)); assertPreconditionViolationFor(() -> selectNestedMethod(List.of("ClassName"), "ClassName", "methodName", new Class[] { int.class, null })); // @formatter:on } /** * @since 1.10 */ @SuppressWarnings("DataFlowIssue") @Test @DisplayName("Preconditions: selectNestedMethod(enclosingClasses, nestedClass, methodName, parameterTypes)") void selectNestedMethodByEnclosingClassesClassMethodNameAndParameterTypesPreconditions() { List> enclosingClassList = List.of(ClassWithNestedInnerClass.class); Class nestedClass = ClassWithNestedInnerClass.NestedClass.class; // @formatter:off assertPreconditionViolationFor(() -> selectNestedMethod(null, nestedClass, "methodName", int.class)); assertPreconditionViolationFor(() -> selectNestedMethod(List.of(), nestedClass, "methodName", int.class)); assertPreconditionViolationFor(() -> selectNestedMethod(enclosingClassList, null, "methodName", int.class)); assertPreconditionViolationFor(() -> selectNestedMethod(enclosingClassList, nestedClass, null, int.class)); assertPreconditionViolationFor(() -> selectNestedMethod(enclosingClassList, nestedClass, " ", int.class)); assertPreconditionViolationFor(() -> selectNestedMethod(enclosingClassList, nestedClass, "methodName", (Class) null)); assertPreconditionViolationFor(() -> selectNestedMethod(enclosingClassList, nestedClass, "methodName", new Class[] { int.class, null })); // @formatter:on } static class ClassWithNestedInnerClass { // @Nested @SuppressWarnings({ "InnerClassMayBeStatic", "unused" }) class NestedClass { // @Test void nestedTest() { } // @Test void nestedTest(String parameter) { } // @Nested class DoubleNestedClass { // @Test void doubleNestedTest() { } } } } } @Nested class SelectIterationTests { @Test void selectsIteration() throws Exception { Class clazz = DiscoverySelectorsTests.class; var method = clazz.getDeclaredMethod("myTest", int.class); var parentSelector = selectMethod(clazz, method); var selector = DiscoverySelectors.selectIteration(parentSelector, 23, 42); assertThat(selector.getParentSelector()).isSameAs(parentSelector); assertThat(selector.getIterationIndices()).containsExactly(23, 42); assertThat(parseIdentifier(selector)).isEqualTo(selector); } } @Nested class SelectUniqueIdTests { @Test void selectsUniqueId() { var selector = selectUniqueId(uniqueIdForMethod(DiscoverySelectorsTests.class, "myTest(int)")); assertThat(selector.getUniqueId()).isNotNull(); assertThat(parseIdentifier(selector)).isEqualTo(selector); } } // ------------------------------------------------------------------------- private static DiscoverySelector parseIdentifier(DiscoverySelector selector) { return DiscoverySelectors.parse(toIdentifierString(selector)).orElseThrow(); } private static Stream parseIdentifiers( Collection selectors) { return DiscoverySelectors.parseAll( selectors.stream().map(it -> DiscoverySelectorIdentifier.parse(toIdentifierString(it))).toList()); } private static String toIdentifierString(DiscoverySelector selector) { return selector.toIdentifier().orElseThrow().toString(); } private static String fqmn(Class... params) { return fqmn(DiscoverySelectorsTests.class, "myTest", params); } private static String fqmn(Class clazz, String methodName, Class... params) { return ReflectionUtils.getFullyQualifiedMethodName(clazz, methodName, params); } private static String fqmnWithParamNames(String... params) { return "%s#%s(%s)".formatted(DiscoverySelectorsTests.class.getName(), "myTest", join(", ", params)); } interface TestInterface { @Test default void myTest() { } } private static class TestCaseWithDefaultMethod implements TestInterface { } @SuppressWarnings("unused") void myTest() { } @SuppressWarnings("unused") void myTest(int num) { } @SuppressWarnings("unused") void myTest(int[] nums) { } @SuppressWarnings("unused") void myTest(int[][] grid) { } @SuppressWarnings("unused") void myTest(int[][][][][] grid) { } @SuppressWarnings("unused") void myTest(String info) { } @SuppressWarnings("unused") void myTest(String info, boolean[] flags) { } @SuppressWarnings("unused") void myTest(String[] info) { } @SuppressWarnings("unused") void myTest(String[][] info) { } @SuppressWarnings("unused") void myTest(Double[][][][][] data) { } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/discovery/FilePositionTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import java.util.stream.Stream; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; /** * Unit tests for {@link FilePosition}. * * @since 1.7 */ @DisplayName("FilePosition unit tests") class FilePositionTests { @Test @DisplayName("factory method preconditions") void preconditions() { assertPreconditionViolationFor(() -> FilePosition.from(-1)); assertPreconditionViolationFor(() -> FilePosition.from(0, -1)); } @Test @DisplayName("create FilePosition from factory method with line number") void filePositionFromLine() { var filePosition = FilePosition.from(42); assertThat(filePosition.getLine()).isEqualTo(42); assertThat(filePosition.getColumn()).isEmpty(); } @Test @DisplayName("create FilePosition from factory method with line number and column number") void filePositionFromLineAndColumn() { var filePosition = FilePosition.from(42, 99); assertThat(filePosition.getLine()).isEqualTo(42); assertThat(filePosition.getColumn()).contains(99); } /** * @since 1.3 */ @ParameterizedTest @MethodSource void filePositionFromQuery(String query, int expectedLine, int expectedColumn) { var optionalFilePosition = FilePosition.fromQuery(query); if (optionalFilePosition.isPresent()) { var filePosition = optionalFilePosition.get(); assertThat(filePosition.getLine()).isEqualTo(expectedLine); assertThat(filePosition.getColumn().orElse(-1)).isEqualTo(expectedColumn); } else { assertEquals(-1, expectedColumn); assertEquals(-1, expectedLine); } } @SuppressWarnings("unused") static Stream filePositionFromQuery() { return Stream.of( // arguments(null, -1, -1), // arguments("?!", -1, -1), // arguments("line=ZZ", -1, -1), // arguments("line=42", 42, -1), // arguments("line=42&column=99", 42, 99), // arguments("line=42&column=ZZ", 42, -1), // arguments("line=42&abc=xyz&column=99", 42, 99), // arguments("1=3&foo=X&line=42&abc=xyz&column=99&enigma=393939", 42, 99), // // First one wins: arguments("line=42&line=555", 42, -1), // arguments("line=42&line=555&column=99&column=555", 42, 99) // ); } @Test @DisplayName("equals() and hashCode() with column number cached by Integer.valueOf()") void equalsAndHashCode() { var same = FilePosition.from(42, 99); var sameSame = FilePosition.from(42, 99); var different = FilePosition.from(1, 2); assertEqualsAndHashCode(same, sameSame, different); } @Test @DisplayName("equals() and hashCode() with column number not cached by Integer.valueOf()") void equalsAndHashCodeWithColumnNumberNotCachedByJavaLangIntegerDotValueOf() { var same = FilePosition.from(42, 99999); var sameSame = FilePosition.from(42, 99999); var different = FilePosition.from(1, 2); assertEqualsAndHashCode(same, sameSame, different); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/discovery/FileSelectorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import org.junit.jupiter.api.Test; /** * Unit tests for {@link FileSelector}. * * @since 1.3 * @see DiscoverySelectorsTests */ class FileSelectorTests { @Test void equalsAndHashCode() { var selector1 = new FileSelector("/example/foo.txt", null); var selector2 = new FileSelector("/example/foo.txt", null); var selector3 = new FileSelector("/example/bar.txt", null); assertEqualsAndHashCode(selector1, selector2, selector3); } @Test void equalsAndHashCodeWithFilePosition() { var selector1 = new FileSelector("/example/foo.txt", FilePosition.from(1)); var selector2 = new FileSelector("/example/foo.txt", FilePosition.from(1)); var selector3 = new FileSelector("/example/bar.txt", FilePosition.from(2)); assertEqualsAndHashCode(selector1, selector2, selector3); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/discovery/IterationSelectorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectIteration; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.lang.reflect.Array; import java.util.Optional; import java.util.stream.IntStream; import org.junit.jupiter.api.extension.AnnotatedElementContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.aggregator.AggregateWith; import org.junit.jupiter.params.aggregator.ArgumentsAccessor; import org.junit.jupiter.params.aggregator.ArgumentsAggregationException; import org.junit.jupiter.params.aggregator.SimpleArgumentsAggregator; import org.junit.jupiter.params.provider.CsvSource; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.DiscoverySelectorIdentifier; class IterationSelectorTests { @ParameterizedTest @CsvSource(delimiter = '|', textBlock = """ 1 | 1 1,2 | 1 | 2 1..3 | 1 | 2 | 3 1,3 | 1 | 3 1..3,5..7 | 1 | 2 | 3 | 5 | 6 | 7 """) void collapsesRangesWhenConvertingToIdentifier(String expected, @AggregateWith(VarargsAggregator.class) int... iterationIndices) { var parent = "parent:value"; var parentSelector = selectorWithIdentifier(parent); var selector = selectIteration(parentSelector, iterationIndices); var identifier = selector.toIdentifier().orElseThrow(); assertEquals("iteration:%s[%s]".formatted(parent, expected), identifier.toString()); DiscoverySelectorIdentifierParser.Context context = mock(); when(context.parse(parent)).thenAnswer(__ -> Optional.of(parentSelector)); assertEquals(selector, new IterationSelector.IdentifierParser().parse(identifier, context).orElseThrow()); } private static DiscoverySelector selectorWithIdentifier(String identifier) { DiscoverySelector parent = mock(); when(parent.toIdentifier()) // .thenReturn(Optional.of(DiscoverySelectorIdentifier.parse(identifier))); return parent; } private static class VarargsAggregator extends SimpleArgumentsAggregator { @Override protected Object aggregateArguments(ArgumentsAccessor accessor, Class targetType, AnnotatedElementContext context, int parameterIndex) throws ArgumentsAggregationException { Preconditions.condition(targetType.isArray(), () -> "must be an array type, but was " + targetType); Class componentType = targetType.getComponentType(); IntStream indices = IntStream.range(parameterIndex, accessor.size()); if (componentType == int.class) { return indices.map(accessor::getInteger).toArray(); } return indices.mapToObj(index -> accessor.get(index, componentType)).toArray( size -> (Object[]) Array.newInstance(componentType, size)); } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/discovery/MethodSelectorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.platform.commons.JUnitException; /** * Unit tests for {@link MethodSelector}. * * @since 1.3 * @see DiscoverySelectorsTests */ class MethodSelectorTests { private static final String TEST_CASE_NAME = TestCase.class.getName(); @Test void equalsAndHashCode() { var selector1 = new MethodSelector(null, TEST_CASE_NAME, "method", "int, boolean"); var selector2 = new MethodSelector(null, TEST_CASE_NAME, "method", "int, boolean"); var selector3 = new MethodSelector(TestCase.class, "method", "int, boolean"); var selector4 = new MethodSelector(TestCase.class, "method", int.class, boolean.class); Stream.of(selector2, selector3, selector4).forEach(selector -> { assertEqualsAndHashCode(selector1, selector, new MethodSelector(null, TEST_CASE_NAME, "method", "int")); assertEqualsAndHashCode(selector1, selector, new MethodSelector(null, TEST_CASE_NAME, "method", "")); assertEqualsAndHashCode(selector1, selector, new MethodSelector(null, TEST_CASE_NAME, "X", "int, boolean")); assertEqualsAndHashCode(selector1, selector, new MethodSelector(null, TEST_CASE_NAME, "X", "")); assertEqualsAndHashCode(selector1, selector, new MethodSelector(null, "X", "method", "int, boolean")); assertEqualsAndHashCode(selector1, selector, new MethodSelector(null, "X", "method", "")); }); } @Test void preservesOriginalExceptionWhenTryingToLoadJavaClass() { var selector = new MethodSelector(null, "org.example.BogusClass", "method", "int, boolean"); assertThat(selector.getClassName()).isEqualTo("org.example.BogusClass"); assertThat(selector.getMethodName()).isEqualTo("method"); assertThat(selector.getParameterTypeNames()).isEqualTo("int, boolean"); assertPreconditionViolationFor(selector::getJavaClass)// .withMessage("Could not load class with name: org.example.BogusClass")// .withCauseInstanceOf(ClassNotFoundException.class); } @Test void preservesOriginalExceptionWhenTryingToLoadClassForParameterType() { var selector = new MethodSelector(null, TEST_CASE_NAME, "method", "int[], org.example.Bogus"); assertThat(selector.getClassName()).isEqualTo(TEST_CASE_NAME); assertThat(selector.getMethodName()).isEqualTo("method"); assertThat(selector.getParameterTypeNames()).isEqualTo("int[], org.example.Bogus"); assertThatExceptionOfType(JUnitException.class)// .isThrownBy(selector::getJavaMethod)// .withMessage("Failed to load parameter type [org.example.Bogus] for method [method] in class [%s].", TEST_CASE_NAME)// .withCauseInstanceOf(ClassNotFoundException.class); } @Test void usesClassClassLoader() { var selector = new MethodSelector(getClass(), "usesClassClassLoader", ""); assertThat(selector.getClassLoader()).isNotNull().isSameAs(getClass().getClassLoader()); } private static class TestCase { @SuppressWarnings("unused") void method(int num, boolean flag) { } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/discovery/ModuleSelectorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import java.util.logging.Logger; import org.junit.jupiter.api.Test; /** * Unit tests for {@link ModuleSelector}. * * @since 1.3 * @see DiscoverySelectorsTests */ class ModuleSelectorTests { @Test void equalsAndHashCode() { var selector1 = new ModuleSelector("foo-api"); var selector2 = new ModuleSelector("foo-api"); var selector3 = new ModuleSelector("bar-impl"); assertEqualsAndHashCode(selector1, selector2, selector3); } @Test void equalsAndHashCodeForModuleInstances() { var selector1 = new ModuleSelector(Object.class.getModule()); // java.base var selector2 = new ModuleSelector(Object.class.getModule()); // java.base var selector3 = new ModuleSelector(Logger.class.getModule()); // java.logging assertEqualsAndHashCode(selector1, selector2, selector3); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedClassSelectorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import java.util.List; import org.junit.jupiter.api.Test; /** * Unit tests for {@link NestedClassSelector}. * * @since 1.6 * @see DiscoverySelectorsTests */ class NestedClassSelectorTests { @Test void equalsAndHashCode() { var selector1 = new NestedClassSelector(null, List.of("org.example.EnclosingTestClass"), "org.example.NestedTestClass"); var selector2 = new NestedClassSelector(null, List.of("org.example.EnclosingTestClass"), "org.example.NestedTestClass"); var selector3 = new NestedClassSelector(null, List.of("org.example.X"), "org.example.Y"); assertEqualsAndHashCode(selector1, selector2, selector3); } @Test void preservesOriginalExceptionWhenTryingToLoadEnclosingClasses() { var selector = new NestedClassSelector(null, List.of("org.example.EnclosingTestClass"), "org.example.NestedTestClass"); assertPreconditionViolationFor(selector::getEnclosingClasses)// .withMessage("Could not load class with name: org.example.EnclosingTestClass")// .withCauseInstanceOf(ClassNotFoundException.class); } @Test void preservesOriginalExceptionWhenTryingToLoadNestedClass() { var selector = new NestedClassSelector(null, List.of("org.example.EnclosingTestClass"), "org.example.NestedTestClass"); assertPreconditionViolationFor(selector::getNestedClass)// .withMessage("Could not load class with name: org.example.NestedTestClass")// .withCauseInstanceOf(ClassNotFoundException.class); } @Test void usesClassClassLoader() { var selector = new NestedClassSelector(List.of(getClass()), NestedTestCase.class); assertThat(selector.getClassLoader()).isNotNull().isSameAs(getClass().getClassLoader()); } @SuppressWarnings("InnerClassMayBeStatic") class NestedTestCase { } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedMethodSelectorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import java.util.List; import org.junit.jupiter.api.Test; /** * Unit tests for {@link NestedMethodSelector}. * * @since 1.6 * @see DiscoverySelectorsTests */ class NestedMethodSelectorTests { @Test void equalsAndHashCode() { var selector1 = new NestedMethodSelector(null, List.of("EnclosingClass"), "NestedTestClass", "method", "int, boolean"); var selector2 = new NestedMethodSelector(null, List.of("EnclosingClass"), "NestedTestClass", "method", "int, boolean"); assertEqualsAndHashCode(selector1, selector2, new NestedMethodSelector(null, List.of("X"), "NestedTestClass", "method", "int, boolean")); assertEqualsAndHashCode(selector1, selector2, new NestedMethodSelector(null, List.of("X"), "NestedTestClass", "method", "")); assertEqualsAndHashCode(selector1, selector2, new NestedMethodSelector(null, List.of("EnclosingClass"), "NestedTestClass", "method", "int")); assertEqualsAndHashCode(selector1, selector2, new NestedMethodSelector(null, List.of("EnclosingClass"), "NestedTestClass", "method", "")); assertEqualsAndHashCode(selector1, selector2, new NestedMethodSelector(null, List.of("EnclosingClass"), "NestedTestClass", "X", "int, boolean")); assertEqualsAndHashCode(selector1, selector2, new NestedMethodSelector(null, List.of("EnclosingClass"), "NestedTestClass", "X", "")); assertEqualsAndHashCode(selector1, selector2, new NestedMethodSelector(null, List.of("EnclosingClass"), "X", "method", "int, boolean")); assertEqualsAndHashCode(selector1, selector2, new NestedMethodSelector(null, List.of("EnclosingClass"), "X", "method", "")); } @Test void preservesOriginalExceptionWhenTryingToLoadEnclosingClass() { var selector = new NestedMethodSelector(null, List.of("EnclosingClass"), "NestedTestClass", "method", "int, boolean"); assertPreconditionViolationFor(selector::getEnclosingClasses)// .withMessage("Could not load class with name: EnclosingClass")// .withCauseInstanceOf(ClassNotFoundException.class); } @Test void preservesOriginalExceptionWhenTryingToLoadNestedClass() { var selector = new NestedMethodSelector(null, List.of("EnclosingClass"), "NestedTestClass", "method", "int, boolean"); assertPreconditionViolationFor(selector::getNestedClass)// .withMessage("Could not load class with name: NestedTestClass")// .withCauseInstanceOf(ClassNotFoundException.class); } @Test void usesClassClassLoader() { var selector = new NestedMethodSelector(List.of(getClass()), NestedTestCase.class, "method", ""); assertThat(selector.getClassLoader()).isNotNull().isSameAs(getClass().getClassLoader()); } @SuppressWarnings("InnerClassMayBeStatic") class NestedTestCase { void method() { } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/discovery/PackageNameFilterTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationContainsNoNullElementsFor; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullOrEmptyFor; import org.junit.jupiter.api.Test; /** * @since 1.0 */ class PackageNameFilterTests { @SuppressWarnings("DataFlowIssue") @Test void includePackageChecksPreconditions() { assertPreconditionViolationNotNullOrEmptyFor("packageNames array", () -> PackageNameFilter.includePackageNames((String[]) null)); assertPreconditionViolationNotNullOrEmptyFor("packageNames array", () -> PackageNameFilter.includePackageNames(new String[0])); assertPreconditionViolationContainsNoNullElementsFor("packageNames array", () -> PackageNameFilter.includePackageNames(new String[] { null })); } @Test void includePackageWithMultiplePackages() { var includedPackage1 = "java.lang"; var includedPackage2 = "java.util"; var filter = PackageNameFilter.includePackageNames(includedPackage1, includedPackage2); assertThat(filter).hasToString( "IncludePackageNameFilter that includes packages whose names are either equal to or start with one of the following: '" + includedPackage1 + "' OR '" + includedPackage2 + "'"); assertTrue(filter.apply("java.lang.String").included()); assertTrue(filter.toPredicate().test("java.lang.String")); assertThat(filter.apply("java.lang.String").getReason()).contains( "Package name [java.lang.String] matches included name: '" + includedPackage1 + "'"); assertTrue(filter.apply("java.util.Collection").included()); assertTrue(filter.toPredicate().test("java.util.Collection")); assertThat(filter.apply("java.util.Collection").getReason()).contains( "Package name [java.util.Collection] matches included name: '" + includedPackage2 + "'"); assertTrue(filter.apply("java.util.function.Consumer").included()); assertTrue(filter.toPredicate().test("java.util.function.Consumer")); assertThat(filter.apply("java.util.function.Consumer").getReason()).contains( "Package name [java.util.function.Consumer] matches included name: '" + includedPackage2 + "'"); assertFalse(filter.apply("java.time.Instant").included()); assertFalse(filter.toPredicate().test("java.time.Instant")); assertThat(filter.apply("java.time.Instant").getReason()).contains( "Package name [java.time.Instant] does not match any included names: '" + includedPackage1 + "' OR '" + includedPackage2 + "'"); assertFalse(filter.apply("java.language.Test").included()); assertFalse(filter.toPredicate().test("java.language.Test")); assertThat(filter.apply("java.language.Test").getReason()).contains( "Package name [java.language.Test] does not match any included names: '" + includedPackage1 + "' OR '" + includedPackage2 + "'"); } @SuppressWarnings("DataFlowIssue") @Test void excludePackageChecksPreconditions() { assertPreconditionViolationNotNullOrEmptyFor("packageNames", () -> PackageNameFilter.excludePackageNames((String[]) null)); assertPreconditionViolationNotNullOrEmptyFor("packageNames", () -> PackageNameFilter.excludePackageNames(new String[0])); assertPreconditionViolationFor(() -> PackageNameFilter.excludePackageNames(new String[] { null }))// .withMessage("packageNames must not contain null elements"); } @Test void excludePackageWithMultiplePackages() { var excludedPackage1 = "java.lang"; var excludedPackage2 = "java.util"; var filter = PackageNameFilter.excludePackageNames(excludedPackage1, excludedPackage2); assertThat(filter).hasToString( "ExcludePackageNameFilter that excludes packages whose names are either equal to or start with one of the following: '" + excludedPackage1 + "' OR '" + excludedPackage2 + "'"); assertTrue(filter.apply("java.lang.String").excluded()); assertFalse(filter.toPredicate().test("java.lang.String")); assertThat(filter.apply("java.lang.String").getReason()).contains( "Package name [java.lang.String] matches excluded name: '" + excludedPackage1 + "'"); assertTrue(filter.apply("java.util.Collection").excluded()); assertFalse(filter.toPredicate().test("java.util.Collection")); assertThat(filter.apply("java.util.Collection").getReason()).contains( "Package name [java.util.Collection] matches excluded name: '" + excludedPackage2 + "'"); assertTrue(filter.apply("java.util.function.Consumer").excluded()); assertFalse(filter.toPredicate().test("java.util.function.Consumer")); assertThat(filter.apply("java.util.function.Consumer").getReason()).contains( "Package name [java.util.function.Consumer] matches excluded name: '" + excludedPackage2 + "'"); assertTrue(filter.apply("java.time.Instant").included()); assertTrue(filter.toPredicate().test("java.time.Instant")); assertThat(filter.apply("java.time.Instant").getReason()).contains( "Package name [java.time.Instant] does not match any excluded names: '" + excludedPackage1 + "' OR '" + excludedPackage2 + "'"); assertTrue(filter.apply("java.language.Test").included()); assertTrue(filter.toPredicate().test("java.language.Test")); assertThat(filter.apply("java.language.Test").getReason()).contains( "Package name [java.language.Test] does not match any excluded names: '" + excludedPackage1 + "' OR '" + excludedPackage2 + "'"); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/discovery/PackageSelectorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import org.junit.jupiter.api.Test; /** * Unit tests for {@link PackageSelector}. * * @since 1.3 * @see DiscoverySelectorsTests */ class PackageSelectorTests { @Test void equalsAndHashCode() { var selector1 = new PackageSelector("org.example.foo"); var selector2 = new PackageSelector("org.example.foo"); var selector3 = new PackageSelector("org.example.bar"); assertEqualsAndHashCode(selector1, selector2, selector3); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/discovery/UniqueIdSelectorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import org.junit.jupiter.api.Test; import org.junit.platform.engine.UniqueId; /** * Unit tests for {@link UniqueIdSelector}. * * @since 1.3 * @see DiscoverySelectorsTests */ class UniqueIdSelectorTests { @Test void equalsAndHashCode() { var testEngine = UniqueId.forEngine("test-engine"); var selector1 = new UniqueIdSelector(testEngine.append("test-class", "org.example.TestClass")); var selector2 = new UniqueIdSelector(testEngine.append("test-class", "org.example.TestClass")); var selector3 = new UniqueIdSelector(testEngine.append("test-class", "org.example.FooBar")); assertEqualsAndHashCode(selector1, selector2, selector3); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/discovery/UriSelectorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.discovery; import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import java.net.URI; import org.junit.jupiter.api.Test; /** * Unit tests for {@link UriSelector}. * * @since 1.3 * @see DiscoverySelectorsTests */ class UriSelectorTests { @Test void equalsAndHashCode() throws Exception { var selector1 = new UriSelector(new URI("https://junit.org")); var selector2 = new UriSelector(new URI("https://junit.org")); var selector3 = new UriSelector(new URI("https://example.org")); assertEqualsAndHashCode(selector1, selector2, selector3); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/config/PrefixedConfigurationParametersTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.config; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.Optional; import java.util.function.Function; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.platform.engine.ConfigurationParameters; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; /** * Unit tests for {@link PrefixedConfigurationParameters}. * * @since 1.3 */ @ExtendWith(MockitoExtension.class) class PrefixedConfigurationParametersTests { @Mock private ConfigurationParameters delegate; @SuppressWarnings("DataFlowIssue") @Test void preconditions() { assertPreconditionViolationFor(() -> new PrefixedConfigurationParameters(null, "example.")); assertPreconditionViolationFor(() -> new PrefixedConfigurationParameters(delegate, null)); assertPreconditionViolationFor(() -> new PrefixedConfigurationParameters(delegate, "")); assertPreconditionViolationFor(() -> new PrefixedConfigurationParameters(delegate, " ")); } @Test void delegatesGetCalls() { when(delegate.get(any())).thenReturn(Optional.of("result")); var parameters = new PrefixedConfigurationParameters(delegate, "foo.bar."); assertThat(parameters.get("qux")).contains("result"); verify(delegate).get("foo.bar.qux"); } @Test void delegatesGetBooleanCalls() { when(delegate.getBoolean(any())).thenReturn(Optional.of(true)); var parameters = new PrefixedConfigurationParameters(delegate, "foo.bar."); assertThat(parameters.getBoolean("qux")).contains(true); verify(delegate).getBoolean("foo.bar.qux"); } @Test void delegatesGetWithTransformerCalls() { when(delegate.get(any(), any())).thenReturn(Optional.of("QUX")); var parameters = new PrefixedConfigurationParameters(delegate, "foo.bar."); Function transformer = String::toUpperCase; assertThat(parameters.get("qux", transformer)).contains("QUX"); verify(delegate).get("foo.bar.qux", transformer); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/AbstractTestDescriptorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.descriptor; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.junit.platform.commons.JUnitException; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; /** * Unit tests for {@link AbstractTestDescriptor} and {@link EngineDescriptor}. * * @since 1.0 */ class AbstractTestDescriptorTests implements TestDescriptorOrderChildrenTests { private EngineDescriptor engineDescriptor; private GroupDescriptor group1; private GroupDescriptor group11; private LeafDescriptor leaf111; @BeforeEach void initTree() { engineDescriptor = new EngineDescriptor(UniqueId.forEngine("testEngine"), "testEngine"); group1 = new GroupDescriptor(UniqueId.root("group", "group1")); engineDescriptor.addChild(group1); var group2 = new GroupDescriptor(UniqueId.root("group", "group2")); engineDescriptor.addChild(group2); group11 = new GroupDescriptor(UniqueId.root("group", "group1-1")); group1.addChild(group11); group1.addChild(new LeafDescriptor(UniqueId.root("leaf", "leaf1-1"))); group1.addChild(new LeafDescriptor(UniqueId.root("leaf", "leaf1-2"))); group2.addChild(new LeafDescriptor(UniqueId.root("leaf", "leaf2-1"))); leaf111 = new LeafDescriptor(UniqueId.root("leaf", "leaf11-1")); group11.addChild(leaf111); } @Override public TestDescriptor createEmptyTestDescriptor() { return new GroupDescriptor(UniqueId.root("group", "1")); } @Test void removeRootFromHierarchyFails() { var e = assertThrows(JUnitException.class, () -> engineDescriptor.removeFromHierarchy()); assertTrue(e.toString().contains("cannot remove the root of a hierarchy")); } @Test void removeFromHierarchyClearsParentFromAllChildren() { var group = engineDescriptor.getChildren().iterator().next(); assertSame(engineDescriptor, group.getParent().orElseThrow(Error::new)); assertTrue(group.getChildren().stream().allMatch(d -> d.getParent().orElseThrow(Error::new) == group)); var formerChildren = group.getChildren(); group.removeFromHierarchy(); assertFalse(group.getParent().isPresent()); assertTrue(group.getChildren().isEmpty()); assertTrue(formerChildren.stream().noneMatch(d -> d.getParent().isPresent())); } @Test void setParentToOtherInstance() { TestDescriptor newEngine = new EngineDescriptor(UniqueId.forEngine("newEngine"), "newEngine"); var group = engineDescriptor.getChildren().iterator().next(); assertSame(engineDescriptor, group.getParent().orElseThrow(Error::new)); group.setParent(newEngine); assertSame(newEngine, group.getParent().orElseThrow(Error::new)); } @Test void setParentToNull() { var group = engineDescriptor.getChildren().iterator().next(); assertTrue(group.getParent().isPresent()); group.setParent(null); assertFalse(group.getParent().isPresent()); } @Test void visitAllNodes() { List visited = new ArrayList<>(); engineDescriptor.accept(visited::add); assertEquals(8, visited.size()); } @Test void pruneLeaf() { TestDescriptor.Visitor visitor = descriptor -> { if (descriptor.getUniqueId().equals(UniqueId.root("leaf", "leaf1-1"))) { descriptor.removeFromHierarchy(); } }; engineDescriptor.accept(visitor); List visited = new ArrayList<>(); engineDescriptor.accept(descriptor -> visited.add(descriptor.getUniqueId())); assertEquals(7, visited.size()); assertTrue(visited.contains(UniqueId.root("group", "group1"))); assertFalse(visited.contains(UniqueId.root("leaf", "leaf1-1"))); } @Test void pruneGroup() { final var countVisited = new AtomicInteger(); TestDescriptor.Visitor visitor = descriptor -> { if (descriptor.getUniqueId().equals(UniqueId.root("group", "group1"))) { descriptor.removeFromHierarchy(); } countVisited.incrementAndGet(); }; engineDescriptor.accept(visitor); assertEquals(4, countVisited.get(), "Children of pruned element are not visited"); List visited = new ArrayList<>(); engineDescriptor.accept(descriptor -> visited.add(descriptor.getUniqueId())); assertEquals(3, visited.size()); assertFalse(visited.contains(UniqueId.root("group", "group1"))); } @Test void getAncestors() { assertThat(getAncestorsUniqueIds(engineDescriptor)).isEmpty(); assertThat(getAncestorsUniqueIds(group1)).containsExactly( // UniqueId.forEngine("testEngine")); assertThat(getAncestorsUniqueIds(group11)).containsExactly( // UniqueId.root("group", "group1"), // UniqueId.forEngine("testEngine")); assertThat(getAncestorsUniqueIds(leaf111)).containsExactly( // UniqueId.root("group", "group1-1"), // UniqueId.root("group", "group1"), // UniqueId.forEngine("testEngine")); } private List getAncestorsUniqueIds(TestDescriptor descriptor) { return descriptor.getAncestors().stream().map(TestDescriptor::getUniqueId).toList(); } @ParameterizedTest(name = "{0} \u27A1 {1}") // NOTE: "\uFFFD" is the Unicode replacement character: � @CsvSource(delimiterString = "->", textBlock = """ 'carriage \r return' -> 'carriage return' 'line \n feed' -> 'line feed' 'form \f feed' -> 'form \uFFFD feed' 'back \b space' -> 'back \uFFFD space' 'tab \t tab' -> 'tab \uFFFD tab' # Latin-1 'üñåé' -> 'üñåé' # "hello" in Japanese 'こんにちは' -> 'こんにちは' # 'hello world' in Thai 'สวัสดีชาวโลก' -> 'สวัสดีชาวโลก' # bell sound/character 'ding \u0007 dong' -> 'ding \uFFFD dong' 'Munch 😱 emoji' -> 'Munch 😱 emoji' 'Zero\u200BWidth\u200BSpaces' -> 'Zero\u200BWidth\u200BSpaces' """) void specialCharactersInDisplayNamesAreEscaped(String input, String expected) { assertThat(new DemoDescriptor(input).getDisplayName()).isEqualTo(expected); } } class GroupDescriptor extends AbstractTestDescriptor { GroupDescriptor(UniqueId uniqueId) { super(uniqueId, "group: " + uniqueId); } @Override public Type getType() { return Type.CONTAINER; } } class LeafDescriptor extends AbstractTestDescriptor { LeafDescriptor(UniqueId uniqueId) { super(uniqueId, "leaf: " + uniqueId); } @Override public Type getType() { return Type.TEST; } } class DemoDescriptor extends AbstractTestDescriptor { DemoDescriptor(String displayName) { super(mock(), displayName); } @Override public Type getType() { return Type.CONTAINER; } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/AbstractTestSourceTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.descriptor; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.stream.Stream; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.TestFactory; import org.junit.platform.engine.TestSource; /** * Abstract base class for unit tests involving {@link TestSource TestSources} * and {@link FilePosition FilePositions}. * * @since 1.0 */ abstract class AbstractTestSourceTests { abstract Stream createSerializableInstances() throws Exception; @TestFactory Stream assertToString() throws Exception { return createSerializableInstances() // .map(instance -> dynamicTest(instance.toString(), () -> assertToString(instance))); } private void assertToString(Object instance) { assertNotNull(instance); assertTrue(instance.toString().startsWith(instance.getClass().getSimpleName())); } @TestFactory Stream assertSerializable() throws Exception { return createSerializableInstances() // .map(instance -> dynamicTest(instance.toString(), () -> assertSerializable(instance))); } private void assertSerializable(T instance) { try { Class type = instance.getClass(); var serialized = serialize(instance); var deserialized = deserialize(serialized); assertTrue(type.isAssignableFrom(deserialized.getClass())); assertEquals(instance, deserialized); } catch (Exception e) { fail("assertSerializable failed: " + instance, e); } } private byte[] serialize(Object obj) throws Exception { var b = new ByteArrayOutputStream(); var o = new ObjectOutputStream(b); o.writeObject(obj); return b.toByteArray(); } private Object deserialize(byte[] bytes) throws Exception { var b = new ByteArrayInputStream(bytes); var o = new ObjectInputStream(b); return o.readObject(); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClassSourceTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.descriptor; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import java.io.Serializable; import java.net.URI; import java.util.stream.Stream; import org.junit.jupiter.api.Test; /** * Unit tests for {@link ClassSource}. * * @since 1.0 */ class ClassSourceTests extends AbstractTestSourceTests { @Override Stream createSerializableInstances() { return Stream.of( // ClassSource.from("class.source"), // ClassSource.from("class.and.position", FilePosition.from(1, 2)), // ClassSource.from(getClass()), // ClassSource.from(getClass(), FilePosition.from(1, 2)) // ); } @SuppressWarnings("DataFlowIssue") @Test void preconditions() { assertPreconditionViolationFor(() -> ClassSource.from((String) null)); assertPreconditionViolationFor(() -> ClassSource.from(" ")); assertPreconditionViolationFor(() -> ClassSource.from((String) null, null)); assertPreconditionViolationFor(() -> ClassSource.from(" ", null)); assertPreconditionViolationFor(() -> ClassSource.from((Class) null)); assertPreconditionViolationFor(() -> ClassSource.from((Class) null, null)); assertPreconditionViolationFor(() -> ClassSource.from((URI) null)); assertPreconditionViolationFor(() -> ClassSource.from(new URI("badscheme:/com.foo.Bar"))); assertPreconditionViolationFor(() -> ClassSource.from(new URI("class:?line=1"))); } @Test void classSourceFromName() { var testClassName = "com.unknown.mypackage.ClassByName"; var source = ClassSource.from(testClassName); assertThat(source.getClassName()).isEqualTo(testClassName); assertThat(source.getPosition()).isEmpty(); assertPreconditionViolationFor(source::getJavaClass)// .withMessage("Could not load class with name: " + testClassName); } @Test void classSourceFromNameAndFilePosition() { var testClassName = "com.unknown.mypackage.ClassByName"; var position = FilePosition.from(42, 23); var source = ClassSource.from(testClassName, position); assertThat(source.getClassName()).isEqualTo(testClassName); assertThat(source.getPosition()).isNotEmpty(); assertThat(source.getPosition()).hasValue(position); } @Test void classSourceFromReference() { var testClass = getClass(); var source = ClassSource.from(testClass); assertThat(source.getJavaClass()).isEqualTo(testClass); assertThat(source.getPosition()).isEmpty(); } @Test void classSourceFromReferenceAndFilePosition() { var testClass = getClass(); var position = FilePosition.from(42, 23); var source = ClassSource.from(testClass, position); assertThat(source.getJavaClass()).isEqualTo(testClass); assertThat(source.getPosition()).isNotEmpty(); assertThat(source.getPosition()).hasValue(position); } @Test void classSourceFromUri() throws Exception { var source = ClassSource.from(new URI("class:java.lang.Object")); assertThat(source.getJavaClass()).isEqualTo(Object.class); assertThat(source.getPosition()).isEmpty(); } @Test void classSourceFromUriWithLineNumber() throws Exception { var position = FilePosition.from(42); var source = ClassSource.from(new URI("class:java.lang.Object?line=42")); assertThat(source.getJavaClass()).isEqualTo(Object.class); assertThat(source.getPosition()).hasValue(position); } @Test void classSourceFromUriWithLineAndColumnNumbers() throws Exception { var position = FilePosition.from(42, 23); var source = ClassSource.from(new URI("class:java.lang.Object?line=42&foo=bar&column=23")); assertThat(source.getJavaClass()).isEqualTo(Object.class); assertThat(source.getPosition()).hasValue(position); } @Test void classSourceFromUriWithEmptyQuery() throws Exception { var source = ClassSource.from(new URI("class:java.lang.Object?")); assertThat(source.getJavaClass()).isEqualTo(Object.class); assertThat(source.getPosition()).isEmpty(); } @Test void classSourceFromUriWithUnsupportedParametersInQuery() throws Exception { var source = ClassSource.from(new URI("class:java.lang.Object?foo=42&bar")); assertThat(source.getJavaClass()).isEqualTo(Object.class); assertThat(source.getPosition()).isEmpty(); } @Test void equalsAndHashCodeForClassSourceFromName() { var name1 = String.class.getName(); var name2 = Number.class.getName(); assertEqualsAndHashCode(ClassSource.from(name1), ClassSource.from(name1), ClassSource.from(name2)); } @Test void equalsAndHashCodeForClassSourceFromNameAndFilePosition() { var name1 = String.class.getName(); var name2 = Number.class.getName(); var position1 = FilePosition.from(42, 23); var position2 = FilePosition.from(1, 2); assertEqualsAndHashCode(ClassSource.from(name1, position1), ClassSource.from(name1, position1), ClassSource.from(name2, position1)); assertEqualsAndHashCode(ClassSource.from(name1, position1), ClassSource.from(name1, position1), ClassSource.from(name1, position2)); } @Test void equalsAndHashCodeForClassSourceFromReference() { var class1 = String.class; var class2 = Number.class; assertEqualsAndHashCode(ClassSource.from(class1), ClassSource.from(class1), ClassSource.from(class2)); } @Test void equalsAndHashCodeForClassSourceFromReferenceAndFilePosition() { var class1 = String.class; var class2 = Number.class; var position1 = FilePosition.from(42, 23); var position2 = FilePosition.from(1, 2); assertEqualsAndHashCode(ClassSource.from(class1, position1), ClassSource.from(class1, position1), ClassSource.from(class2, position1)); assertEqualsAndHashCode(ClassSource.from(class1, position1), ClassSource.from(class1, position1), ClassSource.from(class1, position2)); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSourceTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.descriptor; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.junit.platform.engine.support.descriptor.ClasspathResourceSource.CLASSPATH_SCHEME; import java.net.URI; import java.util.stream.Stream; import org.junit.jupiter.api.Test; /** * Unit tests for {@link ClasspathResourceSource}. * * @since 1.0 */ class ClasspathResourceSourceTests extends AbstractTestSourceTests { private static final String FOO_RESOURCE = "test/foo.xml"; private static final String BAR_RESOURCE = "/config/bar.json"; private static final URI FOO_RESOURCE_URI = URI.create(CLASSPATH_SCHEME + ":/" + FOO_RESOURCE); @Override Stream createSerializableInstances() { return Stream.of(ClasspathResourceSource.from(FOO_RESOURCE)); } @SuppressWarnings("DataFlowIssue") @Test void preconditions() { assertPreconditionViolationFor(() -> ClasspathResourceSource.from((String) null)); assertPreconditionViolationFor(() -> ClasspathResourceSource.from("")); assertPreconditionViolationFor(() -> ClasspathResourceSource.from(" ")); assertPreconditionViolationFor(() -> ClasspathResourceSource.from((URI) null)); assertPreconditionViolationFor(() -> ClasspathResourceSource.from(URI.create("file:/foo.txt"))); } @Test void resourceWithoutPosition() { var source = ClasspathResourceSource.from(FOO_RESOURCE); assertThat(source).isNotNull(); assertThat(source.getClasspathResourceName()).isEqualTo(FOO_RESOURCE); assertThat(source.getPosition()).isEmpty(); } @Test void resourceWithLeadingSlashWithoutPosition() { var source = ClasspathResourceSource.from("/" + FOO_RESOURCE); assertThat(source).isNotNull(); assertThat(source.getClasspathResourceName()).isEqualTo(FOO_RESOURCE); assertThat(source.getPosition()).isEmpty(); } @Test void resourceWithPosition() { var position = FilePosition.from(42, 23); var source = ClasspathResourceSource.from(FOO_RESOURCE, position); assertThat(source).isNotNull(); assertThat(source.getClasspathResourceName()).isEqualTo(FOO_RESOURCE); assertThat(source.getPosition()).hasValue(position); } @Test void resourceFromUriWithoutPosition() { var source = ClasspathResourceSource.from(FOO_RESOURCE_URI); assertThat(source).isNotNull(); assertThat(source.getClasspathResourceName()).isEqualTo(FOO_RESOURCE); assertThat(source.getPosition()).isEmpty(); } @Test void resourceFromUriWithLineNumber() { var position = FilePosition.from(42); var uri = URI.create(FOO_RESOURCE_URI + "?line=42"); var source = ClasspathResourceSource.from(uri); assertThat(source).isNotNull(); assertThat(source.getClasspathResourceName()).isEqualTo(FOO_RESOURCE); assertThat(source.getPosition()).hasValue(position); } @Test void resourceFromUriWithLineAndColumnNumbers() { var position = FilePosition.from(42, 23); var uri = URI.create(FOO_RESOURCE_URI + "?line=42&foo=bar&column=23"); var source = ClasspathResourceSource.from(uri); assertThat(source).isNotNull(); assertThat(source.getClasspathResourceName()).isEqualTo(FOO_RESOURCE); assertThat(source.getPosition()).hasValue(position); } @Test void equalsAndHashCode() { assertEqualsAndHashCode(ClasspathResourceSource.from(FOO_RESOURCE), ClasspathResourceSource.from(FOO_RESOURCE), ClasspathResourceSource.from(BAR_RESOURCE)); var position = FilePosition.from(42, 23); assertEqualsAndHashCode(ClasspathResourceSource.from(FOO_RESOURCE, position), ClasspathResourceSource.from(FOO_RESOURCE, position), ClasspathResourceSource.from(BAR_RESOURCE, position)); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/CompositeTestSourceTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.descriptor; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; import org.junit.jupiter.api.Test; /** * Unit tests for {@link CompositeTestSource}. * * @since 1.0 */ class CompositeTestSourceTests extends AbstractTestSourceTests { @Override Stream createSerializableInstances() { var fileSource = FileSource.from(new File("sample.instance")); var classSource = ClassSource.from(getClass()); var sources = List.of(fileSource, classSource); return Stream.of(CompositeTestSource.from(sources)); } @SuppressWarnings("DataFlowIssue") @Test void createCompositeTestSourceFromNullList() { assertPreconditionViolationFor(() -> CompositeTestSource.from(null)); } @Test void createCompositeTestSourceFromEmptyList() { assertPreconditionViolationFor(() -> CompositeTestSource.from(List.of())); } @Test void createCompositeTestSourceFromClassAndFileSources() { var fileSource = FileSource.from(new File("example.test")); var classSource = ClassSource.from(getClass()); var sources = new ArrayList<>(List.of(fileSource, classSource)); var compositeTestSource = CompositeTestSource.from(sources); assertThat(compositeTestSource.getSources()).hasSize(2); assertThat(compositeTestSource.getSources()).contains(fileSource, classSource); // Ensure the supplied sources list was defensively copied. sources.remove(1); assertThat(compositeTestSource.getSources()).hasSize(2); // Ensure the returned sources list is immutable. assertThrows(UnsupportedOperationException.class, () -> compositeTestSource.getSources().add(fileSource)); } @Test void equalsAndHashCode() { var sources1 = List.of(ClassSource.from(Number.class)); var sources2 = List.of(ClassSource.from(String.class)); assertEqualsAndHashCode(CompositeTestSource.from(sources1), CompositeTestSource.from(sources1), CompositeTestSource.from(sources2)); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DefaultUriSourceTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.descriptor; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import java.net.URI; import java.util.stream.Stream; import org.junit.jupiter.api.Test; /** * Unit tests for {@link DefaultUriSource}. * * @since 1.3 */ class DefaultUriSourceTests extends AbstractTestSourceTests { @Override Stream createSerializableInstances() { return Stream.of(new DefaultUriSource(URI.create("sample://instance"))); } @SuppressWarnings("DataFlowIssue") @Test void nullSourceUriYieldsException() { assertPreconditionViolationFor(() -> new DefaultUriSource(null)); } @Test void getterReturnsSameUriInstanceAsSuppliedToTheConstructor() throws Exception { var expected = new URI("foo.txt"); var actual = new DefaultUriSource(expected).getUri(); assertSame(expected, actual); } @Test void equalsAndHashCode() throws Exception { var uri1 = new URI("foo.txt"); var uri2 = new URI("bar.txt"); assertEqualsAndHashCode(new DefaultUriSource(uri1), new DefaultUriSource(uri1), new DefaultUriSource(uri2)); } @Test void testToString() { var actual = new DefaultUriSource(URI.create("foo.txt")).toString(); assertEquals("DefaultUriSource [uri = foo.txt]", actual); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DemoClassTestDescriptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.descriptor; import static java.util.stream.Collectors.toCollection; import static org.junit.platform.commons.support.AnnotationSupport.findRepeatableAnnotations; import java.util.LinkedHashSet; import java.util.Set; import org.junit.jupiter.api.Tag; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.TestTag; import org.junit.platform.engine.UniqueId; /** * @since 1.0 */ public class DemoClassTestDescriptor extends AbstractTestDescriptor { private final Class testClass; public DemoClassTestDescriptor(UniqueId uniqueId, Class testClass) { super(uniqueId, Preconditions.notNull(testClass, "Class must not be null").getSimpleName(), ClassSource.from(testClass)); this.testClass = testClass; } @Override public Set getTags() { return findRepeatableAnnotations(this.testClass, Tag.class).stream() // .map(Tag::value) // .filter(TestTag::isValid) // .map(TestTag::create) // .collect(toCollection(LinkedHashSet::new)); } @Override public Type getType() { return Type.CONTAINER; } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DemoMethodTestDescriptor.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.descriptor; import static java.util.stream.Collectors.toCollection; import static org.junit.platform.commons.support.AnnotationSupport.findRepeatableAnnotations; import java.lang.reflect.Method; import java.util.LinkedHashSet; import java.util.Set; import org.junit.jupiter.api.Tag; import org.junit.platform.commons.util.ClassUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.TestTag; import org.junit.platform.engine.UniqueId; /** * @since 1.0 */ public class DemoMethodTestDescriptor extends AbstractTestDescriptor { private final Method testMethod; public DemoMethodTestDescriptor(UniqueId uniqueId, Method testMethod) { super(uniqueId, "%s(%s)".formatted(Preconditions.notNull(testMethod, "Method must not be null").getName(), ClassUtils.nullSafeToString(Class::getSimpleName, testMethod.getParameterTypes())), MethodSource.from(testMethod)); this.testMethod = testMethod; } @Override public Set getTags() { Set methodTags = findRepeatableAnnotations(this.testMethod, Tag.class).stream() // .map(Tag::value) // .filter(TestTag::isValid) // .map(TestTag::create) // .collect(toCollection(LinkedHashSet::new)); getParent().ifPresent(parentDescriptor -> methodTags.addAll(parentDescriptor.getTags())); return methodTags; } @Override public Type getType() { return Type.TEST; } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FilePositionTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.descriptor; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import java.util.stream.Stream; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; /** * Unit tests for {@link FilePosition}. * * @since 1.0 */ @DisplayName("FilePosition unit tests") class FilePositionTests extends AbstractTestSourceTests { @Override Stream createSerializableInstances() { return Stream.of(FilePosition.from(42, 99)); } @Test @DisplayName("factory method preconditions") void preconditions() { assertPreconditionViolationFor(() -> FilePosition.from(-1)); assertPreconditionViolationFor(() -> FilePosition.from(0, -1)); } @Test @DisplayName("create FilePosition from factory method with line number") void filePositionFromLine() { var filePosition = FilePosition.from(42); assertThat(filePosition.getLine()).isEqualTo(42); assertThat(filePosition.getColumn()).isEmpty(); } @Test @DisplayName("create FilePosition from factory method with line number and column number") void filePositionFromLineAndColumn() { var filePosition = FilePosition.from(42, 99); assertThat(filePosition.getLine()).isEqualTo(42); assertThat(filePosition.getColumn()).contains(99); } /** * @since 1.3 */ @ParameterizedTest @MethodSource void filePositionFromQuery(String query, int expectedLine, int expectedColumn) { var optionalFilePosition = FilePosition.fromQuery(query); if (optionalFilePosition.isPresent()) { var filePosition = optionalFilePosition.get(); assertThat(filePosition.getLine()).isEqualTo(expectedLine); assertThat(filePosition.getColumn().orElse(-1)).isEqualTo(expectedColumn); } else { assertEquals(-1, expectedColumn); assertEquals(-1, expectedLine); } } @SuppressWarnings("unused") static Stream filePositionFromQuery() { return Stream.of( // arguments(null, -1, -1), // arguments("?!", -1, -1), // arguments("line=ZZ", -1, -1), // arguments("line=42", 42, -1), // arguments("line=42&column=99", 42, 99), // arguments("line=42&column=ZZ", 42, -1), // arguments("line=42&abc=xyz&column=99", 42, 99), // arguments("1=3&foo=X&line=42&abc=xyz&column=99&enigma=393939", 42, 99), // // First one wins: arguments("line=42&line=555", 42, -1), // arguments("line=42&line=555&column=99&column=555", 42, 99) // ); } @Test @DisplayName("equals() and hashCode() with column number cached by Integer.valueOf()") void equalsAndHashCode() { var same = FilePosition.from(42, 99); var sameSame = FilePosition.from(42, 99); var different = FilePosition.from(1, 2); assertEqualsAndHashCode(same, sameSame, different); } @Test @DisplayName("equals() and hashCode() with column number not cached by Integer.valueOf()") void equalsAndHashCodeWithColumnNumberNotCachedByJavaLangIntegerDotValueOf() { var same = FilePosition.from(42, 99999); var sameSame = FilePosition.from(42, 99999); var different = FilePosition.from(1, 2); assertEqualsAndHashCode(same, sameSame, different); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FileSystemSourceTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.descriptor; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import java.io.File; import java.util.stream.Stream; import org.junit.jupiter.api.Test; /** * Unit tests for {@link FileSource} and {@link DirectorySource}. * * @since 1.0 */ class FileSystemSourceTests extends AbstractTestSourceTests { @Override Stream createSerializableInstances() { return Stream.of( // FileSource.from(new File("file.source")), // FileSource.from(new File("file.and.position"), FilePosition.from(42, 23))); } @SuppressWarnings("DataFlowIssue") @Test void nullSourceFileOrDirectoryYieldsException() { assertPreconditionViolationFor(() -> FileSource.from(null)); } @Test void directory() throws Exception { var canonicalDir = new File(".").getCanonicalFile(); var relativeDir = new File("..", canonicalDir.getName()); var source = DirectorySource.from(relativeDir); assertThat(source.getUri()).isEqualTo(canonicalDir.toURI()); assertThat(source.getFile()).isEqualTo(canonicalDir); } @Test void fileWithoutPosition() throws Exception { var canonicalDir = new File(".").getCanonicalFile(); var relativeDir = new File("..", canonicalDir.getName()); var relativeFile = new File(relativeDir, "test.txt"); var canonicalFile = relativeFile.getCanonicalFile(); var source = FileSource.from(relativeFile); assertThat(source.getUri()).isEqualTo(canonicalFile.toURI()); assertThat(source.getFile()).isEqualTo(canonicalFile); assertThat(source.getPosition()).isEmpty(); } @Test void fileWithPosition() { var file = new File("test.txt"); var position = FilePosition.from(42, 23); var source = FileSource.from(file, position); assertThat(source.getUri()).isEqualTo(file.getAbsoluteFile().toURI()); assertThat(source.getFile()).isEqualTo(file.getAbsoluteFile()); assertThat(source.getPosition()).hasValue(position); } @Test void fileReuseWithPosition() { var file = new File("test.txt"); var position = FilePosition.from(42, 23); var source = FileSource.from(file); var sourceWithPosition = source.withPosition(position); assertThat(source.getUri()).isEqualTo(file.getAbsoluteFile().toURI()); assertThat(source.getFile()).isEqualTo(file.getAbsoluteFile()); assertThat(source.getPosition()).isEmpty(); assertThat(source).isNotSameAs(sourceWithPosition); assertThat(source.getFile()).isSameAs(sourceWithPosition.getFile()); assertThat(sourceWithPosition.getPosition()).hasValue(position); assertThat(sourceWithPosition.withPosition(null).getPosition()).isEmpty(); assertThat(source.withPosition(null)).isSameAs(source); assertThat(sourceWithPosition.withPosition(position)).isSameAs(sourceWithPosition); } @Test void equalsAndHashCodeForFileSource() { var file1 = new File("foo.txt"); var file2 = new File("bar.txt"); assertEqualsAndHashCode(FileSource.from(file1), FileSource.from(file1), FileSource.from(file2)); var position = FilePosition.from(42, 23); assertEqualsAndHashCode(FileSource.from(file1, position), FileSource.from(file1, position), FileSource.from(file2, position)); } @Test void equalsAndHashCodeForDirectorySource() { var dir1 = new File("."); var dir2 = new File(".."); assertEqualsAndHashCode(DirectorySource.from(dir1), DirectorySource.from(dir1), DirectorySource.from(dir2)); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/MethodSourceTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.descriptor; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import java.io.Serializable; import java.lang.reflect.Method; import java.util.stream.Stream; import org.junit.jupiter.api.Test; /** * Unit tests for {@link MethodSource}. * * @since 1.0 */ class MethodSourceTests extends AbstractTestSourceTests { @Override Stream createSerializableInstances() throws Exception { return Stream.of( // MethodSource.from(getMethod("method1")), // MethodSource.from(getMethod("method2")) // ); } @Test void methodSource() throws Exception { var testMethod = getMethod("method1"); var source = MethodSource.from(testMethod); assertThat(source.getClassName()).isEqualTo(getClass().getName()); assertThat(source.getMethodName()).isEqualTo(testMethod.getName()); assertThat(source.getMethodParameterTypes()).isEqualTo(String.class.getName()); assertThat(source.getJavaClass()).isEqualTo(getClass()); assertThat(source.getJavaMethod()).isEqualTo(testMethod); } @Test void equalsAndHashCodeForMethodSource() throws Exception { var method1 = getMethod("method1"); var method2 = getMethod("method2"); assertEqualsAndHashCode(MethodSource.from(method1), MethodSource.from(method1), MethodSource.from(method2)); } @SuppressWarnings("DataFlowIssue") @Test void instantiatingWithNullNamesShouldThrowPreconditionViolationException() { assertPreconditionViolationFor(() -> MethodSource.from("foo", null)); assertPreconditionViolationFor(() -> MethodSource.from(null, "foo")); } @Test void instantiatingWithEmptyNamesShouldThrowPreconditionViolationException() { assertPreconditionViolationFor(() -> MethodSource.from("foo", "")); assertPreconditionViolationFor(() -> MethodSource.from("", "foo")); } @Test void instantiatingWithBlankNamesShouldThrowPreconditionViolationException() { assertPreconditionViolationFor(() -> MethodSource.from("foo", " ")); assertPreconditionViolationFor(() -> MethodSource.from(" ", "foo")); } @SuppressWarnings("DataFlowIssue") @Test void instantiationWithNullMethodShouldThrowPreconditionViolationException() { assertPreconditionViolationFor(() -> MethodSource.from(null)); } @SuppressWarnings("DataFlowIssue") @Test void instantiationWithNullClassOrMethodShouldThrowPreconditionViolationException() { assertPreconditionViolationFor(() -> MethodSource.from(null, String.class.getDeclaredMethod("getBytes"))); assertPreconditionViolationFor(() -> MethodSource.from(String.class, null)); } @Test void instantiationWithClassAndMethodShouldResultInACorrectObject() throws Exception { var source = MethodSource.from(String.class, String.class.getDeclaredMethod("lastIndexOf", String.class, int.class)); assertEquals(String.class.getName(), source.getClassName()); assertEquals("lastIndexOf", source.getMethodName()); assertEquals("java.lang.String, int", source.getMethodParameterTypes()); } @Test void instantiationWithClassAndMethodAsStringAndParamsAsClassVarargsShouldResultInACorrectObject() { var source = MethodSource.from(String.class.getName(), "lastIndexOf", String.class, int.class); assertEquals(String.class.getName(), source.getClassName()); assertEquals("lastIndexOf", source.getMethodName()); assertEquals("java.lang.String, int", source.getMethodParameterTypes()); } @Test void twoEqualMethodsShouldHaveEqualMethodSourceObjects() { assertEquals(MethodSource.from("TestClass1", "testMethod1"), MethodSource.from("TestClass1", "testMethod1")); } @Test void twoUnequalMethodsShouldHaveUnequalMethodSourceObjects() { assertNotEquals(MethodSource.from("TestClass1", "testMethod1"), MethodSource.from("TestClass2", "testMethod1")); } @Test void twoUnequalMethodsInTheSameClassShouldHaveUnequalMethodSourceObjects() { assertNotEquals(MethodSource.from("TestClass1", "testMethod1"), MethodSource.from("TestClass1", "testMethod2")); } @Test void twoEqualMethodSourceObjectsShouldHaveEqualHashCodes() { assertEquals(MethodSource.from("TestClass1", "testMethod1").hashCode(), MethodSource.from("TestClass1", "testMethod1").hashCode()); } @Test void twoEqualMethodsWithEqualParametersShouldHaveEqualMethodSourceObjects() { assertEquals(MethodSource.from("TestClass1", "testMethod1", "int, String"), MethodSource.from("TestClass1", "testMethod1", "int, String")); } @Test void twoUnequalMethodsWithEqualParametersShouldHaveUnequalMethodSourceObjects() { assertNotEquals(MethodSource.from("TestClass1", "testMethod1", "int, String"), MethodSource.from("TestClass1", "testMethod2", "int, String")); } @Test void twoEqualMethodsWithUnequalParametersShouldHaveUnequalMethodSourceObjects() { assertNotEquals(MethodSource.from("TestClass1", "testMethod1", "int, String"), MethodSource.from("TestClass1", "testMethod1", "float, int, String")); } @Test void twoEqualMethodsWithEqualParametersShouldHaveEqualMethodSourceHashCodes() { assertEquals(MethodSource.from("TestClass1", "testMethod1", "int, String").hashCode(), MethodSource.from("TestClass1", "testMethod1", "int, String").hashCode()); } @Test void twoEqualMethodsWithUnequalParametersShouldHaveUnequalMethodSourceHashCodes() { assertNotEquals(MethodSource.from("TestClass1", "testMethod1", "int, String").hashCode(), MethodSource.from("TestClass1", "testMethod1", "float, int, String").hashCode()); } @Test void aReflectedMethodsClassNameShouldBeConsistent() throws Exception { var m = String.class.getDeclaredMethod("valueOf", int.class); assertEquals("java.lang.String", MethodSource.from(m).getClassName()); } @Test void aReflectedMethodsMethodNameShouldBeConsistent() throws Exception { var m = String.class.getDeclaredMethod("valueOf", int.class); assertEquals("valueOf", MethodSource.from(m).getMethodName()); } @Test void aReflectedMethodsParameterTypesShouldBeConsistent() throws Exception { var m = String.class.getDeclaredMethod("valueOf", float.class); assertEquals("float", MethodSource.from(m).getMethodParameterTypes()); } @Test void twoEqualReflectedMethodsShouldHaveEqualMethodSourceObjects() throws Exception { var m1 = String.class.getDeclaredMethod("valueOf", int.class); var m2 = String.class.getDeclaredMethod("valueOf", int.class); assertEquals(MethodSource.from(m1), MethodSource.from(m2)); } @Test void twoEqualReflectedMethodsShouldHaveEqualMethodSourceHashCodes() throws Exception { var m1 = String.class.getDeclaredMethod("valueOf", int.class); var m2 = String.class.getDeclaredMethod("valueOf", int.class); assertEquals(MethodSource.from(m1).hashCode(), MethodSource.from(m2).hashCode()); } @Test void twoUnequalReflectedMethodsShouldNotHaveEqualMethodSourceObjects() throws Exception { var m1 = String.class.getDeclaredMethod("valueOf", int.class); var m2 = Byte.class.getDeclaredMethod("byteValue"); assertNotEquals(MethodSource.from(m1), MethodSource.from(m2)); } @Test void twoUnequalReflectedMethodsShouldNotHaveEqualMethodSourceHashCodes() throws Exception { var m1 = String.class.getDeclaredMethod("valueOf", int.class); var m2 = Byte.class.getDeclaredMethod("byteValue"); assertNotEquals(MethodSource.from(m1).hashCode(), MethodSource.from(m2).hashCode()); } @Test void getJavaClassFromString() { var source = MethodSource.from(getClass().getName(), "method1"); assertThat(source.getJavaClass()).isEqualTo(getClass()); } @Test void getJavaClassShouldThrowExceptionIfClassNotFound() { var source = MethodSource.from(getClass().getName() + "X", "method1"); assertPreconditionViolationFor(source::getJavaClass); } @Test void getJavaMethodShouldReturnGivenMethodIfOverloadExists() throws Exception { var testMethod = getMethod("method3"); var source = MethodSource.from(testMethod); assertThat(source.getJavaMethod()).isEqualTo(testMethod); } @Test void getJavaMethodFromStringShouldFindVoidMethod() throws Exception { var testMethod = getClass().getDeclaredMethod("methodVoid"); var source = MethodSource.from(getClass().getName(), testMethod.getName()); assertThat(source.getJavaMethod()).isEqualTo(testMethod); } @Test void getJavaMethodFromStringShouldFindMethodWithParameter() throws Exception { var testMethod = getClass().getDeclaredMethod("method3", int.class); var source = MethodSource.from(getClass().getName(), testMethod.getName(), testMethod.getParameterTypes()); assertThat(source.getJavaMethod()).isEqualTo(testMethod); } @Test void getJavaMethodFromStringShouldThrowExceptionIfParameterTypesAreNotSupplied() { var source = MethodSource.from(getClass().getName(), "method3"); assertPreconditionViolationFor(source::getJavaMethod); } @Test void getJavaMethodFromStringShouldThrowExceptionIfParameterTypesDoNotMatch() { var source = MethodSource.from(getClass().getName(), "method3", double.class); assertPreconditionViolationFor(source::getJavaMethod); } @Test void getJavaMethodFromStringShouldThrowExceptionIfMethodDoesNotExist() { var source = MethodSource.from(getClass().getName(), "methodX"); assertPreconditionViolationFor(source::getJavaMethod); } private Method getMethod(String name) throws Exception { return getClass().getDeclaredMethod(name, String.class); } @SuppressWarnings("unused") void method1(String text) { } @SuppressWarnings("unused") void method2(String text) { } @SuppressWarnings("unused") void method3(String text) { } @SuppressWarnings("unused") void method3(int number) { } @SuppressWarnings("unused") void methodVoid() { } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/PackageSourceTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.descriptor; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import java.io.Serializable; import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; /** * Unit tests for {@link PackageSource}. * * @since 1.0 */ class PackageSourceTests extends AbstractTestSourceTests { @Override Stream createSerializableInstances() { return Stream.of(PackageSource.from("package.source")); } @SuppressWarnings("DataFlowIssue") @Test void packageSourceFromNullPackageName() { assertPreconditionViolationFor(() -> PackageSource.from((String) null)); } @Test void packageSourceFromEmptyPackageName() { assertPreconditionViolationFor(() -> PackageSource.from(" ")); } @SuppressWarnings("DataFlowIssue") @Test void packageSourceFromNullPackageReference() { assertPreconditionViolationFor(() -> PackageSource.from((Package) null)); } @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); } @Test void packageSourceFromPackageReference() { var testPackage = getClass().getPackage(); var source = PackageSource.from(testPackage); assertThat(source.getPackageName()).isEqualTo(testPackage.getName()); } @Test void equalsAndHashCodeForPackageSource() { var pkg1 = getClass().getPackage(); var pkg2 = String.class.getPackage(); assertEqualsAndHashCode(PackageSource.from(pkg1), PackageSource.from(pkg1), PackageSource.from(pkg2)); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/TestDescriptorOrderChildrenTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.descriptor; import static java.util.Collections.emptyList; import static java.util.Comparator.comparing; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import java.util.function.UnaryOperator; import org.junit.jupiter.api.Test; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; public interface TestDescriptorOrderChildrenTests { /** * @return a test descriptor without any children. */ TestDescriptor createEmptyTestDescriptor(); default TestDescriptor createTestDescriptorWithChildren() { var testDescriptor = createEmptyTestDescriptor(); testDescriptor.addChild(new StubTestDescriptor(UniqueId.root("child", "0"))); testDescriptor.addChild(new StubTestDescriptor(UniqueId.root("child", "1"))); testDescriptor.addChild(new StubTestDescriptor(UniqueId.root("child", "2"))); return testDescriptor; } @Test default void orderChildrenInReverseOrder() { var testDescriptor = createTestDescriptorWithChildren(); var childrenInOriginalOrder = new ArrayList<>(testDescriptor.getChildren()); testDescriptor.orderChildren(children -> { children.sort(comparing((TestDescriptor o) -> childrenInOriginalOrder.indexOf(o)).reversed()); return children; }); List children = new ArrayList<>(testDescriptor.getChildren()); assertThat(children).isEqualTo(childrenInOriginalOrder.reversed()); } @Test default void orderChildrenEmptyList() { var testDescriptor = createTestDescriptorWithChildren(); assertPreconditionViolationFor(() -> testDescriptor.orderChildren(children -> emptyList()))// .withMessage("orderer may not add or remove test descriptors"); } @Test default void orderChildrenInSameOrder() { var testDescriptor = createTestDescriptorWithChildren(); var childrenInOriginalOrder = new ArrayList<>(testDescriptor.getChildren()); testDescriptor.orderChildren(children -> { children.sort(comparing(childrenInOriginalOrder::indexOf)); return children; }); List children = new ArrayList<>(testDescriptor.getChildren()); assertThat(children).isEqualTo(childrenInOriginalOrder); } @Test default void orderChildrenRemovesDescriptor() { var testDescriptor = createTestDescriptorWithChildren(); UnaryOperator> orderer = children -> { children.remove(1); return children; }; assertPreconditionViolationFor(() -> testDescriptor.orderChildren(orderer))// .withMessage("orderer may not add or remove test descriptors"); } @Test default void orderChildrenAddsDescriptor() { var testDescriptor = createTestDescriptorWithChildren(); UnaryOperator> orderer = children -> { children.add(1, new StubTestDescriptor(UniqueId.root("extra", "extra1"))); return children; }; assertPreconditionViolationFor(() -> testDescriptor.orderChildren(orderer))// .withMessage("orderer may not add or remove test descriptors"); } @Test default void orderChildrenReplacesDescriptor() { var testDescriptor = createTestDescriptorWithChildren(); UnaryOperator> orderer = children -> { children.set(1, new StubTestDescriptor(UniqueId.root("replaced", "replaced1"))); return children; }; assertPreconditionViolationFor(() -> testDescriptor.orderChildren(orderer))// .withMessage("orderer may not add or remove test descriptors"); } @Test default void orderChildrenDuplicatesDescriptor() { var testDescriptor = createTestDescriptorWithChildren(); UnaryOperator> orderer = children -> { children.add(1, children.getLast()); return children; }; assertPreconditionViolationFor(() -> testDescriptor.orderChildren(orderer))// .withMessage("orderer may not add or remove test descriptors"); } @SuppressWarnings("DataFlowIssue") @Test default void orderChildrenOrdererReturnsNull() { var testDescriptor = createTestDescriptorWithChildren(); assertPreconditionViolationFor(() -> testDescriptor.orderChildren(children -> null))// .withMessage("orderer may not return null"); } @Test default void orderChildrenProvidedChildrenAreModifiable() { var testDescriptor = createTestDescriptorWithChildren(); AtomicReference> childrenRef = new AtomicReference<>(); testDescriptor.orderChildren(children -> { childrenRef.set(children); return children; }); assertThat(childrenRef.get()).isInstanceOf(ArrayList.class); } } class StubTestDescriptor extends AbstractTestDescriptor { StubTestDescriptor(UniqueId uniqueId) { super(uniqueId, "stub: " + uniqueId); } @Override public Type getType() { return Type.TEST; } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/TestDescriptorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.descriptor; import java.util.Collections; import java.util.LinkedHashSet; import java.util.Optional; import java.util.Set; import org.jspecify.annotations.Nullable; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestSource; import org.junit.platform.engine.TestTag; import org.junit.platform.engine.UniqueId; class TestDescriptorTests implements TestDescriptorOrderChildrenTests { @Override public TestDescriptor createEmptyTestDescriptor() { return new MinimalTestDescriptorImplementation(); } private static class MinimalTestDescriptorImplementation implements TestDescriptor { private final Set children = Collections.synchronizedSet(new LinkedHashSet<>()); @Override public UniqueId getUniqueId() { return UniqueId.root("root", "value"); } @Override public String getDisplayName() { return "TestDescriptorImplementation"; } @Override public Set getTags() { return Set.of(); } @Override public Optional getSource() { return Optional.empty(); } @Override public Optional getParent() { return Optional.empty(); } @Override public void setParent(@Nullable TestDescriptor parent) { throw new UnsupportedOperationException("Not implemented"); } @Override public Set getChildren() { return Collections.unmodifiableSet(children); } @Override public void addChild(TestDescriptor descriptor) { children.add(descriptor); } @Override public void removeChild(TestDescriptor descriptor) { children.remove(descriptor); } @Override public void removeFromHierarchy() { throw new UnsupportedOperationException("Not implemented"); } @Override public Type getType() { return Type.CONTAINER; } @Override public Optional findByUniqueId(UniqueId uniqueId) { throw new UnsupportedOperationException("Not implemented"); } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolverTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.discovery; import static org.junit.platform.engine.DiscoveryIssue.Severity.INFO; import static org.junit.platform.engine.DiscoveryIssue.Severity.WARNING; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.unresolved; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import org.jspecify.annotations.NullMarked; import org.junit.jupiter.api.Test; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.discovery.ClassSelector; import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.platform.engine.support.descriptor.EngineDescriptor; import org.junit.platform.launcher.LauncherDiscoveryListener; @NullMarked class EngineDiscoveryRequestResolverTests { @Test void allowsSelectorResolversToReportDiscoveryIssues() { var resolver = EngineDiscoveryRequestResolver.builder() // .addSelectorResolver(ctx -> new SelectorResolver() { @Override public Resolution resolve(ClassSelector selector, Context context) { ctx.getIssueReporter() // .reportIssue(DiscoveryIssue.builder(INFO, "test") // .source(ClassSource.from(selector.getClassName()))); return unresolved(); } }) // .build(); var engineId = UniqueId.forEngine("engine"); var engineDescriptor = new EngineDescriptor(engineId, "Engine"); var listener = mock(LauncherDiscoveryListener.class); var request = request() // .selectors(selectClass(EngineDiscoveryRequestResolverTests.class)) // .listeners(listener) // .build(); resolver.resolve(request, engineDescriptor); var issue = DiscoveryIssue.builder(INFO, "test") // .source(ClassSource.from(EngineDiscoveryRequestResolverTests.class)) // .build(); verify(listener).issueEncountered(engineId, issue); } @Test void allowsVisitorsToReportDiscoveryIssues() { var resolver = EngineDiscoveryRequestResolver.builder() // .addTestDescriptorVisitor(ctx -> // descriptor -> ctx.getIssueReporter() // .reportIssue(DiscoveryIssue.create(WARNING, descriptor.getDisplayName()))) // .build(); var engineId = UniqueId.forEngine("engine"); var engineDescriptor = new EngineDescriptor(engineId, "Engine"); var listener = mock(LauncherDiscoveryListener.class); var request = request() // .selectors(selectClass(EngineDiscoveryRequestResolverTests.class)) // .listeners(listener) // .build(); resolver.resolve(request, engineDescriptor); verify(listener).issueEncountered(engineId, DiscoveryIssue.create(WARNING, "Engine")); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/discovery/ResourceContainerSelectorResolverTest.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.discovery; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathRoots; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; import static org.junit.platform.engine.discovery.PackageNameFilter.includePackageNames; import static org.junit.platform.engine.support.discovery.SelectorResolver.Match.exact; import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.match; import java.net.URI; import java.nio.file.Path; import java.util.Optional; import java.util.Set; import org.junit.jupiter.api.Test; import org.junit.platform.commons.io.ResourceFilter; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.discovery.ClasspathResourceSelector; import org.junit.platform.engine.support.descriptor.EngineDescriptor; import org.junit.platform.fakes.TestDescriptorStub; import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; class ResourceContainerSelectorResolverTest { final TestDescriptor engineDescriptor = new EngineDescriptor(UniqueId.forEngine("resource-engine"), "Resource Engine"); final ResourceFilter resourceFilter = ResourceFilter.of(resource -> resource.getName().endsWith(".resource")); final EngineDiscoveryRequestResolver resolver = EngineDiscoveryRequestResolver.builder() // .addResourceContainerSelectorResolver(resourceFilter) // .addSelectorResolver(new ResourceSelectorResolver()) // .build(); @Test void shouldDiscoverAllResourcesInPackage() { var request = LauncherDiscoveryRequestBuilder.request() // .selectors(selectPackage("org.junit.platform.commons")) // .build(); resolver.resolve(request, engineDescriptor); // @formatter:off assertThat(engineDescriptor.getChildren()) .extracting(TestDescriptor::getDisplayName) .containsExactlyInAnyOrder( "org/junit/platform/commons/example.resource", "org/junit/platform/commons/other-example.resource"); // @formatter:on } @Test void shouldDiscoverAllResourcesInRootPackage() { var request = LauncherDiscoveryRequestBuilder.request() // .selectors(selectPackage("")) // .build(); resolver.resolve(request, engineDescriptor); // @formatter:off assertThat(engineDescriptor.getChildren()) .extracting(TestDescriptor::getDisplayName) .containsExactlyInAnyOrder( "default-package.resource", "org/junit/platform/commons/example.resource", "org/junit/platform/commons/other-example.resource"); // @formatter:on } @Test void shouldFilterPackages() { var request = LauncherDiscoveryRequestBuilder.request() // .selectors(selectPackage("")) // .filters(includePackageNames("org.junit.platform")) // .build(); resolver.resolve(request, engineDescriptor); // @formatter:off assertThat(engineDescriptor.getChildren()) .extracting(TestDescriptor::getDisplayName) .containsExactlyInAnyOrder( "org/junit/platform/commons/example.resource", "org/junit/platform/commons/other-example.resource"); // @formatter:on } @Test void shouldDiscoverAllResourcesInClasspathRoot() { var request = LauncherDiscoveryRequestBuilder.request() // .selectors(selectClasspathRoots(getTestClasspathResourceRoot())) // .build(); resolver.resolve(request, engineDescriptor); // @formatter:off assertThat(engineDescriptor.getChildren()) .extracting(TestDescriptor::getDisplayName) .containsExactlyInAnyOrder( "default-package.resource", "org/junit/platform/commons/example.resource", "org/junit/platform/commons/other-example.resource"); // @formatter:on } private Set getTestClasspathResourceRoot() { // Gradle puts classes and resources in different roots. var defaultPackageResource = "/default-package.resource"; var resourceUri = getClass().getResource(defaultPackageResource).toString(); var uri = URI.create(resourceUri.substring(0, resourceUri.length() - defaultPackageResource.length())); return Set.of(Path.of(uri)); } private static class ResourceSelectorResolver implements SelectorResolver { @Override public Resolution resolve(ClasspathResourceSelector selector, Context context) { return context.addToParent(parent -> createTestDescriptor(parent, selector.getClasspathResourceName())) // .map(testDescriptor -> match(exact(testDescriptor))) // .orElseGet(Resolution::unresolved); } private static Optional createTestDescriptor(TestDescriptor parent, String classpathResourceName) { var uniqueId = parent.getUniqueId().append("resource", classpathResourceName); var descriptor = new TestDescriptorStub(uniqueId, classpathResourceName); return Optional.of(descriptor); } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/CompositeLockTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.locks.Lock; import java.util.stream.IntStream; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; import org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode; /** * @since 1.3 */ class CompositeLockTests { @Test @SuppressWarnings({ "resource", "ResultOfMethodCallIgnored" }) void triesToAcquireAllLocksInOrder() { var lock1 = mock(Lock.class, "lock1"); var lock2 = mock(Lock.class, "lock2"); when(lock1.tryLock()).thenReturn(true); when(lock2.tryLock()).thenReturn(true); new CompositeLock(anyResources(2), List.of(lock1, lock2)).tryAcquire(); var inOrder = inOrder(lock1, lock2); inOrder.verify(lock1).tryLock(); inOrder.verify(lock2).tryLock(); } @Test @SuppressWarnings("resource") void acquiresAllLocksInOrder() throws Exception { var lock1 = mock(Lock.class, "lock1"); var lock2 = mock(Lock.class, "lock2"); new CompositeLock(anyResources(2), List.of(lock1, lock2)).acquire(); var inOrder = inOrder(lock1, lock2); inOrder.verify(lock1).lockInterruptibly(); inOrder.verify(lock2).lockInterruptibly(); } @Test @SuppressWarnings("resource") void releasesAllLocksInReverseOrder() throws Exception { var lock1 = mock(Lock.class, "lock1"); var lock2 = mock(Lock.class, "lock2"); new CompositeLock(anyResources(2), List.of(lock1, lock2)).acquire().close(); var inOrder = inOrder(lock1, lock2); inOrder.verify(lock2).unlock(); inOrder.verify(lock1).unlock(); } @Test @SuppressWarnings("resource") void releasesLocksInReverseOrderWhenInterruptedDuringAcquire() throws Exception { var firstTwoLocksWereLocked = new CountDownLatch(2); var firstLock = mockLock("firstLock", firstTwoLocksWereLocked::countDown); var secondLock = mockLock("secondLock", firstTwoLocksWereLocked::countDown); var unavailableLock = mockLock("unavailableLock", new CountDownLatch(1)::await); var thread = new Thread(() -> { try { new CompositeLock(anyResources(3), List.of(firstLock, secondLock, unavailableLock)).acquire(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); thread.start(); firstTwoLocksWereLocked.await(); thread.interrupt(); thread.join(); var inOrder = inOrder(firstLock, secondLock); inOrder.verify(secondLock).unlock(); inOrder.verify(firstLock).unlock(); verify(unavailableLock, never()).unlock(); } @Test @SuppressWarnings("resource") void releasesLocksInReverseOrderOnUnsuccessfulAttempt() { var firstLock = mock(Lock.class, "firstLock"); var secondLock = mock(Lock.class, "secondLock"); var unavailableLock = mock(Lock.class, "unavailableLock"); when(firstLock.tryLock()).thenReturn(true); when(secondLock.tryLock()).thenReturn(true); when(unavailableLock.tryLock()).thenReturn(false); new CompositeLock(anyResources(3), List.of(firstLock, secondLock, unavailableLock)).tryAcquire(); var inOrder = inOrder(firstLock, secondLock); inOrder.verify(secondLock).unlock(); inOrder.verify(firstLock).unlock(); verify(unavailableLock, never()).unlock(); } private Lock mockLock(String name, Executable lockAction) throws InterruptedException { var lock = mock(Lock.class, name); doAnswer(invocation -> { lockAction.execute(); return null; }).when(lock).lockInterruptibly(); return lock; } private List anyResources(int n) { return IntStream.range(0, n) // .mapToObj(j -> new ExclusiveResource("key" + j, LockMode.READ)) // .toList(); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategyTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.junit.platform.commons.JUnitException; import org.junit.platform.engine.ConfigurationParameters; /** * @since 1.3 */ class DefaultParallelExecutionConfigurationStrategyTests { final ConfigurationParameters configParams = mock(); @BeforeEach void setUp() { when(configParams.get(any(), any())).thenCallRealMethod(); } @Test void fixedStrategyCreatesValidConfiguration() { when(configParams.get("fixed.parallelism")).thenReturn(Optional.of("42")); ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.FIXED; var configuration = strategy.createConfiguration(configParams); assertThat(configuration.getParallelism()).isEqualTo(42); assertThat(configuration.getCorePoolSize()).isEqualTo(42); assertThat(configuration.getMinimumRunnable()).isEqualTo(42); assertThat(configuration.getMaxPoolSize()).isEqualTo(256 + 42); assertThat(configuration.getKeepAliveSeconds()).isEqualTo(30); assertThat(configuration.getSaturatePredicate()).isNotNull(); assertThat(configuration.getSaturatePredicate().test(null)).isTrue(); } @Test void fixedSaturateStrategyCreatesValidConfiguration() { when(configParams.get("fixed.parallelism")).thenReturn(Optional.of("42")); when(configParams.get("fixed.max-pool-size")).thenReturn(Optional.of("42")); when(configParams.get("fixed.saturate")).thenReturn(Optional.of("false")); ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.FIXED; var configuration = strategy.createConfiguration(configParams); assertThat(configuration.getParallelism()).isEqualTo(42); assertThat(configuration.getMaxPoolSize()).isEqualTo(42); assertThat(configuration.getSaturatePredicate()).isNotNull(); assertThat(configuration.getSaturatePredicate().test(null)).isFalse(); } @Test void dynamicStrategyCreatesValidConfiguration() { when(configParams.get("dynamic.factor")).thenReturn(Optional.of("2.0")); ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.DYNAMIC; var configuration = strategy.createConfiguration(configParams); var availableProcessors = Runtime.getRuntime().availableProcessors(); assertThat(configuration.getParallelism()).isEqualTo(availableProcessors * 2); assertThat(configuration.getCorePoolSize()).isEqualTo(availableProcessors * 2); assertThat(configuration.getMinimumRunnable()).isEqualTo(availableProcessors * 2); assertThat(configuration.getMaxPoolSize()).isEqualTo(256 + (availableProcessors * 2)); assertThat(configuration.getKeepAliveSeconds()).isEqualTo(30); assertThat(configuration.getSaturatePredicate()).isNotNull(); assertThat(configuration.getSaturatePredicate().test(null)).isTrue(); } @Test void dynamicSaturateStrategyCreatesValidConfiguration() { when(configParams.get("dynamic.factor")).thenReturn(Optional.of("2.0")); when(configParams.get("dynamic.max-pool-size-factor")).thenReturn(Optional.of("3.0")); when(configParams.get("dynamic.saturate")).thenReturn(Optional.of("false")); ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.DYNAMIC; var configuration = strategy.createConfiguration(configParams); var availableProcessors = Runtime.getRuntime().availableProcessors(); assertThat(configuration.getParallelism()).isEqualTo(availableProcessors * 2); assertThat(configuration.getCorePoolSize()).isEqualTo(availableProcessors * 2); assertThat(configuration.getMinimumRunnable()).isEqualTo(availableProcessors * 2); assertThat(configuration.getMaxPoolSize()).isEqualTo(availableProcessors * 6); assertThat(configuration.getKeepAliveSeconds()).isEqualTo(30); assertThat(configuration.getSaturatePredicate()).isNotNull(); assertThat(configuration.getSaturatePredicate().test(null)).isFalse(); } @Test void customStrategyCreatesValidConfiguration() { when(configParams.get("custom.class")).thenReturn( Optional.of(CustomParallelExecutionConfigurationStrategy.class.getName())); ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.CUSTOM; var configuration = strategy.createConfiguration(configParams); assertThat(configuration.getParallelism()).isEqualTo(1); assertThat(configuration.getCorePoolSize()).isEqualTo(4); assertThat(configuration.getMinimumRunnable()).isEqualTo(2); assertThat(configuration.getMaxPoolSize()).isEqualTo(3); assertThat(configuration.getKeepAliveSeconds()).isEqualTo(5); assertThat(configuration.getSaturatePredicate()).isNotNull(); assertThat(configuration.getSaturatePredicate().test(null)).isTrue(); } @ParameterizedTest @EnumSource void createsStrategyFromConfigParam(DefaultParallelExecutionConfigurationStrategy strategy) { when(configParams.get("strategy")).thenReturn(Optional.of(strategy.name().toLowerCase())); assertThat(DefaultParallelExecutionConfigurationStrategy.getStrategy(configParams)).isSameAs(strategy); } @Test void fixedStrategyThrowsExceptionWhenPropertyIsNotPresent() { when(configParams.get("fixed.parallelism")).thenReturn(Optional.empty()); ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.FIXED; assertThrows(JUnitException.class, () -> strategy.createConfiguration(configParams)); } @Test void fixedStrategyThrowsExceptionWhenPropertyIsNotAnInteger() { when(configParams.get("fixed.parallelism")).thenReturn(Optional.of("foo")); ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.FIXED; assertThrows(JUnitException.class, () -> strategy.createConfiguration(configParams)); } @Test void dynamicStrategyUsesDefaultWhenPropertyIsNotPresent() { when(configParams.get("dynamic.factor")).thenReturn(Optional.empty()); ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.DYNAMIC; var configuration = strategy.createConfiguration(configParams); var availableProcessors = Runtime.getRuntime().availableProcessors(); assertThat(configuration.getParallelism()).isEqualTo(availableProcessors); assertThat(configuration.getCorePoolSize()).isEqualTo(availableProcessors); assertThat(configuration.getMinimumRunnable()).isEqualTo(availableProcessors); assertThat(configuration.getMaxPoolSize()).isEqualTo(256 + availableProcessors); assertThat(configuration.getKeepAliveSeconds()).isEqualTo(30); } @Test void dynamicStrategyThrowsExceptionWhenPropertyIsNotAnInteger() { when(configParams.get("dynamic.factor")).thenReturn(Optional.of("foo")); ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.DYNAMIC; assertThrows(JUnitException.class, () -> strategy.createConfiguration(configParams)); } @Test void dynamicStrategyThrowsExceptionWhenFactorIsZero() { when(configParams.get("dynamic.factor")).thenReturn(Optional.of("0")); ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.DYNAMIC; assertThrows(JUnitException.class, () -> strategy.createConfiguration(configParams)); } @Test void dynamicStrategyThrowsExceptionWhenFactorIsNegative() { when(configParams.get("dynamic.factor")).thenReturn(Optional.of("-1")); ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.DYNAMIC; assertThrows(JUnitException.class, () -> strategy.createConfiguration(configParams)); } @Test void dynamicStrategyUsesAtLeastParallelismOfOneWhenPropertyIsTooSmall() { when(configParams.get("dynamic.factor")).thenReturn(Optional.of("0.00000000001")); ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.DYNAMIC; var configuration = strategy.createConfiguration(configParams); assertThat(configuration.getParallelism()).isEqualTo(1); assertThat(configuration.getCorePoolSize()).isEqualTo(1); assertThat(configuration.getMinimumRunnable()).isEqualTo(1); assertThat(configuration.getMaxPoolSize()).isEqualTo(256 + 1); assertThat(configuration.getKeepAliveSeconds()).isEqualTo(30); } @Test void customStrategyThrowsExceptionWhenPropertyIsNotPresent() { when(configParams.get("custom.class")).thenReturn(Optional.empty()); ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.CUSTOM; assertThrows(JUnitException.class, () -> strategy.createConfiguration(configParams)); } @Test void customStrategyThrowsExceptionWhenClassDoesNotExist() { when(configParams.get("custom.class")).thenReturn(Optional.of("com.acme.ClassDoesNotExist")); ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.CUSTOM; assertThrows(JUnitException.class, () -> strategy.createConfiguration(configParams)); } static class CustomParallelExecutionConfigurationStrategy implements ParallelExecutionConfigurationStrategy { @Override public ParallelExecutionConfiguration createConfiguration(ConfigurationParameters configurationParameters) { return new DefaultParallelExecutionConfiguration(1, 2, 3, 4, 5, __ -> true); } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ForkJoinDeadLockTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ; import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ_WRITE; import static org.junit.jupiter.api.parallel.Resources.SYSTEM_PROPERTIES; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasses; import java.time.LocalTime; import java.util.concurrent.CountDownLatch; import org.junit.jupiter.api.Constants; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.AfterTestExecutionCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.Isolated; import org.junit.jupiter.api.parallel.ResourceLock; import org.junit.jupiter.params.ParameterizedClass; import org.junit.jupiter.params.provider.EnumSource; import org.junit.platform.engine.support.hierarchical.ParallelHierarchicalTestExecutorServiceFactory.ParallelExecutorServiceType; import org.junit.platform.testkit.engine.EngineTestKit; // https://github.com/junit-team/junit-framework/issues/3945 @Timeout(10) @ParameterizedClass @EnumSource(ParallelExecutorServiceType.class) record ForkJoinDeadLockTests(ParallelExecutorServiceType executorServiceType) { @Test void forkJoinExecutionDoesNotLeadToDeadLock() { run(NonIsolatedTestCase.class, IsolatedTestCase.class, Isolated2TestCase.class); } @Test void nestedResourceLocksShouldStillWork() { run(SharedResourceTestCase.class); } @Test void multiLevelLocks() { run(ClassLevelTestCase.class); } private void run(Class... classes) { EngineTestKit.engine("junit-jupiter") // .selectors(selectClasses(classes)) // .configurationParameter(Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, "true") // .configurationParameter(Constants.PARALLEL_CONFIG_EXECUTOR_SERVICE_PROPERTY_NAME, executorServiceType.name()) // .configurationParameter(Constants.DEFAULT_EXECUTION_MODE_PROPERTY_NAME, "concurrent") // .configurationParameter(Constants.DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME, "concurrent") // .configurationParameter(Constants.PARALLEL_CONFIG_STRATEGY_PROPERTY_NAME, "fixed") // .configurationParameter(Constants.PARALLEL_CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME, "3") // .configurationParameter(Constants.PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME, "3") // .configurationParameter(Constants.PARALLEL_CONFIG_FIXED_SATURATE_PROPERTY_NAME, "false") // .execute(); } @ExtendWith(StartFinishLogger.class) static class BaseTestCase { } @Execution(CONCURRENT) public static class NonIsolatedTestCase extends BaseTestCase { public static CountDownLatch otherThreadRunning = new CountDownLatch(1); public static CountDownLatch sameThreadFinishing = new CountDownLatch(1); @Test @Execution(CONCURRENT) void otherThread() throws Exception { otherThreadRunning.countDown(); sameThreadFinishing.await(); Thread.sleep(100); } @Test @Execution(SAME_THREAD) void sameThread() throws Exception { otherThreadRunning.await(); sameThreadFinishing.countDown(); } } @Isolated public static class IsolatedTestCase extends BaseTestCase { @Test void test() throws Exception { Thread.sleep(100); } } static class Isolated2TestCase extends IsolatedTestCase { } public static class SharedResourceTestCase { @Test @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ) void customPropertyIsNotSetByDefault() { } @Test @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE) void canSetCustomPropertyToApple() { } @Test @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE) void canSetCustomPropertyToBanana() { } } @ResourceLock(value = "foo", mode = READ_WRITE) public static class ClassLevelTestCase { @Test @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ) void customPropertyIsNotSetByDefault() { } @Test @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE) void canSetCustomPropertyToApple() { } @Test @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE) void canSetCustomPropertyToBanana() { } } static class StartFinishLogger implements BeforeTestExecutionCallback, AfterTestExecutionCallback, BeforeAllCallback, AfterAllCallback { @Override public void beforeAll(ExtensionContext context) { log("starting class " + context.getTestClass().orElseThrow().getSimpleName()); } @Override public void beforeTestExecution(ExtensionContext context) { log("starting method " + context.getTestClass().orElseThrow().getSimpleName() + "." + context.getTestMethod().orElseThrow().getName()); } @Override public void afterTestExecution(ExtensionContext context) { log("finishing method " + context.getTestClass().orElseThrow().getSimpleName() + "." + context.getTestMethod().orElseThrow().getName()); } @Override public void afterAll(ExtensionContext context) { log("finishing class " + context.getTestClass().orElseThrow().getSimpleName()); } } private static void log(String message) { System.out.println("[" + LocalTime.now() + "] " + Thread.currentThread().getName() + " - " + message); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorServiceTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_READ; import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_READ_WRITE; import static org.junit.platform.engine.support.hierarchical.Node.ExecutionMode.CONCURRENT; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicReference; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.api.function.ThrowingConsumer; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.junit.platform.commons.JUnitException; import org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode; import org.junit.platform.engine.support.hierarchical.ForkJoinPoolHierarchicalTestExecutorService.TaskEventListener; import org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutorService.TestTask; import org.junit.platform.engine.support.hierarchical.Node.ExecutionMode; @Timeout(5) class ForkJoinPoolHierarchicalTestExecutorServiceTests { DummyTaskFactory taskFactory = new DummyTaskFactory(); LockManager lockManager = new LockManager(); @Test void exceptionsFromInvalidConfigurationAreNotSwallowed() { var configuration = new DefaultParallelExecutionConfiguration(2, 1, 1, 1, 0, __ -> true); JUnitException exception = assertThrows(JUnitException.class, () -> { try (var pool = new ForkJoinPoolHierarchicalTestExecutorService(configuration, TaskEventListener.NOOP)) { assertNotNull(pool, "we won't get here"); } }); assertThat(exception).hasMessage("Failed to create ForkJoinPool"); assertThat(exception).rootCause().isInstanceOf(IllegalArgumentException.class); } static List incompatibleLockCombinations() { return List.of(// arguments(// Set.of(GLOBAL_READ), // Set.of(GLOBAL_READ_WRITE) // ), // arguments(// Set.of(new ExclusiveResource("a", LockMode.READ)), // Set.of(new ExclusiveResource("a", LockMode.READ_WRITE)) // ), // arguments(// Set.of(new ExclusiveResource("a", LockMode.READ_WRITE)), // Set.of(new ExclusiveResource("a", LockMode.READ_WRITE)) // ), // arguments(// Set.of(GLOBAL_READ, new ExclusiveResource("a", LockMode.READ_WRITE)), // Set.of(GLOBAL_READ, new ExclusiveResource("b", LockMode.READ_WRITE)) // ), // arguments(// Set.of(new ExclusiveResource("b", LockMode.READ)), // Set.of(new ExclusiveResource("a", LockMode.READ)) // ), // arguments(// Set.of(GLOBAL_READ, new ExclusiveResource("a", LockMode.READ_WRITE)), // Set.of(GLOBAL_READ, new ExclusiveResource("a", LockMode.READ)) // ), // arguments(// Set.of(GLOBAL_READ_WRITE), // Set.of(GLOBAL_READ) // ), // arguments(// Set.of(GLOBAL_READ, new ExclusiveResource("a", LockMode.READ), new ExclusiveResource("b", LockMode.READ), new ExclusiveResource("d", LockMode.READ)), Set.of(GLOBAL_READ, new ExclusiveResource("a", LockMode.READ), new ExclusiveResource("c", LockMode.READ)) // )// ); } @SuppressWarnings("NullAway") @ParameterizedTest @MethodSource("incompatibleLockCombinations") void defersTasksWithIncompatibleLocks(Set initialResources, Set incompatibleResources) throws Throwable { var initialLock = lockManager.getLockForResources(initialResources); var incompatibleLock = lockManager.getLockForResources(incompatibleResources); var deferred = new CountDownLatch(1); var deferredTask = new AtomicReference(); TaskEventListener taskEventListener = testTask -> { deferredTask.set(testTask); deferred.countDown(); }; var incompatibleTask = taskFactory.create("incompatibleTask", incompatibleLock); var tasks = runWithAttemptedWorkStealing(taskEventListener, incompatibleTask, initialLock, () -> await(deferred, "Interrupted while waiting for task to be deferred")); assertEquals(incompatibleTask, deferredTask.get()); assertEquals(tasks.get("nestedTask").threadName, tasks.get("leafTaskB").threadName); assertNotEquals(tasks.get("leafTaskA").threadName, tasks.get("leafTaskB").threadName); } static List compatibleLockCombinations() { return List.of(// arguments(// Set.of(GLOBAL_READ), // Set.of(new ExclusiveResource("a", LockMode.READ)) // ), // arguments(// Set.of(GLOBAL_READ), // Set.of(new ExclusiveResource("a", LockMode.READ_WRITE)) // ), // arguments(// Set.of(GLOBAL_READ), // Set.of(GLOBAL_READ, new ExclusiveResource("a", LockMode.READ_WRITE)) // ), // arguments(// Set.of(GLOBAL_READ), // Set.of(GLOBAL_READ, new ExclusiveResource("a", LockMode.READ)) // ), // arguments(// Set.of(GLOBAL_READ, new ExclusiveResource("a", LockMode.READ)), // Set.of(GLOBAL_READ, new ExclusiveResource("a", LockMode.READ), new ExclusiveResource("b", LockMode.READ), new ExclusiveResource("c", LockMode.READ)) // ), // arguments(// Set.of(GLOBAL_READ, new ExclusiveResource("a", LockMode.READ)), // Set.of(GLOBAL_READ, new ExclusiveResource("b", LockMode.READ)) // ), // arguments(// Set.of(GLOBAL_READ, new ExclusiveResource("a", LockMode.READ)), // Set.of(new ExclusiveResource("a", LockMode.READ), new ExclusiveResource("b", LockMode.READ), new ExclusiveResource("c", LockMode.READ)) // )// ); } @SuppressWarnings("NullAway") @ParameterizedTest @MethodSource("compatibleLockCombinations") void canWorkStealTaskWithCompatibleLocks(Set initialResources, Set compatibleResources) throws Throwable { var initialLock = lockManager.getLockForResources(initialResources); var compatibleLock = lockManager.getLockForResources(compatibleResources); var deferredTask = new AtomicReference(); var workStolen = new CountDownLatch(1); var compatibleTask = taskFactory.create("compatibleTask", compatibleLock, workStolen::countDown); var tasks = runWithAttemptedWorkStealing(deferredTask::set, compatibleTask, initialLock, () -> await(workStolen, "Interrupted while waiting for work to be stolen")); assertNull(deferredTask.get()); assertEquals(tasks.get("nestedTask").threadName, tasks.get("leafTaskB").threadName); assertNotEquals(tasks.get("leafTaskA").threadName, tasks.get("leafTaskB").threadName); } @Test void defersTasksWithIncompatibleLocksOnMultipleLevels() throws Throwable { var initialLock = lockManager.getLockForResources( Set.of(GLOBAL_READ, new ExclusiveResource("a", LockMode.READ))); var incompatibleLock1 = lockManager.getLockForResource(new ExclusiveResource("a", LockMode.READ_WRITE)); var compatibleLock1 = lockManager.getLockForResource(new ExclusiveResource("b", LockMode.READ)); var incompatibleLock2 = lockManager.getLockForResource(new ExclusiveResource("b", LockMode.READ_WRITE)); var deferred = new ConcurrentHashMap(); var deferredTasks = new CopyOnWriteArrayList(); @SuppressWarnings("NullAway") TaskEventListener taskEventListener = testTask -> { deferredTasks.add(testTask); deferred.get(testTask).countDown(); }; var incompatibleTask1 = taskFactory.create("incompatibleTask1", incompatibleLock1); deferred.put(incompatibleTask1, new CountDownLatch(1)); var incompatibleTask2 = taskFactory.create("incompatibleTask2", incompatibleLock2); deferred.put(incompatibleTask2, new CountDownLatch(1)); var configuration = new DefaultParallelExecutionConfiguration(2, 2, 2, 2, 1, __1 -> true); withForkJoinPoolHierarchicalTestExecutorService(configuration, taskEventListener, service -> { var nestedTask2 = createNestedTaskWithTwoConcurrentLeafTasks(service, "2", compatibleLock1, List.of(incompatibleTask2), // () -> await(deferred.get(incompatibleTask2), incompatibleTask2.identifier + " to be deferred")); var nestedTask1 = createNestedTaskWithTwoConcurrentLeafTasks(service, "1", initialLock, List.of(incompatibleTask1, nestedTask2), // () -> { await(deferred.get(incompatibleTask1), incompatibleTask1.identifier + " to be deferred"); await(nestedTask2.started, nestedTask2.identifier + " to be started"); }); service.submit(nestedTask1).get(); }); assertThat(deferredTasks) // .startsWith(incompatibleTask1, incompatibleTask2) // .containsOnly(incompatibleTask1, incompatibleTask2) // incompatibleTask1 may be deferred multiple times .containsOnlyOnce(incompatibleTask2); assertThat(taskFactory.tasks) // .hasSize(3 + 3 + 2) // .values().extracting(it -> it.completion.isDone()).containsOnly(true); assertThat(taskFactory.tasks) // .values().extracting(it -> it.completion.isCompletedExceptionally()).containsOnly(false); } private Map runWithAttemptedWorkStealing(TaskEventListener taskEventListener, DummyTestTask taskToBeStolen, ResourceLock initialLock, Runnable waitAction) throws Throwable { var configuration = new DefaultParallelExecutionConfiguration(2, 2, 2, 2, 1, __ -> true); withForkJoinPoolHierarchicalTestExecutorService(configuration, taskEventListener, service -> { var nestedTask = createNestedTaskWithTwoConcurrentLeafTasks(service, "", initialLock, List.of(taskToBeStolen), waitAction); service.submit(nestedTask).get(); }); return taskFactory.tasks; } private DummyTestTask createNestedTaskWithTwoConcurrentLeafTasks( ForkJoinPoolHierarchicalTestExecutorService service, String identifierSuffix, ResourceLock parentLock, List tasksToFork, Runnable waitAction) { return taskFactory.create("nestedTask" + identifierSuffix, parentLock, () -> { var bothLeafTasksAreRunning = new CountDownLatch(2); var leafTaskA = taskFactory.create("leafTaskA" + identifierSuffix, NopLock.INSTANCE, () -> { tasksToFork.forEach(task -> service.new ExclusiveTask(task).fork()); bothLeafTasksAreRunning.countDown(); bothLeafTasksAreRunning.await(); waitAction.run(); }); var leafTaskB = taskFactory.create("leafTaskB" + identifierSuffix, NopLock.INSTANCE, () -> { bothLeafTasksAreRunning.countDown(); bothLeafTasksAreRunning.await(); }); service.invokeAll(List.of(leafTaskA, leafTaskB)); }); } private static void await(CountDownLatch latch, String message) { try { latch.await(); } catch (InterruptedException e) { System.out.println("Interrupted while waiting for " + message); } } private void withForkJoinPoolHierarchicalTestExecutorService(ParallelExecutionConfiguration configuration, TaskEventListener taskEventListener, ThrowingConsumer<@NonNull ForkJoinPoolHierarchicalTestExecutorService> action) throws Throwable { try (var service = new ForkJoinPoolHierarchicalTestExecutorService(configuration, taskEventListener)) { action.accept(service); service.forkJoinPool.shutdown(); assertTrue(service.forkJoinPool.awaitTermination(5, SECONDS), "Pool did not terminate within timeout"); } } @NullMarked static final class DummyTestTask implements TestTask { private final String identifier; private final ResourceLock resourceLock; private final Executable action; private volatile @Nullable String threadName; private final CountDownLatch started = new CountDownLatch(1); private final CompletableFuture completion = new CompletableFuture<>(); DummyTestTask(String identifier, ResourceLock resourceLock, Executable action) { this.identifier = identifier; this.resourceLock = resourceLock; this.action = action; } @Override public ExecutionMode getExecutionMode() { return CONCURRENT; } @Override public ResourceLock getResourceLock() { return resourceLock; } @Override public void execute() { threadName = Thread.currentThread().getName(); started.countDown(); try { action.execute(); completion.complete(null); } catch (Throwable e) { completion.completeExceptionally(e); throw new RuntimeException("Action " + identifier + " failed", e); } } @Override public String toString() { return identifier; } } static final class DummyTaskFactory { final Map tasks = new HashMap<>(); DummyTestTask create(String identifier, ResourceLock resourceLock) { return create(identifier, resourceLock, () -> { }); } DummyTestTask create(String identifier, ResourceLock resourceLock, Executable action) { DummyTestTask task = new DummyTestTask(identifier, resourceLock, action); tasks.put(task.identifier, task); return task; } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.engine.TestExecutionResult.Status.ABORTED; import static org.junit.platform.engine.TestExecutionResult.Status.FAILED; import static org.junit.platform.engine.TestExecutionResult.Status.SUCCESSFUL; import static org.junit.platform.engine.TestExecutionResult.successful; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.quality.Strictness.LENIENT; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.NullMarked; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.function.ThrowingConsumer; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; import org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode; import org.junit.platform.engine.support.hierarchical.Node.DynamicTestExecutor; import org.junit.platform.engine.support.hierarchical.ParallelHierarchicalTestExecutorServiceFactory.ParallelExecutorServiceType; import org.junit.platform.launcher.core.ConfigurationParametersFactoryForTests; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.stubbing.Answer; import org.opentest4j.TestAbortedException; /** * Micro-tests that verify behavior of {@link HierarchicalTestExecutor}. * * @since 1.0 */ @ExtendWith(MockitoExtension.class) class HierarchicalTestExecutorTests { @Spy MyContainer root = new MyContainer(UniqueId.root("container", "root")); @Mock EngineExecutionListener listener; CancellationToken cancellationToken = CancellationToken.create(); MyEngineExecutionContext rootContext = new MyEngineExecutionContext(); HierarchicalTestExecutor<@NonNull MyEngineExecutionContext> executor; @BeforeEach void init() { executor = createExecutor(new SameThreadHierarchicalTestExecutorService()); } private HierarchicalTestExecutor<@NonNull MyEngineExecutionContext> createExecutor( HierarchicalTestExecutorService executorService) { ExecutionRequest request = mock(); when(request.getRootTestDescriptor()).thenReturn(root); when(request.getEngineExecutionListener()).thenReturn(listener); when(request.getCancellationToken()).thenReturn(cancellationToken); return new HierarchicalTestExecutor<>(request, rootContext, executorService, OpenTest4JAwareThrowableCollector::new); } @Test void emptyRootDescriptor() throws Exception { var inOrder = inOrder(listener, root); executor.execute(); var rootExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); inOrder.verify(root).prepare(rootContext); inOrder.verify(root).shouldBeSkipped(rootContext); inOrder.verify(listener).executionStarted(root); inOrder.verify(root).before(rootContext); inOrder.verify(root).after(rootContext); inOrder.verify(listener).executionFinished(eq(root), rootExecutionResult.capture()); assertThat(rootExecutionResult.getValue().getStatus()).isEqualTo(SUCCESSFUL); } @Test void rootDescriptorWithOneChildContainer() throws Exception { var child = spy(new MyContainer(UniqueId.root("container", "child container"))); root.addChild(child); var inOrder = inOrder(listener, root, child); executor.execute(); var childExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); inOrder.verify(listener).executionStarted(root); inOrder.verify(child).prepare(rootContext); inOrder.verify(child).shouldBeSkipped(rootContext); inOrder.verify(listener).executionStarted(child); inOrder.verify(child).before(rootContext); inOrder.verify(child).after(rootContext); inOrder.verify(listener).executionFinished(eq(child), childExecutionResult.capture()); inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); assertThat(childExecutionResult.getValue().getStatus()).isEqualTo(SUCCESSFUL); } @Test void rootDescriptorWithOneChildLeaf() throws Exception { var child = spy(new MyLeaf(UniqueId.root("leaf", "child leaf"))); root.addChild(child); var inOrder = inOrder(listener, root, child); executor.execute(); var aTestExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); inOrder.verify(listener).executionStarted(root); inOrder.verify(child).prepare(rootContext); inOrder.verify(child).shouldBeSkipped(rootContext); inOrder.verify(listener).executionStarted(child); inOrder.verify(child).execute(eq(rootContext), any()); inOrder.verify(listener).executionFinished(eq(child), aTestExecutionResult.capture()); inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); assertThat(aTestExecutionResult.getValue().getStatus()).isEqualTo(SUCCESSFUL); } @Test void skippingAContainer() throws Exception { var child = spy(new MyContainer(UniqueId.root("container", "child container"))); when(child.shouldBeSkipped(rootContext)).thenReturn(Node.SkipResult.skip("in test")); root.addChild(child); var inOrder = inOrder(listener, root, child); executor.execute(); inOrder.verify(listener).executionStarted(root); inOrder.verify(child).prepare(rootContext); inOrder.verify(child).shouldBeSkipped(rootContext); inOrder.verify(child).cleanUp(rootContext); inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); verify(listener, never()).executionStarted(child); verify(child, never()).execute(any(), any()); verify(listener, never()).executionFinished(eq(child), any(TestExecutionResult.class)); } @Test void skippingALeaf() throws Exception { var child = spy(new MyLeaf(UniqueId.root("leaf", "child leaf"))); when(child.shouldBeSkipped(rootContext)).thenReturn(Node.SkipResult.skip("in test")); root.addChild(child); var inOrder = inOrder(listener, root, child); executor.execute(); inOrder.verify(listener).executionStarted(root); inOrder.verify(child).prepare(rootContext); inOrder.verify(child).shouldBeSkipped(rootContext); inOrder.verify(child).cleanUp(rootContext); inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); verify(listener, never()).executionStarted(child); verify(child, never()).execute(any(), any()); verify(listener, never()).executionFinished(eq(child), any(TestExecutionResult.class)); } @Test void exceptionInShouldBeSkipped() throws Exception { var child = spy(new MyContainer(UniqueId.root("container", "child container"))); var anException = new RuntimeException("in skip"); when(child.shouldBeSkipped(rootContext)).thenThrow(anException); root.addChild(child); var inOrder = inOrder(listener, child); executor.execute(); var childExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); inOrder.verify(listener).executionStarted(root); inOrder.verify(child).prepare(rootContext); inOrder.verify(child).shouldBeSkipped(rootContext); inOrder.verify(child).cleanUp(rootContext); inOrder.verify(listener).executionStarted(child); inOrder.verify(listener).executionFinished(eq(child), childExecutionResult.capture()); inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); verify(child, never()).execute(any(), any()); assertThat(childExecutionResult.getValue().getStatus()).isEqualTo(FAILED); assertThat(childExecutionResult.getValue().getThrowable()).containsSame(anException); } @Test void exceptionInContainerBeforeAll() throws Exception { var child = spy(new MyContainer(UniqueId.root("container", "child container"))); root.addChild(child); var anException = new RuntimeException("in test"); when(root.before(rootContext)).thenThrow(anException); var inOrder = inOrder(listener, root, child); executor.execute(); var rootExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); inOrder.verify(root).prepare(rootContext); inOrder.verify(root).shouldBeSkipped(rootContext); inOrder.verify(listener).executionStarted(root); inOrder.verify(root).before(rootContext); inOrder.verify(root).after(rootContext); inOrder.verify(listener).executionFinished(eq(root), rootExecutionResult.capture()); assertThat(rootExecutionResult.getValue().getStatus()).isEqualTo(FAILED); assertThat(rootExecutionResult.getValue().getThrowable()).containsSame(anException); verify(child, never()).execute(any(), any()); } @Test void exceptionInContainerAfterAllAndCleanUp() throws Exception { var child = spy(new MyLeaf(UniqueId.root("leaf", "child container"))); root.addChild(child); var afterException = new RuntimeException("in after()"); doThrow(afterException).when(root).after(rootContext); var cleanUpException = new RuntimeException("in cleanUp()"); doThrow(cleanUpException).when(root).cleanUp(rootContext); executor.execute(); var rootExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); var inOrder = inOrder(listener, root, child); inOrder.verify(root).prepare(rootContext); inOrder.verify(root).shouldBeSkipped(rootContext); inOrder.verify(listener).executionStarted(root); inOrder.verify(root).before(rootContext); inOrder.verify(listener).executionStarted(child); inOrder.verify(child).execute(eq(rootContext), any()); inOrder.verify(listener).executionFinished(eq(child), any(TestExecutionResult.class)); inOrder.verify(root).after(rootContext); inOrder.verify(root).cleanUp(rootContext); inOrder.verify(listener).executionFinished(eq(root), rootExecutionResult.capture()); inOrder.verifyNoMoreInteractions(); assertThat(rootExecutionResult.getValue().getStatus()).isEqualTo(FAILED); assertThat(rootExecutionResult.getValue().getThrowable()).containsSame(afterException); assertThat(afterException.getSuppressed()).containsExactly(cleanUpException); } @Test void exceptionInPrepare() throws Exception { var prepareException = new RuntimeException("in prepare()"); doThrow(prepareException).when(root).prepare(rootContext); executor.execute(); var rootExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); var inOrder = inOrder(listener, root); inOrder.verify(root).prepare(rootContext); inOrder.verify(listener).executionStarted(root); inOrder.verify(listener).executionFinished(eq(root), rootExecutionResult.capture()); inOrder.verifyNoMoreInteractions(); assertThat(rootExecutionResult.getValue().getStatus()).isEqualTo(FAILED); assertThat(rootExecutionResult.getValue().getThrowable()).containsSame(prepareException); assertThat(prepareException.getSuppressed()).isEmpty(); } @Test void exceptionInCleanUp() throws Exception { var cleanUpException = new RuntimeException("in cleanUp()"); doThrow(cleanUpException).when(root).cleanUp(rootContext); executor.execute(); var rootExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); var inOrder = inOrder(listener, root); inOrder.verify(root).prepare(rootContext); inOrder.verify(root).shouldBeSkipped(rootContext); inOrder.verify(listener).executionStarted(root); inOrder.verify(root).execute(eq(rootContext), any()); inOrder.verify(root).after(rootContext); inOrder.verify(root).cleanUp(rootContext); inOrder.verify(listener).executionFinished(eq(root), rootExecutionResult.capture()); inOrder.verifyNoMoreInteractions(); assertThat(rootExecutionResult.getValue().getStatus()).isEqualTo(FAILED); assertThat(rootExecutionResult.getValue().getThrowable()).containsSame(cleanUpException); assertThat(cleanUpException.getSuppressed()).isEmpty(); } @Test void exceptionInShouldBeSkippedAndCleanUp() throws Exception { var shouldBeSkippedException = new RuntimeException("in prepare()"); doThrow(shouldBeSkippedException).when(root).shouldBeSkipped(rootContext); var cleanUpException = new RuntimeException("in cleanUp()"); doThrow(cleanUpException).when(root).cleanUp(rootContext); executor.execute(); var rootExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); var inOrder = inOrder(listener, root); inOrder.verify(root).prepare(rootContext); inOrder.verify(root).shouldBeSkipped(rootContext); inOrder.verify(root).cleanUp(rootContext); inOrder.verify(listener).executionStarted(root); inOrder.verify(listener).executionFinished(eq(root), rootExecutionResult.capture()); inOrder.verifyNoMoreInteractions(); assertThat(rootExecutionResult.getValue().getStatus()).isEqualTo(FAILED); assertThat(rootExecutionResult.getValue().getThrowable()).containsSame(shouldBeSkippedException); assertThat(shouldBeSkippedException.getSuppressed()).containsExactly(cleanUpException); } @Test void exceptionInLeafExecute() throws Exception { var child = spy(new MyLeaf(UniqueId.root("leaf", "leaf"))); var anException = new RuntimeException("in test"); when(child.execute(eq(rootContext), any())).thenThrow(anException); root.addChild(child); var inOrder = inOrder(listener, root, child); executor.execute(); var childExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); inOrder.verify(listener).executionStarted(root); inOrder.verify(root).before(rootContext); inOrder.verify(listener).executionStarted(child); inOrder.verify(child).execute(eq(rootContext), any()); inOrder.verify(listener).executionFinished(eq(child), childExecutionResult.capture()); inOrder.verify(root).after(rootContext); inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); assertThat(childExecutionResult.getValue().getStatus()).isEqualTo(FAILED); assertThat(childExecutionResult.getValue().getThrowable()).containsSame(anException); } @Test void abortInRootBeforeAll() throws Exception { var child = spy(new MyContainer(UniqueId.root("container", "child container"))); root.addChild(child); var anAbortedException = new TestAbortedException("in BeforeAll"); when(root.before(rootContext)).thenThrow(anAbortedException); var inOrder = inOrder(listener, root, child); executor.execute(); var rootExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); inOrder.verify(root).prepare(rootContext); inOrder.verify(root).shouldBeSkipped(rootContext); inOrder.verify(listener).executionStarted(root); inOrder.verify(root).before(rootContext); inOrder.verify(root).after(rootContext); inOrder.verify(listener).executionFinished(eq(root), rootExecutionResult.capture()); assertThat(rootExecutionResult.getValue().getStatus()).isEqualTo(ABORTED); assertThat(rootExecutionResult.getValue().getThrowable()).containsSame(anAbortedException); verify(child, never()).execute(any(), any()); } @Test void abortInChildContainerBeforeAll() throws Exception { var child = spy(new MyContainer(UniqueId.root("container", "child container"))); root.addChild(child); var anAbortedException = new TestAbortedException("in BeforeAll"); when(child.before(rootContext)).thenThrow(anAbortedException); var inOrder = inOrder(listener, root, child); executor.execute(); var childExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); inOrder.verify(root).prepare(rootContext); inOrder.verify(root).shouldBeSkipped(rootContext); inOrder.verify(listener).executionStarted(root); inOrder.verify(root).before(rootContext); inOrder.verify(child).shouldBeSkipped(rootContext); inOrder.verify(child).before(rootContext); inOrder.verify(child).after(rootContext); inOrder.verify(listener).executionFinished(eq(child), childExecutionResult.capture()); inOrder.verify(root).after(rootContext); assertThat(childExecutionResult.getValue().getStatus()).isEqualTo(ABORTED); assertThat(childExecutionResult.getValue().getThrowable()).containsSame(anAbortedException); verify(child, never()).execute(any(), any()); } @Test void abortInLeafExecute() throws Exception { var child = spy(new MyLeaf(UniqueId.root("leaf", "leaf"))); var anAbortedException = new TestAbortedException("in test"); when(child.execute(eq(rootContext), any())).thenThrow(anAbortedException); root.addChild(child); var inOrder = inOrder(listener, root, child); executor.execute(); var childExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); inOrder.verify(listener).executionStarted(root); inOrder.verify(root).before(rootContext); inOrder.verify(listener).executionStarted(child); inOrder.verify(child).execute(eq(rootContext), any()); inOrder.verify(listener).executionFinished(eq(child), childExecutionResult.capture()); inOrder.verify(root).after(rootContext); inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); assertThat(childExecutionResult.getValue().getStatus()).isEqualTo(ABORTED); assertThat(childExecutionResult.getValue().getThrowable()).containsSame(anAbortedException); } @Test void executesDynamicTestDescriptors() throws Exception { var leafUniqueId = UniqueId.root("leaf", "child leaf"); var child = spy(new MyLeaf(leafUniqueId)); var dynamicTestDescriptor = spy(new MyLeaf(leafUniqueId.append("dynamic", "child"))); when(child.execute(any(), any())).thenAnswer(execute(dynamicTestDescriptor)); root.addChild(child); var inOrder = inOrder(listener, root, child, dynamicTestDescriptor); executor.execute(); var aTestExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); inOrder.verify(listener).executionStarted(root); inOrder.verify(child).prepare(rootContext); inOrder.verify(child).shouldBeSkipped(rootContext); inOrder.verify(listener).executionStarted(child); inOrder.verify(child).execute(eq(rootContext), any()); inOrder.verify(listener).dynamicTestRegistered(dynamicTestDescriptor); inOrder.verify(dynamicTestDescriptor).prepare(rootContext); inOrder.verify(dynamicTestDescriptor).shouldBeSkipped(rootContext); inOrder.verify(listener).executionStarted(dynamicTestDescriptor); inOrder.verify(dynamicTestDescriptor).execute(eq(rootContext), any()); inOrder.verify(listener).executionFinished(eq(dynamicTestDescriptor), aTestExecutionResult.capture()); inOrder.verify(listener).executionFinished(eq(child), aTestExecutionResult.capture()); inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); assertThat(aTestExecutionResult.getAllValues()).extracting(TestExecutionResult::getStatus).containsExactly( SUCCESSFUL, SUCCESSFUL); } @Test void executesDynamicTestDescriptorsUsingContainerAndTestType() throws Exception { var child = spy(new MyContainerAndTestTestCase(root.getUniqueId().append("c&t", "child"))); var dynamicContainerAndTest = spy( new MyContainerAndTestTestCase(child.getUniqueId().append("c&t", "dynamicContainerAndTest"))); var dynamicLeaf = spy(new MyLeaf(dynamicContainerAndTest.getUniqueId().append("test", "dynamicLeaf"))); root.addChild(child); when(child.execute(any(), any())).thenAnswer(execute(dynamicContainerAndTest)); when(dynamicContainerAndTest.execute(any(), any())).thenAnswer(execute(dynamicLeaf)); when(dynamicLeaf.execute(any(), any())).thenAnswer(invocation -> { throw new AssertionError("test fails"); }); var inOrder = inOrder(listener, root, child, dynamicContainerAndTest, dynamicLeaf); executor.execute(); var aTestExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); inOrder.verify(listener).executionStarted(root); inOrder.verify(child).prepare(rootContext); inOrder.verify(child).shouldBeSkipped(rootContext); inOrder.verify(listener).executionStarted(child); inOrder.verify(child).execute(eq(rootContext), any()); inOrder.verify(listener).dynamicTestRegistered(dynamicContainerAndTest); inOrder.verify(dynamicContainerAndTest).prepare(rootContext); inOrder.verify(dynamicContainerAndTest).shouldBeSkipped(rootContext); inOrder.verify(listener).executionStarted(dynamicContainerAndTest); inOrder.verify(dynamicContainerAndTest).execute(eq(rootContext), any()); inOrder.verify(listener).dynamicTestRegistered(dynamicLeaf); inOrder.verify(dynamicLeaf).prepare(rootContext); inOrder.verify(dynamicLeaf).shouldBeSkipped(rootContext); inOrder.verify(listener).executionStarted(dynamicLeaf); inOrder.verify(dynamicLeaf).execute(eq(rootContext), any()); inOrder.verify(listener).executionFinished(eq(dynamicLeaf), aTestExecutionResult.capture()); inOrder.verify(listener).executionFinished(eq(dynamicContainerAndTest), aTestExecutionResult.capture()); inOrder.verify(listener).executionFinished(eq(child), aTestExecutionResult.capture()); inOrder.verify(listener).executionFinished(eq(root), any(TestExecutionResult.class)); assertThat(aTestExecutionResult.getAllValues()).extracting(TestExecutionResult::getStatus).containsExactly( FAILED, SUCCESSFUL, SUCCESSFUL); } @Test void executesDynamicTestDescriptorsWithCustomListener() { var leafUniqueId = UniqueId.root("leaf", "child leaf"); var child = spy(new MyLeaf(leafUniqueId)); var dynamicTestDescriptor = spy(new MyLeaf(leafUniqueId.append("dynamic", "child"))); root.addChild(child); var anotherListener = mock(EngineExecutionListener.class); when(child.execute(any(), any())).thenAnswer( useDynamicTestExecutor(executor -> executor.execute(dynamicTestDescriptor, anotherListener))); executor.execute(); var inOrder = inOrder(listener, anotherListener, root, child, dynamicTestDescriptor); inOrder.verify(anotherListener).dynamicTestRegistered(dynamicTestDescriptor); inOrder.verify(anotherListener).executionStarted(dynamicTestDescriptor); inOrder.verify(dynamicTestDescriptor).execute(eq(rootContext), any()); inOrder.verify(dynamicTestDescriptor).nodeFinished(rootContext, dynamicTestDescriptor, successful()); inOrder.verify(anotherListener).executionFinished(dynamicTestDescriptor, successful()); } @ParameterizedTest @EnumSource(ParallelExecutorServiceType.class) @MockitoSettings(strictness = LENIENT) void canAbortExecutionOfDynamicChild(ParallelExecutorServiceType executorServiceType) throws Exception { var leafUniqueId = UniqueId.root("leaf", "child leaf"); var child = spy(new MyLeaf(leafUniqueId)); var dynamicTestDescriptor = spy(new MyLeaf(leafUniqueId.append("dynamic", "child"))); root.addChild(child); var startedLatch = new CountDownLatch(1); var interrupted = new CompletableFuture(); when(child.execute(any(), any())).thenAnswer(useDynamicTestExecutor(executor -> { var future = executor.execute(dynamicTestDescriptor, EngineExecutionListener.NOOP); startedLatch.await(); future.cancel(true); executor.awaitFinished(); })); when(dynamicTestDescriptor.execute(any(), any())).thenAnswer(invocation -> { startedLatch.countDown(); try { new CountDownLatch(1).await(); // block until interrupted interrupted.complete(false); return null; } catch (InterruptedException e) { interrupted.complete(true); throw e; } }); var parameters = ConfigurationParametersFactoryForTests.create(Map.of(// ParallelHierarchicalTestExecutorServiceFactory.EXECUTOR_SERVICE_PROPERTY_NAME, executorServiceType, // DefaultParallelExecutionConfigurationStrategy.CONFIG_STRATEGY_PROPERTY_NAME, "fixed", // DefaultParallelExecutionConfigurationStrategy.CONFIG_FIXED_PARALLELISM_PROPERTY_NAME, 2)); try (var executorService = ParallelHierarchicalTestExecutorServiceFactory.create(parameters)) { createExecutor(executorService).execute().get(); } verify(listener).executionFinished(child, successful()); assertTrue(interrupted.get(), "dynamic node was interrupted"); } private Answer execute(TestDescriptor dynamicChild) { return useDynamicTestExecutor(executor -> executor.execute(dynamicChild)); } private Answer useDynamicTestExecutor(ThrowingConsumer<@NonNull DynamicTestExecutor> action) { return invocation -> { DynamicTestExecutor dynamicTestExecutor = invocation.getArgument(1); action.accept(dynamicTestExecutor); return invocation.getArgument(0); }; } /** * Verifies support for unrecoverable exceptions. */ @Test void outOfMemoryErrorInShouldBeSkipped() throws Exception { var child = spy(new MyContainer(UniqueId.root("container", "child container"))); var outOfMemoryError = new OutOfMemoryError("in skip"); when(child.shouldBeSkipped(rootContext)).thenThrow(outOfMemoryError); root.addChild(child); Throwable actualException = assertThrows(OutOfMemoryError.class, () -> executor.execute()); assertSame(outOfMemoryError, actualException); } /** * Verifies support for unrecoverable exceptions. */ @Test void outOfMemoryErrorInLeafExecution() { var child = spy(new MyLeaf(UniqueId.root("leaf", "leaf"))); var outOfMemoryError = new OutOfMemoryError("in test"); when(child.execute(eq(rootContext), any())).thenThrow(outOfMemoryError); root.addChild(child); Throwable actualException = assertThrows(OutOfMemoryError.class, () -> executor.execute()); assertSame(outOfMemoryError, actualException); } @Test void exceptionInAfterDoesNotHideEarlierException() throws Exception { var child = spy(new MyLeaf(UniqueId.root("leaf", "leaf"))); Exception exceptionInExecute = new RuntimeException("execute"); Exception exceptionInAfter = new RuntimeException("after"); doThrow(exceptionInExecute).when(child).execute(eq(rootContext), any()); doThrow(exceptionInAfter).when(child).after(eq(rootContext)); root.addChild(child); var inOrder = inOrder(listener, child); executor.execute(); var childExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); inOrder.verify(child).execute(eq(rootContext), any()); inOrder.verify(child).after(eq(rootContext)); inOrder.verify(listener).executionFinished(eq(child), childExecutionResult.capture()); assertThat(childExecutionResult.getValue().getStatus()).isEqualTo(FAILED); assertThat(childExecutionResult.getValue().getThrowable().orElseThrow()).isSameAs( exceptionInExecute).hasSuppressedException(exceptionInAfter); } @Test void dynamicTestDescriptorsMustNotDeclareExclusiveResources() { var leafUniqueId = UniqueId.root("leaf", "child leaf"); var child = spy(new MyLeaf(leafUniqueId)); var dynamicTestDescriptor = spy(new MyLeaf(leafUniqueId.append("dynamic", "child"))); when(dynamicTestDescriptor.getExclusiveResources()).thenReturn( Set.of(new ExclusiveResource("foo", LockMode.READ))); when(child.execute(any(), any())).thenAnswer(execute(dynamicTestDescriptor)); root.addChild(child); executor.execute(); var aTestExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); verify(listener).executionStarted(dynamicTestDescriptor); verify(listener).executionFinished(eq(dynamicTestDescriptor), aTestExecutionResult.capture()); var executionResult = aTestExecutionResult.getValue(); assertThat(executionResult.getStatus()).isEqualTo(FAILED); assertThat(executionResult.getThrowable()).isPresent(); assertThat(executionResult.getThrowable().get()).hasMessageContaining( "Dynamic test descriptors must not declare exclusive resources"); } @Test void exceptionInAfterIsReportedInsteadOfEarlierTestAbortedException() throws Exception { var child = spy(new MyLeaf(UniqueId.root("leaf", "leaf"))); Exception exceptionInExecute = new TestAbortedException("execute"); Exception exceptionInAfter = new RuntimeException("after"); doThrow(exceptionInExecute).when(child).execute(eq(rootContext), any()); doThrow(exceptionInAfter).when(child).after(eq(rootContext)); root.addChild(child); var inOrder = inOrder(listener, child); executor.execute(); var childExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); inOrder.verify(child).execute(eq(rootContext), any()); inOrder.verify(child).after(eq(rootContext)); inOrder.verify(listener).executionFinished(eq(child), childExecutionResult.capture()); assertThat(childExecutionResult.getValue().getStatus()).isEqualTo(FAILED); assertThat(childExecutionResult.getValue().getThrowable().orElseThrow()).isSameAs( exceptionInAfter).hasSuppressedException(exceptionInExecute); } @Test void reportsNodeAsSkippedWhenCancelledPriorToExecution() { var child = spy(new MyLeaf(UniqueId.root("leaf", "child container"))); root.addChild(child); cancellationToken.cancel(); executor.execute(); var inOrder = inOrder(listener, root, child); inOrder.verify(root).nodeSkipped(rootContext, root, NodeTestTask.CANCELLED_SKIP_RESULT); inOrder.verify(listener).executionSkipped(root, NodeTestTask.CANCELLED_SKIP_RESULT.getReason().orElseThrow()); inOrder.verifyNoMoreInteractions(); } @Test void reportsNodeAsSkippedWhenCancelledDuringPrepare() throws Exception { var child = spy(new MyLeaf(UniqueId.root("leaf", "child container"))); root.addChild(child); when(root.prepare(any())).thenAnswer(invocation -> { cancellationToken.cancel(); return invocation.callRealMethod(); }); executor.execute(); var inOrder = inOrder(listener, root, child); inOrder.verify(root).prepare(rootContext); inOrder.verify(root).nodeSkipped(rootContext, root, NodeTestTask.CANCELLED_SKIP_RESULT); inOrder.verify(listener).executionSkipped(root, NodeTestTask.CANCELLED_SKIP_RESULT.getReason().orElseThrow()); inOrder.verifyNoMoreInteractions(); } @Test void reportsNodeAsSkippedWhenCancelledDuringBefore() throws Exception { var child = spy(new MyLeaf(UniqueId.root("leaf", "child container"))); root.addChild(child); when(root.before(any())).thenAnswer(invocation -> { cancellationToken.cancel(); return invocation.callRealMethod(); }); executor.execute(); var inOrder = inOrder(listener, root, child); inOrder.verify(listener).executionStarted(root); inOrder.verify(root).before(any()); inOrder.verify(root).execute(any(), any()); inOrder.verify(child).nodeSkipped(any(), eq(child), eq(NodeTestTask.CANCELLED_SKIP_RESULT)); inOrder.verify(listener).executionSkipped(child, NodeTestTask.CANCELLED_SKIP_RESULT.getReason().orElseThrow()); inOrder.verify(root).after(any()); inOrder.verify(root).cleanUp(any()); inOrder.verify(listener).executionFinished(root, TestExecutionResult.successful()); inOrder.verifyNoMoreInteractions(); } // ------------------------------------------------------------------- private static class MyEngineExecutionContext implements EngineExecutionContext { } @NullMarked private static class MyContainer extends AbstractTestDescriptor implements Node { MyContainer(UniqueId uniqueId) { super(uniqueId, uniqueId.toString()); } @Override public Type getType() { return Type.CONTAINER; } } @NullMarked private static class MyLeaf extends AbstractTestDescriptor implements Node { MyLeaf(UniqueId uniqueId) { super(uniqueId, uniqueId.toString()); } @Override public MyEngineExecutionContext execute(MyEngineExecutionContext context, DynamicTestExecutor dynamicTestExecutor) { return context; } @Override public Type getType() { return Type.TEST; } } @NullMarked private static class MyContainerAndTestTestCase extends AbstractTestDescriptor implements Node { MyContainerAndTestTestCase(UniqueId uniqueId) { super(uniqueId, uniqueId.toString()); } @Override public Type getType() { return Type.CONTAINER_AND_TEST; } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/LockManagerTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_KEY; import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode.READ; import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode.READ_WRITE; import java.util.Collection; import java.util.List; import java.util.Set; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode; /** * @since 1.3 */ class LockManagerTests { private final LockManager lockManager = new LockManager(); @Test void returnsNopLockWithoutExclusiveResources() { Collection resources = Set.of(); var locks = getLocks(resources, NopLock.class); assertThat(locks).isEmpty(); } @Test void returnsSingleLockForSingleExclusiveResource() { Collection resources = Set.of(new ExclusiveResource("foo", READ)); var locks = getLocks(resources, SingleLock.class); assertThat(locks).hasSize(1); assertThat(locks.getFirst()).isInstanceOf(ReadLock.class); } @Test void returnsCompositeLockForMultipleDifferentExclusiveResources() { Collection resources = List.of( // new ExclusiveResource("a", READ), // new ExclusiveResource("b", READ_WRITE)); var locks = getLocks(resources, CompositeLock.class); assertThat(locks).hasSize(2); assertThat(locks.get(0)).isInstanceOf(ReadLock.class); assertThat(locks.get(1)).isInstanceOf(WriteLock.class); } @Test void reusesSameLockForExclusiveResourceWithSameKey() { Collection resources = Set.of(new ExclusiveResource("foo", READ)); var locks1 = getLocks(resources, SingleLock.class); var locks2 = getLocks(resources, SingleLock.class); assertThat(locks1).hasSize(1); assertThat(locks2).hasSize(1); assertThat(locks1.getFirst()).isSameAs(locks2.getFirst()); } @Test void returnsWriteLockForExclusiveResourceWithBothLockModes() { Collection resources = List.of( // new ExclusiveResource("bar", READ), // new ExclusiveResource("foo", READ), // new ExclusiveResource("foo", READ_WRITE), // new ExclusiveResource("bar", READ_WRITE)); var locks = getLocks(resources, CompositeLock.class); assertThat(locks).hasSize(2); assertThat(locks.get(0)).isInstanceOf(WriteLock.class); assertThat(locks.get(1)).isInstanceOf(WriteLock.class); } @ParameterizedTest @EnumSource void globalLockComesFirst(LockMode globalLockMode) { Collection resources = List.of( // new ExclusiveResource("___foo", READ), // new ExclusiveResource("foo", READ_WRITE), // new ExclusiveResource(GLOBAL_KEY, globalLockMode), // new ExclusiveResource("bar", READ_WRITE)); var locks = getLocks(resources, CompositeLock.class); assertThat(locks).hasSize(4); assertThat(locks.get(0)).isEqualTo(getSingleLock(GLOBAL_KEY, globalLockMode)); assertThat(locks.get(1)).isEqualTo(getSingleLock("___foo", READ)); assertThat(locks.get(2)).isEqualTo(getSingleLock("bar", READ_WRITE)); assertThat(locks.get(3)).isEqualTo(getSingleLock("foo", READ_WRITE)); } @Test void usesSingleInstanceForGlobalReadLock() { var lock = lockManager.getLockForResources(List.of(ExclusiveResource.GLOBAL_READ)); assertThat(lock) // .isInstanceOf(SingleLock.class) // .isSameAs(lockManager.getLockForResource(ExclusiveResource.GLOBAL_READ)); } @Test void usesSingleInstanceForGlobalReadWriteLock() { var lock = lockManager.getLockForResources(List.of(ExclusiveResource.GLOBAL_READ_WRITE)); assertThat(lock) // .isInstanceOf(SingleLock.class) // .isSameAs(lockManager.getLockForResource(ExclusiveResource.GLOBAL_READ_WRITE)); } private Lock getSingleLock(String key, LockMode lockMode) { return getLocks(Set.of(new ExclusiveResource(key, lockMode)), SingleLock.class).getFirst(); } private List getLocks(Collection resources, Class type) { var lock = lockManager.getLockForResources(resources); assertThat(lock).isInstanceOf(type); return ResourceLockSupport.getLocks(lock); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/MemoryLeakTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; /** * Integration tests intended to verify that memory leaks do not * exist with regard to the "context" held by {@link NodeTestTask}. * * @since 5.3.1 * @see GitHub issue #1578 */ // Explicitly specifying Lifecycle.PER_METHOD to be certain that the // test instance state is recreated for every test method executed. @TestInstance(Lifecycle.PER_METHOD) class MemoryLeakTests { // Allocate 500 MB of memory per test method. // // If the test instance is garbage collected, this should not cause any // problems for the JUnit build; however, if the instances of this test // class are NOT garbage collected, we should run out of memory pretty // quickly since the instances of this test class would consume 5GB of // heap space. final byte[] state = new byte[524_288_000]; @Test void test01() { } @Test void test02() { } @Test void test03() { } @Test void test04() { } @Test void test05() { } @Test void test06() { } @Test void test07() { } @Test void test08() { } @Test void test09() { } @Test void test10() { } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/NodeTreeWalkerIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.InstanceOfAssertFactories.LIST; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_READ; import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_READ_WRITE; import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode.READ; import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode.READ_WRITE; import static org.junit.platform.engine.support.hierarchical.Node.ExecutionMode.SAME_THREAD; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.concurrent.locks.Lock; import java.util.function.Function; import org.jspecify.annotations.NullMarked; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.ResourceAccessMode; import org.junit.jupiter.api.parallel.ResourceLock; import org.junit.jupiter.api.parallel.Resources; import org.junit.jupiter.engine.JupiterTestEngine; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; import org.junit.platform.engine.support.descriptor.EngineDescriptor; /** * @since 1.3 */ class NodeTreeWalkerIntegrationTests { LockManager lockManager = new LockManager(); NodeTreeWalker nodeTreeWalker = new NodeTreeWalker(lockManager); @Test void pullUpExclusiveChildResourcesToTestClass() { var engineDescriptor = discover(TestCaseWithResourceLock.class); var advisor = nodeTreeWalker.walk(engineDescriptor); var testClassDescriptor = getOnlyElement(engineDescriptor.getChildren()); assertThat(advisor.getResourceLock(testClassDescriptor)).extracting(allLocks()) // .isEqualTo(List.of(getLock(GLOBAL_READ), getReadWriteLock("a"), getReadWriteLock("b"))); assertThat(advisor.getForcedExecutionMode(testClassDescriptor)).isEmpty(); var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren()); assertThat(advisor.getResourceLock(testMethodDescriptor)).extracting(allLocks()).isEqualTo(List.of()); assertThat(advisor.getForcedExecutionMode(testMethodDescriptor)).contains(SAME_THREAD); } @Test void setsForceExecutionModeForChildrenWithWriteLocksOnClass() { var engineDescriptor = discover(TestCaseWithResourceWriteLockOnClass.class); var advisor = nodeTreeWalker.walk(engineDescriptor); var testClassDescriptor = getOnlyElement(engineDescriptor.getChildren()); assertThat(advisor.getResourceLock(testClassDescriptor)).extracting(allLocks()) // .isEqualTo(List.of(getLock(GLOBAL_READ), getReadWriteLock("a"))); assertThat(advisor.getForcedExecutionMode(testClassDescriptor)).isEmpty(); var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren()); assertThat(advisor.getResourceLock(testMethodDescriptor)).extracting(allLocks()).isEqualTo(List.of()); assertThat(advisor.getForcedExecutionMode(testMethodDescriptor)).contains(SAME_THREAD); } @Test void doesntSetForceExecutionModeForChildrenWithReadLocksOnClass() { var engineDescriptor = discover(TestCaseWithResourceReadLockOnClass.class); var advisor = nodeTreeWalker.walk(engineDescriptor); var testClassDescriptor = getOnlyElement(engineDescriptor.getChildren()); assertThat(advisor.getResourceLock(testClassDescriptor)).extracting(allLocks()) // .isEqualTo(List.of(getLock(GLOBAL_READ), getReadLock("a"))); assertThat(advisor.getForcedExecutionMode(testClassDescriptor)).isEmpty(); var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren()); assertThat(advisor.getResourceLock(testMethodDescriptor)).extracting(allLocks()).isEqualTo(List.of()); assertThat(advisor.getForcedExecutionMode(testMethodDescriptor)).isEmpty(); } @Test void setsForceExecutionModeForChildrenWithReadLocksOnClassAndWriteLockOnTest() { var engineDescriptor = discover(TestCaseWithResourceReadLockOnClassAndWriteClockOnTestCase.class); var advisor = nodeTreeWalker.walk(engineDescriptor); var testClassDescriptor = getOnlyElement(engineDescriptor.getChildren()); assertThat(advisor.getResourceLock(testClassDescriptor)).extracting(allLocks()) // .isEqualTo(List.of(getLock(GLOBAL_READ), getReadWriteLock("a"))); assertThat(advisor.getForcedExecutionMode(testClassDescriptor)).isEmpty(); var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren()); assertThat(advisor.getResourceLock(testMethodDescriptor)).extracting(allLocks()).isEqualTo(List.of()); assertThat(advisor.getForcedExecutionMode(testMethodDescriptor)).contains(SAME_THREAD); } @Test void doesntSetForceExecutionModeForChildrenWithReadLocksOnClassAndReadLockOnTest() { var engineDescriptor = discover(TestCaseWithResourceReadLockOnClassAndReadClockOnTestCase.class); var advisor = nodeTreeWalker.walk(engineDescriptor); var testClassDescriptor = getOnlyElement(engineDescriptor.getChildren()); assertThat(advisor.getResourceLock(testClassDescriptor)).extracting(allLocks()) // .isEqualTo(List.of(getLock(GLOBAL_READ), getReadLock("a"), getReadLock("b"))); assertThat(advisor.getForcedExecutionMode(testClassDescriptor)).isEmpty(); var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren()); assertThat(advisor.getResourceLock(testMethodDescriptor)).extracting(allLocks()).isEqualTo(List.of()); assertThat(advisor.getForcedExecutionMode(testMethodDescriptor)).isEmpty(); } @Test void leavesResourceLockOnTestMethodWhenClassDoesNotUseResource() { var engineDescriptor = discover(TestCaseWithoutResourceLock.class); var advisor = nodeTreeWalker.walk(engineDescriptor); var testClassDescriptor = getOnlyElement(engineDescriptor.getChildren()); assertThat(advisor.getResourceLock(testClassDescriptor)).extracting(allLocks()) // .isEqualTo(List.of(getLock(GLOBAL_READ))); assertThat(advisor.getForcedExecutionMode(testClassDescriptor)).isEmpty(); assertThat(testClassDescriptor.getChildren()).hasSize(2); var children = testClassDescriptor.getChildren().iterator(); var testMethodDescriptor = children.next(); assertThat(advisor.getResourceLock(testMethodDescriptor)).extracting(allLocks()) // .isEqualTo(List.of(getReadWriteLock("a"))); assertThat(advisor.getForcedExecutionMode(testMethodDescriptor)).isEmpty(); var nestedTestClassDescriptor = children.next(); assertThat(advisor.getResourceLock(nestedTestClassDescriptor)).extracting(allLocks()) // .isEqualTo(List.of(getReadWriteLock("b"), getReadWriteLock("c"))); assertThat(advisor.getForcedExecutionMode(nestedTestClassDescriptor)).isEmpty(); var nestedTestMethodDescriptor = getOnlyElement(nestedTestClassDescriptor.getChildren()); assertThat(advisor.getResourceLock(nestedTestMethodDescriptor)).extracting(allLocks()).isEqualTo(List.of()); assertThat(advisor.getForcedExecutionMode(nestedTestMethodDescriptor)).contains(SAME_THREAD); } @Test void coarsensGlobalLockToEngineDescriptorChild() { var engineDescriptor = discover(TestCaseWithGlobalLockRequiringChild.class); var advisor = nodeTreeWalker.walk(engineDescriptor); var testClassDescriptor = getOnlyElement(engineDescriptor.getChildren()); assertThat(advisor.getResourceLock(testClassDescriptor)).extracting(allLocks()) // .isEqualTo(List.of(getLock(GLOBAL_READ_WRITE))); assertThat(advisor.getForcedExecutionMode(testClassDescriptor)).isEmpty(); var nestedTestClassDescriptor = getOnlyElement(testClassDescriptor.getChildren()); assertThat(advisor.getResourceLock(nestedTestClassDescriptor)).extracting(allLocks()) // .isEqualTo(List.of()); assertThat(advisor.getForcedExecutionMode(nestedTestClassDescriptor)).contains(SAME_THREAD); var testMethodDescriptor = getOnlyElement(nestedTestClassDescriptor.getChildren()); assertThat(advisor.getResourceLock(testMethodDescriptor)).extracting(allLocks()) // .isEqualTo(List.of()); assertThat(advisor.getForcedExecutionMode(testMethodDescriptor)).contains(SAME_THREAD); } @Test void putsGlobalReadLockOnFirstNodeThatRequiresIt() { var engineDescriptor = new EngineDescriptor(UniqueId.forEngine("dummy"), "Dummy"); var containerWithoutBehavior = new NodeStub(engineDescriptor.getUniqueId().append("container", "1"), "Container 1") // .withGlobalReadLockRequired(false); var test1 = new NodeStub(containerWithoutBehavior.getUniqueId().append("test", "1"), "Test 1") // .withExclusiveResource(new ExclusiveResource("key1", READ_WRITE)); containerWithoutBehavior.addChild(test1); var containerWithBehavior = new NodeStub(engineDescriptor.getUniqueId().append("container", "2"), "Container 2") // .withGlobalReadLockRequired(true); var test2 = new NodeStub(containerWithBehavior.getUniqueId().append("test", "2"), "Test 2") // .withExclusiveResource(new ExclusiveResource("key2", READ_WRITE)); containerWithBehavior.addChild(test2); engineDescriptor.addChild(containerWithoutBehavior); engineDescriptor.addChild(containerWithBehavior); var advisor = nodeTreeWalker.walk(engineDescriptor); assertThat(advisor.getResourceLock(containerWithoutBehavior)) // .extracting(allLocks(), LIST) // .isEmpty(); assertThat(advisor.getResourceLock(test1)) // .extracting(allLocks(), LIST) // .containsExactly(getLock(GLOBAL_READ), getReadWriteLock("key1")); assertThat(advisor.getResourceLock(containerWithBehavior)) // .extracting(allLocks(), LIST) // .containsExactly(getLock(GLOBAL_READ)); assertThat(advisor.getResourceLock(test2)) // .extracting(allLocks(), LIST) // .containsExactly(getReadWriteLock("key2")); } @Test void doesNotAllowExclusiveResourcesWithoutRequiringGlobalReadLock() { var engineDescriptor = new EngineDescriptor(UniqueId.forEngine("dummy"), "Dummy"); var invalidNode = new NodeStub(engineDescriptor.getUniqueId().append("container", "1"), "Container") // .withGlobalReadLockRequired(false) // .withExclusiveResource(new ExclusiveResource("key", READ_WRITE)); engineDescriptor.addChild(invalidNode); assertPreconditionViolationFor(() -> nodeTreeWalker.walk(engineDescriptor)) // .withMessage("Node requiring exclusive resources must also require global read lock: " + invalidNode); } private static Function> allLocks() { return ResourceLockSupport::getLocks; } private Lock getReadWriteLock(String key) { return getLock(new ExclusiveResource(key, READ_WRITE)); } private Lock getReadLock(String key) { return getLock(new ExclusiveResource(key, READ)); } private Lock getLock(ExclusiveResource exclusiveResource) { return getOnlyElement(ResourceLockSupport.getLocks(lockManager.getLockForResource(exclusiveResource))); } private TestDescriptor discover(Class testClass) { var discoveryRequest = request().selectors(selectClass(testClass)).build(); return new JupiterTestEngine().discover(discoveryRequest, UniqueId.forEngine("junit-jupiter")); } @ResourceLock("a") static class TestCaseWithResourceLock { @Test @ResourceLock("b") void test() { } } static class TestCaseWithoutResourceLock { @Test @ResourceLock("a") void test() { } @Nested @ResourceLock("c") class NestedTestCaseWithResourceLock { @Test @ResourceLock("b") void test() { } } } @ResourceLock(Resources.SYSTEM_PROPERTIES) static class TestCaseWithGlobalLockRequiringChild { @Nested class NestedTestCaseWithResourceLock { @Test @ResourceLock(ExclusiveResource.GLOBAL_KEY) void test() { } } } @ResourceLock("a") static class TestCaseWithResourceWriteLockOnClass { @Test void test() { } } @ResourceLock(value = "a", mode = ResourceAccessMode.READ) static class TestCaseWithResourceReadLockOnClass { @Test void test() { } } @ResourceLock(value = "a", mode = ResourceAccessMode.READ) static class TestCaseWithResourceReadLockOnClassAndWriteClockOnTestCase { @Test @ResourceLock("a") void test() { } } @ResourceLock(value = "a", mode = ResourceAccessMode.READ) static class TestCaseWithResourceReadLockOnClassAndReadClockOnTestCase { @Test @ResourceLock(value = "b", mode = ResourceAccessMode.READ) void test() { } } @NullMarked static class NodeStub extends AbstractTestDescriptor implements Node { private final Set exclusiveResources = new LinkedHashSet<>(); private boolean globalReadLockRequired = true; NodeStub(UniqueId uniqueId, String displayName) { super(uniqueId, displayName); } NodeStub withExclusiveResource(ExclusiveResource exclusiveResource) { exclusiveResources.add(exclusiveResource); return this; } NodeStub withGlobalReadLockRequired(boolean globalReadLockRequired) { this.globalReadLockRequired = globalReadLockRequired; return this; } @Override public boolean isGlobalReadLockRequired() { return globalReadLockRequired; } @Override public Type getType() { throw new UnsupportedOperationException("should not be called"); } @Override public Set getExclusiveResources() { return exclusiveResources; } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Constants.DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.DEFAULT_EXECUTION_MODE_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.PARALLEL_CONFIG_EXECUTOR_SERVICE_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.PARALLEL_CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.PARALLEL_CONFIG_STRATEGY_PROPERTY_NAME; import static org.junit.jupiter.api.Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME; import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ_WRITE; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasses; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_KEY; import static org.junit.platform.testkit.engine.EventConditions.container; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.EventConditions.started; import static org.junit.platform.testkit.engine.EventConditions.test; import static org.junit.platform.testkit.engine.EventConditions.type; import static org.junit.platform.testkit.engine.EventType.REPORTING_ENTRY_PUBLISHED; import java.net.URL; import java.net.URLClassLoader; import java.time.Instant; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantLock; import java.util.function.UnaryOperator; import java.util.stream.IntStream; import java.util.stream.Stream; import org.assertj.core.api.Condition; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DynamicContainer; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.MethodOrderer.MethodName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.TestReporter; import org.junit.jupiter.api.extension.AfterTestExecutionCallback; import org.junit.jupiter.api.extension.DynamicTestInvocationContext; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.InvocationInterceptor; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.Isolated; import org.junit.jupiter.api.parallel.ResourceLock; import org.junit.jupiter.params.ParameterizedClass; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.engine.support.descriptor.MethodSource; import org.junit.platform.engine.support.hierarchical.ParallelHierarchicalTestExecutorServiceFactory.ParallelExecutorServiceType; import org.junit.platform.testkit.engine.EngineExecutionResults; import org.junit.platform.testkit.engine.EngineTestKit; import org.junit.platform.testkit.engine.Event; import org.junit.platform.testkit.engine.Events; /** * @since 1.3 */ @ParameterizedClass @EnumSource(ParallelExecutorServiceType.class) record ParallelExecutionIntegrationTests(ParallelExecutorServiceType executorServiceType) { @Test void successfulParallelTest(TestReporter reporter) { var events = executeConcurrentlySuccessfully(3, SuccessfulParallelTestCase.class).list(); var startedTimestamps = getTimestampsFor(events, event(test(), started())); var finishedTimestamps = getTimestampsFor(events, event(test(), finishedSuccessfully())); reporter.publishEntry("startedTimestamps", startedTimestamps.toString()); reporter.publishEntry("finishedTimestamps", finishedTimestamps.toString()); assertThat(startedTimestamps).hasSize(3); assertThat(finishedTimestamps).hasSize(3); assertThat(startedTimestamps).allMatch(startTimestamp -> finishedTimestamps.stream().noneMatch( finishedTimestamp -> finishedTimestamp.isBefore(startTimestamp))); assertThat(ThreadReporter.getThreadNames(events)).hasSize(3); } @Test void failingTestWithoutLock() { var events = executeConcurrently(3, FailingWithoutLockTestCase.class).list(); assertThat(events.stream().filter(event(test(), finishedWithFailure())::matches)).hasSize(2); } @Test void successfulTestWithMethodLock() { var events = executeConcurrentlySuccessfully(3, SuccessfulWithMethodLockTestCase.class).list(); assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(3); assertThat(ThreadReporter.getThreadNames(events)).hasSize(3); } @Test void successfulTestWithClassLock() { var events = executeConcurrentlySuccessfully(3, SuccessfulWithClassLockTestCase.class).list(); assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(3); assertThat(ThreadReporter.getThreadNames(events)).hasSize(1); } @Test void testCaseWithFactory() { var events = executeConcurrentlySuccessfully(3, TestCaseWithTestFactory.class).list(); assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(3); assertThat(ThreadReporter.getThreadNames(events)).hasSize(1); } @Test void customContextClassLoader() { var currentThread = Thread.currentThread(); var currentLoader = currentThread.getContextClassLoader(); var smilingLoader = new URLClassLoader("(-:", new URL[0], ClassLoader.getSystemClassLoader()); currentThread.setContextClassLoader(smilingLoader); try { var events = executeConcurrentlySuccessfully(3, SuccessfulWithMethodLockTestCase.class).list(); assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(3); assertThat(ThreadReporter.getThreadNames(events)).hasSize(3); assertThat(ThreadReporter.getLoaderNames(events)).containsExactly("(-:"); } finally { currentThread.setContextClassLoader(currentLoader); } } @RepeatedTest(10) void mixingClassAndMethodLevelLocks() { var events = executeConcurrentlySuccessfully(4, TestCaseWithSortedLocks.class, TestCaseWithUnsortedLocks.class).list(); assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(6); assertThat(ThreadReporter.getThreadNames(events).count()).isLessThanOrEqualTo(2); } @RepeatedTest(10) void locksOnNestedTests() { var events = executeConcurrentlySuccessfully(3, TestCaseWithNestedLocks.class).list(); assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(6); assertThat(ThreadReporter.getThreadNames(events)).hasSize(1); } @Test void afterHooksAreCalledAfterConcurrentDynamicTestsAreFinished() { var events = executeConcurrentlySuccessfully(3, ConcurrentDynamicTestCase.class).list(); assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(1); var timestampedEvents = ConcurrentDynamicTestCase.events; assertThat(timestampedEvents.get("afterEach")).isAfterOrEqualTo(timestampedEvents.get("dynamicTestFinished")); } /** * @since 1.4 * @see gh-1688 */ @Test void threadInterruptedByUserCode() { var events = executeConcurrentlySuccessfully(3, InterruptedThreadTestCase.class).list(); assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(4); } @Test void executesTestTemplatesWithResourceLocksInSameThread() { var events = executeConcurrentlySuccessfully(2, ConcurrentTemplateTestCase.class).list(); assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(10); assertThat(ThreadReporter.getThreadNames(events)).hasSize(1); } @Test void executesClassesInParallelIfEnabledViaConfigurationParameter() { ParallelClassesTestCase.GLOBAL_BARRIER.reset(); var configParams = Map.of(DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME, "concurrent"); var results = executeWithFixedParallelism(3, configParams, ParallelClassesTestCaseA.class, ParallelClassesTestCaseB.class, ParallelClassesTestCaseC.class); results.testEvents().assertStatistics(stats -> stats.succeeded(9)); assertThat(ThreadReporter.getThreadNames(results.allEvents().list())).hasSize(3); var testClassA = findFirstTestDescriptor(results, container(ParallelClassesTestCaseA.class)); assertThat(ThreadReporter.getThreadNames(getEventsOfChildren(results, testClassA))).hasSize(1); var testClassB = findFirstTestDescriptor(results, container(ParallelClassesTestCaseB.class)); assertThat(ThreadReporter.getThreadNames(getEventsOfChildren(results, testClassB))).hasSize(1); var testClassC = findFirstTestDescriptor(results, container(ParallelClassesTestCaseC.class)); assertThat(ThreadReporter.getThreadNames(getEventsOfChildren(results, testClassC))).hasSize(1); } @Test void executesMethodsInParallelIfEnabledViaConfigurationParameter() { ParallelMethodsTestCase.barriersPerClass.clear(); var configParams = Map.of( // DEFAULT_EXECUTION_MODE_PROPERTY_NAME, "concurrent", // DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME, "same_thread"); var results = executeWithFixedParallelism(3, configParams, ParallelMethodsTestCaseA.class, ParallelMethodsTestCaseB.class, ParallelMethodsTestCaseC.class); results.testEvents().assertStatistics(stats -> stats.succeeded(9)); assertThat(ThreadReporter.getThreadNames(results.allEvents().list())).hasSizeGreaterThanOrEqualTo(3); var testClassA = findFirstTestDescriptor(results, container(ParallelMethodsTestCaseA.class)); assertThat(ThreadReporter.getThreadNames(getEventsOfChildren(results, testClassA))).hasSize(3); var testClassB = findFirstTestDescriptor(results, container(ParallelMethodsTestCaseB.class)); assertThat(ThreadReporter.getThreadNames(getEventsOfChildren(results, testClassB))).hasSize(3); var testClassC = findFirstTestDescriptor(results, container(ParallelMethodsTestCaseC.class)); assertThat(ThreadReporter.getThreadNames(getEventsOfChildren(results, testClassC))).hasSize(3); } @Test void canRunTestsIsolatedFromEachOther() { executeConcurrentlySuccessfully(2, IsolatedTestCase.class); } @Test void canRunTestsIsolatedFromEachOtherWithNestedCases() { executeConcurrentlySuccessfully(4, NestedIsolatedTestCase.class); } @Test void canRunTestsIsolatedFromEachOtherAcrossClasses() { executeConcurrentlySuccessfully(4, IndependentClasses.A.class, IndependentClasses.B.class); } @RepeatedTest(10) void canRunTestsIsolatedFromEachOtherAcrossClassesWithOtherResourceLocks() { executeConcurrentlySuccessfully(4, IndependentClasses.B.class, IndependentClasses.C.class); } @Test void runsIsolatedTestsLastToMaximizeParallelism() { var configParams = Map.of( // DEFAULT_EXECUTION_MODE_PROPERTY_NAME, "concurrent", // PARALLEL_CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME, "3" // ); Class[] testClasses = { IsolatedTestCase.class, SuccessfulParallelTestCase.class }; var events = executeWithFixedParallelism(3, configParams, testClasses) // .allEvents() // .assertStatistics(it -> it.failed(0)); List parallelTestMethodEvents = events.reportingEntryPublished() // .filter(e -> e.getTestDescriptor().getSource() // .filter(it -> // it instanceof MethodSource methodSource && SuccessfulParallelTestCase.class.equals(methodSource.getJavaClass()) // ).isPresent() // ) // .toList(); assertThat(ThreadReporter.getThreadNames(parallelTestMethodEvents)).hasSize(3); var parallelClassFinish = getOnlyElement(getTimestampsFor(events.list(), event(container(SuccessfulParallelTestCase.class), finishedSuccessfully()))); var isolatedClassStart = getOnlyElement( getTimestampsFor(events.list(), event(container(IsolatedTestCase.class), started()))); assertThat(isolatedClassStart).isAfterOrEqualTo(parallelClassFinish); } @ParameterizedTest @ValueSource(classes = { IsolatedMethodFirstTestCase.class, IsolatedMethodLastTestCase.class, IsolatedNestedMethodFirstTestCase.class, IsolatedNestedMethodLastTestCase.class }) void canRunTestsIsolatedFromEachOtherWhenDeclaredOnMethodLevel(Class testClass) { List events = executeConcurrentlySuccessfully(1, testClass).list(); assertThat(ThreadReporter.getThreadNames(events)).hasSize(1); } @ParameterizedTest @ValueSource(strings = { "testFactoryImplicitContainerAndExplicitChildExecutionMode", "testFactoryExplicitContainerAndExplicitChildExecutionModeOnContainer", "testFactoryExplicitContainerAndExplicitChildExecutionModeOnEachChild" }) void allowsToControlExecutionModeOfDynamicTestsAndContainers(String methodName) { var results = executeWithFixedParallelism(3, Map.of(), List.of(selectMethod(ConcurrentDynamicContainerTestCase.class, methodName))); results.testEvents().assertStatistics(it -> it.succeeded(4)); assertThat(ThreadReporter.getThreadNames(results.testEvents().list())).hasSize(2); } @Isolated("testing") static class IsolatedTestCase { static AtomicInteger sharedResource; static CountDownLatch countDownLatch; @BeforeAll static void initialize() { sharedResource = new AtomicInteger(); countDownLatch = new CountDownLatch(2); } @Test void a() throws Exception { incrementBlockAndCheck(sharedResource, countDownLatch); } @Test void b() throws Exception { storeAndBlockAndCheck(sharedResource, countDownLatch); } } static class NestedIsolatedTestCase { static AtomicInteger sharedResource; static CountDownLatch countDownLatch; @BeforeAll static void initialize() { sharedResource = new AtomicInteger(); countDownLatch = new CountDownLatch(6); } @Test void a() throws Exception { storeAndBlockAndCheck(sharedResource, countDownLatch); } @Test void b() throws Exception { storeAndBlockAndCheck(sharedResource, countDownLatch); } @Nested class Inner { @Test void a() throws Exception { storeAndBlockAndCheck(sharedResource, countDownLatch); } @Test void b() throws Exception { storeAndBlockAndCheck(sharedResource, countDownLatch); } @Nested @Isolated class InnerInner { @Test void a() throws Exception { incrementBlockAndCheck(sharedResource, countDownLatch); } @Test void b() throws Exception { storeAndBlockAndCheck(sharedResource, countDownLatch); } } } } @ExtendWith(ThreadReporter.class) static class IsolatedMethodFirstTestCase { static AtomicInteger sharedResource; static CountDownLatch countDownLatch; @BeforeAll static void initialize() { sharedResource = new AtomicInteger(); countDownLatch = new CountDownLatch(2); } @Test @ResourceLock(value = GLOBAL_KEY, mode = READ_WRITE) // effectively @Isolated void test1() throws InterruptedException { incrementBlockAndCheck(sharedResource, countDownLatch); } @Test @ResourceLock(value = "b", mode = READ_WRITE) void test2() throws InterruptedException { incrementBlockAndCheck(sharedResource, countDownLatch); } } @ExtendWith(ThreadReporter.class) static class IsolatedMethodLastTestCase { static AtomicInteger sharedResource; static CountDownLatch countDownLatch; @BeforeAll static void initialize() { sharedResource = new AtomicInteger(); countDownLatch = new CountDownLatch(2); } @Test @ResourceLock(value = "b", mode = READ_WRITE) void test1() throws InterruptedException { incrementBlockAndCheck(sharedResource, countDownLatch); } @Test @ResourceLock(value = GLOBAL_KEY, mode = READ_WRITE) // effectively @Isolated void test2() throws InterruptedException { incrementBlockAndCheck(sharedResource, countDownLatch); } } @ExtendWith(ThreadReporter.class) static class IsolatedNestedMethodFirstTestCase { static AtomicInteger sharedResource; static CountDownLatch countDownLatch; @BeforeAll static void initialize() { sharedResource = new AtomicInteger(); countDownLatch = new CountDownLatch(2); } @Nested class Test1 { @Test @ResourceLock(value = GLOBAL_KEY, mode = READ_WRITE) // effectively @Isolated void test1() throws InterruptedException { incrementBlockAndCheck(sharedResource, countDownLatch); } } @Nested class Test2 { @Test @ResourceLock(value = "b", mode = READ_WRITE) void test2() throws InterruptedException { incrementBlockAndCheck(sharedResource, countDownLatch); } } } @ExtendWith(ThreadReporter.class) static class IsolatedNestedMethodLastTestCase { static AtomicInteger sharedResource; static CountDownLatch countDownLatch; @BeforeAll static void initialize() { sharedResource = new AtomicInteger(); countDownLatch = new CountDownLatch(2); } @Nested class Test1 { @Test @ResourceLock(value = "b", mode = READ_WRITE) void test1() throws InterruptedException { incrementBlockAndCheck(sharedResource, countDownLatch); } } @Nested class Test2 { @Test @ResourceLock(value = GLOBAL_KEY, mode = READ_WRITE) // effectively @Isolated void test2() throws InterruptedException { incrementBlockAndCheck(sharedResource, countDownLatch); } } } @SuppressWarnings("NewClassNamingConvention") static class IndependentClasses { static AtomicInteger sharedResource = new AtomicInteger(); static CountDownLatch countDownLatch = new CountDownLatch(4); static class A { @Test void a() throws Exception { storeAndBlockAndCheck(sharedResource, countDownLatch); } @Test void b() throws Exception { storeAndBlockAndCheck(sharedResource, countDownLatch); } } @Isolated static class B { @Test void a() throws Exception { incrementBlockAndCheck(sharedResource, countDownLatch); } @Test void b() throws Exception { storeAndBlockAndCheck(sharedResource, countDownLatch); } } @ResourceLock("other") static class C { @Test void a() throws Exception { storeAndBlockAndCheck(sharedResource, countDownLatch); } @Test void b() throws Exception { storeAndBlockAndCheck(sharedResource, countDownLatch); } } } private List getEventsOfChildren(EngineExecutionResults results, TestDescriptor container) { return results.testEvents().filter( event -> event.getTestDescriptor().getParent().orElseThrow().equals(container)).toList(); } private TestDescriptor findFirstTestDescriptor(EngineExecutionResults results, Condition condition) { return results.allEvents().filter(condition::matches).map(Event::getTestDescriptor).findFirst().orElseThrow(); } private List getTimestampsFor(List events, Condition condition) { // @formatter:off return events.stream() .filter(condition::matches) .map(Event::getTimestamp) .toList(); // @formatter:on } private Events executeConcurrentlySuccessfully(int parallelism, Class... testClasses) { var events = executeConcurrently(parallelism, testClasses); try { return events.assertStatistics(it -> it.failed(0)); } catch (AssertionError error) { events.debug(); throw error; } } private Events executeConcurrently(int parallelism, Class... testClasses) { Map configParams = Map.of(DEFAULT_EXECUTION_MODE_PROPERTY_NAME, "concurrent"); return executeWithFixedParallelism(parallelism, configParams, testClasses) // .allEvents(); } private EngineExecutionResults executeWithFixedParallelism(int parallelism, Map configParams, Class... testClasses) { return executeWithFixedParallelism(parallelism, configParams, selectClasses(testClasses)); } private EngineExecutionResults executeWithFixedParallelism(int parallelism, Map configParams, List selectors) { return EngineTestKit.engine("junit-jupiter") // .selectors(selectors) // .configurationParameter(PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, String.valueOf(true)) // .configurationParameter(PARALLEL_CONFIG_EXECUTOR_SERVICE_PROPERTY_NAME, executorServiceType.name()) // .configurationParameter(PARALLEL_CONFIG_STRATEGY_PROPERTY_NAME, "fixed") // .configurationParameter(PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME, String.valueOf(parallelism)) // .configurationParameters(configParams) // .execute(); } // ------------------------------------------------------------------------- @ExtendWith(ThreadReporter.class) @Execution(SAME_THREAD) static class SuccessfulParallelTestCase { static AtomicInteger sharedResource; static CountDownLatch countDownLatch; @BeforeAll static void initialize() { sharedResource = new AtomicInteger(); countDownLatch = new CountDownLatch(3); } @Test @Execution(CONCURRENT) void firstTest() throws Exception { incrementAndBlock(sharedResource, countDownLatch); } @Test @Execution(CONCURRENT) void secondTest() throws Exception { incrementAndBlock(sharedResource, countDownLatch); } @Test @Execution(CONCURRENT) void thirdTest() throws Exception { incrementAndBlock(sharedResource, countDownLatch); } } @ExtendWith(ThreadReporter.class) static class FailingWithoutLockTestCase { static AtomicInteger sharedResource; static CountDownLatch countDownLatch; @BeforeAll static void initialize() { sharedResource = new AtomicInteger(); countDownLatch = new CountDownLatch(3); } @Test void firstTest() throws Exception { incrementBlockAndCheck(sharedResource, countDownLatch); } @Test void secondTest() throws Exception { incrementBlockAndCheck(sharedResource, countDownLatch); } @Test void thirdTest() throws Exception { incrementBlockAndCheck(sharedResource, countDownLatch); } } @ExtendWith(ThreadReporter.class) static class SuccessfulWithMethodLockTestCase { static AtomicInteger sharedResource; static CountDownLatch countDownLatch; @BeforeAll static void initialize() { sharedResource = new AtomicInteger(); countDownLatch = new CountDownLatch(3); } @Test @ResourceLock("sharedResource") void firstTest() throws Exception { incrementBlockAndCheck(sharedResource, countDownLatch); } @Test @ResourceLock("sharedResource") void secondTest() throws Exception { incrementBlockAndCheck(sharedResource, countDownLatch); } @Test @ResourceLock("sharedResource") void thirdTest() throws Exception { incrementBlockAndCheck(sharedResource, countDownLatch); } } @ExtendWith(ThreadReporter.class) @ResourceLock("sharedResource") static class SuccessfulWithClassLockTestCase { static AtomicInteger sharedResource; static CountDownLatch countDownLatch; @BeforeAll static void initialize() { sharedResource = new AtomicInteger(); countDownLatch = new CountDownLatch(3); } @Test void firstTest() throws Exception { incrementBlockAndCheck(sharedResource, countDownLatch); } @Test void secondTest() throws Exception { incrementBlockAndCheck(sharedResource, countDownLatch); } @Test void thirdTest() throws Exception { incrementBlockAndCheck(sharedResource, countDownLatch); } } static class TestCaseWithTestFactory { @TestFactory @Execution(SAME_THREAD) Stream testFactory(TestReporter testReporter) { var sharedResource = new AtomicInteger(0); var countDownLatch = new CountDownLatch(3); return IntStream.range(0, 3).mapToObj(i -> dynamicTest("test " + i, () -> { incrementBlockAndCheck(sharedResource, countDownLatch); testReporter.publishEntry("thread", Thread.currentThread().getName()); })); } } private static final ReentrantLock A = new ReentrantLock(); private static final ReentrantLock B = new ReentrantLock(); @ExtendWith(ThreadReporter.class) @ResourceLock("A") static class TestCaseWithSortedLocks { @ResourceLock("B") @Test void firstTest() { assertTrue(A.tryLock()); assertTrue(B.tryLock()); } @Execution(CONCURRENT) @ResourceLock("B") @Test void secondTest() { assertTrue(A.tryLock()); assertTrue(B.tryLock()); } @ResourceLock("B") @Test void thirdTest() { assertTrue(A.tryLock()); assertTrue(B.tryLock()); } @AfterEach void unlock() { B.unlock(); A.unlock(); } } @ExtendWith(ThreadReporter.class) @ResourceLock("B") static class TestCaseWithUnsortedLocks { @ResourceLock("A") @Test void firstTest() { assertTrue(B.tryLock()); assertTrue(A.tryLock()); } @Execution(CONCURRENT) @ResourceLock("A") @Test void secondTest() { assertTrue(B.tryLock()); assertTrue(A.tryLock()); } @ResourceLock("A") @Test void thirdTest() { assertTrue(B.tryLock()); assertTrue(A.tryLock()); } @AfterEach void unlock() { A.unlock(); B.unlock(); } } @ExtendWith(ThreadReporter.class) @ResourceLock("A") static class TestCaseWithNestedLocks { @ResourceLock("B") @Test void firstTest() { assertTrue(A.tryLock()); assertTrue(B.tryLock()); } @Execution(CONCURRENT) @ResourceLock("B") @Test void secondTest() { assertTrue(A.tryLock()); assertTrue(B.tryLock()); } @Test void thirdTest() { assertTrue(A.tryLock()); assertTrue(B.tryLock()); } @AfterEach void unlock() { A.unlock(); B.unlock(); } @Nested @ResourceLock("B") class B { @ResourceLock("A") @Test void firstTest() { assertTrue(B.tryLock()); assertTrue(A.tryLock()); } @ResourceLock("A") @Test void secondTest() { assertTrue(B.tryLock()); assertTrue(A.tryLock()); } @Test void thirdTest() { assertTrue(B.tryLock()); assertTrue(A.tryLock()); } } } @Execution(CONCURRENT) static class ConcurrentDynamicTestCase { static Map events; @BeforeAll static void beforeAll() { events = new ConcurrentHashMap<>(); } @AfterEach void afterEach() { events.put("afterEach", Instant.now()); } @TestFactory DynamicTest testFactory() { return dynamicTest("slow", () -> { Thread.sleep(100); events.put("dynamicTestFinished", Instant.now()); }); } } @TestMethodOrder(MethodName.class) static class InterruptedThreadTestCase { @Test void test1() { Thread.currentThread().interrupt(); } @Test void test2() throws InterruptedException { Thread.sleep(10); } @Test void test3() { Thread.currentThread().interrupt(); } @Test void test4() throws InterruptedException { Thread.sleep(10); } } @Execution(CONCURRENT) @ExtendWith(ThreadReporter.class) static class ConcurrentTemplateTestCase { @RepeatedTest(10) @ResourceLock("a") void repeatedTest() throws Exception { Thread.sleep(100); } } @ExtendWith(ThreadReporter.class) static abstract class BarrierTestCase { @Test void test1() throws Exception { getBarrier().await(); } @Test void test2() throws Exception { getBarrier().await(); } @Test void test3() throws Exception { getBarrier().await(); } abstract CyclicBarrier getBarrier(); } static class ParallelMethodsTestCase extends BarrierTestCase { static final Map, CyclicBarrier> barriersPerClass = new ConcurrentHashMap<>(); @Override CyclicBarrier getBarrier() { return barriersPerClass.computeIfAbsent(this.getClass(), key -> new CyclicBarrier(3)); } } static class ParallelClassesTestCase extends BarrierTestCase { static final CyclicBarrier GLOBAL_BARRIER = new CyclicBarrier(3); @Override CyclicBarrier getBarrier() { return GLOBAL_BARRIER; } } @SuppressWarnings("NewClassNamingConvention") static class ParallelClassesTestCaseA extends ParallelClassesTestCase { } @SuppressWarnings("NewClassNamingConvention") static class ParallelClassesTestCaseB extends ParallelClassesTestCase { } @SuppressWarnings("NewClassNamingConvention") static class ParallelClassesTestCaseC extends ParallelClassesTestCase { } @SuppressWarnings("NewClassNamingConvention") static class ParallelMethodsTestCaseA extends ParallelMethodsTestCase { } @SuppressWarnings("NewClassNamingConvention") static class ParallelMethodsTestCaseB extends ParallelMethodsTestCase { } @SuppressWarnings("NewClassNamingConvention") static class ParallelMethodsTestCaseC extends ParallelMethodsTestCase { } @ExtendWith(ThreadReporter.class) static class ConcurrentDynamicContainerTestCase { @TestFactory @Execution(CONCURRENT) Stream testFactoryImplicitContainerAndExplicitChildExecutionMode() { return testFactory(container -> container.childExecutionMode(SAME_THREAD), UnaryOperator.identity()); } @TestFactory Stream testFactoryExplicitContainerAndExplicitChildExecutionModeOnContainer() { return testFactory(container -> container.executionMode(CONCURRENT).childExecutionMode(SAME_THREAD), UnaryOperator.identity()); } @TestFactory Stream testFactoryExplicitContainerAndExplicitChildExecutionModeOnEachChild() { return testFactory(container -> container.executionMode(CONCURRENT), test -> test.executionMode(SAME_THREAD)); } private Stream testFactory(UnaryOperator containerConfigurer, UnaryOperator testsConfigurer) { var sharedResource1 = new AtomicInteger(); var latch1 = new CountDownLatch(2); var sharedResource2 = new AtomicInteger(); var latch2 = new CountDownLatch(2); var tests1 = Stream.of( // dynamicTest(config -> testsConfigurer.apply(config) // .displayName("test1") // .executable(() -> incrementAndBlock(sharedResource1, latch1))), // dynamicTest(config -> testsConfigurer.apply(config) // .displayName("test2") // .executable(() -> incrementAndBlock(sharedResource2, latch2)))); var tests2 = Stream.of( // dynamicTest(config -> testsConfigurer.apply(config) // .displayName("test3") // .executable(() -> incrementAndBlock(sharedResource1, latch1))), // dynamicTest(config -> testsConfigurer.apply(config) // .displayName("test4") // .executable(() -> incrementAndBlock(sharedResource2, latch2)))); return Stream.of( // dynamicContainer(config -> containerConfigurer.apply(config) // .displayName("suite1") // .children(tests1)), // dynamicContainer(config -> containerConfigurer.apply(config) // .displayName("suite2") // .children(tests2))); } } private static void incrementBlockAndCheck(AtomicInteger sharedResource, CountDownLatch countDownLatch) throws InterruptedException { var value = incrementAndBlock(sharedResource, countDownLatch); assertEquals(value, sharedResource.get()); } @SuppressWarnings("ResultOfMethodCallIgnored") private static int incrementAndBlock(AtomicInteger sharedResource, CountDownLatch countDownLatch) throws InterruptedException { var value = sharedResource.incrementAndGet(); countDownLatch.countDown(); countDownLatch.await(estimateSimulatedTestDurationInMilliseconds(), MILLISECONDS); return value; } @SuppressWarnings("ResultOfMethodCallIgnored") private static void storeAndBlockAndCheck(AtomicInteger sharedResource, CountDownLatch countDownLatch) throws InterruptedException { var value = sharedResource.get(); countDownLatch.countDown(); countDownLatch.await(estimateSimulatedTestDurationInMilliseconds(), MILLISECONDS); assertEquals(value, sharedResource.get()); } /* * To simulate tests running in parallel tests will modify a shared * resource, simulate work by waiting, then check if the shared resource was * not modified by any other thread. * * Depending on system performance the simulation of work needs to be longer * on slower systems to ensure tests can run in parallel. * * Currently, CI is known to be slow. */ private static long estimateSimulatedTestDurationInMilliseconds() { var runningInCi = Boolean.parseBoolean(System.getenv("CI")); return runningInCi ? 1000 : 100; } @NullMarked static class ThreadReporter implements AfterTestExecutionCallback, InvocationInterceptor { private static Stream getLoaderNames(List events) { return getValues(events, "loader"); } private static Stream getThreadNames(List events) { return getValues(events, "thread"); } private static Stream getValues(List events, String key) { // @formatter:off return events.stream() .filter(type(REPORTING_ENTRY_PUBLISHED)::matches) .map(event -> event.getPayload(ReportEntry.class).orElseThrow()) .map(ReportEntry::getKeyValuePairs) .filter(keyValuePairs -> keyValuePairs.containsKey(key)) .map(keyValuePairs -> keyValuePairs.get(key)) .distinct(); // @formatter:on } @Override public void afterTestExecution(ExtensionContext context) { publishReportEntries(context); } @Override public void interceptDynamicTest(Invocation<@Nullable Void> invocation, DynamicTestInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { try { invocation.proceed(); } finally { publishReportEntries(extensionContext); } } private static void publishReportEntries(ExtensionContext context) { context.publishReportEntry("thread", Thread.currentThread().getName()); context.publishReportEntry("loader", Thread.currentThread().getContextClassLoader().getName()); } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ResourceLockSupport.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import java.util.List; import java.util.concurrent.locks.Lock; class ResourceLockSupport { static List getLocks(ResourceLock resourceLock) { if (resourceLock instanceof NopLock) { return List.of(); } if (resourceLock instanceof SingleLock lock) { return List.of(lock.getLock()); } return ((CompositeLock) resourceLock).getLocks(); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ResourceLockTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_READ; import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_READ_WRITE; import java.util.Arrays; import java.util.List; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.junit.jupiter.api.Test; import org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode; class ResourceLockTests { @Test void nopLocks() { assertCompatible(nopLock(), nopLock()); assertCompatible(nopLock(), singleLock(anyReadOnlyResource())); assertCompatible(nopLock(), compositeLock(anyReadOnlyResource())); } @Test void readOnlySingleLocks() { ExclusiveResource bR = readOnlyResource("b"); assertCompatible(singleLock(bR), nopLock()); assertCompatible(singleLock(bR), singleLock(bR)); assertIncompatible(singleLock(bR), singleLock(readWriteResource("b")), "read-write conflict"); assertIncompatible(singleLock(bR), singleLock(readOnlyResource("a")), "lock acquisition order"); assertCompatible(singleLock(bR), singleLock(readOnlyResource("c"))); assertIncompatible(singleLock(bR), singleLock(GLOBAL_READ), "lock acquisition order"); assertIncompatible(singleLock(bR), singleLock(GLOBAL_READ_WRITE), "lock acquisition order"); assertCompatible(singleLock(bR), compositeLock(bR, readOnlyResource("c"))); assertIncompatible(singleLock(bR), compositeLock(readOnlyResource("a1"), readOnlyResource("a2"), bR), "lock acquisition order"); } @Test void readWriteSingleLocks() { ExclusiveResource bRW = readWriteResource("b"); assertCompatible(singleLock(bRW), nopLock()); assertIncompatible(singleLock(bRW), singleLock(bRW), "isolation guarantees"); assertIncompatible(singleLock(bRW), compositeLock(bRW), "isolation guarantees"); assertIncompatible(singleLock(bRW), singleLock(readOnlyResource("a")), "lock acquisition order"); assertIncompatible(singleLock(bRW), singleLock(readOnlyResource("b")), "isolation guarantees"); assertIncompatible(singleLock(bRW), singleLock(readOnlyResource("c")), "isolation guarantees"); assertIncompatible(singleLock(bRW), singleLock(GLOBAL_READ), "lock acquisition order"); assertIncompatible(singleLock(bRW), singleLock(GLOBAL_READ_WRITE), "lock acquisition order"); assertIncompatible(singleLock(bRW), compositeLock(bRW, readOnlyResource("c")), "isolation guarantees"); assertIncompatible(singleLock(bRW), compositeLock(readOnlyResource("a1"), readOnlyResource("a2"), bRW), "lock acquisition order"); } @Test void globalReadLock() { assertCompatible(singleLock(GLOBAL_READ), nopLock()); assertCompatible(singleLock(GLOBAL_READ), singleLock(GLOBAL_READ)); assertCompatible(singleLock(GLOBAL_READ), singleLock(anyReadOnlyResource())); assertCompatible(singleLock(GLOBAL_READ), singleLock(anyReadWriteResource())); assertIncompatible(singleLock(GLOBAL_READ), singleLock(GLOBAL_READ_WRITE), "read-write conflict"); } @Test void readOnlyCompositeLocks() { ExclusiveResource bR = readOnlyResource("b"); assertCompatible(compositeLock(bR), nopLock()); assertCompatible(compositeLock(bR), singleLock(bR)); assertCompatible(compositeLock(bR), compositeLock(bR)); assertIncompatible(compositeLock(bR), singleLock(GLOBAL_READ), "lock acquisition order"); assertIncompatible(compositeLock(bR), singleLock(GLOBAL_READ_WRITE), "lock acquisition order"); assertIncompatible(compositeLock(bR), compositeLock(readOnlyResource("a")), "lock acquisition order"); assertCompatible(compositeLock(bR), compositeLock(readOnlyResource("c"))); assertIncompatible(compositeLock(bR), compositeLock(readWriteResource("b")), "read-write conflict"); assertIncompatible(compositeLock(bR), compositeLock(bR, readWriteResource("b")), "read-write conflict"); } @Test void readWriteCompositeLocks() { ExclusiveResource bRW = readWriteResource("b"); assertCompatible(compositeLock(bRW), nopLock()); assertIncompatible(compositeLock(bRW), singleLock(bRW), "isolation guarantees"); assertIncompatible(compositeLock(bRW), compositeLock(bRW), "isolation guarantees"); assertIncompatible(compositeLock(bRW), singleLock(readOnlyResource("a")), "lock acquisition order"); assertIncompatible(compositeLock(bRW), singleLock(readOnlyResource("b")), "isolation guarantees"); assertIncompatible(compositeLock(bRW), singleLock(readOnlyResource("c")), "isolation guarantees"); assertIncompatible(compositeLock(bRW), singleLock(GLOBAL_READ), "lock acquisition order"); assertIncompatible(compositeLock(bRW), singleLock(GLOBAL_READ_WRITE), "lock acquisition order"); assertIncompatible(compositeLock(bRW), compositeLock(readOnlyResource("a")), "lock acquisition order"); assertIncompatible(compositeLock(bRW), compositeLock(readOnlyResource("b"), readOnlyResource("c")), "isolation guarantees"); } private static void assertCompatible(ResourceLock first, ResourceLock second) { assertTrue(first.isCompatible(second), "Expected locks to be compatible:\n(1) %s\n(2) %s".formatted(first, second)); } private static void assertIncompatible(ResourceLock first, ResourceLock second, String reason) { assertFalse(first.isCompatible(second), "Expected locks to be incompatible due to %s:\n(1) %s\n(2) %s".formatted(reason, first, second)); } private static ResourceLock nopLock() { return NopLock.INSTANCE; } private static SingleLock singleLock(ExclusiveResource resource) { return new SingleLock(resource, anyLock()); } private static CompositeLock compositeLock(ExclusiveResource... resources) { return new CompositeLock(List.of(resources), Arrays.stream(resources).map(__ -> anyLock()).toList()); } private static ExclusiveResource anyReadOnlyResource() { return readOnlyResource("key"); } private static ExclusiveResource anyReadWriteResource() { return readWriteResource("key"); } private static ExclusiveResource readOnlyResource(String key) { return new ExclusiveResource(key, LockMode.READ); } private static ExclusiveResource readWriteResource(String key) { return new ExclusiveResource(key, LockMode.READ_WRITE); } private static Lock anyLock() { return new ReentrantLock(); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SameThreadExecutionIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import java.util.logging.Level; import java.util.logging.LogRecord; import org.junit.jupiter.api.MethodOrderer.MethodName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.fixtures.TrackLogRecords; import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.testkit.engine.EngineTestKit; /** * @since 1.4 */ class SameThreadExecutionIntegrationTests { /** * @see gh-1688 */ @Test void threadInterruptedByUserCode(@TrackLogRecords LogRecordListener listener) { EngineTestKit.engine("junit-jupiter")// .selectors(selectClass(InterruptedThreadTestCase.class))// .execute()// .testEvents()// .assertStatistics(stats -> stats.succeeded(4)); assertThat(firstDebugLogRecord(listener).getMessage()).matches( "Execution of TestDescriptor with display name .+test1.+ and " + "unique ID .+ failed to clear the 'interrupted status' flag " + "for the current thread. JUnit has cleared the flag, but you " + "may wish to investigate why the flag was not cleared by user code."); } private LogRecord firstDebugLogRecord(LogRecordListener listener) throws AssertionError { return listener.stream(NodeTestTask.class, Level.FINE).findFirst().orElseThrow( () -> new AssertionError("Failed to find debug log record")); } // ------------------------------------------------------------------------- @TestMethodOrder(MethodName.class) static class InterruptedThreadTestCase { @Test void test1() { Thread.currentThread().interrupt(); } @Test void test2() throws Exception { Thread.sleep(10); } @Test void test3() { Thread.currentThread().interrupt(); } @Test void test4() throws Exception { Thread.sleep(10); } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SingleLockTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.concurrent.locks.ReentrantLock; import org.junit.jupiter.api.Test; import org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode; /** * @since 1.3 */ class SingleLockTests { @Test @SuppressWarnings("resource") void acquire() throws Exception { var lock = new ReentrantLock(); new SingleLock(anyResource(), lock).acquire(); assertTrue(lock.isLocked()); } @Test @SuppressWarnings("resource") void release() throws Exception { var lock = new ReentrantLock(); new SingleLock(anyResource(), lock).acquire().close(); assertFalse(lock.isLocked()); } @Test @SuppressWarnings("resource") void tryAcquireAndRelease() { var lock = new ReentrantLock(); var singleLock = new SingleLock(anyResource(), lock); singleLock.tryAcquire(); assertTrue(lock.isLocked()); singleLock.release(); assertFalse(lock.isLocked()); } private static ExclusiveResource anyResource() { return new ExclusiveResource("key", LockMode.READ); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ThrowableCollectorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.platform.engine.TestExecutionResult.Status.ABORTED; import static org.junit.platform.engine.TestExecutionResult.Status.FAILED; import static org.junit.platform.engine.TestExecutionResult.Status.SUCCESSFUL; import java.util.Optional; import org.junit.jupiter.api.Test; /** * @since 1.6 */ class ThrowableCollectorTests { @Test void successfulExecution() { var collector = new ThrowableCollector(x -> true); collector.execute(() -> { }); var result = collector.toTestExecutionResult(); assertEquals(SUCCESSFUL, result.getStatus()); assertEquals(Optional.empty(), result.getThrowable()); } @Test void abortedExecution() { var customAbort = new CustomAbort(); var collector = new ThrowableCollector(CustomAbort.class::isInstance); collector.execute(() -> { throw customAbort; }); var result = collector.toTestExecutionResult(); assertEquals(ABORTED, result.getStatus()); assertSame(customAbort, result.getThrowable().get()); } @Test void failedExecution() { var assertionError = new AssertionError("assertion violated"); var collector = new ThrowableCollector(CustomAbort.class::isInstance); collector.execute(() -> { throw assertionError; }); var result = collector.toTestExecutionResult(); assertEquals(FAILED, result.getStatus()); assertSame(assertionError, result.getThrowable().get()); } private static class CustomAbort extends Error { private static final long serialVersionUID = 1L; } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/WorkerLeaseManagerTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static org.assertj.core.api.Assertions.assertThat; import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.Test; import org.junit.platform.engine.support.hierarchical.WorkerThreadPoolHierarchicalTestExecutorService.WorkerLeaseManager; class WorkerLeaseManagerTests { @Test void releasingIsIdempotent() { var released = new AtomicInteger(); var manager = new WorkerLeaseManager(1, __ -> released.incrementAndGet()); var lease = manager.tryAcquire(); assertThat(lease).isNotNull(); lease.release(); assertThat(released.get()).isEqualTo(1); lease.release(); assertThat(released.get()).isEqualTo(1); } @Test void leaseCanBeReacquired() throws Exception { var released = new AtomicInteger(); var manager = new WorkerLeaseManager(1, __ -> released.incrementAndGet()); var lease = manager.tryAcquire(); assertThat(lease).isNotNull(); lease.release(); assertThat(released.get()).isEqualTo(1); lease.reacquire(); assertThat(released.get()).isEqualTo(1); lease.release(); assertThat(released.get()).isEqualTo(2); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/WorkerThreadPoolHierarchicalTestExecutorServiceTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.hierarchical; import static java.util.Objects.requireNonNull; import static java.util.concurrent.Future.State.SUCCESS; import static java.util.function.Predicate.isEqual; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.junit.platform.commons.util.ExceptionUtils.throwAsUncheckedException; import static org.junit.platform.engine.TestDescriptor.Type.CONTAINER; import static org.junit.platform.engine.TestDescriptor.Type.TEST; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.net.URL; import java.net.URLClassLoader; import java.time.Instant; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.stream.Stream; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AutoClose; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode; import org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutorService.TestTask; import org.junit.platform.engine.support.hierarchical.Node.ExecutionMode; import org.junit.platform.fakes.TestDescriptorStub; import org.opentest4j.AssertionFailedError; /** * @since 6.1 */ @SuppressWarnings("resource") @Timeout(5) class WorkerThreadPoolHierarchicalTestExecutorServiceTests { @AutoClose @Nullable WorkerThreadPoolHierarchicalTestExecutorService service; @ParameterizedTest @EnumSource(ExecutionMode.class) void executesSingleTask(ExecutionMode executionMode) throws Exception { var task = new TestTaskStub(executionMode); var customClassLoader = new URLClassLoader(new URL[0], this.getClass().getClassLoader()); try (customClassLoader) { service = new WorkerThreadPoolHierarchicalTestExecutorService(configuration(1), customClassLoader); service.submit(task).get(); } task.assertExecutedSuccessfully(); var executionThread = task.executionThread(); assertThat(executionThread).isNotNull().isNotSameAs(Thread.currentThread()); assertThat(executionThread.getName()).matches("junit-\\d+-worker-1"); assertThat(executionThread.getContextClassLoader()).isSameAs(customClassLoader); } @Test void invokeAllMustBeExecutedFromWithinThreadPool() { var tasks = List.of(new TestTaskStub(ExecutionMode.CONCURRENT)); service = new WorkerThreadPoolHierarchicalTestExecutorService(configuration(1)); assertPreconditionViolationFor(() -> requiredService().invokeAll(tasks)) // .withMessage("invokeAll() must be called from a worker thread that belongs to this executor"); } @ParameterizedTest @EnumSource(ExecutionMode.class) void executesSingleChildInSameThreadRegardlessOfItsExecutionMode(ExecutionMode childExecutionMode) throws Exception { service = new WorkerThreadPoolHierarchicalTestExecutorService(configuration(1)); var child = new TestTaskStub(childExecutionMode); var root = new TestTaskStub(ExecutionMode.CONCURRENT, () -> requiredService().invokeAll(List.of(child))); service.submit(root).get(); root.assertExecutedSuccessfully(); child.assertExecutedSuccessfully(); assertThat(root.executionThread()).isNotNull(); assertThat(child.executionThread()).isSameAs(root.executionThread()); } @Test void executesTwoChildrenConcurrently() throws Exception { service = new WorkerThreadPoolHierarchicalTestExecutorService(configuration(2)); var latch = new CountDownLatch(2); Executable behavior = () -> { latch.countDown(); latch.await(); }; var children = List.of(new TestTaskStub(ExecutionMode.CONCURRENT, behavior), new TestTaskStub(ExecutionMode.CONCURRENT, behavior)); var root = new TestTaskStub(ExecutionMode.CONCURRENT, () -> requiredService().invokeAll(children)); service.submit(root).get(); root.assertExecutedSuccessfully(); assertThat(children).allSatisfy(TestTaskStub::assertExecutedSuccessfully); } @Test void executesTwoChildrenInSameThread() throws Exception { service = new WorkerThreadPoolHierarchicalTestExecutorService(configuration(1)); var children = List.of(new TestTaskStub(ExecutionMode.SAME_THREAD), new TestTaskStub(ExecutionMode.SAME_THREAD)); var root = new TestTaskStub(ExecutionMode.CONCURRENT, () -> requiredService().invokeAll(children)); service.submit(root).get(); assertThat(root.executionThread()).isNotNull(); assertThat(children).extracting(TestTaskStub::executionThread).containsOnly(root.executionThread()); root.assertExecutedSuccessfully(); assertThat(children).allSatisfy(TestTaskStub::assertExecutedSuccessfully); } @Test void acquiresResourceLockForRootTask() throws Exception { var resourceLock = mock(ResourceLock.class); when(resourceLock.acquire()).thenReturn(resourceLock); var task = new TestTaskStub(ExecutionMode.CONCURRENT).withResourceLock(resourceLock); service = new WorkerThreadPoolHierarchicalTestExecutorService(configuration(1)); service.submit(task).get(); task.assertExecutedSuccessfully(); var inOrder = inOrder(resourceLock); inOrder.verify(resourceLock).acquire(); inOrder.verify(resourceLock).close(); inOrder.verifyNoMoreInteractions(); } @Test void acquiresResourceLockForChildTasks() throws Exception { service = new WorkerThreadPoolHierarchicalTestExecutorService(configuration(2)); var resourceLock = mock(ResourceLock.class); when(resourceLock.tryAcquire()).thenReturn(true, false); when(resourceLock.acquire()).thenReturn(resourceLock); var child1 = new TestTaskStub(ExecutionMode.CONCURRENT).withResourceLock(resourceLock).withName("child1"); var child2 = new TestTaskStub(ExecutionMode.CONCURRENT).withResourceLock(resourceLock).withName("child2"); var children = List.of(child1, child2); var root = new TestTaskStub(ExecutionMode.SAME_THREAD, () -> requiredService().invokeAll(children)).withName( "root"); service.submit(root).get(); root.assertExecutedSuccessfully(); assertThat(children).allSatisfy(TestTaskStub::assertExecutedSuccessfully); assertThat(children).extracting(TestTaskStub::executionThread) // .filteredOn(isEqual(root.executionThread())).hasSizeLessThanOrEqualTo(2); verify(resourceLock, atLeast(2)).tryAcquire(); verify(resourceLock, atLeast(1)).acquire(); verify(resourceLock, times(2)).close(); } @Test void runsTasksWithoutConflictingLocksConcurrently() throws Exception { service = new WorkerThreadPoolHierarchicalTestExecutorService(configuration(3)); var resourceLock = new SingleLock(exclusiveResource(LockMode.READ_WRITE), new ReentrantLock()); var latch = new CountDownLatch(3); Executable behavior = () -> { latch.countDown(); latch.await(); }; var child1 = new TestTaskStub(ExecutionMode.CONCURRENT, behavior).withResourceLock(resourceLock).withName( "child1"); var child2 = new TestTaskStub(ExecutionMode.SAME_THREAD).withResourceLock(resourceLock).withName("child2"); var leaf1 = new TestTaskStub(ExecutionMode.CONCURRENT, behavior).withName("leaf1"); var leaf2 = new TestTaskStub(ExecutionMode.CONCURRENT, behavior).withName("leaf2"); var leaves = List.of(leaf1, leaf2); var child3 = new TestTaskStub(ExecutionMode.CONCURRENT, () -> requiredService().invokeAll(leaves)).withName( "child3"); var children = List.of(child1, child2, child3); var root = new TestTaskStub(ExecutionMode.SAME_THREAD, () -> requiredService().invokeAll(children)).withName( "root"); service.submit(root).get(); root.assertExecutedSuccessfully(); assertThat(children).allSatisfy(TestTaskStub::assertExecutedSuccessfully); assertThat(leaves).allSatisfy(TestTaskStub::assertExecutedSuccessfully); } @Test void processingQueueEntriesSkipsOverUnavailableResources() throws Exception { service = new WorkerThreadPoolHierarchicalTestExecutorService(configuration(2)); var resourceLock = new SingleLock(exclusiveResource(LockMode.READ_WRITE), new ReentrantLock()); var lockFreeChildrenStarted = new CountDownLatch(2); var child1Started = new CountDownLatch(1); Executable child1Behaviour = () -> { child1Started.countDown(); lockFreeChildrenStarted.await(); }; Executable child4Behaviour = () -> { lockFreeChildrenStarted.countDown(); child1Started.await(); }; var child1 = new TestTaskStub(ExecutionMode.CONCURRENT, child1Behaviour) // .withResourceLock(resourceLock) // .withName("child1"); var child2 = new TestTaskStub(ExecutionMode.CONCURRENT, lockFreeChildrenStarted::countDown) // .withName("child2"); // var child3 = new TestTaskStub(ExecutionMode.CONCURRENT) // .withResourceLock(resourceLock) // .withName("child3"); var child4 = new TestTaskStub(ExecutionMode.CONCURRENT, child4Behaviour) // .withName("child4"); var children = List.of(child1, child2, child3, child4); var root = new TestTaskStub(ExecutionMode.CONCURRENT, () -> requiredService().invokeAll(children)) // .withName("root"); service.submit(root).get(); root.assertExecutedSuccessfully(); assertThat(children).allSatisfy(TestTaskStub::assertExecutedSuccessfully); assertThat(child4.executionThread).isEqualTo(child2.executionThread); assertThat(child3.startTime).isAfterOrEqualTo(child2.startTime); } @Test void invokeAllQueueEntriesSkipsOverUnavailableResources() throws Exception { service = new WorkerThreadPoolHierarchicalTestExecutorService(configuration(2)); var resourceLock = new SingleLock(exclusiveResource(LockMode.READ_WRITE), new ReentrantLock()); var lockFreeChildrenStarted = new CountDownLatch(2); var child2Started = new CountDownLatch(1); Executable child1Behaviour = () -> { lockFreeChildrenStarted.countDown(); child2Started.await(); }; Executable child2Behaviour = () -> { child2Started.countDown(); lockFreeChildrenStarted.await(); }; var child1 = new TestTaskStub(ExecutionMode.CONCURRENT, child1Behaviour) // .withName("child1"); var child2 = new TestTaskStub(ExecutionMode.CONCURRENT, child2Behaviour) // .withResourceLock(resourceLock) // .withName("child2"); var child3 = new TestTaskStub(ExecutionMode.CONCURRENT) // .withResourceLock(resourceLock) // .withName("child3"); // var child4 = new TestTaskStub(ExecutionMode.CONCURRENT, lockFreeChildrenStarted::countDown) // .withName("child4"); var children = List.of(child1, child2, child3, child4); var root = new TestTaskStub(ExecutionMode.CONCURRENT, () -> requiredService().invokeAll(children)) // .withName("root"); service.submit(root).get(); root.assertExecutedSuccessfully(); assertThat(children).allSatisfy(TestTaskStub::assertExecutedSuccessfully); assertThat(child1.executionThread).isEqualTo(child4.executionThread); assertThat(child3.startTime).isAfterOrEqualTo(child4.startTime); } @Test void prioritizesChildrenOfStartedContainers() throws Exception { service = new WorkerThreadPoolHierarchicalTestExecutorService(configuration(2, 2)); var leafSubmitted = new CountDownLatch(1); var child2AndLeafStarted = new CountDownLatch(2); var leaf = new TestTaskStub(ExecutionMode.CONCURRENT, child2AndLeafStarted::countDown) // .withName("leaf").withLevel(3); Executable child3Behavior = () -> { var future = requiredService().submit(leaf); leafSubmitted.countDown(); child2AndLeafStarted.await(); future.get(); }; var child1 = new TestTaskStub(ExecutionMode.CONCURRENT, leafSubmitted::await) // .withName("child1").withLevel(2); var child2 = new TestTaskStub(ExecutionMode.CONCURRENT, child2AndLeafStarted::countDown) // .withName("child2").withLevel(2); var child3 = new TestTaskStub(ExecutionMode.CONCURRENT, child3Behavior) // .withType(CONTAINER).withName("child3").withLevel(2); var root = new TestTaskStub(ExecutionMode.SAME_THREAD, () -> requiredService().invokeAll(List.of(child1, child2, child3))) // .withName("root").withLevel(1); service.submit(root).get(); root.assertExecutedSuccessfully(); assertThat(List.of(root, child1, child2, leaf, child3)).allSatisfy(TestTaskStub::assertExecutedSuccessfully); assertThat(leaf.startTime).isBeforeOrEqualTo(child2.startTime); assertThat(leaf.executionThread).isSameAs(child2.executionThread).isNotSameAs(child3.executionThread); } @Test void prioritizesTestsOverContainers() throws Exception { service = new WorkerThreadPoolHierarchicalTestExecutorService(configuration(2)); var leavesStarted = new CountDownLatch(2); var leaf = new TestTaskStub(ExecutionMode.CONCURRENT, leavesStarted::countDown) // .withName("leaf").withLevel(3).withType(TEST); var child1 = new TestTaskStub(ExecutionMode.CONCURRENT, () -> requiredService().submit(leaf).get()) // .withName("child1").withLevel(2).withType(CONTAINER); var child2 = new TestTaskStub(ExecutionMode.CONCURRENT, leavesStarted::countDown) // .withName("child2").withLevel(2).withType(TEST); var child3 = new TestTaskStub(ExecutionMode.SAME_THREAD, leavesStarted::await) // .withName("child3").withLevel(2).withType(TEST); var root = new TestTaskStub(ExecutionMode.SAME_THREAD, () -> requiredService().invokeAll(List.of(child1, child2, child3))) // .withName("root").withLevel(1); service.submit(root).get(); root.assertExecutedSuccessfully(); assertThat(List.of(child1, child2, child3)).allSatisfy(TestTaskStub::assertExecutedSuccessfully); leaf.assertExecutedSuccessfully(); assertThat(child2.startTime).isBeforeOrEqualTo(child1.startTime); } @Test void limitsWorkerThreadsToMaxPoolSize() throws Exception { service = new WorkerThreadPoolHierarchicalTestExecutorService(configuration(3, 3)); CountDownLatch latch = new CountDownLatch(3); Executable behavior = () -> { latch.countDown(); latch.await(); }; var leaf1a = new TestTaskStub(ExecutionMode.CONCURRENT, behavior) // .withName("leaf1a").withLevel(3); var leaf1b = new TestTaskStub(ExecutionMode.CONCURRENT, behavior) // .withName("leaf1b").withLevel(3); var leaf2a = new TestTaskStub(ExecutionMode.CONCURRENT, behavior) // .withName("leaf2a").withLevel(3); var leaf2b = new TestTaskStub(ExecutionMode.CONCURRENT, behavior) // .withName("leaf2b").withLevel(3); // When executed, there are 2 worker threads active and 1 available. // Both invokeAlls race each other trying to start 1 more. var child1 = new TestTaskStub(ExecutionMode.CONCURRENT, () -> requiredService().invokeAll(List.of(leaf1a, leaf1b))) // .withName("child1").withLevel(2); var child2 = new TestTaskStub(ExecutionMode.CONCURRENT, () -> requiredService().invokeAll(List.of(leaf2a, leaf2b))) // .withName("child2").withLevel(2); var root = new TestTaskStub(ExecutionMode.SAME_THREAD, () -> requiredService().invokeAll(List.of(child1, child2))) // .withName("root").withLevel(1); service.submit(root).get(); assertThat(List.of(root, child1, child2, leaf1a, leaf1b, leaf2a, leaf2b)) // .allSatisfy(TestTaskStub::assertExecutedSuccessfully); assertThat(Stream.of(leaf1a, leaf1b, leaf2a, leaf2b).map(TestTaskStub::executionThread).distinct()) // .hasSize(3); } @Test void stealsBlockingChildren() throws Exception { service = new WorkerThreadPoolHierarchicalTestExecutorService(configuration(2, 2)); var child1Started = new CountDownLatch(1); var leaf2aStarted = new CountDownLatch(1); var leaf2bStarted = new CountDownLatch(1); var readWriteLock = new ReentrantReadWriteLock(); var readOnlyResourceLock = new SingleLock(exclusiveResource(LockMode.READ), readWriteLock.readLock()) { @Override public void release() { super.release(); try { leaf2aStarted.await(); } catch (InterruptedException e) { fail(e); } } }; var readWriteResourceLock = new SingleLock(exclusiveResource(LockMode.READ_WRITE), readWriteLock.writeLock()); var leaf2a = new TestTaskStub(ExecutionMode.CONCURRENT, leaf2aStarted::countDown) // .withResourceLock(readWriteResourceLock) // .withName("leaf2a").withLevel(3); var leaf2b = new TestTaskStub(ExecutionMode.SAME_THREAD, leaf2bStarted::countDown) // .withName("leaf2b").withLevel(3); var child1 = new TestTaskStub(ExecutionMode.CONCURRENT, () -> { child1Started.countDown(); leaf2bStarted.await(); }) // .withResourceLock(readOnlyResourceLock) // .withName("child1").withLevel(2); var child2 = new TestTaskStub(ExecutionMode.SAME_THREAD, () -> { child1Started.await(); requiredService().invokeAll(List.of(leaf2a, leaf2b)); }) // .withName("child2").withLevel(2); var root = new TestTaskStub(ExecutionMode.SAME_THREAD, () -> requiredService().invokeAll(List.of(child1, child2))) // .withName("root").withLevel(1); service.submit(root).get(); assertThat(List.of(root, child1, child2, leaf2a, leaf2b)) // .allSatisfy(TestTaskStub::assertExecutedSuccessfully); assertThat(List.of(leaf2a, leaf2b)).map(TestTaskStub::executionThread) // .containsOnly(child2.executionThread); } @Test void executesChildrenInOrder() throws Exception { service = new WorkerThreadPoolHierarchicalTestExecutorService(configuration(1, 1)); var leaf1a = new TestTaskStub(ExecutionMode.CONCURRENT) // .withName("leaf1a").withLevel(2); var leaf1b = new TestTaskStub(ExecutionMode.CONCURRENT) // .withName("leaf1b").withLevel(2); var leaf1c = new TestTaskStub(ExecutionMode.CONCURRENT) // .withName("leaf1c").withLevel(2); var leaf1d = new TestTaskStub(ExecutionMode.CONCURRENT) // .withName("leaf1d").withLevel(2); List children = Arrays.asList(leaf1d, leaf1a, leaf1b, leaf1c); Collections.shuffle(children); var root = new TestTaskStub(ExecutionMode.SAME_THREAD, // () -> requiredService().invokeAll(children)) // .withName("root").withLevel(1); service.submit(root).get(); assertThat(List.of(root, leaf1a, leaf1b, leaf1c, leaf1d)) // .allSatisfy(TestTaskStub::assertExecutedSuccessfully); assertThat(children) // .extracting(TestTaskStub::startTime) // .isSorted(); } @Test void testsAreStolenRatherThanContainers() throws Exception { service = new WorkerThreadPoolHierarchicalTestExecutorService(configuration(2, 2)); // Execute tasks pairwise CyclicBarrier cyclicBarrier = new CyclicBarrier(2); Executable behavior = cyclicBarrier::await; // With half of the leaves being containers var container1 = new TestTaskStub(ExecutionMode.CONCURRENT, behavior) // .withName("container1").withType(CONTAINER).withLevel(2); var container2 = new TestTaskStub(ExecutionMode.CONCURRENT, behavior) // .withName("container2").withType(CONTAINER).withLevel(2); var container3 = new TestTaskStub(ExecutionMode.CONCURRENT, behavior) // .withName("container3").withType(CONTAINER).withLevel(2); // And half of the leaves being tests, to be stolen var test1 = new TestTaskStub(ExecutionMode.CONCURRENT, behavior) // .withName("test1").withType(TEST).withLevel(2); var test2 = new TestTaskStub(ExecutionMode.CONCURRENT, behavior) // .withName("test2").withType(TEST).withLevel(2); var test3 = new TestTaskStub(ExecutionMode.CONCURRENT, behavior) // .withName("test3").withType(TEST).withLevel(2); var root = new TestTaskStub(ExecutionMode.SAME_THREAD, () -> requiredService().invokeAll(List.of(container1, container2, container3, test1, test2, test3))) // .withName("root").withLevel(1); service.submit(root).get(); assertThat(List.of(root, container1, container2, container3, test1, test2, test3)) // .allSatisfy(TestTaskStub::assertExecutedSuccessfully); // If the last test node was stolen assertThat(container1.executionThread).isNotEqualTo(test3.executionThread); // Then it must follow that the test nodes were stolen assertThat(Stream.of(container1, container2, container3)) // .extracting(TestTaskStub::executionThread) // .containsOnly(container1.executionThread); assertThat(Stream.of(test1, test2, test3)) // .extracting(TestTaskStub::executionThread) // .containsOnly(test3.executionThread); assertThat(Stream.of(container1, container2, container3)) // .extracting(TestTaskStub::startTime) // .isSorted(); assertThat(Stream.of(test1, test2, test3)) // .extracting(TestTaskStub::startTime) // .isSorted(); } @Test void stealsDynamicChildren() throws Exception { service = new WorkerThreadPoolHierarchicalTestExecutorService(configuration(2, 2)); var child1Started = new CountDownLatch(1); var child2Finished = new CountDownLatch(1); var child1 = new TestTaskStub(ExecutionMode.CONCURRENT, () -> { child1Started.countDown(); child2Finished.await(); }) // .withName("child1").withLevel(2); var child2 = new TestTaskStub(ExecutionMode.CONCURRENT, child2Finished::countDown) // .withName("child2").withLevel(2); var root = new TestTaskStub(ExecutionMode.SAME_THREAD, () -> { var future1 = requiredService().submit(child1); child1Started.await(); var future2 = requiredService().submit(child2); future1.get(); future2.get(); }) // .withName("root").withLevel(1); service.submit(root).get(); assertThat(Stream.of(root, child1, child2)) // .allSatisfy(TestTaskStub::assertExecutedSuccessfully); assertThat(child2.executionThread).isEqualTo(root.executionThread).isNotEqualTo(child1.executionThread); } @Test void stealsDynamicChildrenInOrder() throws Exception { service = new WorkerThreadPoolHierarchicalTestExecutorService(configuration(2, 2)); var child1Started = new CountDownLatch(1); var childrenSubmitted = new CountDownLatch(1); var childrenFinished = new CountDownLatch(2); var child1 = new TestTaskStub(ExecutionMode.CONCURRENT, () -> { child1Started.countDown(); childrenSubmitted.await(); }) // .withName("child1").withLevel(2); var child2 = new TestTaskStub(ExecutionMode.CONCURRENT, childrenFinished::countDown) // .withName("child2").withLevel(2); var child3 = new TestTaskStub(ExecutionMode.CONCURRENT, childrenFinished::countDown) // .withName("child3").withLevel(2); var root = new TestTaskStub(ExecutionMode.SAME_THREAD, () -> { var future1 = requiredService().submit(child1); child1Started.await(); var future2 = requiredService().submit(child2); var future3 = requiredService().submit(child3); childrenSubmitted.countDown(); childrenFinished.await(); future1.get(); future2.get(); future3.get(); }) // .withName("root").withLevel(1); service.submit(root).get(); assertThat(Stream.of(root, child1, child2, child3)) // .allSatisfy(TestTaskStub::assertExecutedSuccessfully); assertThat(List.of(child1, child2, child3)) // .extracting(TestTaskStub::startTime) // .isSorted(); } @Test void executesDynamicChildrenInSubmitOrder() throws Exception { service = new WorkerThreadPoolHierarchicalTestExecutorService(configuration(1, 1)); var child1 = new TestTaskStub(ExecutionMode.CONCURRENT) // .withName("child1").withLevel(2); var child2 = new TestTaskStub(ExecutionMode.CONCURRENT) // .withName("child2").withLevel(2); var child3 = new TestTaskStub(ExecutionMode.CONCURRENT) // .withName("child3").withLevel(2); var child4 = new TestTaskStub(ExecutionMode.CONCURRENT) // .withName("child3").withLevel(2); List children = Arrays.asList(child1, child2, child3, child4); Collections.shuffle(children); var root = new TestTaskStub(ExecutionMode.SAME_THREAD, () -> { var executor = requiredService(); var features = children.stream().map(executor::submit).toList(); for (var future : features) { future.get(); } }) // .withName("root").withLevel(1); service.submit(root).get(); assertThat(Stream.of(root, child1, child2)) // .allSatisfy(TestTaskStub::assertExecutedSuccessfully); assertThat(children) // .extracting(TestTaskStub::startTime) // .isSorted(); } @Test void stealsNestedDynamicChildren() throws Exception { service = new WorkerThreadPoolHierarchicalTestExecutorService(configuration(2, 2)); var barrier = new CyclicBarrier(2); var leaf1a = new TestTaskStub(ExecutionMode.CONCURRENT) // .withName("leaf1a").withLevel(3); var leaf1b = new TestTaskStub(ExecutionMode.CONCURRENT) // .withName("leaf1b").withLevel(3); var child1 = new TestTaskStub(ExecutionMode.CONCURRENT, () -> { barrier.await(); var futureA = requiredService().submit(leaf1a); barrier.await(); var futureB = requiredService().submit(leaf1b); futureA.get(); futureB.get(); barrier.await(); }) // .withName("child1").withLevel(2); var leaf2a = new TestTaskStub(ExecutionMode.CONCURRENT) // .withName("leaf2a").withLevel(3); var leaf2b = new TestTaskStub(ExecutionMode.CONCURRENT) // .withName("leaf2b").withLevel(3); var child2 = new TestTaskStub(ExecutionMode.CONCURRENT, () -> { barrier.await(); var futureA = requiredService().submit(leaf2a); barrier.await(); var futureB = requiredService().submit(leaf2b); futureB.get(); futureA.get(); barrier.await(); }) // .withName("child2").withLevel(2); var root = new TestTaskStub(ExecutionMode.SAME_THREAD, () -> { var future1 = requiredService().submit(child1); var future2 = requiredService().submit(child2); future1.get(); future2.get(); }) // .withName("root").withLevel(1); service.submit(root).get(); assertThat(Stream.of(root, child1, child2, leaf1a, leaf1b, leaf2a, leaf2b)) // .allSatisfy(TestTaskStub::assertExecutedSuccessfully); assertThat(child2.executionThread).isNotEqualTo(child1.executionThread); assertThat(child1.executionThread).isEqualTo(leaf1a.executionThread).isEqualTo(leaf1b.executionThread); assertThat(child2.executionThread).isEqualTo(leaf2a.executionThread).isEqualTo(leaf2b.executionThread); } @Test void stealsSiblingDynamicChildrenOnly() throws Exception { service = new WorkerThreadPoolHierarchicalTestExecutorService(configuration(2, 3)); var child1Started = new CountDownLatch(1); var child3Started = new CountDownLatch(1); var leaf2ASubmitted = new CountDownLatch(1); var leaf2AStarted = new CountDownLatch(1); var child1 = new TestTaskStub(ExecutionMode.CONCURRENT, () -> { child1Started.countDown(); leaf2ASubmitted.await(); }) // .withName("child1").withLevel(2); var leaf2a = new TestTaskStub(ExecutionMode.CONCURRENT, () -> { leaf2AStarted.countDown(); child3Started.await(); }) // .withName("leaf1a").withLevel(3); var child2 = new TestTaskStub(ExecutionMode.CONCURRENT, () -> { var futureA = requiredService().submit(leaf2a); leaf2ASubmitted.countDown(); leaf2AStarted.await(); futureA.get(); }) // .withName("child2").withType(CONTAINER).withLevel(2); var child3 = new TestTaskStub(ExecutionMode.CONCURRENT, child3Started::countDown) // .withName("child3").withLevel(2); var root = new TestTaskStub(ExecutionMode.SAME_THREAD, () -> { var future1 = requiredService().submit(child1); child1Started.await(); var future2 = requiredService().submit(child2); var future3 = requiredService().submit(child3); future1.get(); future2.get(); future3.get(); }) // .withName("root").withLevel(1); service.submit(root).get(); assertThat(Stream.of(root, child1, child2, leaf2a, child3)) // .allSatisfy(TestTaskStub::assertExecutedSuccessfully); assertThat(child2.executionThread).isNotEqualTo(child1.executionThread).isNotEqualTo(child3.executionThread); assertThat(child1.executionThread).isNotEqualTo(child3.executionThread); assertThat(child1.executionThread).isEqualTo(leaf2a.executionThread); } private static ExclusiveResource exclusiveResource(LockMode lockMode) { return new ExclusiveResource("key", lockMode); } private WorkerThreadPoolHierarchicalTestExecutorService requiredService() { return requireNonNull(service); } private static ParallelExecutionConfiguration configuration(int parallelism) { return configuration(parallelism, 256 + parallelism); } private static ParallelExecutionConfiguration configuration(int parallelism, int maxPoolSize) { return new DefaultParallelExecutionConfiguration(parallelism, parallelism, maxPoolSize, parallelism, 0, __ -> true); } @NullMarked private static final class TestTaskStub implements TestTask { private final ExecutionMode executionMode; private final Executable behavior; private ResourceLock resourceLock = NopLock.INSTANCE; private @Nullable String name; private int level = 1; private TestDescriptor.Type type = TEST; private final CompletableFuture<@Nullable Void> result = new CompletableFuture<>(); private volatile @Nullable Instant startTime; private volatile @Nullable Thread executionThread; TestTaskStub(ExecutionMode executionMode) { this(executionMode, () -> { }); } TestTaskStub(ExecutionMode executionMode, Executable behavior) { this.executionMode = executionMode; this.behavior = behavior; } TestTaskStub withName(String name) { this.name = name; return this; } TestTaskStub withLevel(int level) { this.level = level; return this; } TestTaskStub withType(TestDescriptor.Type type) { this.type = type; return this; } TestTaskStub withResourceLock(ResourceLock resourceLock) { this.resourceLock = resourceLock; return this; } @Override public ExecutionMode getExecutionMode() { return executionMode; } @Override public ResourceLock getResourceLock() { return resourceLock; } @Override public TestDescriptor getTestDescriptor() { var name = String.valueOf(this.name); var uniqueId = UniqueId.root("root", name); for (var i = 1; i < level; i++) { uniqueId = uniqueId.append("child", name); } return new TestDescriptorStub(uniqueId, name) { @Override public Type getType() { return type; } }; } @Override public void execute() { startTime = Instant.now(); Preconditions.condition(!result.isDone(), "task was already executed"); executionThread = Thread.currentThread(); try { behavior.execute(); result.complete(null); } catch (Throwable t) { result.completeExceptionally(t); throw throwAsUncheckedException(t); } } void assertExecutedSuccessfully() { if (result.isCompletedExceptionally()) { throw new AssertionFailedError("Failure during execution", result.exceptionNow()); } assertThat(result.state()).isEqualTo(SUCCESS); } @Nullable Thread executionThread() { return executionThread; } @Nullable Instant startTime() { return startTime; } @Override public String toString() { return "%s @ %s".formatted(new ToStringBuilder(this).append("name", name), Integer.toHexString(hashCode())); } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/store/NamespaceTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.store; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import org.junit.jupiter.api.Test; class NamespaceTests { @Test void namespacesEqualForSamePartsSequence() { Namespace ns1 = Namespace.create("part1", "part2"); Namespace ns2 = Namespace.create("part1", "part2"); Namespace ns3 = Namespace.create("part2", "part1"); assertEqualsAndHashCode(ns1, ns2, ns3); } @Test void orderOfNamespacePartsDoesMatter() { Namespace ns1 = Namespace.create("part1", "part2"); Namespace ns2 = Namespace.create("part2", "part1"); assertNotEquals(ns1, ns2); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStoreTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.engine.support.store; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.commons.test.ConcurrencyTestingUtils.executeConcurrently; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions; import java.io.Serial; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; /** * Unit tests for {@link NamespacedHierarchicalStore}. * * @since 5.0 */ public class NamespacedHierarchicalStoreTests { private final Object key = "key"; private final Object value = "value"; private final String namespace = "ns"; private final NamespacedHierarchicalStore.CloseAction closeAction = mock(); private final NamespacedHierarchicalStore grandParentStore = new NamespacedHierarchicalStore<>(null, closeAction); private final NamespacedHierarchicalStore parentStore = grandParentStore.newChild(); private final NamespacedHierarchicalStore store = parentStore.newChild(); @Nested class StoringValuesTests { @Test void getWithUnknownKeyReturnsNull() { assertNull(store.get(namespace, "unknown key")); } @Test void putAndGetWithSameKey() { store.put(namespace, key, value); assertEquals(value, store.get(namespace, key)); } @Test void valueCanBeReplaced() { store.put(namespace, key, value); Object newValue = new Object(); assertEquals(value, store.put(namespace, key, newValue)); assertEquals(newValue, store.get(namespace, key)); } @Test void valueIsComputedIfAbsent() { assertNull(store.get(namespace, key)); assertEquals(value, store.computeIfAbsent(namespace, key, __ -> value)); assertEquals(value, store.get(namespace, key)); } @Test void valueIsComputedIfNull() { assertNull(store.put(namespace, key, null)); assertEquals(value, store.computeIfAbsent(namespace, key, __ -> value)); assertEquals(value, store.get(namespace, key)); } @SuppressWarnings("deprecation") @Test void valueIsNotComputedIfPresentLocally() { store.put(namespace, key, value); assertEquals(value, store.getOrComputeIfAbsent(namespace, key, __ -> "a different value")); assertEquals(value, store.computeIfAbsent(namespace, key, __ -> "a different value")); assertEquals(value, store.get(namespace, key)); } @SuppressWarnings("deprecation") @Test void valueIsNotComputedIfPresentInParent() { parentStore.put(namespace, key, value); assertEquals(value, store.getOrComputeIfAbsent(namespace, key, __ -> "a different value")); assertEquals(value, store.computeIfAbsent(namespace, key, __ -> "a different value")); assertEquals(value, store.get(namespace, key)); } @SuppressWarnings("deprecation") @Test void valueIsNotComputedIfPresentInGrandParent() { grandParentStore.put(namespace, key, value); assertEquals(value, store.getOrComputeIfAbsent(namespace, key, __ -> "a different value")); assertEquals(value, store.computeIfAbsent(namespace, key, __ -> "a different value")); assertEquals(value, store.get(namespace, key)); } @SuppressWarnings("deprecation") @Test void nullIsAValidValueToPut() { store.put(namespace, key, null); assertNull(store.getOrComputeIfAbsent(namespace, key, __ -> "a different value")); assertNull(store.get(namespace, key)); assertEquals("a different value", store.computeIfAbsent(namespace, key, __ -> "a different value")); assertEquals("a different value", store.get(namespace, key)); } @SuppressWarnings("deprecation") @Test void keysCanBeRemoved() { store.put(namespace, key, value); assertEquals(value, store.remove(namespace, key)); assertNull(store.get(namespace, key)); assertEquals("a different value", store.getOrComputeIfAbsent(namespace, key, __ -> "a different value")); assertEquals("a different value", store.remove(namespace, key)); assertNull(store.get(namespace, key)); assertEquals("another different value", store.computeIfAbsent(namespace, key, __ -> "another different value")); assertEquals("another different value", store.remove(namespace, key)); assertNull(store.get(namespace, key)); } @Test void sameKeyWithDifferentNamespaces() { Object value1 = createObject("value1"); String namespace1 = "ns1"; Object value2 = createObject("value2"); String namespace2 = "ns2"; store.put(namespace1, key, value1); store.put(namespace2, key, value2); assertEquals(value1, store.get(namespace1, key)); assertEquals(value2, store.get(namespace2, key)); } @Test void valueIsComputedIfAbsentInDifferentNamespace() { String namespace1 = "ns1"; String namespace2 = "ns2"; assertEquals(value, store.computeIfAbsent(namespace1, key, __ -> value)); assertEquals(value, store.get(namespace1, key)); assertNull(store.get(namespace2, key)); } @Test void keyIsOnlyRemovedInGivenNamespace() { String namespace1 = "ns1"; String namespace2 = "ns2"; Object value1 = createObject("value1"); Object value2 = createObject("value2"); store.put(namespace1, key, value1); store.put(namespace2, key, value2); store.remove(namespace1, key); assertNull(store.get(namespace1, key)); assertEquals(value2, store.get(namespace2, key)); } @Test void getWithTypeSafetyAndInvalidRequiredTypeThrowsException() { Integer key = 42; String value = "enigma"; store.put(namespace, key, value); Exception exception = assertThrows(NamespacedHierarchicalStoreException.class, () -> store.get(namespace, key, Number.class)); assertEquals( "Object stored under key [42] is not of required type [java.lang.Number], but was [java.lang.String]: enigma", exception.getMessage()); } @Test void getWithTypeSafety() { Integer key = 42; String value = "enigma"; store.put(namespace, key, value); // The fact that we can declare this as a String suffices for testing the required type. String requiredTypeValue = store.get(namespace, key, String.class); assertEquals(value, requiredTypeValue); } @SuppressWarnings("DataFlowIssue") @Test void getWithTypeSafetyAndPrimitiveValueType() { String key = "enigma"; int value = 42; store.put(namespace, key, value); // The fact that we can declare this as an int/Integer suffices for testing the required type. int requiredInt = store.get(namespace, key, int.class); Integer requiredInteger = store.get(namespace, key, Integer.class); assertEquals(value, requiredInt); assertEquals(value, requiredInteger.intValue()); } @Test void getNullValueWithTypeSafety() { store.put(namespace, key, null); // The fact that we can declare this as a String suffices for testing the required type. String requiredTypeValue = store.get(namespace, key, String.class); assertNull(requiredTypeValue); } @SuppressWarnings("deprecation") @Test void getOrComputeIfAbsentWithTypeSafetyAndInvalidRequiredTypeThrowsException() { String key = "pi"; Float value = 3.14f; // Store a Float... store.put(namespace, key, value); // But declare that our function creates a String... Function defaultCreator = k -> "enigma"; Exception exception = assertThrows(NamespacedHierarchicalStoreException.class, () -> store.getOrComputeIfAbsent(namespace, key, defaultCreator, String.class)); assertEquals( "Object stored under key [pi] is not of required type [java.lang.String], but was [java.lang.Float]: 3.14", exception.getMessage()); } @Test void computeIfAbsentWithTypeSafetyAndInvalidRequiredTypeThrowsException() { String key = "pi"; Float value = 3.14f; // Store a Float... store.put(namespace, key, value); // But declare that our function creates a String... Function defaultCreator = k -> "enigma"; Exception exception = assertThrows(NamespacedHierarchicalStoreException.class, () -> store.computeIfAbsent(namespace, key, defaultCreator, String.class)); assertEquals( "Object stored under key [pi] is not of required type [java.lang.String], but was [java.lang.Float]: 3.14", exception.getMessage()); } @SuppressWarnings("deprecation") @Test void getOrComputeIfAbsentWithTypeSafety() { Integer key = 42; String value = "enigma"; // The fact that we can declare this as a String suffices for testing the required type. String computedValue = store.getOrComputeIfAbsent(namespace, key, k -> value, String.class); assertEquals(value, computedValue); } @Test void computeIfAbsentWithTypeSafety() { Integer key = 42; String value = "enigma"; // The fact that we can declare this as a String suffices for testing the required type. String computedValue = store.computeIfAbsent(namespace, key, __ -> value, String.class); assertEquals(value, computedValue); } @SuppressWarnings({ "DataFlowIssue", "deprecation" }) @Test void getOrComputeIfAbsentWithTypeSafetyAndPrimitiveValueType() { String key = "enigma"; int value = 42; // The fact that we can declare this as an int/Integer suffices for testing the required type. int computedInt = store.getOrComputeIfAbsent(namespace, key, k -> value, int.class); Integer computedInteger = store.getOrComputeIfAbsent(namespace, key, k -> value, Integer.class); assertEquals(value, computedInt); assertEquals(value, computedInteger.intValue()); } @Test void computeIfAbsentWithTypeSafetyAndPrimitiveValueType() { String key = "enigma"; int value = 42; // The fact that we can declare this as an int/Integer suffices for testing the required type. int computedInt = store.computeIfAbsent(namespace, key, k -> value, int.class); Integer computedInteger = store.computeIfAbsent(namespace, key, k -> value, Integer.class); assertEquals(value, computedInt); assertEquals(value, computedInteger.intValue()); } @SuppressWarnings("deprecation") @Test void getOrComputeIfAbsentWithExceptionThrowingCreatorFunction() { var e = assertThrows(ComputeException.class, () -> store.getOrComputeIfAbsent(namespace, key, __ -> { throw new ComputeException("boom"); })); assertSame(e, assertThrows(ComputeException.class, () -> store.get(namespace, key))); assertSame(e, assertThrows(ComputeException.class, () -> store.remove(namespace, key))); } @Test void computeIfAbsentWithExceptionThrowingCreatorFunction() { assertThrows(ComputeException.class, () -> store.computeIfAbsent(namespace, key, __ -> { throw new ComputeException("boom"); })); assertNull(store.get(namespace, key)); assertNull(store.remove(namespace, key)); } @SuppressWarnings("deprecation") @Test void getOrComputeIfAbsentDoesNotSeeComputeIfAbsentWithExceptionThrowingCreatorFunction() { assertThrows(ComputeException.class, () -> store.computeIfAbsent(namespace, key, __ -> { throw new ComputeException("boom"); })); assertNull(store.get(namespace, key)); assertEquals(value, store.getOrComputeIfAbsent(namespace, key, __ -> value)); } @SuppressWarnings("deprecation") @Test void computeIfAbsentSeesGetOrComputeIfAbsentWithExceptionThrowingCreatorFunction() { assertThrows(ComputeException.class, () -> store.getOrComputeIfAbsent(namespace, key, __ -> { throw new ComputeException("boom"); })); assertThrows(ComputeException.class, () -> store.get(namespace, key)); assertThrows(ComputeException.class, () -> store.computeIfAbsent(namespace, key, __ -> value)); } @Test void removeWithTypeSafetyAndInvalidRequiredTypeThrowsException() { Integer key = 42; String value = "enigma"; store.put(namespace, key, value); Exception exception = assertThrows(NamespacedHierarchicalStoreException.class, () -> store.remove(namespace, key, Number.class)); assertEquals( "Object stored under key [42] is not of required type [java.lang.Number], but was [java.lang.String]: enigma", exception.getMessage()); } @Test void removeWithTypeSafety() { Integer key = 42; String value = "enigma"; store.put(namespace, key, value); // The fact that we can declare this as a String suffices for testing the required type. String removedValue = store.remove(namespace, key, String.class); assertEquals(value, removedValue); assertNull(store.get(namespace, key)); } @SuppressWarnings("DataFlowIssue") @Test void removeWithTypeSafetyAndPrimitiveValueType() { String key = "enigma"; int value = 42; store.put(namespace, key, value); // The fact that we can declare this as an int suffices for testing the required type. int requiredInt = store.remove(namespace, key, int.class); assertEquals(value, requiredInt); store.put(namespace, key, value); // The fact that we can declare this as an Integer suffices for testing the required type. Integer requiredInteger = store.get(namespace, key, Integer.class); assertEquals(value, requiredInteger.intValue()); } @Test void removeNullValueWithTypeSafety() { Integer key = 42; store.put(namespace, key, null); // The fact that we can declare this as a String suffices for testing the required type. String removedValue = store.remove(namespace, key, String.class); assertNull(removedValue); assertNull(store.get(namespace, key)); } @SuppressWarnings("deprecation") @Test void simulateRaceConditionInGetOrComputeIfAbsent() throws Exception { int threads = 10; AtomicInteger counter = new AtomicInteger(); List values; try (var localStore = new NamespacedHierarchicalStore<>(null)) { values = executeConcurrently(threads, // () -> requireNonNull( localStore.getOrComputeIfAbsent(namespace, key, it -> counter.incrementAndGet()))); } assertEquals(1, counter.get()); assertThat(values).hasSize(threads).containsOnly(1); } @SuppressWarnings("deprecation") @RepeatedTest(value = 10, failureThreshold = 1) void simulateRaceConditionInGetOrComputeIfAbsentWithResizingMap() throws Exception { int threads = 10; AtomicInteger counter = new AtomicInteger(); List values; try (var localStore = new NamespacedHierarchicalStore<>(null)) { values = executeConcurrently(threads, // () -> { // Simulate other extensions computing values, // this will trigger several resizes of the store for (int i = 0; i < 16; i++) { localStore.getOrComputeIfAbsent(namespace, i, __ -> value); } return requireNonNull( localStore.getOrComputeIfAbsent(namespace, key, it -> counter.incrementAndGet())); }); } assertEquals(1, counter.get()); assertThat(values).hasSize(threads).containsOnly(1); } @Test void simulateRaceConditionInComputeIfAbsent() throws Exception { int threads = 10; AtomicInteger counter = new AtomicInteger(); List values; try (var localStore = new NamespacedHierarchicalStore<>(null)) { values = executeConcurrently(threads, // () -> requireNonNull(localStore.computeIfAbsent(namespace, key, it -> counter.incrementAndGet()))); } assertEquals(1, counter.get()); assertThat(values).hasSize(threads).containsOnly(1); } @RepeatedTest(value = 10, failureThreshold = 1) void simulateRaceConditionInComputeIfAbsentWithResizingMap() throws Exception { int threads = 10; AtomicInteger counter = new AtomicInteger(); List values; try (var localStore = new NamespacedHierarchicalStore<>(null)) { values = executeConcurrently(threads, // () -> { // Simulate other extensions computing values, // this will trigger several resizes of the store for (int i = 0; i < 16; i++) { localStore.computeIfAbsent(namespace, i, s -> value); } return requireNonNull( localStore.computeIfAbsent(namespace, key, it -> counter.incrementAndGet())); }); } assertEquals(1, counter.get()); assertThat(values).hasSize(threads).containsOnly(1); } @SuppressWarnings("deprecation") @Test void updateRecursivelyGetOrComputeIfAbsent() { try (var localStore = new NamespacedHierarchicalStore<>(null)) { var value = localStore.getOrComputeIfAbsent(namespace, new CollidingKey("a"), // a -> requireNonNull(localStore.getOrComputeIfAbsent(namespace, new CollidingKey("b"), // b -> "enigma"))); assertEquals("enigma", value); } } @Test void updateRecursivelyComputeIfAbsent() { try (var localStore = new NamespacedHierarchicalStore<>(null)) { var value = localStore.computeIfAbsent(namespace, new CollidingKey("a"), // a -> localStore.computeIfAbsent(namespace, new CollidingKey("b"), // b -> "enigma")); assertEquals("enigma", value); } } private record CollidingKey(String value) { @Override public int hashCode() { return 42; } } } @Nested class InheritedValuesTests { @SuppressWarnings("deprecation") @Test void presentValueFromParentIsPresent() { parentStore.put(namespace, key, value); assertEquals(value, store.get(namespace, key)); assertEquals(value, store.getOrComputeIfAbsent(namespace, key, __ -> "enigma")); assertEquals(value, store.computeIfAbsent(namespace, key, __ -> "enigma")); } @SuppressWarnings("deprecation") @Test void absentValueFromParentIsOverriddenByComputeIfAbsent() { parentStore.put(namespace, key, null); assertNull(store.get(namespace, key)); assertNull(store.getOrComputeIfAbsent(namespace, key, __ -> value)); assertEquals(value, store.computeIfAbsent(namespace, key, __ -> value)); } @Test void valueFromParentCanBeOverriddenInChild() { parentStore.put(namespace, key, value); Object otherValue = new Object(); store.put(namespace, key, otherValue); assertEquals(otherValue, store.get(namespace, key)); assertEquals(value, parentStore.get(namespace, key)); } } @Nested class CompositeNamespaceTests { @Test void additionNamespacePartMakesADifference() { String ns1 = "part1/part2"; String ns2 = "part1"; Object value2 = createObject("value2"); parentStore.put(ns1, key, value); parentStore.put(ns2, key, value2); assertEquals(value, store.get(ns1, key)); assertEquals(value2, store.get(ns2, key)); } } @Nested class CloseActionTests { @BeforeEach void prerequisites() { assertNotClosed(); } @Test void callsCloseActionInReverseInsertionOrderWhenClosingStore() throws Throwable { store.put(namespace, "key1", "value1"); store.put(namespace, "key2", "value2"); store.put(namespace, "key3", "value3"); verifyNoInteractions(closeAction); store.close(); assertClosed(); var inOrder = inOrder(closeAction); inOrder.verify(closeAction).close(namespace, "key3", "value3"); inOrder.verify(closeAction).close(namespace, "key2", "value2"); inOrder.verify(closeAction).close(namespace, "key1", "value1"); verifyNoMoreInteractions(closeAction); } @Test void doesNotCallCloseActionForRemovedValues() { store.put(namespace, key, value); store.remove(namespace, key); store.close(); assertClosed(); verifyNoInteractions(closeAction); } @Test void doesNotCallCloseActionForReplacedValues() throws Throwable { store.put(namespace, key, "value1"); store.put(namespace, key, "value2"); store.close(); assertClosed(); verify(closeAction).close(namespace, key, "value2"); verifyNoMoreInteractions(closeAction); } @Test void doesNotCallCloseActionForNullValues() { store.put(namespace, key, null); store.close(); assertClosed(); verifyNoInteractions(closeAction); } @SuppressWarnings("deprecation") @Test void doesNotCallCloseActionForValuesThatThrowExceptionsDuringCleanup() throws Throwable { store.put(namespace, "key1", "value1"); assertThrows(ComputeException.class, () -> store.computeIfAbsent(namespace, "key2", __ -> { throw new ComputeException("boom"); })); assertThrows(ComputeException.class, () -> store.getOrComputeIfAbsent(namespace, "key3", __ -> { throw new ComputeException("boom"); })); store.put(namespace, "key4", "value4"); store.close(); assertClosed(); var inOrder = inOrder(closeAction); inOrder.verify(closeAction).close(namespace, "key4", "value4"); inOrder.verify(closeAction).close(namespace, "key1", "value1"); inOrder.verifyNoMoreInteractions(); } @SuppressWarnings("deprecation") @Test void abortsCloseIfAnyStoredValueThrowsAnUnrecoverableExceptionDuringCleanup() throws Throwable { store.put(namespace, "key1", "value1"); assertThrows(OutOfMemoryError.class, () -> store.getOrComputeIfAbsent(namespace, "key2", __ -> { throw new OutOfMemoryError("boom"); })); store.put(namespace, "key3", "value3"); assertThrows(OutOfMemoryError.class, store::close); assertClosed(); verifyNoInteractions(closeAction); store.close(); assertClosed(); } @Test void closesStoreEvenIfCloseActionThrowsException() throws Throwable { store.put(namespace, key, value); doThrow(IllegalStateException.class).when(closeAction).close(namespace, key, value); assertThrows(IllegalStateException.class, store::close); assertClosed(); verify(closeAction).close(namespace, key, value); verifyNoMoreInteractions(closeAction); store.close(); assertClosed(); } @Test void closesStoreEvenIfCloseActionThrowsUnrecoverableException() throws Throwable { store.put(namespace, key, value); doThrow(OutOfMemoryError.class).when(closeAction).close(namespace, key, value); assertThrows(OutOfMemoryError.class, store::close); assertClosed(); verify(closeAction).close(namespace, key, value); verifyNoMoreInteractions(closeAction); store.close(); assertClosed(); } @Test void closesStoreEvenIfNoCloseActionIsConfigured() { @SuppressWarnings("resource") var localStore = new NamespacedHierarchicalStore<>(null); assertThat(localStore.isClosed()).isFalse(); localStore.close(); assertThat(localStore.isClosed()).isTrue(); } @Test void closeIsIdempotent() throws Throwable { store.put(namespace, key, value); verifyNoInteractions(closeAction); store.close(); assertClosed(); verify(closeAction, times(1)).close(namespace, key, value); store.close(); assertClosed(); verifyNoMoreInteractions(closeAction); } /** * @see #3944 */ @SuppressWarnings("deprecation") @Test void acceptsQueryAfterClose() { store.put(namespace, key, value); store.close(); assertClosed(); assertThat(store.get(namespace, key)).isEqualTo(value); assertThat(store.get(namespace, key, String.class)).isEqualTo(value); assertThat(store.getOrComputeIfAbsent(namespace, key, __ -> "new")).isEqualTo(value); assertThat(store.getOrComputeIfAbsent(namespace, key, __ -> "new", String.class)).isEqualTo(value); assertThat(store.computeIfAbsent(namespace, key, __ -> "new")).isEqualTo(value); assertThat(store.computeIfAbsent(namespace, key, __ -> "new", String.class)).isEqualTo(value); } @SuppressWarnings("deprecation") @Test void rejectsModificationAfterClose() { store.close(); assertClosed(); assertThrows(NamespacedHierarchicalStoreException.class, () -> store.put(namespace, key, value)); assertThrows(NamespacedHierarchicalStoreException.class, () -> store.remove(namespace, key)); assertThrows(NamespacedHierarchicalStoreException.class, () -> store.remove(namespace, key, int.class)); // Since key does not exist, an invocation of getOrComputeIfAbsent(...) or computeIfAbsent(...) will attempt // to compute a new value. assertThrows(NamespacedHierarchicalStoreException.class, () -> store.getOrComputeIfAbsent(namespace, key, __ -> "new")); assertThrows(NamespacedHierarchicalStoreException.class, () -> store.getOrComputeIfAbsent(namespace, key, __ -> "new", String.class)); assertThrows(NamespacedHierarchicalStoreException.class, () -> store.computeIfAbsent(namespace, key, __ -> "new")); assertThrows(NamespacedHierarchicalStoreException.class, () -> store.computeIfAbsent(namespace, key, __ -> "new", String.class)); } private void assertNotClosed() { assertThat(store.isClosed()).as("closed").isFalse(); } private void assertClosed() { assertThat(store.isClosed()).as("closed").isTrue(); } } @Nested class DeferredSupplierTests { @Test void getCanBeInterrupted() { var supplier = new NamespacedHierarchicalStore.DeferredSupplier(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { throw new ComputeException(e); } return value; }); Thread.currentThread().interrupt(); assertThrows(InterruptedException.class, () -> { supplier.get(); }); assertTrue(Thread.interrupted()); } @Test void getThrowsIfUnrecoverable() { var supplier = new NamespacedHierarchicalStore.DeferredSupplier(() -> { throw new OutOfMemoryError("boom"); }); supplier.run(); assertThrows(OutOfMemoryError.class, () -> { supplier.get(); }); } @Test void getOrThrowThrowsIfUnrecoverable() { var supplier = new NamespacedHierarchicalStore.DeferredSupplier(() -> { throw new OutOfMemoryError("boom"); }); supplier.run(); assertThrows(OutOfMemoryError.class, () -> { supplier.getOrThrow(); }); } @Test void getOrThrowCanBeInterrupted() { var supplier = new NamespacedHierarchicalStore.DeferredSupplier(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { throw new ComputeException(e); } return value; }); Thread.currentThread().interrupt(); assertThrows(InterruptedException.class, () -> { supplier.getOrThrow(); }); assertTrue(Thread.interrupted()); } } private static Object createObject(String display) { return new Object() { @Override public String toString() { return display; } }; } /** * To avoid confusion with other Runtime exceptions that can be thrown. */ private static final class ComputeException extends RuntimeException { @Serial private static final long serialVersionUID = 1L; ComputeException(String msg) { super(msg); } ComputeException(InterruptedException e) { super(e); } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/DiscoveryFilterStub.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher; import java.util.function.Function; import java.util.function.Supplier; import org.junit.platform.engine.DiscoveryFilter; import org.junit.platform.engine.FilterResult; /** * @since 1.0 */ public class DiscoveryFilterStub extends FilterStub implements DiscoveryFilter { public DiscoveryFilterStub(String toString) { super(toString); } public DiscoveryFilterStub(Function function, Supplier toString) { super(function, toString); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/FilterStub.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher; import java.util.function.Function; import java.util.function.Supplier; import org.junit.platform.engine.Filter; import org.junit.platform.engine.FilterResult; /** * @since 1.0 */ public class FilterStub implements Filter { private final Function function; private final Supplier toString; public FilterStub(String toString) { this(o -> FilterResult.included("always"), () -> toString); } public FilterStub(Function function, Supplier toString) { this.function = function; this.toString = toString; } @Override public FilterResult apply(T object) { return function.apply(object); } @Override public String toString() { return toString.get(); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/InterceptedTestEngine.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher; import org.junit.platform.fakes.TestEngineSpy; public class InterceptedTestEngine extends TestEngineSpy { public static final String ID = "intercepted-engine"; public InterceptedTestEngine() { super(ID); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/InterceptorInjectedLauncherSessionListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; public class InterceptorInjectedLauncherSessionListener implements LauncherSessionListener { public static int CALLS; public InterceptorInjectedLauncherSessionListener() { assertEquals(TestLauncherInterceptor1.CLASSLOADER_NAME, Thread.currentThread().getContextClassLoader().getName()); assertTrue(TestLauncherInterceptor2.INTERCEPTING); } @Override public void launcherSessionOpened(LauncherSession session) { CALLS++; } @Override public void launcherSessionClosed(LauncherSession session) { assertEquals(TestLauncherInterceptor1.CLASSLOADER_NAME, Thread.currentThread().getContextClassLoader().getName()); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/MethodFilterTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationContainsNoNullElementsFor; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullOrEmptyFor; import static org.junit.platform.launcher.MethodFilter.excludeMethodNamePatterns; import static org.junit.platform.launcher.MethodFilter.includeMethodNamePatterns; import org.junit.jupiter.api.Test; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.engine.FilterResult; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.DemoMethodTestDescriptor; /** * Unit tests for {@link MethodFilter}. * * @since 1.12 */ class MethodFilterTests { private static final String CLASS1_TEST1_NAME = "org.junit.platform.launcher.MethodFilterTests$Class1#test1"; private static final String CLASS1_TEST2_NAME = "org.junit.platform.launcher.MethodFilterTests$Class1#test2"; private static final String CLASS2_TEST1_NAME = "org.junit.platform.launcher.MethodFilterTests$Class2#test1"; private static final String CLASS2_TEST2_NAME = "org.junit.platform.launcher.MethodFilterTests$Class2#test2"; private static final TestDescriptor CLASS1_TEST1 = methodTestDescriptor("class1", Class1.class, "test1"); private static final TestDescriptor CLASS1_TEST2 = methodTestDescriptor("class1", Class1.class, "test2"); private static final TestDescriptor CLASS2_TEST1 = methodTestDescriptor("class2", Class2.class, "test1"); private static final TestDescriptor CLASS2_TEST2 = methodTestDescriptor("class2", Class2.class, "test2"); @SuppressWarnings("DataFlowIssue") @Test void includeMethodNamePatternsChecksPreconditions() { assertPreconditionViolationNotNullOrEmptyFor("patterns array", () -> includeMethodNamePatterns((String[]) null)); assertPreconditionViolationNotNullOrEmptyFor("patterns array", () -> includeMethodNamePatterns(new String[0])); assertPreconditionViolationContainsNoNullElementsFor("patterns array", () -> includeMethodNamePatterns(new String[] { null })); } @Test void includeSingleMethodNamePattern() { var regex = "^org\\.junit\\.platform\\.launcher\\.MethodFilterTests\\$Class1#test.*"; var filter = includeMethodNamePatterns(regex); assertIncluded(filter.apply(CLASS1_TEST1), "Method name [%s] matches included pattern: '%s'".formatted(CLASS1_TEST1_NAME, regex)); assertIncluded(filter.apply(CLASS1_TEST2), "Method name [%s] matches included pattern: '%s'".formatted(CLASS1_TEST2_NAME, regex)); assertExcluded(filter.apply(CLASS2_TEST1), "Method name [%s] does not match any included pattern: '%s'".formatted(CLASS2_TEST1_NAME, regex)); assertExcluded(filter.apply(CLASS2_TEST2), "Method name [%s] does not match any included pattern: '%s'".formatted(CLASS2_TEST2_NAME, regex)); } @Test void includeMultipleMethodNamePatterns() { var firstRegex = "^org\\.junit\\.platform\\.launcher\\.MethodFilterTests\\$Class1#test.*"; var secondRegex = ".+Class.+#test1"; var filter = includeMethodNamePatterns(firstRegex, secondRegex); assertIncluded(filter.apply(CLASS1_TEST1), "Method name [%s] matches included pattern: '%s'".formatted(CLASS1_TEST1_NAME, firstRegex)); assertIncluded(filter.apply(CLASS1_TEST2), "Method name [%s] matches included pattern: '%s'".formatted(CLASS1_TEST2_NAME, firstRegex)); assertIncluded(filter.apply(CLASS2_TEST1), "Method name [%s] matches included pattern: '%s'".formatted(CLASS2_TEST1_NAME, secondRegex)); assertExcluded(filter.apply(CLASS2_TEST2), "Method name [%s] does not match any included pattern: '%s' OR '%s'".formatted(CLASS2_TEST2_NAME, firstRegex, secondRegex)); } @SuppressWarnings("DataFlowIssue") @Test void excludeMethodNamePatternsChecksPreconditions() { assertPreconditionViolationNotNullOrEmptyFor("patterns array", () -> excludeMethodNamePatterns((String[]) null)); assertPreconditionViolationNotNullOrEmptyFor("patterns array", () -> excludeMethodNamePatterns(new String[0])); assertPreconditionViolationContainsNoNullElementsFor("patterns array", () -> excludeMethodNamePatterns(new String[] { null })); } @Test void excludeSingleMethodNamePattern() { var regex = "^org\\.junit\\.platform\\.launcher\\.MethodFilterTests\\$Class1#test.*"; var filter = excludeMethodNamePatterns(regex); assertExcluded(filter.apply(CLASS1_TEST1), "Method name [%s] matches excluded pattern: '%s'".formatted(CLASS1_TEST1_NAME, regex)); assertExcluded(filter.apply(CLASS1_TEST2), "Method name [%s] matches excluded pattern: '%s'".formatted(CLASS1_TEST2_NAME, regex)); assertIncluded(filter.apply(CLASS2_TEST1), "Method name [%s] does not match any excluded pattern: '%s'".formatted(CLASS2_TEST1_NAME, regex)); assertIncluded(filter.apply(CLASS2_TEST2), "Method name [%s] does not match any excluded pattern: '%s'".formatted(CLASS2_TEST2_NAME, regex)); } @Test void excludeMultipleMethodNamePatterns() { var firstRegex = "^org\\.junit\\.platform\\.launcher\\.MethodFilterTests\\$Class1#test.*"; var secondRegex = ".+Class.+#test1"; var filter = excludeMethodNamePatterns(firstRegex, secondRegex); assertExcluded(filter.apply(CLASS1_TEST1), "Method name [%s] matches excluded pattern: '%s'".formatted(CLASS1_TEST1_NAME, firstRegex)); assertExcluded(filter.apply(CLASS1_TEST2), "Method name [%s] matches excluded pattern: '%s'".formatted(CLASS1_TEST2_NAME, firstRegex)); assertExcluded(filter.apply(CLASS2_TEST1), "Method name [%s] matches excluded pattern: '%s'".formatted(CLASS2_TEST1_NAME, secondRegex)); assertIncluded(filter.apply(CLASS2_TEST2), "Method name [%s] does not match any excluded pattern: '%s' OR '%s'".formatted(CLASS2_TEST2_NAME, firstRegex, secondRegex)); } private void assertIncluded(FilterResult filterResult, String expectedReason) { assertTrue(filterResult.included()); assertThat(filterResult.getReason()).isPresent().contains(expectedReason); } private void assertExcluded(FilterResult filterResult, String excludedPattern) { assertTrue(filterResult.excluded()); assertThat(filterResult.getReason()).isPresent().contains(excludedPattern); } private static TestDescriptor methodTestDescriptor(String uniqueId, Class testClass, String methodName) { var method = ReflectionUtils.findMethod(testClass, methodName, new Class[0]).orElseThrow(); return new DemoMethodTestDescriptor(UniqueId.root("method", uniqueId), method); } // ------------------------------------------------------------------------- private static class Class1 { @Test void test1() { } @Test void test2() { } } private static class Class2 { @Test void test1() { } @Test void test2() { } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/PostDiscoveryFilterStub.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher; import java.util.function.Function; import java.util.function.Supplier; import org.junit.platform.engine.FilterResult; import org.junit.platform.engine.TestDescriptor; /** * @since 1.0 */ public class PostDiscoveryFilterStub extends FilterStub implements PostDiscoveryFilter { public PostDiscoveryFilterStub(String toString) { super(toString); } public PostDiscoveryFilterStub(Function function, Supplier toString) { super(function, toString); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/TagFilterTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.junit.platform.launcher.TagFilter.excludeTags; import static org.junit.platform.launcher.TagFilter.includeTags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.platform.engine.FilterResult; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.DemoClassTestDescriptor; /** * Unit tests for {@link TagFilter}. * *

NOTE: part of the behavior of these tests regarding tags is * influenced by the implementation of {@link DemoClassTestDescriptor#getTags()} * rather than any concrete test engine. * * @since 1.0 */ class TagFilterTests { private static final TestDescriptor classWithTag1 = classTestDescriptor("class1", ClassWithTag1.class); private static final TestDescriptor classWithTag1AndSurroundingWhitespace = classTestDescriptor( "class1-surrounding-whitespace", ClassWithTag1AndSurroundingWhitespace.class); private static final TestDescriptor classWithTag2 = classTestDescriptor("class2", ClassWithTag2.class); private static final TestDescriptor classWithBothTags = classTestDescriptor("class12", ClassWithBothTags.class); private static final TestDescriptor classWithDifferentTags = classTestDescriptor("classX", ClassWithDifferentTags.class); private static final TestDescriptor classWithNoTags = classTestDescriptor("class", ClassWithNoTags.class); @Test void includeTagsWithInvalidSyntax() { // @formatter:off assertAll( () -> assertSyntaxViolationForIncludes(null), () -> assertSyntaxViolationForIncludes(""), () -> assertSyntaxViolationForIncludes(" "), () -> assertSyntaxViolationForIncludes("foo bar") ); // @formatter:on } @SuppressWarnings("DataFlowIssue") private void assertSyntaxViolationForIncludes(@Nullable String tag) { assertPreconditionViolationFor(() -> includeTags(tag))// .withMessageStartingWith("Unable to parse tag expression"); } @Test void excludeTagsWithInvalidSyntax() { // @formatter:off assertAll( () -> assertSyntaxViolationForExcludes(null), () -> assertSyntaxViolationForExcludes(""), () -> assertSyntaxViolationForExcludes(" "), () -> assertSyntaxViolationForExcludes("foo bar") ); // @formatter:on } @SuppressWarnings("DataFlowIssue") private void assertSyntaxViolationForExcludes(@Nullable String tag) { assertPreconditionViolationFor(() -> excludeTags(tag))// .withMessageStartingWith("Unable to parse tag expression"); } @Test void includeSingleTag() { includeSingleTag(includeTags("tag1")); } @Test void includeSingleTagAndWhitespace() { includeSingleTag(includeTags("\t \n tag1 ")); } @Test void includeMultipleTags() { var filter = includeTags("tag1", " tag2 "); assertTrue(filter.apply(classWithBothTags).included()); assertTrue(filter.apply(classWithTag1).included()); assertTrue(filter.apply(classWithTag1AndSurroundingWhitespace).included()); assertTrue(filter.apply(classWithTag2).included()); assertTrue(filter.apply(classWithDifferentTags).excluded()); assertTrue(filter.apply(classWithNoTags).excluded()); } @Test void excludeSingleTag() { excludeSingleTag(excludeTags("tag1")); } @Test void excludeSingleTagAndWhitespace() { excludeSingleTag(excludeTags("\t \n tag1 ")); } @Test void excludeMultipleTags() { var filter = excludeTags("tag1", " tag2 "); var exclusionReason = "excluded because tags match tag expression(s): [tag1,tag2]"; assertExcluded(filter.apply(classWithTag1), exclusionReason); assertExcluded(filter.apply(classWithTag1AndSurroundingWhitespace), exclusionReason); assertExcluded(filter.apply(classWithBothTags), exclusionReason); assertExcluded(filter.apply(classWithTag2), exclusionReason); var inclusionReason = "included because tags do not match expression(s): [tag1,tag2]"; assertIncluded(filter.apply(classWithDifferentTags), inclusionReason); assertIncluded(filter.apply(classWithNoTags), inclusionReason); } @Test void rejectSingleUnparsableTagExpressions() { var brokenTagExpression = "tag & "; assertPreconditionViolationFor(() -> TagFilter.includeTags(brokenTagExpression))// .withMessageStartingWith("Unable to parse tag expression \"" + brokenTagExpression + "\""); } @Test void rejectUnparsableTagExpressionFromArray() { var brokenTagExpression = "tag & "; assertPreconditionViolationFor(() -> TagFilter.excludeTags(brokenTagExpression, "foo", "bar"))// .withMessageStartingWith("Unable to parse tag expression \"" + brokenTagExpression + "\""); } private void includeSingleTag(PostDiscoveryFilter filter) { var inclusionReason = "included because tags match expression(s): [tag1]"; assertIncluded(filter.apply(classWithTag1), inclusionReason); assertIncluded(filter.apply(classWithTag1AndSurroundingWhitespace), inclusionReason); assertIncluded(filter.apply(classWithBothTags), inclusionReason); var exclusionReason = "excluded because tags do not match tag expression(s): [tag1]"; assertExcluded(filter.apply(classWithTag2), exclusionReason); assertExcluded(filter.apply(classWithDifferentTags), exclusionReason); assertExcluded(filter.apply(classWithNoTags), exclusionReason); } private void excludeSingleTag(PostDiscoveryFilter filter) { var exclusionReason = "excluded because tags match tag expression(s): [tag1]"; assertExcluded(filter.apply(classWithTag1), exclusionReason); assertExcluded(filter.apply(classWithTag1AndSurroundingWhitespace), exclusionReason); assertExcluded(filter.apply(classWithBothTags), exclusionReason); var inclusionReason = "included because tags do not match expression(s): [tag1]"; assertIncluded(filter.apply(classWithTag2), inclusionReason); assertIncluded(filter.apply(classWithDifferentTags), inclusionReason); assertIncluded(filter.apply(classWithNoTags), inclusionReason); } private void assertIncluded(FilterResult filterResult, String expectedReason) { assertTrue(filterResult.included()); assertThat(filterResult.getReason()).contains(expectedReason); } private void assertExcluded(FilterResult filterResult, String expectedReason) { assertTrue(filterResult.excluded()); assertThat(filterResult.getReason()).contains(expectedReason); } // ------------------------------------------------------------------------- @Retention(RetentionPolicy.RUNTIME) @Tag("tag1") private @interface Tag1 { } @Retention(RetentionPolicy.RUNTIME) @Tag("tag2") private @interface Tag2 { } @Tag1 private static class ClassWithTag1 { } @Tag(" tag1 \t ") private static class ClassWithTag1AndSurroundingWhitespace { } @Tag2 private static class ClassWithTag2 { } @Tag1 @Tag2 private static class ClassWithBothTags { } @Tag("foo") @Tag("bar") private static class ClassWithDifferentTags { } @Tag(" ") // intentionally "blank" private static class ClassWithNoTags { } private static TestDescriptor classTestDescriptor(String uniqueId, Class testClass) { var rootUniqueId = UniqueId.root("class", uniqueId); return new DemoClassTestDescriptor(rootUniqueId, testClass); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/TagIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.launcher.TagFilter.includeTags; import static org.junit.platform.launcher.TagIntegrationTests.TaggedTestCase.doubleTaggedWasExecuted; import static org.junit.platform.launcher.TagIntegrationTests.TaggedTestCase.tag1WasExecuted; import static org.junit.platform.launcher.TagIntegrationTests.TaggedTestCase.tag2WasExecuted; import static org.junit.platform.launcher.TagIntegrationTests.TaggedTestCase.unTaggedWasExecuted; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.testkit.engine.EngineTestKit; class TagIntegrationTests { @BeforeEach void init() { tag1WasExecuted = false; tag2WasExecuted = false; unTaggedWasExecuted = false; doubleTaggedWasExecuted = false; } @Test void includingWrongTagExecutesNothing() { executeTaggedTestCase(includeTags("whatever")); assertFalse(tag1WasExecuted); assertFalse(tag2WasExecuted); assertFalse(doubleTaggedWasExecuted); assertFalse(unTaggedWasExecuted); } @Test void includingSuitableTagExecutesTaggedTestOnly() { executeTaggedTestCase(includeTags("tag1")); assertTrue(tag1WasExecuted); assertFalse(tag2WasExecuted); assertTrue(doubleTaggedWasExecuted); assertFalse(unTaggedWasExecuted); } @ParameterizedTest @ValueSource(strings = { "any()", "!none()" }) void includingTheAnyKeywordExecutesAllTaggedTests(String tagExpression) { executeTaggedTestCase(includeTags(tagExpression)); assertTrue(tag1WasExecuted); assertTrue(tag2WasExecuted); assertTrue(doubleTaggedWasExecuted); assertFalse(unTaggedWasExecuted); } @ParameterizedTest @ValueSource(strings = { "none()", "!any()" }) void includingTheNoneKeywordExecutesAllUntaggedTests(String tagExpression) { executeTaggedTestCase(includeTags(tagExpression)); assertFalse(tag1WasExecuted); assertFalse(tag2WasExecuted); assertFalse(doubleTaggedWasExecuted); assertTrue(unTaggedWasExecuted); } private void executeTaggedTestCase(PostDiscoveryFilter filter) { EngineTestKit.engine("junit-jupiter") // .selectors(selectClass(TaggedTestCase.class)) // .filters(filter) // .execute(); } static class TaggedTestCase { static boolean tag1WasExecuted = false; static boolean tag2WasExecuted = false; static boolean unTaggedWasExecuted = false; static boolean doubleTaggedWasExecuted = false; @Test @Tag("tag1") void tagged1() { tag1WasExecuted = true; } @Test @Tag("tag2") void tagged2() { tag2WasExecuted = true; } @Test @Tag("tag1") @Tag("tag2") void doubleTagged() { doubleTaggedWasExecuted = true; } @Test void unTagged() { unTaggedWasExecuted = true; } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/TestIdentifierTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher; import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.toSet; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.commons.util.SerializationUtils.deserialize; import static org.junit.platform.commons.util.SerializationUtils.serialize; import java.io.Serializable; import java.util.AbstractSet; import java.util.Iterator; import java.util.Set; import java.util.stream.IntStream; import org.jspecify.annotations.NullMarked; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestTag; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.platform.engine.support.descriptor.EngineDescriptor; import org.junit.platform.fakes.TestDescriptorStub; /** * @since 1.0 */ @NullMarked class TestIdentifierTests { @Test void inheritsIdAndNamesFromDescriptor() { TestDescriptor testDescriptor = new TestDescriptorStub(UniqueId.root("aType", "uniqueId"), "displayName"); var testIdentifier = TestIdentifier.from(testDescriptor); assertEquals("[aType:uniqueId]", testIdentifier.getUniqueId()); assertEquals("displayName", testIdentifier.getDisplayName()); } @Test void inheritsTypeFromDescriptor() { TestDescriptor descriptor = new TestDescriptorStub(UniqueId.root("aType", "uniqueId"), "displayName"); var identifier = TestIdentifier.from(descriptor); assertEquals(TestDescriptor.Type.TEST, identifier.getType()); assertTrue(identifier.isTest()); assertFalse(identifier.isContainer()); descriptor.addChild(new TestDescriptorStub(UniqueId.root("aChild", "uniqueId"), "displayName")); identifier = TestIdentifier.from(descriptor); assertEquals(TestDescriptor.Type.CONTAINER, identifier.getType()); assertFalse(identifier.isTest()); assertTrue(identifier.isContainer()); } @ParameterizedTest @ValueSource(ints = { 0, 1, 2 }) void currentVersionCanBeSerializedAndDeserialized(int tagCount) throws Exception { var tags = IntStream.range(0, tagCount) // .mapToObj(i -> TestTag.create("tag-" + i)) // .collect(collectingAndThen(toSet(), TestIdentifierTests::unserializableSet)); var original = createOriginalTestIdentifier(tags); byte[] bytes = serialize(original); var roundTripped = (TestIdentifier) deserialize(bytes); assertDeepEquals(original, roundTripped); assertThat(original.getTags()).isInstanceOf(Serializable.class); } private static Set unserializableSet(Set delegate) { var wrapper = new AbstractSet() { @Override public Iterator iterator() { return delegate.iterator(); } @Override public int size() { return delegate.size(); } }; assertThat(wrapper).isNotInstanceOf(Serializable.class); return wrapper; } @Test void identifierWithNoParentCanBeSerializedAndDeserialized() throws Exception { TestIdentifier originalIdentifier = TestIdentifier.from( new AbstractTestDescriptor(UniqueId.root("example", "id"), "Example") { @Override public Type getType() { return Type.CONTAINER; } }); var deserializedIdentifier = (TestIdentifier) deserialize(serialize(originalIdentifier)); assertDeepEquals(originalIdentifier, deserializedIdentifier); } private static void assertDeepEquals(TestIdentifier first, TestIdentifier second) { assertEquals(first, second); assertEquals(first.getUniqueId(), second.getUniqueId()); assertEquals(first.getUniqueIdObject(), second.getUniqueIdObject()); assertEquals(first.getDisplayName(), second.getDisplayName()); assertEquals(first.getLegacyReportingName(), second.getLegacyReportingName()); assertEquals(first.getSource(), second.getSource()); assertEquals(first.getTags(), second.getTags()); assertEquals(first.getType(), second.getType()); assertEquals(first.getParentId(), second.getParentId()); assertEquals(first.getParentIdObject(), second.getParentIdObject()); } private static TestIdentifier createOriginalTestIdentifier(Set tags) { var engineDescriptor = new EngineDescriptor(UniqueId.forEngine("engine"), "Engine"); var uniqueId = engineDescriptor.getUniqueId().append("child", "child"); var testSource = ClassSource.from(TestIdentifierTests.class); var testDescriptor = new AbstractTestDescriptor(uniqueId, "displayName", testSource) { @Override public Type getType() { return Type.TEST; } @Override public String getLegacyReportingName() { return "reportingName"; } @Override public Set getTags() { return tags; } }; engineDescriptor.addChild(testDescriptor); return TestIdentifier.from(testDescriptor); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherDiscoveryListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher; public class TestLauncherDiscoveryListener implements LauncherDiscoveryListener { public static boolean called; @Override public void launcherDiscoveryStarted(LauncherDiscoveryRequest request) { called = true; } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherInterceptor1.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher; import java.io.IOException; import java.io.UncheckedIOException; import java.net.URL; import java.net.URLClassLoader; public class TestLauncherInterceptor1 implements LauncherInterceptor { public static final String CLASSLOADER_NAME = "interceptor-loader"; private final ClassLoader originalClassLoader; private final URLClassLoader replacedClassLoader; public TestLauncherInterceptor1() { originalClassLoader = Thread.currentThread().getContextClassLoader(); var url = getClass().getClassLoader().getResource("intercepted-testservices/"); replacedClassLoader = new URLClassLoader(CLASSLOADER_NAME, new URL[] { url }, originalClassLoader); Thread.currentThread().setContextClassLoader(replacedClassLoader); } @Override public T intercept(Invocation invocation) { return invocation.proceed(); } @Override public void close() { try { replacedClassLoader.close(); } catch (IOException e) { throw new UncheckedIOException(e); } finally { Thread.currentThread().setContextClassLoader(originalClassLoader); } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherInterceptor2.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher; public class TestLauncherInterceptor2 implements LauncherInterceptor { public static boolean INTERCEPTING; @Override public T intercept(Invocation invocation) { INTERCEPTING = true; try { return invocation.proceed(); } finally { INTERCEPTING = false; } } @Override public void close() { } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherSessionListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher; import org.jspecify.annotations.Nullable; public class TestLauncherSessionListener implements LauncherSessionListener { public static @Nullable LauncherSession session; @Override public void launcherSessionOpened(LauncherSession session) { TestLauncherSessionListener.session = session; } @Override public void launcherSessionClosed(LauncherSession session) { TestLauncherSessionListener.session = null; } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/TestPlanTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher; import static org.junit.platform.launcher.core.OutputDirectoryCreators.dummyOutputDirectoryCreator; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import java.util.List; import org.junit.jupiter.api.Test; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.EngineDescriptor; import org.junit.platform.fakes.TestDescriptorStub; class TestPlanTests { private final ConfigurationParameters configParams = mock(); private final EngineDescriptor engineDescriptor = new EngineDescriptor(UniqueId.forEngine("foo"), "Foo"); @Test void acceptsVisitorsInDepthFirstOrder() { var container = new TestDescriptorStub(engineDescriptor.getUniqueId().append("container", "bar"), "Bar"); var test1 = new TestDescriptorStub(container.getUniqueId().append("test", "bar"), "Bar"); container.addChild(test1); engineDescriptor.addChild(container); var engineDescriptor2 = new EngineDescriptor(UniqueId.forEngine("baz"), "Baz"); var test2 = new TestDescriptorStub(engineDescriptor2.getUniqueId().append("test", "baz1"), "Baz"); var test3 = new TestDescriptorStub(engineDescriptor2.getUniqueId().append("test", "baz2"), "Baz"); engineDescriptor2.addChild(test2); engineDescriptor2.addChild(test3); var testPlan = TestPlan.from(true, List.of(engineDescriptor, engineDescriptor2), configParams, dummyOutputDirectoryCreator()); var visitor = mock(TestPlan.Visitor.class); testPlan.accept(visitor); var inOrder = inOrder(visitor); inOrder.verify(visitor).preVisitContainer(TestIdentifier.from(engineDescriptor)); inOrder.verify(visitor).visit(TestIdentifier.from(engineDescriptor)); inOrder.verify(visitor).preVisitContainer(TestIdentifier.from(container)); inOrder.verify(visitor).visit(TestIdentifier.from(container)); inOrder.verify(visitor).visit(TestIdentifier.from(test1)); inOrder.verify(visitor).postVisitContainer(TestIdentifier.from(container)); inOrder.verify(visitor).postVisitContainer(TestIdentifier.from(engineDescriptor)); inOrder.verify(visitor).preVisitContainer(TestIdentifier.from(engineDescriptor2)); inOrder.verify(visitor).visit(TestIdentifier.from(engineDescriptor2)); inOrder.verify(visitor).visit(TestIdentifier.from(test2)); inOrder.verify(visitor).visit(TestIdentifier.from(test3)); inOrder.verify(visitor).postVisitContainer(TestIdentifier.from(engineDescriptor2)); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/TestPostDiscoveryTagFilter.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher; import org.junit.platform.engine.FilterResult; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestTag; public class TestPostDiscoveryTagFilter implements PostDiscoveryFilter { @Override public FilterResult apply(final TestDescriptor object) { var include = object.getTags().stream().map(TestTag::getName).anyMatch("test-post-discovery"::equals); return FilterResult.includedIf(include); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/core/ClasspathAlignmentCheckerTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.platform.launcher.core.ClasspathAlignmentChecker.WELL_KNOWN_PACKAGES; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.nio.file.Path; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.regex.Pattern; import io.github.classgraph.ClassGraph; import io.github.classgraph.PackageInfo; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.DisabledInEclipse; class ClasspathAlignmentCheckerTests { @Test void classpathIsAligned() { assertThat(ClasspathAlignmentChecker.check(new LinkageError())).isEmpty(); } @Test void wrapsLinkageErrorForUnalignedClasspath() { var cause = new LinkageError(); AtomicInteger counter = new AtomicInteger(); Function packageLookup = name -> { var pkg = mock(Package.class); when(pkg.getName()).thenReturn(name); when(pkg.getImplementationVersion()).thenReturn(counter.incrementAndGet() + ".0.0"); return pkg; }; var result = ClasspathAlignmentChecker.check(cause, packageLookup); assertThat(result).isPresent(); assertThat(result.get()) // .hasMessageStartingWith("The wrapped LinkageError is likely caused by the versions of " + "JUnit jars on the classpath/module path not being properly aligned.") // .hasMessageContaining("Please ensure consistent versions are used") // .hasMessageFindingMatch("https://docs\\.junit\\.org/.*/appendix.html#dependency-metadata") // .hasMessageContaining("The following conflicting versions were detected:") // .hasMessageContaining("- org.junit.jupiter.api: 1.0.0") // .hasMessageContaining("- org.junit.jupiter.engine: 2.0.0") // .cause().isSameAs(cause); } @Test @DisabledInEclipse void allRootPackagesAreChecked() { var allowedFileNames = Pattern.compile("junit-(?:platform|jupiter|vintage)-.+[\\d.]+(?:-SNAPSHOT)?\\.jar"); var classGraph = new ClassGraph() // .acceptPackages("org.junit.platform", "org.junit.jupiter", "org.junit.vintage") // .rejectPackages("org.junit.platform.reporting.shadow", "org.junit.jupiter.params.shadow") // .filterClasspathElements(e -> { var path = Path.of(e); var fileName = path.getFileName().toString(); return allowedFileNames.matcher(fileName).matches(); }); try (var scanResult = classGraph.scan()) { var foundPackages = scanResult.getPackageInfo().stream() // .filter(it -> !it.getClassInfo().isEmpty()) // .map(PackageInfo::getName) // .sorted() // .toList(); assertThat(foundPackages) // .allMatch(name -> WELL_KNOWN_PACKAGES.stream().anyMatch(name::startsWith)); assertThat(WELL_KNOWN_PACKAGES) // .allMatch(name -> foundPackages.stream().anyMatch(it -> it.startsWith(name))); } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeEngineExecutionListenerTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import java.util.logging.LogRecord; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.fixtures.TrackLogRecords; import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.engine.support.descriptor.DemoMethodTestDescriptor; import org.mockito.InOrder; @TrackLogRecords class CompositeEngineExecutionListenerTests { private final List listeners = new ArrayList<>( List.of(new ThrowingEngineExecutionListener())); @Test void shouldNotThrowExceptionButLogIfDynamicTestRegisteredListenerMethodFails(LogRecordListener logRecordListener) { compositeEngineExecutionListener().dynamicTestRegistered(anyTestDescriptor()); assertThatTestListenerErrorLogged(logRecordListener, "dynamicTestRegistered"); } @Test void shouldNotThrowExceptionButLogIfExecutionStartedListenerMethodFails(LogRecordListener logRecordListener) { compositeEngineExecutionListener().executionStarted(anyTestDescriptor()); assertThatTestListenerErrorLogged(logRecordListener, "executionStarted"); } @Test void shouldNotThrowExceptionButLogIfExecutionSkippedListenerMethodFails(LogRecordListener logRecordListener) { compositeEngineExecutionListener().executionSkipped(anyTestDescriptor(), "deliberately skipped container"); assertThatTestListenerErrorLogged(logRecordListener, "executionSkipped"); } @Test void shouldNotThrowExceptionButLogIfExecutionFinishedListenerMethodFails(LogRecordListener logRecordListener) { compositeEngineExecutionListener().executionFinished(anyTestDescriptor(), anyTestExecutionResult()); assertThatTestListenerErrorLogged(logRecordListener, "executionFinished"); } @Test void shouldNotThrowExceptionButLogIfReportingEntryPublishedListenerMethodFails( LogRecordListener logRecordListener) { compositeEngineExecutionListener().reportingEntryPublished(anyTestDescriptor(), ReportEntry.from("one", "two")); assertThatTestListenerErrorLogged(logRecordListener, "reportingEntryPublished"); } @Test void shouldThrowOutOfMemoryExceptionAndStopListenerWithoutLog(LogRecordListener logRecordListener) { listeners.clear(); listeners.add(new EngineExecutionListener() { @Override public void executionStarted(TestDescriptor testDescriptor) { throw new OutOfMemoryError(); } }); var testDescriptor = anyTestDescriptor(); assertThatThrownBy(() -> compositeEngineExecutionListener().executionStarted(testDescriptor)).isInstanceOf( OutOfMemoryError.class); assertNotLogs(logRecordListener); } @Test void callsListenersInReverseOrderForFinishedEvents() { listeners.clear(); var firstListener = mock(EngineExecutionListener.class, "firstListener"); var secondListener = mock(EngineExecutionListener.class, "secondListener"); listeners.add(firstListener); listeners.add(secondListener); var testDescriptor = anyTestDescriptor(); var testExecutionResult = anyTestExecutionResult(); var composite = compositeEngineExecutionListener(); composite.executionStarted(testDescriptor); composite.executionFinished(testDescriptor, testExecutionResult); InOrder inOrder = inOrder(firstListener, secondListener); inOrder.verify(firstListener).executionStarted(testDescriptor); inOrder.verify(secondListener).executionStarted(testDescriptor); inOrder.verify(secondListener).executionFinished(testDescriptor, testExecutionResult); inOrder.verify(firstListener).executionFinished(testDescriptor, testExecutionResult); } private EngineExecutionListener compositeEngineExecutionListener() { return new CompositeEngineExecutionListener(listeners); } private LogRecord firstWarnLogRecord(LogRecordListener logRecordListener) throws AssertionError { return logRecordListener.stream(CompositeEngineExecutionListener.class, Level.WARNING).findFirst().orElseThrow( () -> new AssertionError("Failed to find error log record")); } private void assertNotLogs(LogRecordListener logRecordListener) throws AssertionError { assertThat(logRecordListener.stream(CompositeEngineExecutionListener.class, Level.WARNING).count()).isZero(); } private static TestExecutionResult anyTestExecutionResult() { return mock(); } private void assertThatTestListenerErrorLogged(LogRecordListener logRecordListener, String methodName) { assertThat(firstWarnLogRecord(logRecordListener).getMessage()).startsWith("EngineExecutionListener [" + ThrowingEngineExecutionListener.class.getName() + "] threw exception for method: " + methodName); } private static TestDescriptor anyTestDescriptor() { var testClass = CompositeEngineExecutionListenerTests.class; var method = ReflectionUtils.findMethod(testClass, "anyTestDescriptor", new Class[0]).orElseThrow(); return new DemoMethodTestDescriptor(UniqueId.root("method", "unique_id"), method); } private static class ThrowingEngineExecutionListener implements EngineExecutionListener { @Override public void dynamicTestRegistered(TestDescriptor testDescriptor) { throw new RuntimeException("failed to invoke listener"); } @Override public void executionStarted(TestDescriptor testDescriptor) { throw new RuntimeException("failed to invoke listener"); } @Override public void executionSkipped(TestDescriptor testDescriptor, String reason) { throw new RuntimeException("failed to invoke listener"); } @Override public void executionFinished(TestDescriptor testDescriptor, TestExecutionResult testExecutionResult) { throw new RuntimeException("failed to invoke listener"); } @Override public void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry entry) { throw new RuntimeException("failed to invoke listener"); } @Override public void fileEntryPublished(TestDescriptor testDescriptor, FileEntry file) { throw new RuntimeException("failed to invoke listener"); } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeTestExecutionListenerTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.platform.launcher.core.OutputDirectoryCreators.dummyOutputDirectoryCreator; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.logging.Level; import java.util.logging.LogRecord; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.fixtures.TrackLogRecords; import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.engine.support.descriptor.DemoMethodTestDescriptor; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; import org.junit.platform.launcher.core.CompositeTestExecutionListener.EagerTestExecutionListener; import org.mockito.InOrder; @TrackLogRecords class CompositeTestExecutionListenerTests { private final List listeners = new ArrayList<>(List.of(new ThrowingTestExecutionListener())); @Test void shouldNotThrowExceptionButLogIfDynamicTestRegisteredListenerMethodFails(LogRecordListener logRecordListener) { compositeTestExecutionListener().dynamicTestRegistered(anyTestIdentifier()); assertThatTestListenerErrorLogged(logRecordListener, ThrowingTestExecutionListener.class, "dynamicTestRegistered"); } @Test void shouldNotThrowExceptionButLogIfExecutionStartedListenerMethodFails(LogRecordListener logRecordListener) { compositeTestExecutionListener().executionStarted(anyTestIdentifier()); assertThatTestListenerErrorLogged(logRecordListener, ThrowingTestExecutionListener.class, "executionStarted"); } @Test void shouldNotThrowExceptionButLogIfExecutionSkippedListenerMethodFails(LogRecordListener logRecordListener) { compositeTestExecutionListener().executionSkipped(anyTestIdentifier(), "deliberately skipped container"); assertThatTestListenerErrorLogged(logRecordListener, ThrowingTestExecutionListener.class, "executionSkipped"); } @Test void shouldNotThrowExceptionButLogIfExecutionFinishedListenerMethodFails(LogRecordListener logRecordListener) { compositeTestExecutionListener().executionFinished(anyTestIdentifier(), anyTestExecutionResult()); assertThatTestListenerErrorLogged(logRecordListener, ThrowingTestExecutionListener.class, "executionFinished"); } @Test void shouldNotThrowExceptionButLogIfReportingEntryPublishedListenerMethodFails( LogRecordListener logRecordListener) { compositeTestExecutionListener().reportingEntryPublished(anyTestIdentifier(), ReportEntry.from("one", "two")); assertThatTestListenerErrorLogged(logRecordListener, ThrowingTestExecutionListener.class, "reportingEntryPublished"); } @Test void shouldNotThrowExceptionButLogIfTesPlanExecutionStartedListenerMethodFails( LogRecordListener logRecordListener) { compositeTestExecutionListener().testPlanExecutionStarted(anyTestPlan()); assertThatTestListenerErrorLogged(logRecordListener, ThrowingTestExecutionListener.class, "testPlanExecutionStarted"); } @Test void shouldNotThrowExceptionButLogIfTesPlanExecutionFinishedListenerMethodFails( LogRecordListener logRecordListener) { compositeTestExecutionListener().testPlanExecutionFinished(anyTestPlan()); assertThatTestListenerErrorLogged(logRecordListener, ThrowingTestExecutionListener.class, "testPlanExecutionFinished"); } @Test void shouldNotThrowExceptionButLogIfExecutionJustStartedEagerTestListenerMethodFails( LogRecordListener logRecordListener) { listeners.add(new ThrowingEagerTestExecutionListener()); compositeTestExecutionListener().executionStarted(anyTestIdentifier()); assertThatTestListenerErrorLogged(logRecordListener, ThrowingEagerTestExecutionListener.class, "executionJustStarted"); } @Test void shouldNotThrowExceptionButLogIfExecutionJustFinishedEagerTestListenerMethodFails( LogRecordListener logRecordListener) { listeners.add(new ThrowingEagerTestExecutionListener()); compositeTestExecutionListener().executionFinished(anyTestIdentifier(), anyTestExecutionResult()); assertThatTestListenerErrorLogged(logRecordListener, ThrowingEagerTestExecutionListener.class, "executionJustFinished"); } @Test void shouldThrowOutOfMemoryExceptionAndStopListenerWithoutLog(LogRecordListener logRecordListener) { listeners.clear(); listeners.add(new TestExecutionListener() { @Override public void executionStarted(TestIdentifier testIdentifier) { throw new OutOfMemoryError(); } }); assertThatThrownBy(() -> compositeTestExecutionListener().executionStarted(anyTestIdentifier())).isInstanceOf( OutOfMemoryError.class); assertNotLogs(logRecordListener); } @Test void shouldThrowOutOfMemoryExceptionAndStopEagerListenerWithoutLog(LogRecordListener logRecordListener) { listeners.add(new EagerTestExecutionListener() { @Override public void executionJustStarted(TestIdentifier testIdentifier) { throw new OutOfMemoryError(); } }); assertThatThrownBy(() -> compositeTestExecutionListener().executionStarted(anyTestIdentifier())).isInstanceOf( OutOfMemoryError.class); assertNotLogs(logRecordListener); } @Test void callsListenersInReverseOrderForFinishedEvents() { listeners.clear(); var firstListener = mock(TestExecutionListener.class, "firstListener"); var secondListener = mock(TestExecutionListener.class, "secondListener"); listeners.add(firstListener); listeners.add(secondListener); var testPlan = anyTestPlan(); var testIdentifier = anyTestIdentifier(); var testExecutionResult = anyTestExecutionResult(); var composite = compositeTestExecutionListener(); composite.testPlanExecutionStarted(testPlan); composite.executionStarted(testIdentifier); composite.executionFinished(testIdentifier, testExecutionResult); composite.testPlanExecutionFinished(testPlan); InOrder inOrder = inOrder(firstListener, secondListener); inOrder.verify(firstListener).testPlanExecutionStarted(testPlan); inOrder.verify(secondListener).testPlanExecutionStarted(testPlan); inOrder.verify(firstListener).executionStarted(testIdentifier); inOrder.verify(secondListener).executionStarted(testIdentifier); inOrder.verify(secondListener).executionFinished(testIdentifier, testExecutionResult); inOrder.verify(firstListener).executionFinished(testIdentifier, testExecutionResult); inOrder.verify(secondListener).testPlanExecutionFinished(testPlan); inOrder.verify(firstListener).testPlanExecutionFinished(testPlan); } private TestExecutionListener compositeTestExecutionListener() { return new CompositeTestExecutionListener(listeners); } private LogRecord firstWarnLogRecord(LogRecordListener logRecordListener) throws AssertionError { return logRecordListener.stream(CompositeTestExecutionListener.class, Level.WARNING).findFirst().orElseThrow( () -> new AssertionError("Failed to find error log record")); } private void assertNotLogs(LogRecordListener logRecordListener) throws AssertionError { assertThat(logRecordListener.stream(CompositeTestExecutionListener.class, Level.WARNING).count()).isZero(); } private static TestExecutionResult anyTestExecutionResult() { return TestExecutionResult.successful(); } private static TestIdentifier anyTestIdentifier() { return TestIdentifier.from(anyTestDescriptor()); } private void assertThatTestListenerErrorLogged(LogRecordListener logRecordListener, Class listenerClass, String methodName) { assertThat(firstWarnLogRecord(logRecordListener).getMessage()).startsWith( "TestExecutionListener [" + listenerClass.getName() + "] threw exception for method: " + methodName); } private static TestPlan anyTestPlan() { return TestPlan.from(true, Set.of(anyTestDescriptor()), mock(), dummyOutputDirectoryCreator()); } private static DemoMethodTestDescriptor anyTestDescriptor() { var testClass = CompositeTestExecutionListenerTests.class; var method = ReflectionUtils.findMethod(testClass, "anyTestDescriptor", new Class[0]).orElseThrow(); return new DemoMethodTestDescriptor(UniqueId.root("method", "unique_id"), method); } private static class ThrowingEagerTestExecutionListener extends ThrowingTestExecutionListener implements EagerTestExecutionListener { @Override public void executionJustStarted(TestIdentifier testIdentifier) { throw new RuntimeException("failed to invoke listener"); } @Override public void executionJustFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { throw new RuntimeException("failed to invoke listener"); } } private static class ThrowingTestExecutionListener implements TestExecutionListener { @Override public void testPlanExecutionStarted(TestPlan testPlan) { throw new RuntimeException("failed to invoke listener"); } @Override public void testPlanExecutionFinished(TestPlan testPlan) { throw new RuntimeException("failed to invoke listener"); } @Override public void dynamicTestRegistered(TestIdentifier testIdentifier) { throw new RuntimeException("failed to invoke listener"); } @Override public void executionStarted(TestIdentifier testIdentifier) { throw new RuntimeException("failed to invoke listener"); } @Override public void executionSkipped(TestIdentifier testIdentifier, String reason) { throw new RuntimeException("failed to invoke listener"); } @Override public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { throw new RuntimeException("failed to invoke listener"); } @Override public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry entry) { throw new RuntimeException("failed to invoke listener"); } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherEngineFilterTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static java.util.logging.Level.WARNING; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.launcher.EngineFilter.excludeEngines; import static org.junit.platform.launcher.EngineFilter.includeEngines; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncher; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.fixtures.TrackLogRecords; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.hierarchical.DemoHierarchicalTestEngine; import org.junit.platform.launcher.LauncherDiscoveryRequest; /** * @since 1.0 */ class DefaultLauncherEngineFilterTests { private static final Runnable noOp = () -> { }; @Test void launcherWillNotExecuteEnginesIfNotIncludedByAnEngineFilter() { var firstEngine = new DemoHierarchicalTestEngine("first"); TestDescriptor test1 = firstEngine.addTest("test1", noOp); var secondEngine = new DemoHierarchicalTestEngine("second"); TestDescriptor test2 = secondEngine.addTest("test2", noOp); var launcher = createLauncher(firstEngine, secondEngine); // @formatter:off var testPlan = launcher.discover( request() .selectors(selectUniqueId(test1.getUniqueId()), selectUniqueId(test2.getUniqueId())) .filters(includeEngines("first")) .build()); // @formatter:on assertThat(testPlan.getRoots()).hasSize(1); var rootIdentifier = testPlan.getRoots().iterator().next(); assertThat(testPlan.getChildren(rootIdentifier.getUniqueIdObject())).hasSize(1); assertThat(testPlan.getChildren(UniqueId.forEngine("first"))).hasSize(1); } @Test void launcherWillExecuteAllEnginesExplicitlyIncludedViaSingleEngineFilter() { var firstEngine = new DemoHierarchicalTestEngine("first"); TestDescriptor test1 = firstEngine.addTest("test1", noOp); var secondEngine = new DemoHierarchicalTestEngine("second"); TestDescriptor test2 = secondEngine.addTest("test2", noOp); var launcher = createLauncher(firstEngine, secondEngine); // @formatter:off var testPlan = launcher.discover( request() .selectors(selectUniqueId(test1.getUniqueId()), selectUniqueId(test2.getUniqueId())) .filters(includeEngines("first", "second")) .build()); // @formatter:on assertThat(testPlan.getRoots()).hasSize(2); } @Test void launcherWillNotExecuteEnginesExplicitlyIncludedViaMultipleCompetingEngineFilters() { var firstEngine = new DemoHierarchicalTestEngine("first"); TestDescriptor test1 = firstEngine.addTest("test1", noOp); var secondEngine = new DemoHierarchicalTestEngine("second"); TestDescriptor test2 = secondEngine.addTest("test2", noOp); var launcher = createLauncher(firstEngine, secondEngine); // @formatter:off var testPlan = launcher.discover( request() .selectors(selectUniqueId(test1.getUniqueId()), selectUniqueId(test2.getUniqueId())) .filters(includeEngines("first"), includeEngines("second")) .build()); // @formatter:on assertThat(testPlan.getRoots()).isEmpty(); } @Test void launcherWillNotExecuteEnginesExplicitlyExcludedByAnEngineFilter() { var firstEngine = new DemoHierarchicalTestEngine("first"); TestDescriptor test1 = firstEngine.addTest("test1", noOp); var secondEngine = new DemoHierarchicalTestEngine("second"); TestDescriptor test2 = secondEngine.addTest("test2", noOp); var launcher = createLauncher(firstEngine, secondEngine); // @formatter:off var testPlan = launcher.discover( request() .selectors(selectUniqueId(test1.getUniqueId()), selectUniqueId(test2.getUniqueId())) .filters(excludeEngines("second")) .build()); // @formatter:on assertThat(testPlan.getRoots()).hasSize(1); var rootIdentifier = testPlan.getRoots().iterator().next(); assertThat(testPlan.getChildren(rootIdentifier.getUniqueIdObject())).hasSize(1); assertThat(testPlan.getChildren(UniqueId.forEngine("first"))).hasSize(1); } @Test void launcherWillExecuteEnginesHonoringBothIncludeAndExcludeEngineFilters() { var firstEngine = new DemoHierarchicalTestEngine("first"); TestDescriptor test1 = firstEngine.addTest("test1", noOp); var secondEngine = new DemoHierarchicalTestEngine("second"); TestDescriptor test2 = secondEngine.addTest("test2", noOp); var thirdEngine = new DemoHierarchicalTestEngine("third"); TestDescriptor test3 = thirdEngine.addTest("test3", noOp); var launcher = createLauncher(firstEngine, secondEngine, thirdEngine); // @formatter:off var testPlan = launcher.discover( request() .selectors(selectUniqueId(test1.getUniqueId()), selectUniqueId(test2.getUniqueId()), selectUniqueId(test3.getUniqueId())) .filters(includeEngines("first", "second"), excludeEngines("second")) .build()); // @formatter:on assertThat(testPlan.getRoots()).hasSize(1); var rootIdentifier = testPlan.getRoots().iterator().next(); assertThat(testPlan.getChildren(rootIdentifier.getUniqueIdObject())).hasSize(1); assertThat(testPlan.getChildren(UniqueId.forEngine("first"))).hasSize(1); } @Test void launcherThrowsExceptionWhenNoEngineMatchesIncludeEngineFilter(@TrackLogRecords LogRecordListener log) { var engine = new DemoHierarchicalTestEngine("first"); TestDescriptor test1 = engine.addTest("test1", noOp); LauncherDiscoveryRequest request = request() // .selectors(selectUniqueId(test1.getUniqueId())) // .filters(includeEngines("second")) // .build(); var launcher = createLauncher(engine); var exception = assertThrows(JUnitException.class, () -> launcher.discover(request)); assertThat(exception.getMessage()) // .startsWith("No TestEngine ID matched the following include EngineFilters: [second].") // .contains("Please fix/remove the filter or add the engine.") // .contains("Registered TestEngines:\n- first (") // .endsWith("Registered EngineFilters:\n- EngineFilter that includes engines with IDs [second]"); assertThat(log.stream(WARNING)).isEmpty(); } @Test void launcherThrowsExceptionWhenNoEngineMatchesIdInIncludeEngineFilter(@TrackLogRecords LogRecordListener log) { var engine = new DemoHierarchicalTestEngine("first"); TestDescriptor test1 = engine.addTest("test1", noOp); LauncherDiscoveryRequest request = request() // .selectors(selectUniqueId(test1.getUniqueId())) // .filters(includeEngines("first", "second")) // .build(); var launcher = createLauncher(engine); var exception = assertThrows(JUnitException.class, () -> launcher.discover(request)); assertThat(exception.getMessage()) // .startsWith("No TestEngine ID matched the following include EngineFilters: [second].") // .contains("Please fix/remove the filter or add the engine.") // .contains("Registered TestEngines:\n- first (") // .endsWith("Registered EngineFilters:\n- EngineFilter that includes engines with IDs [first, second]"); assertThat(log.stream(WARNING)).isEmpty(); } @Test void launcherWillLogWarningWhenAllEnginesWereExcluded(@TrackLogRecords LogRecordListener log) { var engine = new DemoHierarchicalTestEngine("first"); TestDescriptor test = engine.addTest("test1", noOp); var launcher = createLauncher(engine); // @formatter:off var testPlan = launcher.discover( request() .selectors(selectUniqueId(test.getUniqueId())) .filters(excludeEngines("first")) .build()); // @formatter:on assertThat(testPlan.getRoots()).isEmpty(); assertThat(log.stream(WARNING)).hasSize(1); assertThat(log.stream(WARNING).findAny().orElseThrow().getMessage()) // .startsWith("All TestEngines were excluded by EngineFilters.") // .contains("Registered TestEngines:\n- first (") // .endsWith("Registered EngineFilters:\n- EngineFilter that excludes engines with IDs [first]"); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static java.util.Objects.requireNonNull; import static java.util.function.UnaryOperator.identity; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import static org.junit.platform.engine.SelectorResolutionResult.unresolved; import static org.junit.platform.engine.TestExecutionResult.Status.ABORTED; import static org.junit.platform.engine.TestExecutionResult.successful; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.fakes.FaultyTestEngines.createEngineThatCannotResolveAnything; import static org.junit.platform.fakes.FaultyTestEngines.createEngineThatFailsToResolveAnything; import static org.junit.platform.launcher.LauncherConstants.DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME; import static org.junit.platform.launcher.LauncherConstants.DRY_RUN_PROPERTY_NAME; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncher; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.time.Instant; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import java.util.function.UnaryOperator; import java.util.logging.Level; import java.util.logging.LogRecord; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.fixtures.TrackLogRecords; import org.junit.jupiter.api.util.SetSystemProperty; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.FilterResult; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.TestExecutionResult.Status; import org.junit.platform.engine.TestTag; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.EngineDescriptor; import org.junit.platform.engine.support.hierarchical.DemoHierarchicalTestDescriptor; import org.junit.platform.engine.support.hierarchical.DemoHierarchicalTestEngine; import org.junit.platform.fakes.TestDescriptorStub; import org.junit.platform.fakes.TestEngineSpy; import org.junit.platform.fakes.TestEngineStub; import org.junit.platform.launcher.EngineDiscoveryResult; import org.junit.platform.launcher.LauncherConstants; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.PostDiscoveryFilter; import org.junit.platform.launcher.PostDiscoveryFilterStub; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; import org.junit.platform.launcher.listeners.SummaryGeneratingListener; import org.mockito.ArgumentCaptor; import org.mockito.InOrder; /** * @since 1.0 */ @NullMarked class DefaultLauncherTests { static final String FOO = "DefaultLauncherTests.foo"; static final String BAR = "DefaultLauncherTests.bar"; private static final Runnable noOp = () -> { }; @Test void constructLauncherWithoutAnyEngines() { var launcher = createLauncher(); assertPreconditionViolationFor(() -> launcher.discover(request().build()))// .withMessageContaining("Cannot create Launcher without at least one TestEngine"); } @Test void constructLauncherWithMultipleTestEnginesWithDuplicateIds() { var launcher = createLauncher(new DemoHierarchicalTestEngine("dummy id"), new DemoHierarchicalTestEngine("dummy id")); var exception = assertThrows(JUnitException.class, () -> launcher.discover(request().build())); assertThat(exception).hasMessageContaining("multiple engines with the same ID"); } @Test void discoverEmptyTestPlanWithEngineWithoutAnyTests() { var launcher = createLauncher(new DemoHierarchicalTestEngine()); var testPlan = launcher.discover(request().build()); assertThat(testPlan.getRoots()).hasSize(1); } @Test void discoverTestPlanForEngineThatReturnsNullForItsRootDescriptor() { TestEngine engine = new TestEngineStub("some-engine-id") { @SuppressWarnings("DataFlowIssue") @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { return null; } }; var discoveryListener = mock(LauncherDiscoveryListener.class); var testPlan = createLauncher(engine).discover(request() // .listeners(discoveryListener) // .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // .build()); assertThat(testPlan.getRoots()).hasSize(1); assertDiscoveryFailed(engine, inOrder(discoveryListener), discoveryListener); } @ParameterizedTest @ValueSource(classes = { Error.class, RuntimeException.class }) void discoverErrorTestDescriptorForEngineThatThrowsInDiscoveryPhase(Class throwableClass) { TestEngine engine = new TestEngineStub("my-engine-id") { @SuppressWarnings("DataFlowIssue") @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { try { var constructor = throwableClass.getDeclaredConstructor(String.class); throw ExceptionUtils.throwAsUncheckedException(constructor.newInstance("ignored")); } catch (Exception ignored) { return null; } } }; var launcher = createLauncher(engine); var discoveryListener = mock(LauncherDiscoveryListener.class); var request = request() // .listeners(discoveryListener) // .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // .build(); var testPlan = launcher.discover(request); assertThat(testPlan.getRoots()).hasSize(1); var engineIdentifier = getOnlyElement(testPlan.getRoots()); assertThat(getOnlyElement(testPlan.getRoots()).getDisplayName()).isEqualTo("my-engine-id"); InOrder inOrder = inOrder(discoveryListener); inOrder.verify(discoveryListener).launcherDiscoveryStarted(request); assertDiscoveryFailed(engine, inOrder, discoveryListener); inOrder.verify(discoveryListener).launcherDiscoveryFinished(request); var listener = mock(TestExecutionListener.class); var executionRequest = LauncherExecutionRequestBuilder.request(testPlan) // .listeners(listener) // .build(); launcher.execute(executionRequest); var testExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); verify(listener).executionStarted(engineIdentifier); verify(listener).executionFinished(eq(engineIdentifier), testExecutionResult.capture()); assertThat(testExecutionResult.getValue().getThrowable()).isPresent(); assertThat(testExecutionResult.getValue().getThrowable().get()) // .hasMessage("TestEngine with ID 'my-engine-id' failed to discover tests"); } private void assertDiscoveryFailed(TestEngine testEngine, InOrder inOrder, LauncherDiscoveryListener discoveryListener) { var engineId = testEngine.getId(); var failureCaptor = ArgumentCaptor.forClass(EngineDiscoveryResult.class); inOrder.verify(discoveryListener).engineDiscoveryStarted(UniqueId.forEngine(engineId)); inOrder.verify(discoveryListener).engineDiscoveryFinished(eq(UniqueId.forEngine(engineId)), failureCaptor.capture()); var result = failureCaptor.getValue(); assertThat(result.getStatus()).isEqualTo(EngineDiscoveryResult.Status.FAILED); assertThat(result.getThrowable()).isPresent(); assertThat(result.getThrowable().get()).hasMessage( "TestEngine with ID '" + engineId + "' failed to discover tests"); } @Test void reportsEngineExecutionFailuresWithoutPriorEvents() { var rootCause = new RuntimeException("something went horribly wrong"); var engine = new TestEngineStub() { @Override public void execute(ExecutionRequest request) { throw rootCause; } }; var listener = mock(TestExecutionListener.class); createLauncher(engine).execute(request().forExecution().listeners(listener).build()); var testExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); verify(listener).executionStarted(any()); verify(listener).executionFinished(any(), testExecutionResult.capture()); assertThat(testExecutionResult.getValue().getThrowable()).isPresent(); assertThat(testExecutionResult.getValue().getThrowable().get()) // .hasMessage("TestEngine with ID 'TestEngineStub' failed to execute tests") // .cause().isSameAs(rootCause); } @Test void reportsEngineExecutionFailuresForSkippedEngine() { var rootCause = new RuntimeException("something went horribly wrong"); var engine = new TestEngineStub() { @Override public void execute(ExecutionRequest request) { var engineDescriptor = request.getRootTestDescriptor(); request.getEngineExecutionListener().executionSkipped(engineDescriptor, "not today"); throw rootCause; } }; var listener = mock(TestExecutionListener.class); createLauncher(engine).execute(request().forExecution().listeners(listener).build()); var testExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); verify(listener).executionStarted(any()); verify(listener).executionFinished(any(), testExecutionResult.capture()); assertThat(testExecutionResult.getValue().getThrowable()).isPresent(); assertThat(testExecutionResult.getValue().getThrowable().get()) // .hasMessage("TestEngine with ID 'TestEngineStub' failed to execute tests") // .cause().isSameAs(rootCause); } @Test void reportsEngineExecutionFailuresForStartedEngine() { var rootCause = new RuntimeException("something went horribly wrong"); var engine = new TestEngineStub() { @Override public void execute(ExecutionRequest request) { var engineDescriptor = request.getRootTestDescriptor(); request.getEngineExecutionListener().executionStarted(engineDescriptor); throw rootCause; } }; var listener = mock(TestExecutionListener.class); createLauncher(engine).execute(request().forExecution().listeners(listener).build()); var testExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); verify(listener).executionStarted(any()); verify(listener).executionFinished(any(), testExecutionResult.capture()); assertThat(testExecutionResult.getValue().getThrowable()).isPresent(); assertThat(testExecutionResult.getValue().getThrowable().get()) // .hasMessage("TestEngine with ID 'TestEngineStub' failed to execute tests") // .cause().isSameAs(rootCause); } @Test void reportsEngineExecutionFailuresForSuccessfullyFinishedEngine() { var rootCause = new RuntimeException("something went horribly wrong"); var engine = new TestEngineStub() { @Override public void execute(ExecutionRequest request) { var engineDescriptor = request.getRootTestDescriptor(); request.getEngineExecutionListener().executionStarted(engineDescriptor); request.getEngineExecutionListener().executionFinished(engineDescriptor, successful()); throw rootCause; } }; var listener = mock(TestExecutionListener.class); createLauncher(engine).execute(request().forExecution().listeners(listener).build()); var testExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); verify(listener).executionStarted(any()); verify(listener).executionFinished(any(), testExecutionResult.capture()); assertThat(testExecutionResult.getValue().getThrowable()).isPresent(); assertThat(testExecutionResult.getValue().getThrowable().get()) // .hasMessage("TestEngine with ID 'TestEngineStub' failed to execute tests") // .cause().isSameAs(rootCause); } @Test void reportsEngineExecutionFailuresForFailedFinishedEngine() { var rootCause = new RuntimeException("something went horribly wrong"); var originalFailure = new RuntimeException("suppressed"); var engine = new TestEngineStub() { @Override public void execute(ExecutionRequest request) { var engineDescriptor = request.getRootTestDescriptor(); var listener = request.getEngineExecutionListener(); listener.executionStarted(engineDescriptor); listener.executionFinished(engineDescriptor, TestExecutionResult.failed(originalFailure)); throw rootCause; } }; var listener = mock(TestExecutionListener.class); createLauncher(engine).execute(request().forExecution().listeners(listener).build()); var testExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); verify(listener).executionStarted(any()); verify(listener).executionFinished(any(), testExecutionResult.capture()); assertThat(testExecutionResult.getValue().getThrowable()).isPresent(); assertThat(testExecutionResult.getValue().getThrowable().get()) // .hasMessage("TestEngine with ID 'TestEngineStub' failed to execute tests") // .hasSuppressedException(originalFailure) // .cause().isSameAs(rootCause); } @Test void reportsSkippedEngines() { var engine = new TestEngineStub() { @Override public void execute(ExecutionRequest request) { var engineDescriptor = request.getRootTestDescriptor(); request.getEngineExecutionListener().executionSkipped(engineDescriptor, "not today"); } }; var listener = mock(TestExecutionListener.class); createLauncher(engine).execute(request().forExecution().listeners(listener).build()); verify(listener).executionSkipped(any(TestIdentifier.class), eq("not today")); verify(listener, times(0)).executionStarted(any()); verify(listener, times(0)).executionFinished(any(), any()); } @Test void reportsFinishedEngines() { var engine = new TestEngineStub() { @Override public void execute(ExecutionRequest request) { var engineDescriptor = request.getRootTestDescriptor(); var listener = request.getEngineExecutionListener(); listener.executionStarted(engineDescriptor); listener.executionFinished(engineDescriptor, successful()); } }; var listener = mock(TestExecutionListener.class); createLauncher(engine).execute(request().forExecution().listeners(listener).build()); verify(listener).executionStarted(any()); verify(listener).executionFinished(any(), eq(successful())); } @Test void discoverTestPlanForSingleEngine() { var engine = new DemoHierarchicalTestEngine("myEngine"); engine.addTest("test1", noOp); engine.addTest("test2", noOp); var launcher = createLauncher(engine); var testPlan = launcher.discover(request().selectors(selectPackage("any")).build()); assertThat(testPlan.getRoots()).hasSize(1); var rootIdentifier = testPlan.getRoots().iterator().next(); assertThat(testPlan.getChildren(rootIdentifier.getUniqueIdObject())).hasSize(2); assertThat(testPlan.getChildren(UniqueId.parse("[engine:myEngine]"))).hasSize(2); } @Test void discoverTestPlanForMultipleEngines() { var firstEngine = new DemoHierarchicalTestEngine("engine1"); TestDescriptor test1 = firstEngine.addTest("test1", noOp); var secondEngine = new DemoHierarchicalTestEngine("engine2"); TestDescriptor test2 = secondEngine.addTest("test2", noOp); var launcher = createLauncher(firstEngine, secondEngine); var testPlan = launcher.discover( request().selectors(selectUniqueId(test1.getUniqueId()), selectUniqueId(test2.getUniqueId())).build()); assertThat(testPlan.getRoots()).hasSize(2); assertThat(testPlan.getChildren(UniqueId.forEngine("engine1"))).hasSize(1); assertThat(testPlan.getChildren(UniqueId.forEngine("engine2"))).hasSize(1); } @Test void launcherAppliesPostDiscoveryFilters() { var engine = new DemoHierarchicalTestEngine("myEngine"); var test1 = engine.addTest("test1", noOp); engine.addTest("test2", noOp); var launcher = createLauncher(engine); PostDiscoveryFilter includeWithUniqueIdContainsTest = new PostDiscoveryFilterStub( descriptor -> FilterResult.includedIf(descriptor.getUniqueId().toString().contains("test")), () -> "filter1"); PostDiscoveryFilter includeWithUniqueIdContains1 = new PostDiscoveryFilterStub( descriptor -> FilterResult.includedIf(descriptor.getUniqueId().toString().contains("1")), () -> "filter2"); var testPlan = launcher.discover( // request() // .selectors(selectPackage("any")) // .filters(includeWithUniqueIdContainsTest, includeWithUniqueIdContains1) // .build()); assertThat(testPlan.getChildren(UniqueId.forEngine("myEngine"))).hasSize(1); assertThat(testPlan.getTestIdentifier(test1.getUniqueId())).isNotNull(); } @Test void withoutConfigurationParameters_LauncherPassesEmptyConfigurationParametersIntoTheExecutionRequest() { var engine = new TestEngineSpy(); var launcher = createLauncher(engine); launcher.execute(request().forExecution().build()); var configurationParameters = requireNonNull(engine.requestForExecution).getConfigurationParameters(); assertThat(configurationParameters.get("key")).isNotPresent(); } @Test void withConfigurationParameters_LauncherPassesPopulatedConfigurationParametersIntoTheExecutionRequest() { var engine = new TestEngineSpy(); var launcher = createLauncher(engine); launcher.execute(request().configurationParameter("key", "value").forExecution().build()); var configurationParameters = requireNonNull(engine.requestForExecution).getConfigurationParameters(); assertThat(configurationParameters.get("key")).isPresent(); assertThat(configurationParameters.get("key")).contains("value"); } @Test @SetSystemProperty(key = DefaultLauncherTests.FOO, value = DefaultLauncherTests.BAR) void withoutConfigurationParameters_LookupFallsBackToSystemProperty() { var engine = new TestEngineSpy(); var launcher = createLauncher(engine); launcher.execute(request().forExecution().build()); var configurationParameters = requireNonNull(engine.requestForExecution).getConfigurationParameters(); var optionalFoo = configurationParameters.get(FOO); assertTrue(optionalFoo.isPresent(), "foo should have been picked up via system property"); assertEquals(BAR, optionalFoo.get(), "foo property"); } @Test void withAdditionalListener() { var engine = new TestEngineSpy(); var listener = new SummaryGeneratingListener(); var launcher = createLauncher(engine); launcher.execute(request().forExecution().listeners(listener).build()); assertThat(listener.getSummary()).isNotNull(); assertThat(listener.getSummary().getContainersFoundCount()).isEqualTo(1); assertThat(listener.getSummary().getTestsFoundCount()).isEqualTo(1); } @Test void prunesTestDescriptorsAfterApplyingPostDiscoveryFilters() { var engine = new TestEngineSpy() { @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { super.discover(discoveryRequest, uniqueId); var engineDescriptor = new TestDescriptorStub(uniqueId, uniqueId.toString()); var containerDescriptor = new TestDescriptorStub(uniqueId.append("container", "a"), "container") { @Override public Type getType() { return Type.CONTAINER; } }; containerDescriptor.addChild( new TestDescriptorStub(containerDescriptor.getUniqueId().append("test", "b"), "test")); engineDescriptor.addChild(containerDescriptor); return engineDescriptor; } }; var launcher = createLauncher(engine); var testPlan = launcher.discover(request().filters( (PostDiscoveryFilter) testDescriptor -> FilterResult.includedIf(testDescriptor.isContainer())).build()); assertThat(testPlan.getRoots()).hasSize(1); var engineIdentifier = getOnlyElement(testPlan.getRoots()); assertThat(testPlan.getChildren(engineIdentifier)).isEmpty(); } @Test void reportsDynamicTestDescriptorsCorrectly() { var engineId = UniqueId.forEngine("engine"); var containerAndTestId = engineId.append("c&t", "c&t"); var dynamicTestId = containerAndTestId.append("test", "test"); var engine = new TestEngineSpy(engineId.getLastSegment().getValue()) { @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { super.discover(discoveryRequest, uniqueId); var engineDescriptor = new TestDescriptorStub(uniqueId, uniqueId.toString()); engineDescriptor.addChild(new TestDescriptorStub(containerAndTestId, "c&t") { @Override public Type getType() { return Type.CONTAINER_AND_TEST; } }); return engineDescriptor; } @Override public void execute(ExecutionRequest request) { super.execute(request); var listener = request.getEngineExecutionListener(); listener.executionStarted(request.getRootTestDescriptor()); var containerAndTest = getOnlyElement(request.getRootTestDescriptor().getChildren()); listener.executionStarted(containerAndTest); var dynamicTest = new TestDescriptorStub(dynamicTestId, "test"); dynamicTest.setParent(containerAndTest); listener.dynamicTestRegistered(dynamicTest); listener.executionStarted(dynamicTest); listener.executionFinished(dynamicTest, successful()); listener.executionFinished(containerAndTest, successful()); listener.executionFinished(request.getRootTestDescriptor(), successful()); } }; var launcher = createLauncher(engine); var listener = mock(TestExecutionListener.class); launcher.execute(request().forExecution().listeners(listener).build()); var inOrder = inOrder(listener); var testPlanArgumentCaptor = ArgumentCaptor.forClass(TestPlan.class); inOrder.verify(listener).testPlanExecutionStarted(testPlanArgumentCaptor.capture()); var testPlan = testPlanArgumentCaptor.getValue(); var engineTestIdentifier = testPlan.getTestIdentifier(engineId); var containerAndTestIdentifier = testPlan.getTestIdentifier(containerAndTestId); var dynamicTestIdentifier = testPlan.getTestIdentifier(dynamicTestId); assertThat(engineTestIdentifier.getParentIdObject()).isEmpty(); assertThat(containerAndTestIdentifier.getParentIdObject()).contains(engineId); assertThat(dynamicTestIdentifier.getParentIdObject()).contains(containerAndTestId); inOrder.verify(listener).executionStarted(engineTestIdentifier); inOrder.verify(listener).executionStarted(containerAndTestIdentifier); inOrder.verify(listener).dynamicTestRegistered(dynamicTestIdentifier); inOrder.verify(listener).executionStarted(dynamicTestIdentifier); inOrder.verify(listener).executionFinished(dynamicTestIdentifier, successful()); inOrder.verify(listener).executionFinished(containerAndTestIdentifier, successful()); inOrder.verify(listener).executionFinished(engineTestIdentifier, successful()); inOrder.verify(listener).testPlanExecutionFinished(same(testPlan)); } @Test void launcherCanExecuteTestPlanExactlyOnce() { var engine = mock(TestEngine.class); when(engine.getId()).thenReturn("some-engine"); when(engine.discover(any(), any())).thenAnswer(invocation -> { UniqueId uniqueId = invocation.getArgument(1); return new EngineDescriptor(uniqueId, uniqueId.toString()); }); var launcher = createLauncher(engine); var testPlan = launcher.discover(request().build()); verify(engine, times(1)).discover(any(), any()); var executionRequest = LauncherExecutionRequestBuilder.request(testPlan).build(); launcher.execute(executionRequest); verify(engine, times(1)).execute(any()); assertPreconditionViolationFor(() -> launcher.execute(executionRequest))// .withMessage("TestPlan must only be executed once"); } @Test void thirdPartyEngineUsingReservedEngineIdPrefixEmitsWarning(@TrackLogRecords LogRecordListener listener) { var id = "junit-using-reserved-prefix"; var launcher = createLauncher(new TestEngineStub(id)); launcher.discover(request().build()); assertThat(listener.stream(EngineIdValidator.class, Level.WARNING).map(LogRecord::getMessage)) // .containsExactly( "Third-party TestEngine implementations are forbidden to use the reserved 'junit-' prefix for their ID: '" + id + "'"); } @Test void thirdPartyEngineClaimingToBeJupiterResultsInException() { assertImposter("junit-jupiter"); } @Test void thirdPartyEngineClaimingToBeVintageResultsInException() { assertImposter("junit-vintage"); } private void assertImposter(String id) { TestEngine impostor = new TestEngineStub(id); var launcher = createLauncher(impostor); Exception exception = assertThrows(JUnitException.class, () -> launcher.discover(request().build())); assertThat(exception).hasMessage( "Third-party TestEngine '%s' is forbidden to use the reserved '%s' TestEngine ID.", impostor.getClass().getName(), id); } @Test void dryRunModeReportsEventsForAllTestsButDoesNotExecuteThem() { var engine = new DemoHierarchicalTestEngine("engine"); var container = engine.addContainer("container", "Container", null); var test = new DemoHierarchicalTestDescriptor(container.getUniqueId().append("test", "test"), "Test", (__, ___) -> { throw new RuntimeException("boom"); }); container.addChild(test); var launcher = createLauncher(engine); TestExecutionListener listener = mock(); var request = request() // .configurationParameter(DRY_RUN_PROPERTY_NAME, "true") // .forExecution() // .listeners(listener) // .build(); launcher.execute(request); var inOrder = inOrder(listener); inOrder.verify(listener).testPlanExecutionStarted(any()); inOrder.verify(listener).executionStarted(TestIdentifier.from(engine.getEngineDescriptor())); inOrder.verify(listener).executionStarted(TestIdentifier.from(container)); inOrder.verify(listener).executionSkipped(TestIdentifier.from(test), "JUnit Platform dry-run mode is enabled"); inOrder.verify(listener).executionFinished(TestIdentifier.from(container), successful()); inOrder.verify(listener).executionFinished(TestIdentifier.from(engine.getEngineDescriptor()), successful()); inOrder.verify(listener).testPlanExecutionFinished(any()); inOrder.verifyNoMoreInteractions(); } @Test void notifiesDiscoveryListenersOfProcessedSelectors() { TestEngine engine = new TestEngineStub("some-engine-id") { @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { //noinspection CodeBlock2Expr discoveryRequest.getSelectorsByType(DiscoverySelector.class).forEach(selector -> { discoveryRequest.getDiscoveryListener().selectorProcessed(uniqueId, selector, unresolved()); }); return new EngineDescriptor(uniqueId, uniqueId.getLastSegment().getValue()); } }; var engineId = UniqueId.forEngine(engine.getId()); var discoveryListenerOnConfig = mock(LauncherDiscoveryListener.class, "discoveryListenerOnConfig"); var discoveryListenerOnLauncher = mock(LauncherDiscoveryListener.class, "discoveryListenerOnLauncher"); var discoveryListenerOnRequest = mock(LauncherDiscoveryListener.class, "discoveryListenerOnRequest"); var selector = mock(DiscoverySelector.class); var launcherConfig = LauncherFactoryForTestingPurposesOnly.createLauncherConfigBuilderWithDisabledServiceLoading() // .addTestEngines(engine) // .addLauncherDiscoveryListeners(discoveryListenerOnConfig) // .build(); var launcher = LauncherFactory.create(launcherConfig); launcher.registerLauncherDiscoveryListeners(discoveryListenerOnLauncher); launcher.discover(request() // .selectors(selector) // .listeners(discoveryListenerOnRequest) // .build()); assertAll( // () -> verify(discoveryListenerOnConfig).selectorProcessed(engineId, selector, unresolved()), // () -> verify(discoveryListenerOnLauncher).selectorProcessed(engineId, selector, unresolved()), // () -> verify(discoveryListenerOnRequest).selectorProcessed(engineId, selector, unresolved()) // ); } @Test void reportsEngineExecutionFailureForCriticalDiscoveryIssuesAndLogsRemaining( @TrackLogRecords LogRecordListener listener) { var result = execute(new TestEngineStub("engine-id") { @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { var listener = discoveryRequest.getDiscoveryListener(); listener.issueEncountered(uniqueId, DiscoveryIssue.create(Severity.ERROR, "error")); listener.issueEncountered(uniqueId, DiscoveryIssue.create(Severity.WARNING, "warning")); return new EngineDescriptor(uniqueId, "Engine") { @Override public Set getTags() { return Set.of(TestTag.create("custom-tag")); } }; } }); assertThat(result.testPlan().containsTests()).isTrue(); assertThat(result.testIdentifier().getDisplayName()).isEqualTo("Engine"); assertThat(result.testIdentifier().getTags()).containsExactly(TestTag.create("custom-tag")); assertThat(result.testExecutionResult().getStatus()).isEqualTo(Status.FAILED); assertThat(result.testExecutionResult().getThrowable().orElseThrow()) // .isInstanceOf(DiscoveryIssueException.class) // .hasMessageStartingWith( "TestEngine with ID 'engine-id' encountered a critical issue during test discovery") // .hasMessageContaining("(1) [ERROR] error"); var logRecord = findFirstDiscoveryIssueLogRecord(listener, Level.WARNING); assertThat(logRecord.getMessage()) // .startsWith("TestEngine with ID 'engine-id' encountered a non-critical issue during test discovery") // .contains("(1) [WARNING] warning"); assertThat(logRecord.getInstant()) // .isBetween(result.startTime(), result.finishTime()); } @Test void logsNonCriticalIssuesForRegularEngineExecution(@TrackLogRecords LogRecordListener listener) { var result = execute(new TestEngineStub("engine-id") { @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { var listener = discoveryRequest.getDiscoveryListener(); listener.issueEncountered(uniqueId, DiscoveryIssue.create(Severity.INFO, "info")); return new EngineDescriptor(uniqueId, "Engine"); } @Override public void execute(ExecutionRequest request) { var executionListener = request.getEngineExecutionListener(); var engineDescriptor = request.getRootTestDescriptor(); executionListener.executionStarted(engineDescriptor); executionListener.executionFinished(engineDescriptor, successful()); } }); assertThat(result.testIdentifier().getDisplayName()).isEqualTo("Engine"); assertThat(result.testExecutionResult().getStatus()).isEqualTo(Status.SUCCESSFUL); var logRecord = findFirstDiscoveryIssueLogRecord(listener, Level.INFO); assertThat(logRecord.getMessage()) // .startsWith("TestEngine with ID 'engine-id' encountered a non-critical issue during test discovery") // .contains("(1) [INFO] info"); assertThat(logRecord.getInstant()) // .isBetween(result.startTime(), result.finishTime()); } @Test void logsAllIssuesForDiscoveryFailure(@TrackLogRecords LogRecordListener listener) { var result = execute(new TestEngineStub("engine-id") { @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { var listener = discoveryRequest.getDiscoveryListener(); listener.issueEncountered(uniqueId, DiscoveryIssue.create(Severity.ERROR, "error")); listener.issueEncountered(uniqueId, DiscoveryIssue.create(Severity.INFO, "info")); throw new RuntimeException("boom"); } }); assertThat(result.testPlan().containsTests()).isTrue(); assertThat(result.testIdentifier().getDisplayName()).isEqualTo("engine-id"); assertThat(result.testExecutionResult().getStatus()).isEqualTo(Status.FAILED); assertThat(result.testExecutionResult().getThrowable().orElseThrow()) // .hasMessage("TestEngine with ID 'engine-id' failed to discover tests") // .cause().hasMessage("boom"); var logRecord = findFirstDiscoveryIssueLogRecord(listener, Level.SEVERE); assertThat(logRecord.getMessage()) // .startsWith("TestEngine with ID 'engine-id' encountered a critical issue during test discovery") // .contains("(1) [ERROR] error"); assertThat(logRecord.getInstant()) // .isBetween(result.startTime(), result.finishTime()); logRecord = findFirstDiscoveryIssueLogRecord(listener, Level.INFO); assertThat(logRecord.getMessage()) // .startsWith("TestEngine with ID 'engine-id' encountered a non-critical issue during test discovery") // .contains("(1) [INFO] info"); assertThat(logRecord.getInstant()) // .isBetween(result.startTime(), result.finishTime()); } @Test void logsNonCriticalIssuesForExecutionFailure(@TrackLogRecords LogRecordListener listener) { var result = execute(new TestEngineStub("engine-id") { @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { var listener = discoveryRequest.getDiscoveryListener(); listener.issueEncountered(uniqueId, DiscoveryIssue.create(Severity.INFO, "info")); return new EngineDescriptor(uniqueId, "Engine"); } @Override public void execute(ExecutionRequest request) { throw new RuntimeException("boom"); } }); assertThat(result.testIdentifier().getDisplayName()).isEqualTo("Engine"); assertThat(result.testExecutionResult().getThrowable().orElseThrow()) // .hasMessage("TestEngine with ID 'engine-id' failed to execute tests") // .cause().hasMessage("boom"); var logRecord = findFirstDiscoveryIssueLogRecord(listener, Level.INFO); assertThat(logRecord.getMessage()) // .startsWith("TestEngine with ID 'engine-id' encountered a non-critical issue during test discovery") // .contains("(1) [INFO] info"); assertThat(logRecord.getInstant()) // .isBetween(result.startTime(), result.finishTime()); } @Test void reportsEngineExecutionFailureOnUnresolvedUniqueIdSelectorWithEnginePrefix() { var engine = createEngineThatCannotResolveAnything("some-engine"); var selector = selectUniqueId(UniqueId.forEngine(engine.getId())); var result = execute(engine, request -> request.selectors(selector)); assertThat(result.testExecutionResult().getStatus()).isEqualTo(Status.FAILED); assertThat(result.testExecutionResult().getThrowable().orElseThrow()) // .isInstanceOf(DiscoveryIssueException.class) // .hasMessageStartingWith( "TestEngine with ID 'some-engine' encountered a critical issue during test discovery") // .hasMessageContaining("(1) [ERROR] %s could not be resolved", selector); } @Test void ignoresUnresolvedUniqueIdSelectorWithoutEnginePrefix() { var engine = createEngineThatCannotResolveAnything("some-engine"); var selector = selectUniqueId(UniqueId.forEngine("some-other-engine")); var result = execute(engine, request -> request.selectors(selector)); assertThat(result.testExecutionResult().getStatus()).isEqualTo(Status.SUCCESSFUL); } @Test void reportsEngineExecutionFailureForSelectorResolutionFailure() { var engine = createEngineThatFailsToResolveAnything("some-engine", new RuntimeException("boom")); var selector = selectClass(Object.class); var result = execute(engine, request -> request.selectors(selector)); assertThat(result.testExecutionResult().getStatus()).isEqualTo(Status.FAILED); assertThat(result.testExecutionResult().getThrowable().orElseThrow()) // .isInstanceOf(DiscoveryIssueException.class) // .hasMessageStartingWith( "TestEngine with ID 'some-engine' encountered a critical issue during test discovery") // .hasMessageContaining("(1) [ERROR] %s resolution failed", selector) // .hasMessageContaining("Cause: java.lang.RuntimeException: boom"); } @Test void allowsConfiguringCriticalDiscoveryIssueSeverity() { var engine = new TestEngineStub("engine-id") { @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { var listener = discoveryRequest.getDiscoveryListener(); listener.issueEncountered(uniqueId, DiscoveryIssue.create(Severity.INFO, "info")); return new EngineDescriptor(uniqueId, "Engine"); } }; var result = execute(engine, request -> request // .configurationParameter(LauncherConstants.CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME, "info")); assertThat(result.testExecutionResult().getStatus()).isEqualTo(Status.FAILED); assertThat(result.testExecutionResult().getThrowable().orElseThrow()) // .isInstanceOf(DiscoveryIssueException.class) // .hasMessageStartingWith( "TestEngine with ID 'engine-id' encountered a critical issue during test discovery") // .hasMessageContaining("(1) [INFO] info"); } @Test void failsIfCriticalSeverityIsConfiguredIncorrectly() { var engine = new TestEngineStub("engine-id") { @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { var listener = discoveryRequest.getDiscoveryListener(); listener.issueEncountered(uniqueId, DiscoveryIssue.create(Severity.INFO, "info")); return new EngineDescriptor(uniqueId, "Engine"); } @Override public void execute(ExecutionRequest request) { var executionListener = request.getEngineExecutionListener(); var engineDescriptor = request.getRootTestDescriptor(); executionListener.executionStarted(engineDescriptor); executionListener.executionFinished(engineDescriptor, successful()); } }; var exception = assertThrows(JUnitException.class, () -> execute(engine, request -> request // .configurationParameter(LauncherConstants.CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME, "wrong"))); assertThat(exception) // .hasRootCauseMessage( "Invalid DiscoveryIssue.Severity 'wrong' set via the '%s' configuration parameter.", LauncherConstants.CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME); } @Test void failsDuringDiscoveryIfConfigurationParameterIsSetAccordingly() { var engine = new TestEngineStub("engine-id") { @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { var listener = discoveryRequest.getDiscoveryListener(); listener.issueEncountered(uniqueId, DiscoveryIssue.create(Severity.ERROR, "error")); return new EngineDescriptor(uniqueId, "Engine"); } }; var exception = assertThrows(DiscoveryIssueException.class, () -> execute(engine, request -> request // .configurationParameter(LauncherConstants.DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME, "discovery"))); assertThat(exception) // .isInstanceOf(DiscoveryIssueException.class) // .hasMessageStartingWith( "TestEngine with ID 'engine-id' encountered a critical issue during test discovery") // .hasMessageContaining("(1) [ERROR] error"); } @ParameterizedTest @ValueSource(strings = { "discovery", "execution" }) void logsNonCriticalIssuesOnlyOnce(String phase, @TrackLogRecords LogRecordListener listener) { var engine = new TestEngineStub("engine-id") { @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { var listener = discoveryRequest.getDiscoveryListener(); listener.issueEncountered(uniqueId, DiscoveryIssue.create(Severity.WARNING, "warning")); return new EngineDescriptor(uniqueId, "Engine"); } @Override public void execute(ExecutionRequest request) { var executionListener = request.getEngineExecutionListener(); var engineDescriptor = request.getRootTestDescriptor(); executionListener.executionStarted(engineDescriptor); executionListener.executionFinished(engineDescriptor, successful()); } }; execute(engine, request -> request // .configurationParameter(LauncherConstants.DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME, phase)); assertThat(listener.stream(DiscoveryIssueNotifier.class, Level.WARNING)).hasSize(1); } @Test void failsDuringDiscoveryIfConfigurationParameterValueIsInvalid() { var engine = new TestEngineStub("engine-id"); var exception = assertThrows(JUnitException.class, () -> execute(engine, request -> request // .configurationParameter(LauncherConstants.DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME, "wrong"))); assertThat(exception) // .hasRootCauseMessage("Invalid LauncherPhase 'wrong' set via the '%s' configuration parameter.", LauncherConstants.DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME); } @Test void reportsChildrenOfEngineDescriptorAsSkippedAfterCancellationWasRequested() { var engine = spy(new TestEngineStub("engine-id") { @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { var engineDescriptor = new EngineDescriptor(uniqueId, "Engine"); var container = new TestDescriptorStub(uniqueId.append("container", "1"), "Container"); var test = new TestDescriptorStub(container.getUniqueId().append("test", "1"), "Test"); container.addChild(test); engineDescriptor.addChild(container); return engineDescriptor; } }); var executionListener = mock(TestExecutionListener.class); var cancellationToken = CancellationToken.create(); cancellationToken.cancel(); execute(engine, identity(), executionRequest -> executionRequest // .listeners(executionListener) // .cancellationToken(cancellationToken)); verify(engine, never()).execute(any()); var inOrder = inOrder(executionListener); inOrder.verify(executionListener).testPlanExecutionStarted(any()); inOrder.verify(executionListener).executionStarted( argThat(d -> d.getUniqueIdObject().equals(UniqueId.forEngine("engine-id")))); inOrder.verify(executionListener).executionSkipped( argThat(d -> d.getUniqueIdObject().getLastSegment().getType().equals("container")), eq("Execution cancelled")); inOrder.verify(executionListener).executionFinished( argThat(d -> d.getUniqueIdObject().equals(UniqueId.forEngine("engine-id"))), argThat(result -> result.getStatus() == ABORTED)); inOrder.verify(executionListener).testPlanExecutionFinished(any()); inOrder.verifyNoMoreInteractions(); } private static ReportedData execute(TestEngine engine) { return execute(engine, identity()); } private static ReportedData execute(TestEngine engine, UnaryOperator discoveryConfigurer) { return execute(engine, discoveryConfigurer, identity()); } private static ReportedData execute(TestEngine engine, UnaryOperator discoveryConfigurer, UnaryOperator executionConfigurer) { var executionListener = mock(TestExecutionListener.class); AtomicReference<@Nullable Instant> startTime = new AtomicReference<>(); doAnswer(invocation -> { startTime.set(Instant.now()); return null; }).when(executionListener).executionStarted(any()); AtomicReference<@Nullable Instant> finishTime = new AtomicReference<>(); doAnswer(invocation -> { finishTime.set(Instant.now()); return null; }).when(executionListener).executionFinished(any(), any()); var launcher = createLauncher(engine); var discoveryRequestBuilder = request() // .enableImplicitConfigurationParameters(false) // .configurationParameter(DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME, "execution") // .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging"); var discoveryRequest = discoveryConfigurer.apply(discoveryRequestBuilder).build(); var testPlan = launcher.discover(discoveryRequest); var executionRequestBuilder = LauncherExecutionRequestBuilder.request(testPlan) // .listeners(executionListener); var executionRequest = executionConfigurer.apply(executionRequestBuilder).build(); launcher.execute(executionRequest); var inOrder = inOrder(executionListener); var testIdentifier = ArgumentCaptor.forClass(TestIdentifier.class); var testExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); inOrder.verify(executionListener).testPlanExecutionStarted(any()); inOrder.verify(executionListener).executionStarted(testIdentifier.capture()); inOrder.verify(executionListener).executionFinished(any(), testExecutionResult.capture()); inOrder.verify(executionListener).testPlanExecutionFinished(any()); inOrder.verifyNoMoreInteractions(); return new ReportedData(testPlan, testIdentifier.getValue(), testExecutionResult.getValue(), requireNonNull(startTime.get()), requireNonNull(finishTime.get())); } private static LogRecord findFirstDiscoveryIssueLogRecord(LogRecordListener listener, Level level) { return listener.stream(DiscoveryIssueNotifier.class, level) // .findFirst() // .orElseThrow(); } private record ReportedData(TestPlan testPlan, TestIdentifier testIdentifier, TestExecutionResult testExecutionResult, Instant startTime, Instant finishTime) { } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/core/DiscoveryIssueCollectorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import org.junit.jupiter.api.Test; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.UniqueId; class DiscoveryIssueCollectorTests { @Test void reportsCollectedDiscoveryIssues() { var collector = new DiscoveryIssueCollector(mock()); var issue = DiscoveryIssue.create(Severity.ERROR, "hello"); collector.issueEncountered(UniqueId.forEngine("dummy"), issue); assertThat(collector.toNotifier().getAllIssues()).containsExactly(issue); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/core/DiscoveryIssueReportingDiscoveryListenerTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathResource; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectDirectory; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectFile; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUri; import java.io.File; import java.net.URI; import java.util.ArrayList; import java.util.stream.Stream; import org.jspecify.annotations.NullMarked; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.RecordArguments; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.SelectorResolutionResult; import org.junit.platform.engine.TestSource; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.discovery.FilePosition; import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.platform.engine.support.descriptor.ClasspathResourceSource; import org.junit.platform.engine.support.descriptor.DirectorySource; import org.junit.platform.engine.support.descriptor.FileSource; import org.junit.platform.engine.support.descriptor.PackageSource; import org.junit.platform.engine.support.descriptor.UriSource; import org.junit.platform.launcher.LauncherDiscoveryListener; @NullMarked class DiscoveryIssueReportingDiscoveryListenerTests { @ParameterizedTest(name = "{0}") @MethodSource("pairs") void reportsFailedResolutionResultAsDiscoveryIssue(DiscoverySelector selector, TestSource source) { var issues = new ArrayList<>(); var collector = new LauncherDiscoveryListener() { @Override public void issueEncountered(UniqueId engineId, DiscoveryIssue issue) { issues.add(issue); } }; var listener = new DiscoveryIssueReportingDiscoveryListener(collector); var failure = SelectorResolutionResult.failed(new RuntimeException("boom")); listener.selectorProcessed(UniqueId.forEngine("dummy"), selector, failure); var expectedIssue = DiscoveryIssue.builder(Severity.ERROR, selector + " resolution failed") // .cause(failure.getThrowable()) // .source(source) // .build(); assertThat(issues).containsExactly(expectedIssue); } public static Stream pairs() { return Stream.of( // new Pair(selectClass("SomeClass"), ClassSource.from("SomeClass")), // new Pair(selectMethod("SomeClass#someMethod(int,int)"), org.junit.platform.engine.support.descriptor.MethodSource.from("SomeClass", "someMethod", "int,int")), // new Pair(selectClasspathResource("someResource"), ClasspathResourceSource.from("someResource")), // new Pair(selectClasspathResource("someResource", FilePosition.from(42)), ClasspathResourceSource.from("someResource", org.junit.platform.engine.support.descriptor.FilePosition.from(42))), // 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)), FileSource.from(new File("someFile"), org.junit.platform.engine.support.descriptor.FilePosition.from(42))), // new Pair(selectFile("someFile", FilePosition.from(42, 23)), FileSource.from(new File("someFile"), org.junit.platform.engine.support.descriptor.FilePosition.from(42, 23))), // new Pair(selectDirectory("someDir"), DirectorySource.from(new File("someDir"))), // new Pair(selectUri("some:uri"), UriSource.from(URI.create("some:uri"))) // ); } record Pair(DiscoverySelector selector, TestSource source) implements RecordArguments { } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/core/EngineDiscoveryResultValidatorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import org.junit.jupiter.api.Test; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.UniqueId; import org.junit.platform.fakes.TestDescriptorStub; import org.junit.platform.fakes.TestEngineStub; /** * @since 1.3 */ class EngineDiscoveryResultValidatorTests { private final TestEngine testEngine = new TestEngineStub("my-engine"); private final EngineDiscoveryResultValidator validator = new EngineDiscoveryResultValidator(); @Test void detectCycleWithDoubleRoot() { var root = new TestDescriptorStub(UniqueId.forEngine("root"), "root"); validator.validate(testEngine, root); root.addChild(root); assertPreconditionViolationFor(() -> validator.validate(testEngine, root)).withMessage(""" The discover() method for TestEngine with ID 'my-engine' returned a cyclic graph; \ [engine:root] exists in at least two paths: (1) [engine:root] (2) [engine:root] -> [engine:root]"""); } @Test void detectCycleWithDoubleGroup() { var rootId = UniqueId.forEngine("root"); var root = new TestDescriptorStub(rootId, "root"); TestDescriptor group1 = new TestDescriptorStub(rootId.append("group", "1"), "1"); TestDescriptor group2 = new TestDescriptorStub(rootId.append("group", "2"), "2"); root.addChild(group1); root.addChild(group2); validator.validate(testEngine, root); group2.addChild(group1); assertPreconditionViolationFor(() -> validator.validate(testEngine, root)).withMessage(""" The discover() method for TestEngine with ID 'my-engine' returned a cyclic graph; \ [engine:root]/[group:1] exists in at least two paths: (1) [engine:root] -> [engine:root]/[group:1] (2) [engine:root] -> [engine:root]/[group:2] -> [engine:root]/[group:1]"""); } @Test void detectCycleWithDoubleTest() { var rootId = UniqueId.forEngine("root"); var root = new TestDescriptorStub(rootId, "root"); TestDescriptor group1 = new TestDescriptorStub(rootId.append("group", "1"), "1"); TestDescriptor group2 = new TestDescriptorStub(rootId.append("group", "2"), "2"); root.addChild(group1); root.addChild(group2); TestDescriptor test1 = new TestDescriptorStub(group1.getUniqueId().append("test", "1"), "1-1"); TestDescriptor test2 = new TestDescriptorStub(group2.getUniqueId().append("test", "2"), "2-2"); group1.addChild(test1); group2.addChild(test2); validator.validate(testEngine, root); group2.addChild(test1); assertPreconditionViolationFor(() -> validator.validate(testEngine, root)).withMessage(""" The discover() method for TestEngine with ID 'my-engine' returned a cyclic graph; \ [engine:root]/[group:1]/[test:1] exists in at least two paths: (1) [engine:root] -> [engine:root]/[group:1] -> [engine:root]/[group:1]/[test:1] (2) [engine:root] -> [engine:root]/[group:2] -> [engine:root]/[group:1]/[test:1]"""); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/core/ExecutionListenerAdapterTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.quality.Strictness.STRICT_STUBS; import java.nio.file.Path; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.engine.support.descriptor.DemoMethodTestDescriptor; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoSettings; /** * @since 1.0 */ @MockitoSettings(strictness = STRICT_STUBS) class ExecutionListenerAdapterTests { final UniqueId uniqueId = UniqueId.root("method", "demoTestMethod"); final TestDescriptor testDescriptor = createDemoMethodTestDescriptor(); final TestIdentifier testIdentifier = TestIdentifier.from(testDescriptor); @Mock TestPlan testPlan; @Mock TestExecutionListener testExecutionListener; @AfterEach void verifyNoMoreInteractions() { Mockito.verifyNoMoreInteractions(testPlan, testExecutionListener); } @Test void dynamicTestRegistered() { var executionListenerAdapter = new ExecutionListenerAdapter(testPlan, testExecutionListener); executionListenerAdapter.dynamicTestRegistered(testDescriptor); verify(testExecutionListener).dynamicTestRegistered(testIdentifier); verify(testPlan).addInternal(testIdentifier); } @Test void executionStarted() { var executionListenerAdapter = new ExecutionListenerAdapter(testPlan, testExecutionListener); when(testPlan.getTestIdentifier(uniqueId)).thenReturn(testIdentifier); executionListenerAdapter.executionStarted(testDescriptor); verify(testExecutionListener).executionStarted(testIdentifier); } @Test void executionSkipped() { var executionListenerAdapter = new ExecutionListenerAdapter(testPlan, testExecutionListener); var reason = "skip reason"; when(testPlan.getTestIdentifier(uniqueId)).thenReturn(testIdentifier); executionListenerAdapter.executionSkipped(testDescriptor, reason); verify(testExecutionListener).executionSkipped(testIdentifier, reason); } @Test void executionFinished() { var executionListenerAdapter = new ExecutionListenerAdapter(testPlan, testExecutionListener); var testExecutionResult = TestExecutionResult.successful(); when(testPlan.getTestIdentifier(uniqueId)).thenReturn(testIdentifier); executionListenerAdapter.executionFinished(testDescriptor, testExecutionResult); verify(testExecutionListener).executionFinished(testIdentifier, testExecutionResult); } @Test void testReportingEntryPublished() { var executionListenerAdapter = new ExecutionListenerAdapter(testPlan, testExecutionListener); var entry = ReportEntry.from("one", "two"); when(testPlan.getTestIdentifier(uniqueId)).thenReturn(testIdentifier); executionListenerAdapter.reportingEntryPublished(testDescriptor, entry); verify(testExecutionListener).reportingEntryPublished(testIdentifier, entry); } @Test void fileEntryPublished() { var executionListenerAdapter = new ExecutionListenerAdapter(testPlan, testExecutionListener); var entry = FileEntry.from(Path.of("entry.txt"), "application/txt"); when(testPlan.getTestIdentifier(uniqueId)).thenReturn(testIdentifier); executionListenerAdapter.fileEntryPublished(testDescriptor, entry); verify(testExecutionListener).fileEntryPublished(testIdentifier, entry); } private TestDescriptor createDemoMethodTestDescriptor() { var demoTestMethod = ReflectionUtils.findMethod(ExecutionListenerAdapterTests.class, "demoTestMethod").orElseThrow(); return new DemoMethodTestDescriptor(uniqueId, demoTestMethod); } //for reflection purposes only @SuppressWarnings("unused") void demoTestMethod() { } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/core/HierarchicalOutputDirectoryCreatorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.nio.file.Path; import java.util.function.Supplier; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoSettings; @MockitoSettings class HierarchicalOutputDirectoryCreatorTests { @TempDir Path tempDir; @Mock Supplier rootDirSupplier; @Mock TestDescriptor testDescriptor; @InjectMocks HierarchicalOutputDirectoryCreator provider; @BeforeEach void prepareMock() { when(rootDirSupplier.get()).thenReturn(tempDir); } @Test void returnsConfiguredRootDir() { assertThat(provider.getRootDirectory()).isEqualTo(tempDir); assertThat(provider.getRootDirectory()).isEqualTo(tempDir); verify(rootDirSupplier, times(1)).get(); } @Test void createsSubDirectoriesBasedOnUniqueId() throws Exception { var uniqueId = UniqueId.forEngine("engine") // .append("irrelevant", "foo") // .append("irrelevant", "bar"); when(testDescriptor.getUniqueId()).thenReturn(uniqueId); var outputDir = provider.createOutputDirectory(testDescriptor); assertThat(outputDir) // .isEqualTo(tempDir.resolve(Path.of("engine", "foo", "bar"))) // .exists(); } @Test void replacesForbiddenCharacters() throws Exception { var uniqueId = UniqueId.forEngine("Engine<>") // .append("irrelevant", "*/abc"); when(testDescriptor.getUniqueId()).thenReturn(uniqueId); var outputDir = provider.createOutputDirectory(testDescriptor); assertThat(outputDir) // .isEqualTo(tempDir.resolve(Path.of("Engine__", "__abc"))) // .exists(); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/core/InternalTestPlanTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.platform.launcher.core.OutputDirectoryCreators.dummyOutputDirectoryCreator; import static org.mockito.Mockito.mock; import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; import org.junit.platform.engine.support.descriptor.EngineDescriptor; import org.junit.platform.launcher.core.LauncherDiscoveryResult.EngineResultInfo; class InternalTestPlanTests { private final ConfigurationParameters configParams = mock(); private final EngineDescriptor engineDescriptor = new EngineDescriptor(UniqueId.forEngine("foo"), "Foo"); @Test void doesNotContainTestsForEmptyContainers() { engineDescriptor.addChild( new AbstractTestDescriptor(engineDescriptor.getUniqueId().append("test", "bar"), "Bar") { @Override public Type getType() { return Type.CONTAINER; } }); var testPlan = InternalTestPlan.from(createLauncherDiscoveryResult( EngineResultInfo.completed(engineDescriptor, DiscoveryIssueNotifier.NO_ISSUES))); assertThat(testPlan.containsTests()).as("contains tests").isFalse(); } @Test void containsTestsForTests() { engineDescriptor.addChild( new AbstractTestDescriptor(engineDescriptor.getUniqueId().append("test", "bar"), "Bar") { @Override public Type getType() { return Type.TEST; } }); var testPlan = InternalTestPlan.from(createLauncherDiscoveryResult( EngineResultInfo.completed(engineDescriptor, DiscoveryIssueNotifier.NO_ISSUES))); assertThat(testPlan.containsTests()).as("contains tests").isTrue(); } @Test void containsTestsForContainersThatMayRegisterTests() { engineDescriptor.addChild( new AbstractTestDescriptor(engineDescriptor.getUniqueId().append("test", "bar"), "Bar") { @Override public Type getType() { return Type.CONTAINER; } @Override public boolean mayRegisterTests() { return true; } }); var testPlan = InternalTestPlan.from(createLauncherDiscoveryResult( EngineResultInfo.completed(engineDescriptor, DiscoveryIssueNotifier.NO_ISSUES))); assertThat(testPlan.containsTests()).as("contains tests").isTrue(); } @Test void containsTestsForEnginesWithDiscoveryError() { var testPlan = InternalTestPlan.from(createLauncherDiscoveryResult( EngineResultInfo.errored(engineDescriptor, DiscoveryIssueNotifier.NO_ISSUES, new RuntimeException()))); assertThat(testPlan.containsTests()).as("contains tests").isTrue(); } @Test void containsTestsForEnginesWithCriticalDiscoveryIssues() { var testPlan = InternalTestPlan.from(createLauncherDiscoveryResult(EngineResultInfo.completed(engineDescriptor, DiscoveryIssueNotifier.from(Severity.ERROR, List.of(DiscoveryIssue.create(Severity.ERROR, "error")))))); assertThat(testPlan.containsTests()).as("contains tests").isTrue(); } @Test void doesNotContainTestsForEnginesWithNonCriticalDiscoveryIssues() { var testPlan = InternalTestPlan.from(createLauncherDiscoveryResult(EngineResultInfo.completed(engineDescriptor, DiscoveryIssueNotifier.from(Severity.ERROR, List.of(DiscoveryIssue.create(Severity.WARNING, "warning")))))); assertThat(testPlan.containsTests()).as("contains tests").isFalse(); } private LauncherDiscoveryResult createLauncherDiscoveryResult(EngineResultInfo result) { var testEngineResults = Map.of(mock(TestEngine.class), result); return new LauncherDiscoveryResult(testEngineResults, configParams, dummyOutputDirectoryCreator()); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import org.junit.jupiter.api.Test; import org.junit.platform.engine.TestEngine; import org.junit.platform.fakes.TestEngineStub; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherSessionListener; import org.junit.platform.launcher.TestExecutionListener; /** * Unit tests for {@link LauncherConfig} and {@link LauncherConfig.Builder}. * * @since 1.3 */ class LauncherConfigTests { @SuppressWarnings("DataFlowIssue") @Test void preconditions() { assertPreconditionViolationFor(() -> LauncherConfig.builder().addTestEngines((TestEngine[]) null)); assertPreconditionViolationFor( () -> LauncherConfig.builder().addTestExecutionListeners((TestExecutionListener[]) null)); TestEngine engine = new TestEngineStub(); var listener = new TestExecutionListener() { }; assertPreconditionViolationFor(() -> LauncherConfig.builder().addTestEngines(engine, engine, null)); assertPreconditionViolationFor( () -> LauncherConfig.builder().addTestExecutionListeners(listener, listener, null)); } @Test void defaultConfig() { var config = LauncherConfig.DEFAULT; assertTrue(config.isTestEngineAutoRegistrationEnabled(), "Test engine auto-registration should be enabled by default"); assertTrue(config.isLauncherDiscoveryListenerAutoRegistrationEnabled(), "Launcher discovery listener auto-registration should be enabled by default"); assertTrue(config.isTestExecutionListenerAutoRegistrationEnabled(), "Test execution listener auto-registration should be enabled by default"); assertTrue(config.isPostDiscoveryFilterAutoRegistrationEnabled(), "Post-discovery filter auto-registration should be enabled by default"); assertThat(config.getAdditionalTestEngines()).isEmpty(); assertThat(config.getAdditionalTestExecutionListeners()).isEmpty(); } @Test void disableTestEngineAutoRegistration() { var config = LauncherConfig.builder().enableTestEngineAutoRegistration(false).build(); assertFalse(config.isTestEngineAutoRegistrationEnabled()); } @Test void disableLauncherDiscoveryListenerAutoRegistration() { var config = LauncherConfig.builder().enableLauncherDiscoveryListenerAutoRegistration(false).build(); assertFalse(config.isLauncherDiscoveryListenerAutoRegistrationEnabled()); } @Test void disableTestExecutionListenerAutoRegistration() { var config = LauncherConfig.builder().enableTestExecutionListenerAutoRegistration(false).build(); assertFalse(config.isTestExecutionListenerAutoRegistrationEnabled()); } @Test void disablePostDiscoveryFilterAutoRegistration() { var config = LauncherConfig.builder().enablePostDiscoveryFilterAutoRegistration(false).build(); assertFalse(config.isPostDiscoveryFilterAutoRegistrationEnabled()); } @Test void addTestEngines() { TestEngine first = new TestEngineStub(); TestEngine second = new TestEngineStub(); var config = LauncherConfig.builder().addTestEngines(first, second).build(); assertThat(config.getAdditionalTestEngines()).containsOnly(first, second); } @Test void addLauncherSessionListeners() { var first = new LauncherSessionListener() { }; var second = new LauncherSessionListener() { }; var config = LauncherConfig.builder().addLauncherSessionListeners(first, second).build(); assertThat(config.getAdditionalLauncherSessionListeners()).containsOnly(first, second); } @Test void addLauncherDiscoveryListeners() { var first = new LauncherDiscoveryListener() { }; var second = new LauncherDiscoveryListener() { }; var config = LauncherConfig.builder().addLauncherDiscoveryListeners(first, second).build(); assertThat(config.getAdditionalLauncherDiscoveryListeners()).containsOnly(first, second); } @Test void addTestExecutionListeners() { var first = new TestExecutionListener() { }; var second = new TestExecutionListener() { }; var config = LauncherConfig.builder().addTestExecutionListeners(first, second).build(); assertThat(config.getAdditionalTestExecutionListeners()).containsOnly(first, second); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigurationParametersTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static org.assertj.core.api.Assertions.as; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Files; import java.nio.file.Path; import java.util.Map; import java.util.Properties; import java.util.logging.Level; import java.util.logging.LogRecord; import org.assertj.core.api.InstanceOfAssertFactories; import org.jspecify.annotations.NullMarked; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestInstancePostProcessor; import org.junit.jupiter.api.fixtures.TrackLogRecords; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.util.SetSystemProperty; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.discovery.DiscoverySelectors; import org.junit.platform.launcher.listeners.SummaryGeneratingListener; /** * Unit tests for {@link LauncherConfigurationParameters}. * * @since 1.0 */ @NullMarked class LauncherConfigurationParametersTests { private static final String CONFIG_FILE_NAME = "test-junit-platform.properties"; private static final String KEY = "org.junit.platform.launcher.core.LauncherConfigurationParametersTests"; private static final String INHERITED_PARAM = "parent config param"; private static final String CONFIG_PARAM = "explicit config param"; private static final String CONFIG_FILE = "from config file"; private static final String SYSTEM_PROPERTY = "system property"; @SuppressWarnings("DataFlowIssue") @Test void constructorPreconditions() { assertPreconditionViolationFor(() -> fromMap(null)); assertPreconditionViolationFor(() -> fromMapAndFile(Map.of(), null)); assertPreconditionViolationFor(() -> fromMapAndFile(Map.of(), "")); assertPreconditionViolationFor(() -> fromMapAndFile(Map.of(), " ")); } @SuppressWarnings("DataFlowIssue") @Test void getPreconditions() { ConfigurationParameters configParams = fromMap(Map.of()); assertPreconditionViolationFor(() -> configParams.get(null)); assertPreconditionViolationFor(() -> configParams.get("")); assertPreconditionViolationFor(() -> configParams.get(" ")); } @Test void noConfigParams() { ConfigurationParameters configParams = fromMap(Map.of()); assertThat(configParams.get(KEY)).isEmpty(); assertThat(configParams.keySet()).doesNotContain(KEY); assertThat(configParams.toString()).doesNotContain(KEY); } @Test void explicitConfigParam() { ConfigurationParameters configParams = fromMap(Map.of(KEY, CONFIG_PARAM)); assertThat(configParams.get(KEY)).contains(CONFIG_PARAM); assertThat(configParams.keySet()).contains(KEY); assertThat(configParams.toString()).contains(CONFIG_PARAM); } @Test @SetSystemProperty(key = KEY, value = SYSTEM_PROPERTY) void systemProperty() { ConfigurationParameters configParams = fromMap(Map.of()); assertThat(configParams.get(KEY)).contains(SYSTEM_PROPERTY); assertThat(configParams.keySet()).contains(KEY); assertThat(configParams.toString()).doesNotContain(KEY); } @Test void configFile() { ConfigurationParameters configParams = fromMapAndFile(Map.of(), CONFIG_FILE_NAME); assertThat(configParams.get(KEY)).contains(CONFIG_FILE); assertThat(configParams.keySet()).contains(KEY); assertThat(configParams.toString()).contains(CONFIG_FILE); } @Test void inherited() { ConfigurationParameters configParams = fromMapAndParent( // Map.of(), // Map.of(KEY, INHERITED_PARAM)); assertThat(configParams.get(KEY)).contains(INHERITED_PARAM); assertThat(configParams.keySet()).contains(KEY); assertThat(configParams.toString()).contains(KEY); } @Test @SetSystemProperty(key = KEY, value = SYSTEM_PROPERTY) void explicitConfigParamOverridesSystemProperty() { ConfigurationParameters configParams = fromMap(Map.of(KEY, CONFIG_PARAM)); assertThat(configParams.get(KEY)).contains(CONFIG_PARAM); assertThat(configParams.keySet()).contains(KEY); assertThat(configParams.toString()).contains(CONFIG_PARAM); } @Test void explicitConfigParamOverridesConfigFile() { ConfigurationParameters configParams = fromMapAndFile(Map.of(KEY, CONFIG_PARAM), CONFIG_FILE_NAME); assertThat(configParams.get(KEY)).contains(CONFIG_PARAM); assertThat(configParams.keySet()).contains(KEY); assertThat(configParams.toString()).contains(CONFIG_PARAM); } @Test @SetSystemProperty(key = KEY, value = SYSTEM_PROPERTY) void explicitConfigParamOverridesInheritedProperty() { ConfigurationParameters configParams = fromMapAndParent( // Map.of(KEY, CONFIG_PARAM), // Map.of(KEY, INHERITED_PARAM)); assertThat(configParams.get(KEY)).contains(CONFIG_PARAM); assertThat(configParams.keySet()).contains(KEY); assertThat(configParams.toString()).contains(CONFIG_PARAM); } @Test @SetSystemProperty(key = KEY, value = SYSTEM_PROPERTY) void systemPropertyOverridesConfigFile() { ConfigurationParameters configParams = fromMapAndFile(Map.of(), CONFIG_FILE_NAME); assertThat(configParams.get(KEY)).contains(SYSTEM_PROPERTY); assertThat(configParams.keySet()).contains(KEY); assertThat(configParams.toString()).contains(CONFIG_FILE); } @Test @SetSystemProperty(key = KEY, value = SYSTEM_PROPERTY) void inheritedPropertyOverridesSystemProperty() { ConfigurationParameters configParams = fromMapAndParent(Map.of(), Map.of(KEY, INHERITED_PARAM)); assertThat(configParams.get(KEY)).contains(INHERITED_PARAM); assertThat(configParams.keySet()).contains(KEY); assertThat(configParams.toString()).contains(KEY); } @Test void getValueInExtensionContext() { var summary = new SummaryGeneratingListener(); var request = LauncherDiscoveryRequestBuilder.request() // .configurationParameter("thing", "one else!") // .selectors(DiscoverySelectors.selectClass(Something.class)) // .forExecution() // .listeners(summary) // .build(); LauncherFactory.create().execute(request); assertEquals(0, summary.getSummary().getTestsFailedCount()); } @Test void getWithSuccessfulTransformer() { ConfigurationParameters configParams = fromMap(Map.of(KEY, "42")); assertThat(configParams.get(KEY, Integer::valueOf)).contains(42); } @Test void getWithErroneousTransformer() { ConfigurationParameters configParams = fromMap(Map.of(KEY, "42")); var exception = assertThrows(JUnitException.class, () -> configParams.get(KEY, input -> { throw new RuntimeException("foo"); })); assertThat(exception).hasMessageContaining( "Failed to transform configuration parameter with key '" + KEY + "' and initial value '42'"); } @Test @SetSystemProperty(key = KEY, value = SYSTEM_PROPERTY) void ignoresSystemPropertyAndConfigFileWhenImplicitLookupsAreDisabled() { ConfigurationParameters configParams = LauncherConfigurationParameters.builder() // .enableImplicitProviders(false) // .build(); assertThat(configParams.get(KEY)).isEmpty(); } @Test void warnsOnMultiplePropertyResources(@TempDir Path tempDir, @TrackLogRecords LogRecordListener logRecordListener) throws Exception { Properties properties = new Properties(); properties.setProperty(KEY, "from second config file"); try (var out = Files.newOutputStream(tempDir.resolve(CONFIG_FILE_NAME))) { properties.store(out, ""); } ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); URL originalResource = originalClassLoader.getResource(CONFIG_FILE_NAME); try (var customClassLoader = new URLClassLoader(new URL[] { tempDir.toUri().toURL() }, originalClassLoader)) { Thread.currentThread().setContextClassLoader(customClassLoader); ConfigurationParameters configParams = fromMapAndFile(Map.of(), CONFIG_FILE_NAME); assertThat(configParams.get(KEY)).contains(CONFIG_FILE); assertThat(logRecordListener.stream(Level.WARNING).map(LogRecord::getMessage)) // .hasSize(1) // .first(as(InstanceOfAssertFactories.STRING)) // .contains(""" Discovered 2 '%s' configuration files on the classpath (see below); \ only the first (*) will be used. - %s (*) - %s"""// .formatted(CONFIG_FILE_NAME, originalResource, tempDir.resolve(CONFIG_FILE_NAME).toUri().toURL())); } finally { Thread.currentThread().setContextClassLoader(originalClassLoader); } } private static LauncherConfigurationParameters fromMap(Map map) { return LauncherConfigurationParameters.builder().explicitParameters(map).build(); } private static LauncherConfigurationParameters fromMapAndFile(Map map, String configFileName) { return LauncherConfigurationParameters.builder() // .explicitParameters(map) // .configFileName(configFileName) // .build(); } private static LauncherConfigurationParameters fromMapAndParent(Map map, Map inherited) { var parameters = LauncherConfigurationParameters.builder() // .explicitParameters(inherited) // .build(); return LauncherConfigurationParameters.builder() // .explicitParameters(map) // .parentConfigurationParameters(parameters) // .build(); } private static class Mutator implements TestInstancePostProcessor { @Override public void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception { var value = context.getConfigurationParameter("thing").orElse("thing"); Something.class.getField("thing").set(testInstance, value); } } @SuppressWarnings("NewClassNamingConvention") @ExtendWith(Mutator.class) static class Something { // `public` is needed for simple "Class#getField(String)" to work public String thing = "body."; @Test void some() { assertEquals("Someone else!", "Some" + thing); } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilderTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.junit.platform.engine.FilterResult.excluded; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectModule; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.launcher.EngineFilter.includeEngines; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.discoveryRequest; import static org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners.abortOnFailure; import static org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners.logging; import java.lang.reflect.Method; import java.util.HashMap; import java.util.List; import java.util.Map; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.platform.engine.DiscoveryFilter; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.discovery.ClassSelector; import org.junit.platform.engine.discovery.MethodSelector; import org.junit.platform.engine.discovery.ModuleSelector; import org.junit.platform.engine.discovery.PackageSelector; import org.junit.platform.engine.discovery.UniqueIdSelector; import org.junit.platform.fakes.TestEngineStub; import org.junit.platform.launcher.DiscoveryFilterStub; import org.junit.platform.launcher.PostDiscoveryFilterStub; /** * @since 1.0 */ class LauncherDiscoveryRequestBuilderTests { @Nested class DiscoverySelectionTests { @Test void modulesAreStoredInDiscoveryRequest() { // @formatter:off var discoveryRequest = LauncherDiscoveryRequestBuilder.request() .selectors( selectModule("java.base") ).build(); // @formatter:on var packageSelectors = discoveryRequest.getSelectorsByType(ModuleSelector.class).stream().map( ModuleSelector::getModuleName).toList(); assertThat(packageSelectors).contains("java.base"); } @Test void packagesAreStoredInDiscoveryRequest() { // @formatter:off var discoveryRequest = discoveryRequest() .selectors( selectPackage("org.junit.platform.engine") ).build(); // @formatter:on var packageSelectors = discoveryRequest.getSelectorsByType(PackageSelector.class).stream().map( PackageSelector::getPackageName).toList(); assertThat(packageSelectors).contains("org.junit.platform.engine"); } @Test void classesAreStoredInDiscoveryRequest() { // @formatter:off var discoveryRequest = discoveryRequest() .selectors( selectClass(LauncherDiscoveryRequestBuilderTests.class.getName()), selectClass(SampleTestClass.class) ) .build(); // @formatter:on @SuppressWarnings("rawtypes") List classes = discoveryRequest.getSelectorsByType(ClassSelector.class).stream()// .map(ClassSelector::getJavaClass).map(Class.class::cast).toList(); assertThat(classes).contains(SampleTestClass.class, LauncherDiscoveryRequestBuilderTests.class); } @Test void methodsByFullyQualifiedNameAreStoredInDiscoveryRequest() { // @formatter:off var discoveryRequest = discoveryRequest() .selectors(selectMethod(fullyQualifiedMethodName())) .build(); // @formatter:on var methodSelectors = discoveryRequest.getSelectorsByType(MethodSelector.class); assertThat(methodSelectors).hasSize(1); var methodSelector = methodSelectors.getFirst(); assertThat(methodSelector.getJavaClass()).isEqualTo(LauncherDiscoveryRequestBuilderTests.class); assertThat(methodSelector.getJavaMethod()).isEqualTo(fullyQualifiedMethod()); } @Test void methodsByNameAreStoredInDiscoveryRequest() throws Exception { Class testClass = SampleTestClass.class; var testMethod = testClass.getDeclaredMethod("test"); // @formatter:off var discoveryRequest = discoveryRequest() .selectors(selectMethod(SampleTestClass.class.getName(), "test")) .build(); // @formatter:on var methodSelectors = discoveryRequest.getSelectorsByType(MethodSelector.class); assertThat(methodSelectors).hasSize(1); var methodSelector = methodSelectors.getFirst(); assertThat(methodSelector.getJavaClass()).isEqualTo(testClass); assertThat(methodSelector.getJavaMethod()).isEqualTo(testMethod); } @Test void methodsByClassAreStoredInDiscoveryRequest() throws Exception { Class testClass = SampleTestClass.class; var testMethod = testClass.getDeclaredMethod("test"); // @formatter:off var discoveryRequest = (DefaultDiscoveryRequest) discoveryRequest() .selectors( selectMethod(testClass, "test") ).build(); // @formatter:on var methodSelectors = discoveryRequest.getSelectorsByType(MethodSelector.class); assertThat(methodSelectors).hasSize(1); var methodSelector = methodSelectors.getFirst(); assertThat(methodSelector.getJavaClass()).isEqualTo(testClass); assertThat(methodSelector.getJavaMethod()).isEqualTo(testMethod); } @Test void uniqueIdsAreStoredInDiscoveryRequest() { var id1 = UniqueId.forEngine("engine").append("foo", "id1"); var id2 = UniqueId.forEngine("engine").append("foo", "id2"); // @formatter:off var discoveryRequest = discoveryRequest() .selectors( selectUniqueId(id1), selectUniqueId(id2) ).build(); // @formatter:on var uniqueIds = discoveryRequest.getSelectorsByType(UniqueIdSelector.class).stream().map( UniqueIdSelector::getUniqueId).map(Object::toString).toList(); assertThat(uniqueIds).contains(id1.toString(), id2.toString()); } } @Nested class DiscoveryFilterTests { @Test void engineFiltersAreStoredInDiscoveryRequest() { TestEngine engine1 = new TestEngineStub("engine1"); TestEngine engine2 = new TestEngineStub("engine2"); TestEngine engine3 = new TestEngineStub("engine3"); // @formatter:off var discoveryRequest = discoveryRequest() .filters(includeEngines(engine1.getId(), engine2.getId())) .build(); // @formatter:on var filters = discoveryRequest.getEngineFilters(); assertThat(filters).hasSize(1); var engineFilter = filters.getFirst(); assertTrue(engineFilter.apply(engine1).included()); assertTrue(engineFilter.apply(engine2).included()); assertTrue(engineFilter.apply(engine3).excluded()); } @Test void discoveryFiltersAreStoredInDiscoveryRequest() { var filter1 = new DiscoveryFilterStub<>("filter1"); var filter2 = new DiscoveryFilterStub<>("filter2"); // @formatter:off var discoveryRequest = discoveryRequest() .filters(filter1, filter2) .build(); // @formatter:on var filters = discoveryRequest.getFiltersByType(DiscoveryFilter.class); assertThat(filters).containsOnly(filter1, filter2); } @Test void postDiscoveryFiltersAreStoredInDiscoveryRequest() { var postFilter1 = new PostDiscoveryFilterStub("postFilter1"); var postFilter2 = new PostDiscoveryFilterStub("postFilter2"); // @formatter:off var discoveryRequest = discoveryRequest() .filters(postFilter1, postFilter2) .build(); // @formatter:on var filters = discoveryRequest.getPostDiscoveryFilters(); assertThat(filters).containsOnly(postFilter1, postFilter2); } @Test void exceptionForIllegalFilterClass() { assertPreconditionViolationFor(() -> discoveryRequest().filters(o -> excluded("reason")))// .withMessageStartingWith("Filter")// .withMessageEndingWith("must implement EngineFilter, PostDiscoveryFilter, or DiscoveryFilter."); } } @Nested class DiscoveryConfigurationParameterTests { @Test void withoutConfigurationParametersSet_NoConfigurationParametersAreStoredInDiscoveryRequest() { var discoveryRequest = discoveryRequest().build(); var configParams = discoveryRequest.getConfigurationParameters(); assertThat(configParams.get("key")).isNotPresent(); } @Test void configurationParameterAddedDirectly_isStoredInDiscoveryRequest() { // @formatter:off var discoveryRequest = discoveryRequest() .configurationParameter("key", "value") .build(); // @formatter:on var configParams = discoveryRequest.getConfigurationParameters(); assertThat(configParams.get("key")).contains("value"); } @Test void configurationParameterAddedDirectlyTwice_overridesPreviousValueInDiscoveryRequest() { // @formatter:off var discoveryRequest = discoveryRequest() .configurationParameter("key", "value") .configurationParameter("key", "value-new") .build(); // @formatter:on var configParams = discoveryRequest.getConfigurationParameters(); assertThat(configParams.get("key")).contains("value-new"); } @Test void multipleConfigurationParametersAddedDirectly_areStoredInDiscoveryRequest() { // @formatter:off var discoveryRequest = discoveryRequest() .configurationParameter("key1", "value1") .configurationParameter("key2", "value2") .build(); // @formatter:on var configParams = discoveryRequest.getConfigurationParameters(); assertThat(configParams.get("key1")).contains("value1"); assertThat(configParams.get("key2")).contains("value2"); } @Test void configurationParameterAddedByMap_isStoredInDiscoveryRequest() { // @formatter:off var discoveryRequest = discoveryRequest() .configurationParameters(Map.of("key", "value")) .build(); // @formatter:on var configParams = discoveryRequest.getConfigurationParameters(); assertThat(configParams.get("key")).contains("value"); } @Test void multipleConfigurationParametersAddedByMap_areStoredInDiscoveryRequest() { Map configurationParams = new HashMap<>(); configurationParams.put("key1", "value1"); configurationParams.put("key2", "value2"); // @formatter:off var discoveryRequest = discoveryRequest() .configurationParameters(configurationParams) .build(); // @formatter:on var configParams = discoveryRequest.getConfigurationParameters(); assertThat(configParams.get("key1")).contains("value1"); assertThat(configParams.get("key2")).contains("value2"); } @Test void configurationParametersResource_areStoredInDiscoveryRequest() { // @formatter:off var discoveryRequest = discoveryRequest() .configurationParametersResources("config-test.properties") .build(); // @formatter:on var configParams = discoveryRequest.getConfigurationParameters(); assertThat(configParams.get("com.example.prop.first")).contains("first value"); assertThat(configParams.get("com.example.prop.second")).contains("second value"); assertThat(configParams.get("com.example.prop.third")).contains("third value"); } @Test void configurationParametersResource_explicitConfigParametersOverrideResource() { // @formatter:off var discoveryRequest = discoveryRequest() .configurationParametersResources("config-test.properties") .configurationParameter("com.example.prop.first", "first value override") .build(); // @formatter:on var configParams = discoveryRequest.getConfigurationParameters(); assertThat(configParams.get("com.example.prop.first")).contains("first value override"); assertThat(configParams.get("com.example.prop.second")).contains("second value"); } @Test void configurationParametersResource_lastDeclaredResourceFileWins() { // @formatter:off var discoveryRequest = discoveryRequest() .configurationParametersResources("config-test.properties") .configurationParametersResources("config-test-override.properties") .build(); // @formatter:on var configParams = discoveryRequest.getConfigurationParameters(); assertThat(configParams.get("com.example.prop.first")).contains("first value from override file"); assertThat(configParams.get("com.example.prop.second")).contains("second value"); } } @Nested class DiscoveryListenerTests { @Test void usesAbortOnFailureByDefault() { var request = discoveryRequest().build(); assertThat(request.getDiscoveryListener()).isEqualTo(abortOnFailure()); } @Test void onlyAddsAbortOnFailureOnce() { var request = discoveryRequest() // .listeners(abortOnFailure()) // .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "abortOnFailure") // .build(); assertThat(request.getDiscoveryListener()).isEqualTo(abortOnFailure()); } @Test void onlyAddsLoggingOnce() { var request = discoveryRequest() // .listeners(logging()) // .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // .build(); assertThat(request.getDiscoveryListener()).isEqualTo(logging()); } @Test void createsCompositeForMultipleListeners() { var request = discoveryRequest() // .listeners(logging(), abortOnFailure()) // .build(); assertThat(request.getDiscoveryListener().getClass().getSimpleName()).startsWith("Composite"); } } private static class SampleTestClass { @Test void test() { } } private static String fullyQualifiedMethodName() { return LauncherDiscoveryRequestBuilderTests.class.getName() + "#" + fullyQualifiedMethod().getName(); } private static Method fullyQualifiedMethod() { try { return LauncherDiscoveryRequestBuilderTests.class.getDeclaredMethod("myTest"); } catch (Exception ex) { throw new IllegalStateException(ex); } } void myTest() { } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherDiscoveryResultTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.LinkedHashMap; import java.util.Map; import org.junit.jupiter.api.Test; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.UniqueId; import org.junit.platform.launcher.core.LauncherDiscoveryResult.EngineResultInfo; /** * Unit tests for {@link LauncherDiscoveryResult}. */ class LauncherDiscoveryResultTests { /** * @see GitHub - Issue #4862 */ @Test void withRetainedEnginesMaintainsOriginalTestEngineRegistrationOrder() { var engine1 = new DemoEngine("Engine 1"); var engine2 = new DemoEngine("Engine 2"); var engine3 = new DemoEngine("Engine 3"); var engine4 = new DemoEngine("Engine 4"); @SuppressWarnings("serial") Map engineResults = new LinkedHashMap<>() { { put(engine1, new DemoEngineResultInfo(true)); put(engine2, new DemoEngineResultInfo(false)); put(engine3, new DemoEngineResultInfo(false)); put(engine4, new DemoEngineResultInfo(true)); } }; assertThat(engineResults.keySet()).containsExactly(engine1, engine2, engine3, engine4); LauncherDiscoveryResult discoveryResult = new LauncherDiscoveryResult(engineResults, mock(), mock()); assertThat(discoveryResult.getTestEngines()).containsExactly(engine1, engine2, engine3, engine4); LauncherDiscoveryResult prunedDiscoveryResult = discoveryResult.withRetainedEngines(TestDescriptor::isTest); assertThat(prunedDiscoveryResult.getTestEngines()).containsExactly(engine1, engine4); } private record DemoEngine(String id) implements TestEngine { @Override public String getId() { return this.id; } @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { throw new UnsupportedOperationException("discover"); } @Override public void execute(ExecutionRequest request) { throw new UnsupportedOperationException("execute"); } @Override public String toString() { return getId(); } } private static class DemoEngineResultInfo extends EngineResultInfo { DemoEngineResultInfo(boolean isTest) { super(createRootDescriptor(isTest), mock(), null); } private static TestDescriptor createRootDescriptor(boolean isTest) { TestDescriptor rootDescriptor = mock(); when(rootDescriptor.isTest()).thenReturn(isTest); return rootDescriptor; } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherFactoryTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.TemporaryClasspathExecutor.withAdditionalClasspathRoot; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasses; import static org.junit.platform.launcher.LauncherConstants.DEACTIVATE_LISTENERS_PATTERN_PROPERTY_NAME; import static org.junit.platform.launcher.LauncherConstants.ENABLE_LAUNCHER_INTERCEPTORS; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.LogRecord; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.StoreScope; import org.junit.jupiter.api.fixtures.TrackLogRecords; import org.junit.jupiter.api.util.SetSystemProperty; import org.junit.jupiter.engine.JupiterTestEngine; import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.store.Namespace; import org.junit.platform.fakes.TestEngineSpy; import org.junit.platform.launcher.InterceptedTestEngine; import org.junit.platform.launcher.InterceptorInjectedLauncherSessionListener; import org.junit.platform.launcher.LauncherConstants; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.LauncherSession; import org.junit.platform.launcher.LauncherSessionListener; import org.junit.platform.launcher.TagFilter; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestLauncherDiscoveryListener; import org.junit.platform.launcher.TestLauncherInterceptor1; import org.junit.platform.launcher.TestLauncherInterceptor2; import org.junit.platform.launcher.TestLauncherSessionListener; import org.junit.platform.launcher.listeners.AnotherUnusedTestExecutionListener; import org.junit.platform.launcher.listeners.NoopTestExecutionListener; import org.junit.platform.launcher.listeners.UnusedTestExecutionListener; /** * @since 1.0 */ @NullMarked class LauncherFactoryTests { @SuppressWarnings("DataFlowIssue") @Test void preconditions() { assertPreconditionViolationFor(() -> LauncherFactory.create(null)); } @Test void testExecutionListenerIsLoadedViaServiceApi() { withTestServices(() -> { var config = LauncherConfig.builder() // .addTestEngines(new TestEngineSpy()) // .enableTestEngineAutoRegistration(false) // .build(); var launcher = LauncherFactory.create(config); NoopTestExecutionListener.called = false; launcher.execute(request().forExecution().build()); assertTrue(NoopTestExecutionListener.called); }); } @Test @SetSystemProperty(key = DEACTIVATE_LISTENERS_PATTERN_PROPERTY_NAME, value = "org.junit.*.launcher.listeners.Unused*,org.junit.*.launcher.listeners.AnotherUnused*") void testExecutionListenersExcludedViaConfigParametersIsNotLoadedViaServiceApi( @TrackLogRecords LogRecordListener listener) { withTestServices(() -> { var config = LauncherConfig.builder() // .addTestEngines(new TestEngineSpy()) // .enableTestEngineAutoRegistration(false) // .build(); var launcher = LauncherFactory.create(config); UnusedTestExecutionListener.called = false; AnotherUnusedTestExecutionListener.called = false; launcher.execute(request().forExecution().build()); var logMessage = listener.stream(ServiceLoaderRegistry.class) // .map(LogRecord::getMessage) // .filter(it -> it.startsWith("Loaded TestExecutionListener instances")) // .findAny(); assertThat(logMessage).isPresent(); assertThat(logMessage.get()) // .contains("NoopTestExecutionListener@") // .endsWith(" (excluded classes: [" + UnusedTestExecutionListener.class.getName() + ", " + AnotherUnusedTestExecutionListener.class.getName() + "])"); assertFalse(UnusedTestExecutionListener.called); assertFalse(AnotherUnusedTestExecutionListener.called); }); } @Test void create() { var discoveryRequest = createLauncherDiscoveryRequestForBothStandardEngineExampleClasses(); var testPlan = LauncherFactory.create().discover(discoveryRequest); var roots = testPlan.getRoots(); assertThat(roots).hasSize(3); // @formatter:off var ids = roots.stream() .map(TestIdentifier::getUniqueId) .toList(); // @formatter:on assertThat(ids).containsOnly("[engine:junit-vintage]", "[engine:junit-jupiter]", "[engine:junit-platform-suite]"); } @Test void createWithConfig() { var discoveryRequest = createLauncherDiscoveryRequestForBothStandardEngineExampleClasses(); var config = LauncherConfig.builder()// .enableTestEngineAutoRegistration(false)// .addTestEngines(new JupiterTestEngine())// .build(); var testPlan = LauncherFactory.create(config).discover(discoveryRequest); var roots = testPlan.getRoots(); assertThat(roots).hasSize(1); // @formatter:off var ids = roots.stream() .map(TestIdentifier::getUniqueId) .toList(); // @formatter:on assertThat(ids).containsOnly("[engine:junit-jupiter]"); } @Test void createWithPostDiscoveryFilters() { var discoveryRequest = createLauncherDiscoveryRequestForBothStandardEngineExampleClasses(); var config = LauncherConfig.builder()// .addPostDiscoveryFilters(TagFilter.includeTags("test-post-discovery")).build(); var testPlan = LauncherFactory.create(config).discover(discoveryRequest); final var vintage = testPlan.getChildren(UniqueId.parse("[engine:junit-vintage]")); assertThat(vintage).isEmpty(); final var jupiter = testPlan.getChildren(UniqueId.parse("[engine:junit-jupiter]")); assertThat(jupiter).hasSize(1); } @Test void applyPostDiscoveryFiltersViaServiceApi() { withTestServices(() -> { var discoveryRequest = createLauncherDiscoveryRequestForBothStandardEngineExampleClasses(); var config = LauncherConfig.builder()// .build(); var testPlan = LauncherFactory.create(config).discover(discoveryRequest); final var vintage = testPlan.getChildren(UniqueId.parse("[engine:junit-vintage]")); assertThat(vintage).isEmpty(); final var jupiter = testPlan.getChildren(UniqueId.parse("[engine:junit-jupiter]")); assertThat(jupiter).hasSize(1); }); } @Test void notApplyIfDisabledPostDiscoveryFiltersViaServiceApi() { withTestServices(() -> { var discoveryRequest = createLauncherDiscoveryRequestForBothStandardEngineExampleClasses(); var config = LauncherConfig.builder()// .enablePostDiscoveryFilterAutoRegistration(false).build(); var testPlan = LauncherFactory.create(config).discover(discoveryRequest); final var vintage = testPlan.getChildren(UniqueId.parse("[engine:junit-vintage]")); assertThat(vintage).hasSize(1); final var jupiter = testPlan.getChildren(UniqueId.parse("[engine:junit-jupiter]")); assertThat(jupiter).hasSize(1); }); } @Test void doesNotDiscoverLauncherDiscoverRequestListenerViaServiceApiWhenDisabled() { withTestServices(() -> { var config = LauncherConfig.builder() // .enableLauncherDiscoveryListenerAutoRegistration(false) // .build(); var launcher = LauncherFactory.create(config); TestLauncherDiscoveryListener.called = false; launcher.discover(request().build()); assertFalse(TestLauncherDiscoveryListener.called); }); } @Test void discoversLauncherDiscoverRequestListenerViaServiceApiByDefault() { withTestServices(() -> { var launcher = LauncherFactory.create(); TestLauncherDiscoveryListener.called = false; launcher.discover(request().build()); assertTrue(TestLauncherDiscoveryListener.called); }); } @Test void doesNotDiscoverLauncherSessionListenerViaServiceApiWhenDisabled() { withTestServices(() -> { try (var session = (DefaultLauncherSession) LauncherFactory.openSession( LauncherConfig.builder().enableLauncherSessionListenerAutoRegistration(false).build())) { assertThat(session.getListener()).isSameAs(LauncherSessionListener.NOOP); } }); } @Test void discoversLauncherSessionListenerViaServiceApiByDefault() { withTestServices(() -> { try (var session = LauncherFactory.openSession()) { assertThat(TestLauncherSessionListener.session).isSameAs(session); } assertThat(TestLauncherSessionListener.session).isNull(); }); } @Test @SetSystemProperty(key = ENABLE_LAUNCHER_INTERCEPTORS, value = "true") void createsLauncherInterceptorsBeforeDiscoveringTestEngines() { withTestServices(() -> { var config = LauncherConfig.builder() // .enableTestEngineAutoRegistration(true) // .build(); var request = request().build(); var testPlan = LauncherFactory.create(config).discover(request); assertThat(testPlan.getRoots()) // .map(TestIdentifier::getUniqueIdObject) // .map(UniqueId::getLastSegment) // .map(UniqueId.Segment::getValue) // .describedAs( "Intercepted test engine is added by class loader created by TestLauncherInterceptor1").contains( InterceptedTestEngine.ID); }); } @Test @SetSystemProperty(key = ENABLE_LAUNCHER_INTERCEPTORS, value = "true") void appliesLauncherInterceptorsToTestDiscovery() { InterceptorInjectedLauncherSessionListener.CALLS = 0; withTestServices(() -> { var engine = new TestEngineSpy() { @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { throw new RuntimeException("from discovery"); } }; var config = LauncherConfig.builder() // .enableTestEngineAutoRegistration(false) // .addTestEngines(engine) // .build(); var launcher = LauncherFactory.create(config); var request = request().build(); var exception = assertThrows(RuntimeException.class, () -> launcher.discover(request)); assertThat(exception) // .hasRootCauseMessage("from discovery") // .hasStackTraceContaining(TestLauncherInterceptor1.class.getName() + ".intercept(") // .hasStackTraceContaining(TestLauncherInterceptor2.class.getName() + ".intercept("); assertThat(InterceptorInjectedLauncherSessionListener.CALLS).isEqualTo(1); }); } @Test @SetSystemProperty(key = ENABLE_LAUNCHER_INTERCEPTORS, value = "true") void appliesLauncherInterceptorsToTestExecution() { InterceptorInjectedLauncherSessionListener.CALLS = 0; withTestServices(() -> { var engine = new TestEngineSpy() { @Override public void execute(ExecutionRequest request) { throw new RuntimeException("from execution"); } }; var config = LauncherConfig.builder() // .enableTestEngineAutoRegistration(false) // .addTestEngines(engine) // .build(); AtomicReference<@Nullable TestExecutionResult> result = new AtomicReference<>(); var listener = new TestExecutionListener() { @Override public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { if (testIdentifier.getParentId().isEmpty()) { result.set(testExecutionResult); } } }; var request = request() // .configurationParameter(LauncherConstants.STACKTRACE_PRUNING_ENABLED_PROPERTY_NAME, "false") // .forExecution() // .listeners(listener) // .build(); var launcher = LauncherFactory.create(config); launcher.execute(request); assertThat(requireNonNull(result.get()).getThrowable().orElseThrow()) // .hasRootCauseMessage("from execution") // .hasStackTraceContaining(TestLauncherInterceptor1.class.getName() + ".intercept(") // .hasStackTraceContaining(TestLauncherInterceptor2.class.getName() + ".intercept("); assertThat(InterceptorInjectedLauncherSessionListener.CALLS).isEqualTo(1); }); } @Test void extensionCanReadValueFromSessionStoreAndReadByLauncherSessionListenerOnOpened() { var config = LauncherConfig.builder() // .addLauncherSessionListeners(new LauncherSessionListenerOpenedExample()) // .build(); try (LauncherSession session = LauncherFactory.openSession(config)) { AtomicReference<@Nullable Throwable> errorRef = new AtomicReference<>(); var listener = new TestExecutionListener() { @Override public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { testExecutionResult.getThrowable().ifPresent(errorRef::set); } }; var request = request() // .selectors(selectClass(SessionTrackingTestCase.class)) // .forExecution() // .listeners(listener) // .build(); session.getLauncher().execute(request); assertThat(errorRef.get()).isNull(); } } @Test void extensionCanReadValueFromSessionStoreAndReadByLauncherSessionListenerOnClose() { var config = LauncherConfig.builder() // .addLauncherSessionListeners(new LauncherSessionListenerClosedExample()) // .build(); try (LauncherSession session = LauncherFactory.openSession(config)) { AtomicReference<@Nullable Throwable> errorRef = new AtomicReference<>(); var listener = new TestExecutionListener() { @Override public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { testExecutionResult.getThrowable().ifPresent(errorRef::set); } }; var request = request() // .selectors(selectClass(SessionStoringTestCase.class)) // .forExecution() // .listeners(listener) // .build(); session.getLauncher().execute(request); assertThat(errorRef.get()).isNull(); } } @Test void sessionResourceClosedOnSessionClose() { CloseTrackingResource.closed = false; var config = LauncherConfig.builder() // .addLauncherSessionListeners(new AutoCloseCheckListener()) // .build(); try (LauncherSession session = LauncherFactory.openSession(config)) { var request = request() // .selectors(selectClass(SessionResourceAutoCloseTestCase.class)) // .forExecution() // .build(); session.getLauncher().execute(request); assertThat(CloseTrackingResource.closed).isFalse(); } assertThat(CloseTrackingResource.closed).isTrue(); } @Test void requestResourceClosedOnExecutionClose() { CloseTrackingResource.closed = false; var config = LauncherConfig.builder().build(); try (LauncherSession session = LauncherFactory.openSession(config)) { var request = request() // .selectors(selectClass(RequestResourceAutoCloseTestCase.class)) // .forExecution() // .build(); session.getLauncher().execute(request); assertThat(CloseTrackingResource.closed).isTrue(); } } private static void withTestServices(Runnable runnable) { withAdditionalClasspathRoot("testservices/", runnable); } private LauncherDiscoveryRequest createLauncherDiscoveryRequestForBothStandardEngineExampleClasses() { // @formatter:off return request() .selectors(selectClasses(JUnit4Example.class, JUnit5Example.class)) .enableImplicitConfigurationParameters(false) .build(); // @formatter:on } @SuppressWarnings("NewClassNamingConvention") public static class JUnit4Example { @org.junit.Test public void testJ4() { } } @SuppressWarnings("NewClassNamingConvention") static class JUnit5Example { @Tag("test-post-discovery") @Test void testJ5() { } } @ExtendWith(SessionTrackingExtension.class) static class SessionTrackingTestCase { @Test void dummyTest() { // Just a placeholder to trigger the extension } } @ExtendWith(SessionStoringExtension.class) static class SessionStoringTestCase { @Test void dummyTest() { // Just a placeholder to trigger the extension } } static class LauncherSessionListenerOpenedExample implements LauncherSessionListener { @Override public void launcherSessionOpened(LauncherSession session) { session.getStore().put(Namespace.GLOBAL, "testKey", "testValue"); } } static class LauncherSessionListenerClosedExample implements LauncherSessionListener { @Override public void launcherSessionClosed(LauncherSession session) { Object storedValue = session.getStore().get(Namespace.GLOBAL, "testKey"); assertThat(storedValue).isEqualTo("testValue"); } } static class SessionTrackingExtension implements BeforeAllCallback { @Override public void beforeAll(ExtensionContext context) { var value = context.getStore(ExtensionContext.Namespace.GLOBAL).get("testKey"); if (!"testValue".equals(value)) { throw new IllegalStateException("Expected 'testValue' but got: " + value); } value = context.getStore(StoreScope.LAUNCHER_SESSION, ExtensionContext.Namespace.GLOBAL).get("testKey"); if (!"testValue".equals(value)) { throw new IllegalStateException("Expected 'testValue' but got: " + value); } } } static class SessionStoringExtension implements BeforeAllCallback { @Override public void beforeAll(ExtensionContext context) { context.getStore(StoreScope.LAUNCHER_SESSION, ExtensionContext.Namespace.GLOBAL).put("testKey", "testValue"); } } private static class CloseTrackingResource implements AutoCloseable { private static boolean closed = false; @Override public void close() { closed = true; } public boolean isClosed() { return closed; } } private static class SessionResourceStoreUsingExtension implements BeforeAllCallback { @Override public void beforeAll(ExtensionContext context) { CloseTrackingResource sessionResource = new CloseTrackingResource(); context.getStore(StoreScope.LAUNCHER_SESSION, ExtensionContext.Namespace.GLOBAL).put("sessionResource", sessionResource); } } private static class RequestResourceStoreUsingExtension implements BeforeAllCallback { @Override public void beforeAll(ExtensionContext context) { CloseTrackingResource requestResource = new CloseTrackingResource(); context.getStore(StoreScope.EXECUTION_REQUEST, ExtensionContext.Namespace.GLOBAL).put("requestResource", requestResource); } } @ExtendWith(SessionResourceStoreUsingExtension.class) static class SessionResourceAutoCloseTestCase { @Test void dummyTest() { // Just a placeholder to trigger the extension } } @ExtendWith(RequestResourceStoreUsingExtension.class) static class RequestResourceAutoCloseTestCase { @Test void dummyTest() { // Just a placeholder to trigger the extension } } private static class AutoCloseCheckListener implements LauncherSessionListener { @Override public void launcherSessionClosed(LauncherSession session) { CloseTrackingResource sessionResource = session // .getStore() // .get(Namespace.GLOBAL, "sessionResource", CloseTrackingResource.class); assertThat(sessionResource).isNotNull(); assertThat(sessionResource.isClosed()).isFalse(); } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherPreconditionTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.params.provider.Arguments.argumentSet; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationContainsNoNullElementsFor; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullFor; import static org.junit.platform.engine.support.store.NamespacedHierarchicalStore.CloseAction.closeAutoCloseables; import static org.mockito.Mockito.mock; import java.util.List; import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedClass; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.fakes.TestEngineStub; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.LauncherInterceptor; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestPlan; @ParameterizedClass @MethodSource("launchers") @SuppressWarnings({ "NullAway", "DataFlowIssue" }) class LauncherPreconditionTests { private final Launcher launcher; LauncherPreconditionTests(Launcher launcher) { this.launcher = launcher; } @Test void discoverRejectsNullDiscoveryRequest() { assertPreconditionViolationNotNullFor("discoveryRequest", () -> launcher.discover(null)); } @Test void executeRejectsNullDiscoveryRequest() { assertPreconditionViolationNotNullFor("discoveryRequest", () -> launcher.execute((LauncherDiscoveryRequest) null)); } @Test void executeRejectsNullTestPlan() { assertPreconditionViolationNotNullFor("testPlan", () -> launcher.execute((TestPlan) null)); } @Test void executeRejectsNullExecutionRequest() { assertPreconditionViolationNotNullFor("executionRequest", () -> launcher.execute(null)); } @Test void rejectNullLauncherDiscoveryListenersArray() { assertPreconditionViolationNotNullFor("listeners", () -> launcher.registerLauncherDiscoveryListeners((LauncherDiscoveryListener[]) null)); } @Test void rejectNullTestExecutionListenersArray() { assertPreconditionViolationNotNullFor("listeners", () -> launcher.registerTestExecutionListeners((TestExecutionListener[]) null)); } @Test void rejectNullListenersArrayWhenExecutingDiscoveryRequest() { var request = mock(LauncherDiscoveryRequest.class); assertPreconditionViolationNotNullFor("listeners", () -> launcher.execute(request, (TestExecutionListener[]) null)); } @Test void rejectNullListenersArrayWhenExecutingTestPlan() { var testPlan = mock(TestPlan.class); assertPreconditionViolationNotNullFor("listeners", () -> launcher.execute(testPlan, (TestExecutionListener[]) null)); } @Test void rejectNullElementsInLauncherDiscoveryListeners() { var listener = mock(LauncherDiscoveryListener.class); assertPreconditionViolationContainsNoNullElementsFor("listener array", () -> launcher.registerLauncherDiscoveryListeners(listener, null)); } @Test void rejectNullElementsInTestExecutionListeners() { var listener = mock(TestExecutionListener.class); assertPreconditionViolationContainsNoNullElementsFor("listener array", () -> launcher.registerTestExecutionListeners(listener, null)); } @Test void rejectNullElementsInListenersWhenExecutingDiscoveryRequest() { var request = mock(LauncherDiscoveryRequest.class); var listener = mock(TestExecutionListener.class); assertPreconditionViolationContainsNoNullElementsFor("listener array", () -> launcher.execute(request, listener, null)); } @Test void rejectNullElementsInListenersWhenExecutingTestPlan() { var testPlan = mock(TestPlan.class); var listener = mock(TestExecutionListener.class); assertPreconditionViolationContainsNoNullElementsFor("listener array", () -> launcher.execute(testPlan, listener, null)); } static Stream launchers() { return Stream.of( // argumentSet("SessionPerRequestLauncher", createSessionPerRequestLauncher()), argumentSet("DefaultLauncher", createDefaultLauncher()), argumentSet("DelegatingLauncher", createDelegatingLauncher()), argumentSet("InterceptingLauncher", createInterceptingLauncher()) // ); } private static SessionPerRequestLauncher createSessionPerRequestLauncher() { LauncherConfig config = LauncherConfig.builder() // .enableTestEngineAutoRegistration(false) // .enableLauncherDiscoveryListenerAutoRegistration(false) // .enableTestExecutionListenerAutoRegistration(false) // .enablePostDiscoveryFilterAutoRegistration(false) // .enableLauncherSessionListenerAutoRegistration(false) // .addTestEngines(new TestEngineStub()) // .build(); // Launcher launcher = LauncherFactory.create(config); return assertInstanceOf(SessionPerRequestLauncher.class, launcher); } private static DefaultLauncher createDefaultLauncher() { return new DefaultLauncher( // List.of(new TestEngineStub()), // List.of(), // new NamespacedHierarchicalStore<>(null, closeAutoCloseables()) // ); } private static DelegatingLauncher createDelegatingLauncher() { return new DelegatingLauncher(mock(Launcher.class)); } private static InterceptingLauncher createInterceptingLauncher() { return new InterceptingLauncher(mock(Launcher.class), mock(LauncherInterceptor.class)); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherSessionTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.discoveryRequest; import static org.junit.platform.launcher.core.LauncherExecutionRequestBuilder.executionRequest; import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncherConfigBuilderWithDisabledServiceLoading; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verifyNoMoreInteractions; import org.junit.jupiter.api.Test; import org.junit.platform.fakes.TestEngineStub; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.LauncherSession; import org.junit.platform.launcher.LauncherSessionListener; import org.mockito.ArgumentCaptor; class LauncherSessionTests { LauncherSessionListener firstSessionListener = mock(LauncherSessionListener.class, "firstSessionListener"); LauncherSessionListener secondSessionListener = mock(LauncherSessionListener.class, "secondSessionListener"); LauncherConfig launcherConfig = createLauncherConfigBuilderWithDisabledServiceLoading() // .addLauncherSessionListeners(firstSessionListener, secondSessionListener) // .addTestEngines(new TestEngineStub()) // .build(); LauncherDiscoveryRequest discoveryRequest = discoveryRequest().build(); @Test void callsRegisteredListenersWhenLauncherIsUsedDirectly() { var launcher = LauncherFactory.create(launcherConfig); var testPlan = launcher.discover(discoveryRequest); var inOrder = inOrder(firstSessionListener, secondSessionListener); var launcherSession = ArgumentCaptor.forClass(LauncherSession.class); inOrder.verify(firstSessionListener).launcherSessionOpened(launcherSession.capture()); inOrder.verify(secondSessionListener).launcherSessionOpened(launcherSession.getValue()); inOrder.verify(secondSessionListener).launcherSessionClosed(launcherSession.getValue()); inOrder.verify(firstSessionListener).launcherSessionClosed(launcherSession.getValue()); launcher.execute(testPlan); inOrder.verify(firstSessionListener).launcherSessionOpened(launcherSession.capture()); inOrder.verify(secondSessionListener).launcherSessionOpened(launcherSession.getValue()); inOrder.verify(secondSessionListener).launcherSessionClosed(launcherSession.getValue()); inOrder.verify(firstSessionListener).launcherSessionClosed(launcherSession.getValue()); launcher.execute(discoveryRequest); inOrder.verify(firstSessionListener).launcherSessionOpened(launcherSession.capture()); inOrder.verify(secondSessionListener).launcherSessionOpened(launcherSession.getValue()); inOrder.verify(secondSessionListener).launcherSessionClosed(launcherSession.getValue()); inOrder.verify(firstSessionListener).launcherSessionClosed(launcherSession.getValue()); testPlan = launcher.discover(discoveryRequest); inOrder.verify(firstSessionListener).launcherSessionOpened(launcherSession.capture()); inOrder.verify(secondSessionListener).launcherSessionOpened(launcherSession.getValue()); inOrder.verify(secondSessionListener).launcherSessionClosed(launcherSession.getValue()); inOrder.verify(firstSessionListener).launcherSessionClosed(launcherSession.getValue()); launcher.execute(executionRequest(testPlan).build()); inOrder.verify(firstSessionListener).launcherSessionOpened(launcherSession.capture()); inOrder.verify(secondSessionListener).launcherSessionOpened(launcherSession.getValue()); inOrder.verify(secondSessionListener).launcherSessionClosed(launcherSession.getValue()); inOrder.verify(firstSessionListener).launcherSessionClosed(launcherSession.getValue()); launcher.execute(executionRequest(discoveryRequest).build()); inOrder.verify(firstSessionListener).launcherSessionOpened(launcherSession.capture()); inOrder.verify(secondSessionListener).launcherSessionOpened(launcherSession.getValue()); inOrder.verify(secondSessionListener).launcherSessionClosed(launcherSession.getValue()); inOrder.verify(firstSessionListener).launcherSessionClosed(launcherSession.getValue()); } @Test @SuppressWarnings("resource") void callsRegisteredListenersWhenLauncherIsUsedViaSession() { var session = LauncherFactory.openSession(launcherConfig); var launcher = session.getLauncher(); var inOrder = inOrder(firstSessionListener, secondSessionListener); inOrder.verify(firstSessionListener).launcherSessionOpened(session); inOrder.verify(secondSessionListener).launcherSessionOpened(session); verifyNoMoreInteractions(firstSessionListener, secondSessionListener); var testPlan = launcher.discover(discoveryRequest); launcher.execute(testPlan); launcher.execute(discoveryRequest); testPlan = launcher.discover(discoveryRequest); launcher.execute(executionRequest(testPlan).build()); launcher.execute(executionRequest(discoveryRequest).build()); verifyNoMoreInteractions(firstSessionListener, secondSessionListener); session.close(); inOrder.verify(secondSessionListener).launcherSessionClosed(session); inOrder.verify(firstSessionListener).launcherSessionClosed(session); verifyNoMoreInteractions(firstSessionListener, secondSessionListener); } @Test @SuppressWarnings("resource") void closedSessionCannotBeUsed() { var session = LauncherFactory.openSession(launcherConfig); var launcher = session.getLauncher(); var testPlan = launcher.discover(discoveryRequest); session.close(); assertPreconditionViolationFor(() -> launcher.discover(discoveryRequest)); assertPreconditionViolationFor(() -> launcher.execute(testPlan)); assertPreconditionViolationFor(() -> launcher.execute(discoveryRequest)); assertPreconditionViolationFor(() -> launcher.execute(executionRequest(testPlan).build())); assertPreconditionViolationFor(() -> launcher.execute(executionRequest(discoveryRequest).build())); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/core/ListenerRegistryTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullFor; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullOrEmptyFor; import java.util.List; import org.junit.jupiter.api.Test; class ListenerRegistryTests { @SuppressWarnings("DataFlowIssue") @Test void registerWithNullArray() { var registry = ListenerRegistry.create(List::getFirst); assertPreconditionViolationNotNullOrEmptyFor("listeners array", () -> registry.addAll((Object[]) null)); } @Test void registerWithEmptyArray() { var registry = ListenerRegistry.create(List::getFirst); assertPreconditionViolationNotNullOrEmptyFor("listeners array", registry::addAll); } @SuppressWarnings("DataFlowIssue") @Test void registerWithArrayContainingNullElements() { var registry = ListenerRegistry.create(List::getFirst); assertPreconditionViolationNotNullFor("individual listeners", () -> registry.addAll(new Object[] { null })); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/core/StoreSharingTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import org.junit.jupiter.api.Test; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.support.store.Namespace; import org.junit.platform.fakes.TestEngineSpy; import org.junit.platform.fakes.TestEngineStub; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherExecutionRequest; /** * @since 5.13 */ class StoreSharingTests { @Test void twoDummyEnginesUseRequestLevelStore() { TestEngineSpy engineWriter = new TestEngineSpy("Writer") { @Override public void execute(ExecutionRequest request) { request.getStore().put(Namespace.GLOBAL, "sharedKey", "Hello from Writer"); super.execute(request); } }; TestEngineStub engineReader = new TestEngineStub("Reader") { @Override public void execute(ExecutionRequest request) { Object value = request.getStore().get(Namespace.GLOBAL, "sharedKey"); assertEquals("Hello from Writer", value); super.execute(request); } }; ExecutionRequest request = mock(ExecutionRequest.class); when(request.getStore()).thenReturn(NamespacedHierarchicalStoreProviders.dummyNamespacedHierarchicalStore()); Launcher launcher = LauncherFactory.create( // LauncherConfig.builder() // .addTestEngines(engineWriter, engineReader) // .build()); LauncherExecutionRequest discoveryRequest = LauncherDiscoveryRequestBuilder // .request() // .forExecution() // .build(); launcher.execute(discoveryRequest); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListenerIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.junit.platform.engine.TestExecutionResult.successful; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.launcher.LauncherConstants.CAPTURE_STDERR_PROPERTY_NAME; import static org.junit.platform.launcher.LauncherConstants.CAPTURE_STDOUT_PROPERTY_NAME; import static org.junit.platform.launcher.LauncherConstants.STDERR_REPORT_ENTRY_KEY; import static org.junit.platform.launcher.LauncherConstants.STDOUT_REPORT_ENTRY_KEY; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncher; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.util.function.Supplier; import java.util.stream.Stream; import org.junit.jupiter.api.extension.AfterTestExecutionCallback; import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Namespace; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.engine.support.hierarchical.DemoHierarchicalTestEngine; import org.junit.platform.launcher.LauncherConstants; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; import org.mockito.ArgumentCaptor; /** * @since 1.3 */ class StreamInterceptingTestExecutionListenerIntegrationTests { @ParameterizedTest(name = "{0}") @MethodSource("systemStreams") @ExtendWith(HiddenSystemOutAndErr.class) void interceptsStream(String configParam, Supplier printStreamSupplier, String reportKey) { var engine = new DemoHierarchicalTestEngine("engine"); TestDescriptor test = engine.addTest("test", () -> printStreamSupplier.get().print("4567890")); var listener = mock(TestExecutionListener.class); doAnswer(invocation -> { TestIdentifier testIdentifier = invocation.getArgument(0); if (testIdentifier.getUniqueIdObject().equals(test.getUniqueId())) { printStreamSupplier.get().print("123"); } return null; }).when(listener).executionStarted(any()); var launcher = createLauncher(engine); var executionRequest = request()// .selectors(selectUniqueId(test.getUniqueId()))// .configurationParameter(configParam, String.valueOf(true))// .configurationParameter(LauncherConstants.CAPTURE_MAX_BUFFER_PROPERTY_NAME, String.valueOf(5))// .forExecution()// .listeners(listener)// .build(); launcher.execute(executionRequest); var testPlanArgumentCaptor = ArgumentCaptor.forClass(TestPlan.class); var inOrder = inOrder(listener); inOrder.verify(listener).testPlanExecutionStarted(testPlanArgumentCaptor.capture()); var testPlan = testPlanArgumentCaptor.getValue(); var testIdentifier = testPlan.getTestIdentifier(test.getUniqueId()); var reportEntryArgumentCaptor = ArgumentCaptor.forClass(ReportEntry.class); inOrder.verify(listener).reportingEntryPublished(same(testIdentifier), reportEntryArgumentCaptor.capture()); inOrder.verify(listener).executionFinished(testIdentifier, successful()); var reportEntry = reportEntryArgumentCaptor.getValue(); assertThat(reportEntry.getKeyValuePairs()).containsExactly(entry(reportKey, "12345")); } @ParameterizedTest(name = "{0}") @MethodSource("systemStreams") @ExtendWith(HiddenSystemOutAndErr.class) void doesNotInterceptStreamWhenAlreadyBeingIntercepted(String configParam, Supplier printStreamSupplier) { var engine = new DemoHierarchicalTestEngine("engine"); TestDescriptor test = engine.addTest("test", () -> printStreamSupplier.get().print("1234567890")); assertThat(StreamInterceptor.registerStdout(1)).isPresent(); assertThat(StreamInterceptor.registerStderr(1)).isPresent(); var launcher = createLauncher(engine); var listener = mock(TestExecutionListener.class); var executionRequest = request()// .selectors(selectUniqueId(test.getUniqueId()))// .configurationParameter(configParam, String.valueOf(true))// .forExecution()// .listeners(listener)// .build(); launcher.execute(executionRequest); verify(listener, never()).reportingEntryPublished(any(), any()); } @SuppressWarnings("unused") // used via @MethodSource("systemStreams") private static Stream systemStreams() { return Stream.of(// streamType(CAPTURE_STDOUT_PROPERTY_NAME, () -> System.out, STDOUT_REPORT_ENTRY_KEY), // streamType(CAPTURE_STDERR_PROPERTY_NAME, () -> System.err, STDERR_REPORT_ENTRY_KEY)); } private static Arguments streamType(String configParam, Supplier printStreamSupplier, String reportKey) { return arguments(configParam, printStreamSupplier, reportKey); } static class HiddenSystemOutAndErr implements BeforeTestExecutionCallback, AfterTestExecutionCallback { private static final Namespace NAMESPACE = Namespace.create(HiddenSystemOutAndErr.class); @Override public void beforeTestExecution(ExtensionContext context) { var store = context.getStore(NAMESPACE); store.put("out", System.out); store.put("err", System.err); System.setOut(new PrintStream(new ByteArrayOutputStream())); System.setErr(new PrintStream(new ByteArrayOutputStream())); } @Override public void afterTestExecution(ExtensionContext context) { var store = context.getStore(NAMESPACE); System.setOut(store.get("out", PrintStream.class)); System.setErr(store.get("err", PrintStream.class)); } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.core; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertSame; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.util.stream.IntStream; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AutoClose; import org.junit.jupiter.api.Test; /** * @since 1.3 */ class StreamInterceptorTests { final ByteArrayOutputStream originalOut = new ByteArrayOutputStream(); PrintStream targetStream = new PrintStream(originalOut); @AutoClose @Nullable StreamInterceptor streamInterceptor; @Test void interceptsWriteOperationsToStreamPerThread() { streamInterceptor = StreamInterceptor.register(targetStream, newStream -> this.targetStream = newStream, 3).orElseThrow(RuntimeException::new); // @formatter:off IntStream.range(0, 1000) .parallel() .mapToObj(String::valueOf) .peek(i -> streamInterceptor.capture()) .peek(i -> targetStream.println(i)) .forEach(i -> assertEquals(i, streamInterceptor.consume().strip())); // @formatter:on } @Test void unregisterRestoresOriginalStream() { var originalStream = targetStream; streamInterceptor = StreamInterceptor.register(targetStream, newStream -> this.targetStream = newStream, 3).orElseThrow(RuntimeException::new); assertSame(streamInterceptor, targetStream); streamInterceptor.unregister(); assertSame(originalStream, targetStream); } @Test void writeForwardsOperationsToOriginalStream() throws Exception { var originalStream = targetStream; streamInterceptor = StreamInterceptor.register(targetStream, newStream -> this.targetStream = newStream, 2).orElseThrow(RuntimeException::new); assertNotSame(originalStream, targetStream); targetStream.write('a'); targetStream.write("b".getBytes()); targetStream.write("c".getBytes(), 0, 1); assertEquals("abc", originalOut.toString()); } @Test void handlesNestedCaptures() { streamInterceptor = StreamInterceptor.register(targetStream, newStream -> this.targetStream = newStream, 100).orElseThrow(RuntimeException::new); String outermost, inner, innermost; streamInterceptor.capture(); streamInterceptor.print("before outermost - "); { streamInterceptor.capture(); streamInterceptor.print("before inner - "); { streamInterceptor.capture(); streamInterceptor.print("innermost"); innermost = streamInterceptor.consume(); } streamInterceptor.print("after inner"); inner = streamInterceptor.consume(); } streamInterceptor.print("after outermost"); outermost = streamInterceptor.consume(); assertAll(// () -> assertEquals("before outermost - after outermost", outermost), // () -> assertEquals("before inner - after inner", inner), // () -> assertEquals("innermost", innermost) // ); } @Test void capturesOutputFromNonTestThreads() throws Exception { streamInterceptor = StreamInterceptor.register(targetStream, newStream -> this.targetStream = newStream, 100).orElseThrow(RuntimeException::new); streamInterceptor.capture(); var thread = new Thread(() -> targetStream.println("from non-test thread")); thread.start(); thread.join(); assertEquals("from non-test thread", streamInterceptor.consume().strip()); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/jfr/FlightRecordingDiscoveryListenerIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.jfr; import static org.junit.platform.commons.util.ExceptionUtils.readStackTrace; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.moditect.jfrunit.ExpectedEvent.event; import static org.moditect.jfrunit.JfrEventsAssert.assertThat; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.DisabledOnOpenJ9; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.platform.fakes.TestEngineStub; import org.junit.platform.testkit.engine.EngineTestKit; import org.moditect.jfrunit.EnableEvent; import org.moditect.jfrunit.JfrEventTest; import org.moditect.jfrunit.JfrEvents; @JfrEventTest @DisabledOnOpenJ9 public class FlightRecordingDiscoveryListenerIntegrationTests { public JfrEvents jfrEvents = new JfrEvents(); @Test @EnableEvent("org.junit.*") void reportsEvents() { var source = ClassSource.from(FlightRecordingDiscoveryListenerIntegrationTests.class); var cause = new RuntimeException("boom"); var issue = DiscoveryIssue.builder(Severity.WARNING, "some message") // .source(source) // .cause(cause) // .build(); var testEngine = new TestEngineStub() { @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { discoveryRequest.getDiscoveryListener().issueEncountered(uniqueId, issue); return super.discover(discoveryRequest, uniqueId); } }; EngineTestKit.discover(testEngine, request() // .selectors(selectClass(FlightRecordingDiscoveryListenerIntegrationTests.class)) // .listeners(new FlightRecordingDiscoveryListener()) // .enableImplicitConfigurationParameters(false) // .build()); jfrEvents.awaitEvents(); assertThat(jfrEvents) // .contains(event("org.junit.LauncherDiscovery") // .with("selectors", 1) // .with("filters", 0)) // .contains(event("org.junit.EngineDiscovery") // .with("uniqueId", "[engine:TestEngineStub]")) // .contains(event("org.junit.DiscoveryIssue") // .with("engineId", "[engine:TestEngineStub]") // .with("severity", "WARNING") // .with("message", "some message") // .with("source", source.toString()) // .with("cause", readStackTrace(cause))); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/jfr/FlightRecordingExecutionListenerIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.jfr; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.junit.platform.launcher.core.OutputDirectoryCreators.hierarchicalOutputDirectoryCreator; import static org.junit.platform.reporting.testutil.FileUtils.findPath; import static org.moditect.jfrunit.ExpectedEvent.event; import static org.moditect.jfrunit.JfrEventsAssert.assertThat; import java.nio.file.Files; import java.nio.file.Path; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.MediaType; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestReporter; import org.junit.jupiter.api.extension.DisabledOnOpenJ9; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.engine.JupiterTestEngine; import org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly; import org.moditect.jfrunit.EnableEvent; import org.moditect.jfrunit.JfrEventTest; import org.moditect.jfrunit.JfrEvents; @JfrEventTest @DisabledOnOpenJ9 public class FlightRecordingExecutionListenerIntegrationTests { public JfrEvents jfrEvents = new JfrEvents(); @Test @EnableEvent("org.junit.*") void reportsEvents(@TempDir Path tempDir) { var launcher = LauncherFactoryForTestingPurposesOnly.createLauncher(new JupiterTestEngine()); var request = request() // .selectors(selectClass(TestCase.class)) // .outputDirectoryCreator(hierarchicalOutputDirectoryCreator(tempDir)) // .forExecution() // .build(); launcher.execute(request); jfrEvents.awaitEvents(); var testFile = findPath(tempDir, "glob:**/test.txt"); assertThat(jfrEvents) // .contains(event("org.junit.TestPlanExecution") // .with("engineNames", "JUnit Jupiter")) // .contains(event("org.junit.TestExecution") // .with("displayName", "JUnit Jupiter") // .with("type", "CONTAINER")) // .contains(event("org.junit.TestExecution") // .with("displayName", FlightRecordingExecutionListenerIntegrationTests.class.getSimpleName() + "$" + TestCase.class.getSimpleName()) // .with("type", "CONTAINER")) // .contains(event("org.junit.TestExecution") // .with("displayName", "test(TestReporter)") // .with("type", "TEST") // .with("result", "SUCCESSFUL")) // .contains(event("org.junit.ReportEntry") // .with("key", "message") // .with("value", "Hello JFR!")) // .contains(event("org.junit.FileEntry") // .with("path", testFile.toAbsolutePath().toString())) // .contains(event("org.junit.SkippedTest") // .with("displayName", "skipped()") // .with("type", "TEST") // .with("reason", "for demonstration purposes")); } static class TestCase { @Test void test(TestReporter reporter) { reporter.publishEntry("message", "Hello JFR!"); reporter.publishFile("test.txt", MediaType.TEXT_PLAIN_UTF_8, file -> Files.writeString(file, "test")); } @Test @Disabled("for demonstration purposes") void skipped() { } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/listeners/AnotherUnusedTestExecutionListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.listeners; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestPlan; public class AnotherUnusedTestExecutionListener implements TestExecutionListener { public static boolean called; @Override public void testPlanExecutionStarted(TestPlan testPlan) { called = true; } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/listeners/LoggingListenerTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.listeners; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assumptions.abort; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.launcher.EngineFilter.includeEngines; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.ArgumentMatchers.startsWith; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import java.util.function.BiConsumer; import java.util.stream.Stream; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.TestMethodOrder; import org.junit.platform.launcher.core.LauncherConfig; import org.junit.platform.launcher.core.LauncherFactory; import org.mockito.ArgumentMatchers; import org.opentest4j.AssertionFailedError; import org.opentest4j.TestAbortedException; class LoggingListenerTests { @Test void logsExecutionEvents() { BiConsumer logger = mock(); executeTestCase(LoggingListener.forBiConsumer((t, m) -> { System.out.println(m.get()); logger.accept(t, m.get()); })); var inOrder = inOrder(logger); inOrder.verify(logger).accept(isNull(), startsWith("TestPlan Execution Started: org.junit.platform.launcher.TestPlan@")); inOrder.verify(logger).accept(isNull(), eq("Execution Started: JUnit Jupiter - [engine:junit-jupiter]")); inOrder.verify(logger).accept(isNull(), eq( "Execution Started: LoggingListenerTests$TestCase - [engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.LoggingListenerTests$TestCase]")); inOrder.verify(logger).accept(isNull(), eq( "Execution Started: success() - [engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.LoggingListenerTests$TestCase]/[test-factory:success()]")); inOrder.verify(logger).accept(isNull(), eq( "Dynamic Test Registered: dynamic - [engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.LoggingListenerTests$TestCase]/[test-factory:success()]/[dynamic-test:#1]")); inOrder.verify(logger).accept(isNull(), eq( "Execution Started: dynamic - [engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.LoggingListenerTests$TestCase]/[test-factory:success()]/[dynamic-test:#1]")); inOrder.verify(logger).accept(isNull(), eq( "Execution Finished: dynamic - [engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.LoggingListenerTests$TestCase]/[test-factory:success()]/[dynamic-test:#1] - TestExecutionResult [status = SUCCESSFUL, throwable = null]")); inOrder.verify(logger).accept(isNull(), eq( "Execution Finished: success() - [engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.LoggingListenerTests$TestCase]/[test-factory:success()] - TestExecutionResult [status = SUCCESSFUL, throwable = null]")); inOrder.verify(logger).accept(isNull(), eq( "Execution Skipped: skipped() - [engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.LoggingListenerTests$TestCase]/[method:skipped()] - void org.junit.platform.launcher.listeners.LoggingListenerTests$TestCase.skipped() is @Disabled")); inOrder.verify(logger).accept(isNull(), eq( "Execution Started: failed() - [engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.LoggingListenerTests$TestCase]/[method:failed()]")); inOrder.verify(logger).accept(ArgumentMatchers.notNull(AssertionFailedError.class), eq( "Execution Finished: failed() - [engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.LoggingListenerTests$TestCase]/[method:failed()] - TestExecutionResult [status = FAILED, throwable = org.opentest4j.AssertionFailedError]")); inOrder.verify(logger).accept(isNull(), eq( "Execution Started: aborted() - [engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.LoggingListenerTests$TestCase]/[method:aborted()]")); inOrder.verify(logger).accept(ArgumentMatchers.notNull(TestAbortedException.class), eq( "Execution Finished: aborted() - [engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.LoggingListenerTests$TestCase]/[method:aborted()] - TestExecutionResult [status = ABORTED, throwable = org.opentest4j.TestAbortedException]")); inOrder.verify(logger).accept(isNull(), eq( "Execution Finished: LoggingListenerTests$TestCase - [engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.LoggingListenerTests$TestCase] - TestExecutionResult [status = SUCCESSFUL, throwable = null]")); inOrder.verify(logger).accept(isNull(), eq( "Execution Finished: JUnit Jupiter - [engine:junit-jupiter] - TestExecutionResult [status = SUCCESSFUL, throwable = null]")); inOrder.verify(logger).accept(isNull(), startsWith("TestPlan Execution Finished: org.junit.platform.launcher.TestPlan@")); inOrder.verifyNoMoreInteractions(); } private static void executeTestCase(LoggingListener listener) { var config = LauncherConfig.builder() // .enableTestExecutionListenerAutoRegistration(false) // .addTestExecutionListeners() // .build(); var request = request() // .selectors(selectClass(TestCase.class)) // .filters(includeEngines("junit-jupiter")) // .forExecution() // .listeners(listener) // .build(); LauncherFactory.create(config) // .execute(request); } @TestMethodOrder(MethodOrderer.OrderAnnotation.class) static class TestCase { @TestFactory @Order(1) Stream success() { return Stream.of(dynamicTest("dynamic", () -> { })); } @Test @Disabled @Order(2) void skipped() { } @Test @Order(3) void failed() { fail(); } @Test @Order(4) void aborted() { abort(); } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/listeners/NoopTestExecutionListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.listeners; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestPlan; /** * @since 1.0 */ public class NoopTestExecutionListener implements TestExecutionListener { public static boolean called; @Override public void testPlanExecutionStarted(TestPlan testPlan) { called = true; } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/listeners/OutputDirTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.listeners; import static org.assertj.core.api.Assertions.as; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.InstanceOfAssertFactories.STRING; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Optional; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.platform.launcher.LauncherConstants; class OutputDirTests { @TempDir Path cwd; @Test void getOutputDirUsesCustomOutputDir() throws Exception { var customDir = cwd.resolve("custom-dir"); var outputDir = OutputDir.create(Optional.of(customDir.toAbsolutePath().toString())).toPath(); assertThat(Files.isSameFile(customDir, outputDir)).isTrue(); assertThat(outputDir).exists(); } @Test void getOutputDirUsesCustomOutputDirWithPlaceholder() { var customDir = cwd.resolve("build").resolve("junit-" + LauncherConstants.OUTPUT_DIR_UNIQUE_NUMBER_PLACEHOLDER); var outputDir = OutputDir.create(Optional.of(customDir.toAbsolutePath().toString())).toPath(); assertThat(outputDir).exists() // .hasParent(cwd.resolve("build")) // .extracting(it -> it.getFileName().toString(), as(STRING)) // .matches("junit-\\d+"); } @Test void getOutputDirFallsBackToCurrentWorkingDir() throws Exception { var expected = cwd; assertOutputDirIsDetected(expected); } @Test void getOutputDirDetectsMavenPom() throws Exception { Files.createFile(cwd.resolve("pom.xml")); var expected = cwd.resolve("target"); assertOutputDirIsDetected(expected); } @Test void getOutputDirDetectsGradleGroovyDefaultBuildScript() throws Exception { Files.createFile(cwd.resolve("build.gradle")); var expected = cwd.resolve("build"); assertOutputDirIsDetected(expected); } @Test void getOutputDirDetectsGradleGroovyCustomBuildScript() throws Exception { Files.createFile(cwd.resolve("sub-project.gradle")); var expected = cwd.resolve("build"); assertOutputDirIsDetected(expected); } @Test void getOutputDirDetectsGradleKotlinDefaultBuildScript() throws Exception { Files.createFile(cwd.resolve("build.gradle.kts")); var expected = cwd.resolve("build"); assertOutputDirIsDetected(expected); } @Test void getOutputDirDetectsGradleKotlinCustomBuildScript() throws Exception { Files.createFile(cwd.resolve("sub-project.gradle.kts")); var expected = cwd.resolve("build"); assertOutputDirIsDetected(expected); } private void assertOutputDirIsDetected(Path expected) throws IOException { var outputDir = OutputDir.createSafely(Optional.empty(), () -> cwd).toPath(); assertThat(Files.isSameFile(expected, outputDir)).isTrue(); assertThat(outputDir).exists(); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/listeners/SummaryGenerationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.listeners; import static org.assertj.core.api.Assertions.assertThat; 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.assertTrue; import static org.junit.platform.commons.test.ConcurrencyTestingUtils.executeConcurrently; import static org.junit.platform.launcher.core.OutputDirectoryCreators.dummyOutputDirectoryCreator; import static org.mockito.Mockito.mock; import java.io.PrintWriter; import java.io.StringWriter; import java.util.List; import java.util.Optional; import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.TestSource; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.platform.fakes.TestDescriptorStub; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; /** * @since 1.0 */ class SummaryGenerationTests { private final SummaryGeneratingListener listener = new SummaryGeneratingListener(); private final TestPlan testPlan = TestPlan.from(true, List.of(), mock(), dummyOutputDirectoryCreator()); @Test void emptyReport() { listener.testPlanExecutionStarted(testPlan); listener.testPlanExecutionFinished(testPlan); assertEquals(0, listener.getSummary().getTestsFailedCount()); var summaryString = summaryAsString(); assertAll("summary", // () -> assertTrue(summaryString.contains("Test run finished after"), "test run"), // () -> assertTrue(summaryString.contains("0 containers found"), "containers found"), // () -> assertTrue(summaryString.contains("0 containers skipped"), "containers skipped"), // () -> assertTrue(summaryString.contains("0 containers started"), "containers started"), // () -> assertTrue(summaryString.contains("0 containers aborted"), "containers aborted"), // () -> assertTrue(summaryString.contains("0 containers successful"), "containers successful"), // () -> assertTrue(summaryString.contains("0 containers failed"), "containers failed"), // () -> assertTrue(summaryString.contains("0 tests found"), "tests found"), // () -> assertTrue(summaryString.contains("0 tests skipped"), "tests skipped"), // () -> assertTrue(summaryString.contains("0 tests started"), "tests started"), // () -> assertTrue(summaryString.contains("0 tests aborted"), "tests aborted"), // () -> assertTrue(summaryString.contains("0 tests successful"), "tests successful"), // () -> assertTrue(summaryString.contains("0 tests failed"), "tests failed") // ); assertEquals("", failuresAsString()); } @Test void reportingCorrectCounts() { var successfulContainer = createContainerIdentifier("c1"); var failedContainer = createContainerIdentifier("c2"); var abortedContainer = createContainerIdentifier("c3"); var skippedContainer = createContainerIdentifier("c4"); var successfulTest = createTestIdentifier("t1"); var failedTest = createTestIdentifier("t2"); var abortedTest = createTestIdentifier("t3"); var skippedTest = createTestIdentifier("t4"); listener.testPlanExecutionStarted(testPlan); listener.executionSkipped(skippedContainer, "skipped"); listener.executionSkipped(skippedTest, "skipped"); listener.executionStarted(successfulContainer); listener.executionFinished(successfulContainer, TestExecutionResult.successful()); listener.executionStarted(successfulTest); listener.executionFinished(successfulTest, TestExecutionResult.successful()); listener.executionStarted(failedContainer); listener.executionFinished(failedContainer, TestExecutionResult.failed(new RuntimeException("failed"))); listener.executionStarted(failedTest); listener.executionFinished(failedTest, TestExecutionResult.failed(new RuntimeException("failed"))); listener.executionStarted(abortedContainer); listener.executionFinished(abortedContainer, TestExecutionResult.aborted(new RuntimeException("aborted"))); listener.executionStarted(abortedTest); listener.executionFinished(abortedTest, TestExecutionResult.aborted(new RuntimeException("aborted"))); listener.testPlanExecutionFinished(testPlan); var summaryString = summaryAsString(); try { assertAll("summary", // () -> assertTrue(summaryString.contains("4 containers found"), "containers found"), // () -> assertTrue(summaryString.contains("1 containers skipped"), "containers skipped"), // () -> assertTrue(summaryString.contains("3 containers started"), "containers started"), // () -> assertTrue(summaryString.contains("1 containers aborted"), "containers aborted"), // () -> assertTrue(summaryString.contains("1 containers successful"), "containers successful"), // () -> assertTrue(summaryString.contains("1 containers failed"), "containers failed"), // () -> assertTrue(summaryString.contains("4 tests found"), "tests found"), // () -> assertTrue(summaryString.contains("1 tests skipped"), "tests skipped"), // () -> assertTrue(summaryString.contains("3 tests started"), "tests started"), // () -> assertTrue(summaryString.contains("1 tests aborted"), "tests aborted"), // () -> assertTrue(summaryString.contains("1 tests successful"), "tests successful"), // () -> assertTrue(summaryString.contains("1 tests failed"), "tests failed") // ); } catch (AssertionError error) { System.err.println(summaryString); throw error; } } @Test void canGetListOfFailures() { var failedException = new RuntimeException("Pow!"); var testDescriptor = new TestDescriptorStub(UniqueId.root("root", "1"), "failingTest") { @Override public Optional getSource() { return Optional.of(ClassSource.from(Object.class)); } }; var failingTest = TestIdentifier.from(testDescriptor); listener.testPlanExecutionStarted(testPlan); listener.executionStarted(failingTest); listener.executionFinished(failingTest, TestExecutionResult.failed(failedException)); listener.testPlanExecutionFinished(testPlan); final var failures = listener.getSummary().getFailures(); assertThat(failures).hasSize(1); assertThat(failures.getFirst().getException()).isEqualTo(failedException); assertThat(failures.getFirst().getTestIdentifier()).isEqualTo(failingTest); } @Test void reportingCorrectFailures() { var iaeCausedBy = new IllegalArgumentException("Illegal Argument Exception"); var failedException = new RuntimeException("Runtime Exception", iaeCausedBy); var npeSuppressed = new NullPointerException("Null Pointer Exception"); failedException.addSuppressed(npeSuppressed); var testDescriptor = new TestDescriptorStub(UniqueId.root("root", "2"), "failingTest") { @Override public Optional getSource() { return Optional.of(ClassSource.from(Object.class)); } }; var failed = TestIdentifier.from(testDescriptor); var aborted = TestIdentifier.from(new TestDescriptorStub(UniqueId.root("root", "3"), "abortedTest")); listener.testPlanExecutionStarted(testPlan); listener.executionStarted(failed); listener.executionFinished(failed, TestExecutionResult.failed(failedException)); listener.executionStarted(aborted); listener.executionFinished(aborted, TestExecutionResult.aborted(new RuntimeException("aborted"))); listener.testPlanExecutionFinished(testPlan); // An aborted test is not a failure assertEquals(1, listener.getSummary().getTestsFailedCount()); var failuresString = failuresAsString(); assertAll("failures", // () -> assertTrue(failuresString.contains("Failures (1)"), "test failures"), // () -> assertTrue(failuresString.contains(Object.class.getName()), "source"), // () -> assertTrue(failuresString.contains("failingTest"), "display name"), // () -> assertTrue(failuresString.contains("=> " + failedException), "main exception"), // () -> assertTrue(failuresString.contains("Caused by: " + iaeCausedBy), "Caused by exception"), // () -> assertTrue(failuresString.contains("Suppressed: " + npeSuppressed), "Suppressed exception") // ); } @Test public void reportingCircularFailure() { var iaeCausedBy = new IllegalArgumentException("Illegal Argument Exception"); var failedException = new RuntimeException("Runtime Exception", iaeCausedBy); var npeSuppressed = new NullPointerException("Null Pointer Exception"); failedException.addSuppressed(npeSuppressed); npeSuppressed.addSuppressed(iaeCausedBy); var testDescriptor = new TestDescriptorStub(UniqueId.root("root", "2"), "failingTest") { @Override public Optional getSource() { return Optional.of(ClassSource.from(Object.class)); } }; var failed = TestIdentifier.from(testDescriptor); listener.testPlanExecutionStarted(testPlan); listener.executionStarted(failed); listener.executionFinished(failed, TestExecutionResult.failed(failedException)); listener.testPlanExecutionFinished(testPlan); assertEquals(1, listener.getSummary().getTestsFailedCount()); var failuresString = failuresAsString(); assertAll("failures", // () -> assertTrue(failuresString.contains("Suppressed: " + npeSuppressed), "Suppressed exception"), // () -> assertTrue(failuresString.contains("Circular reference: " + iaeCausedBy), "Circular reference"), // () -> assertFalse(failuresString.contains("Caused by: "), "'Caused by: ' omitted because of Circular reference") // ); } @RepeatedTest(10) void reportingConcurrentlyFinishedTests() throws Exception { var numThreads = 250; var testIdentifier = TestIdentifier.from(new TestDescriptorStub(UniqueId.root("root", "2"), "failingTest") { @Override public Optional getSource() { return Optional.of(ClassSource.from(Object.class)); } }); var result = TestExecutionResult.failed(new RuntimeException()); listener.testPlanExecutionStarted(testPlan); executeConcurrently(numThreads, () -> { listener.executionStarted(testIdentifier); listener.executionFinished(testIdentifier, result); }); listener.testPlanExecutionFinished(testPlan); assertThat(listener.getSummary().getFailures()).hasSize(numThreads); } private TestIdentifier createTestIdentifier(String uniqueId) { var identifier = TestIdentifier.from(new TestDescriptorStub(UniqueId.root("test", uniqueId), uniqueId)); testPlan.addInternal(identifier); return identifier; } private TestIdentifier createContainerIdentifier(String uniqueId) { var identifier = TestIdentifier.from(new TestDescriptorStub(UniqueId.root("container", uniqueId), uniqueId) { @Override public Type getType() { return Type.CONTAINER; } }); testPlan.addInternal(identifier); return identifier; } private String summaryAsString() { var summaryWriter = new StringWriter(); listener.getSummary().printTo(new PrintWriter(summaryWriter)); return summaryWriter.toString(); } private String failuresAsString() { var failuresWriter = new StringWriter(); listener.getSummary().printFailuresTo(new PrintWriter(failuresWriter)); return failuresWriter.toString(); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListenerIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.listeners; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assumptions.abort; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.junit.platform.commons.util.FunctionUtils.where; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.launcher.EngineFilter.includeEngines; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.junit.platform.launcher.listeners.UniqueIdTrackingListener.DEFAULT_OUTPUT_FILE_PREFIX; import static org.junit.platform.launcher.listeners.UniqueIdTrackingListener.LISTENER_ENABLED_PROPERTY_NAME; import static org.junit.platform.launcher.listeners.UniqueIdTrackingListener.OUTPUT_DIR_PROPERTY_NAME; import static org.junit.platform.launcher.listeners.UniqueIdTrackingListener.OUTPUT_FILE_PREFIX_PROPERTY_NAME; import static org.junit.platform.launcher.listeners.UniqueIdTrackingListener.WORKING_DIR_PROPERTY_NAME; import static org.junit.platform.testkit.engine.Event.byTestDescriptor; import static org.junit.platform.testkit.engine.EventConditions.abortedWithReason; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.EventConditions.test; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.ServiceLoader; import java.util.stream.Stream; import org.assertj.core.api.Condition; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.io.TempDir; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.discovery.ClassSelector; import org.junit.platform.engine.discovery.DiscoverySelectors; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; import org.junit.platform.launcher.core.LauncherFactory; import org.junit.platform.testkit.engine.EngineTestKit; import org.junit.platform.testkit.engine.Event; import org.opentest4j.AssertionFailedError; import org.opentest4j.TestAbortedException; /** * Integration tests for the {@link UniqueIdTrackingListener}. * * @since 1.8 */ class UniqueIdTrackingListenerIntegrationTests { private static final String passingTest = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase1]/[method:passingTest()]"; private static final String skippedTest = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase1]/[method:skippedTest()]"; private static final String abortedTest = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase1]/[method:abortedTest()]"; private static final String failingTest = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase1]/[method:failingTest()]"; private static final String dynamicTest1 = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase1]/[test-factory:dynamicTests()]/[dynamic-test:#1]"; private static final String dynamicTest2 = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase1]/[test-factory:dynamicTests()]/[dynamic-test:#2]"; private static final String testA = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase2]/[method:testA()]"; private static final String testB = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase2]/[method:testB()]"; private static final String testC = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase3]/[method:testC()]"; private static final String testD = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase3]/[method:testD()]"; private static final String testE = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase4]/[method:testE()]"; private static final String testF = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$TestCase4]/[method:testF()]"; private static final String testG = "[engine:junit-jupiter]/[class:org.junit.platform.launcher.listeners.UniqueIdTrackingListenerIntegrationTests$DisabledTestCase]/[nested-class:Inner]/[method:testG()]"; private static final String[] expectedUniqueIds = { passingTest, skippedTest, abortedTest, failingTest, dynamicTest1, dynamicTest2, testA, testB, testG }; private static final String[] expectedConcurrentUniqueIds = { testA, testB, testC, testD, testE, testF }; @TempDir Path workingDir; @BeforeEach void createFakeGradleFiles() throws Exception { Files.createFile(workingDir.resolve("build.gradle")); Files.createDirectory(workingDir.resolve("build")); } @Test void confirmExpectedUniqueIdsViaEngineTestKit() { // @formatter:off EngineTestKit.engine("junit-jupiter") .selectors(selectClasses()) .execute() .testEvents() .assertStatistics(stats -> stats.started(7).skipped(1).aborted(1).succeeded(5).failed(1)) .assertEventsMatchLoosely( event(test(uniqueId(passingTest)), finishedSuccessfully()), event(test(uniqueId(abortedTest)), abortedWithReason(instanceOf(TestAbortedException.class))), event(test(uniqueId(failingTest)), finishedWithFailure(instanceOf(AssertionFailedError.class))), event(test(uniqueId(dynamicTest1)), finishedSuccessfully()), event(test(uniqueId(dynamicTest2)), finishedSuccessfully()), event(test(uniqueId(testA)), finishedSuccessfully()), event(test(uniqueId(testB)), finishedSuccessfully()) ); // @formatter:on } private Condition uniqueId(String uniqueId) { return new Condition<>( byTestDescriptor(where(TestDescriptor::getUniqueId, uid -> uid.toString().equals(uniqueId))), "descriptor with uniqueId '%s'", uniqueId); } @Test void listenerIsRegisteredButDisabledByDefault() throws Exception { var numListenersRegistered = ServiceLoader.load(TestExecutionListener.class).stream()// .filter(provider -> UniqueIdTrackingListener.class.equals(provider.type()))// .count(); assertThat(numListenersRegistered).isEqualTo(1); var actualUniqueIds = executeTests(Map.of()); // Sanity check using the results of our local TestExecutionListener assertThat(actualUniqueIds).containsExactlyInAnyOrder(expectedUniqueIds); // Check that files were not generated by the UniqueIdTrackingListener assertThat(findFiles(workingDir, DEFAULT_OUTPUT_FILE_PREFIX)).isEmpty(); } @Test void verifyUniqueIdsAreTrackedWithDefaults() throws Exception { verifyUniqueIdsAreTracked("build", DEFAULT_OUTPUT_FILE_PREFIX, Map.of()); } @Test void verifyUniqueIdsAreTrackedWithCustomOutputFile() throws Exception { var customPrefix = "test_ids"; verifyUniqueIdsAreTracked("build", customPrefix, Map.of(OUTPUT_FILE_PREFIX_PROPERTY_NAME, customPrefix)); } @Test void verifyUniqueIdsAreTrackedWithCustomOutputDir() throws Exception { var customDir = "build/UniqueIdTrackingListenerIntegrationTests"; verifyUniqueIdsAreTracked(customDir, DEFAULT_OUTPUT_FILE_PREFIX, Map.of(OUTPUT_DIR_PROPERTY_NAME, customDir)); } @Test void verifyUniqueIdsAreTrackedWithCustomOutputFileAndCustomOutputDir() throws Exception { var customPrefix = "test_ids"; var customDir = "build/UniqueIdTrackingListenerIntegrationTests"; verifyUniqueIdsAreTracked(customDir, customPrefix, Map.of(OUTPUT_DIR_PROPERTY_NAME, customDir, OUTPUT_FILE_PREFIX_PROPERTY_NAME, customPrefix)); } private void verifyUniqueIdsAreTracked(String outputDir, String prefix, Map configurationParameters) throws IOException { configurationParameters = new HashMap<>(configurationParameters); configurationParameters.put(LISTENER_ENABLED_PROPERTY_NAME, "true"); var actualUniqueIds = executeTests(configurationParameters); // Sanity check using the results of our local TestExecutionListener assertThat(actualUniqueIds).containsExactlyInAnyOrder(expectedUniqueIds); // Check contents of the file (or files) generated by the UniqueIdTrackingListener assertThat(readAllFiles(workingDir.resolve(outputDir), prefix)).containsExactlyInAnyOrder(expectedUniqueIds); } @Test void verifyUniqueIdsAreTrackedWithConcurrentlyExecutingTestPlans() throws Exception { var customDir = workingDir.resolve("build/UniqueIdTrackingListenerIntegrationTests"); var prefix = DEFAULT_OUTPUT_FILE_PREFIX; Map configurationParameters = new HashMap<>(); configurationParameters.put(LISTENER_ENABLED_PROPERTY_NAME, "true"); configurationParameters.put(OUTPUT_DIR_PROPERTY_NAME, customDir.toAbsolutePath().toString()); Stream.of(TestCase2.class, TestCase3.class, TestCase4.class).parallel()// .forEach(clazz -> executeTests(configurationParameters, selectClass(clazz))); // 3 output files should have been generated. assertThat(findFiles(customDir, prefix)).hasSize(3); // Check contents of the file (or files) generated by the UniqueIdTrackingListener assertThat(readAllFiles(customDir, prefix)).containsExactlyInAnyOrder(expectedConcurrentUniqueIds); } private List executeTests(Map configurationParameters) { return executeTests(configurationParameters, selectClasses()); } private List executeTests(Map configurationParameters, ClassSelector... classSelectors) { return executeTests(configurationParameters, List.of(classSelectors)); } private List executeTests(Map configurationParameters, List classSelectors) { List uniqueIds = new ArrayList<>(); var listener = new TestExecutionListener() { @Nullable private TestPlan testPlan; @Override public void testPlanExecutionStarted(TestPlan testPlan) { this.testPlan = testPlan; } @Override public void executionSkipped(TestIdentifier testIdentifier, String reason) { if (testIdentifier.isTest()) { uniqueIds.add(testIdentifier.getUniqueId()); } else { requireNonNull(this.testPlan).getChildren(testIdentifier).forEach( child -> executionSkipped(child, reason)); } } @Override public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { if (testIdentifier.isTest()) { uniqueIds.add(testIdentifier.getUniqueId()); } } }; var request = request()// .selectors(classSelectors)// .filters(includeEngines("junit-jupiter"))// .configurationParameters(configurationParameters)// .configurationParameter(WORKING_DIR_PROPERTY_NAME, workingDir.toAbsolutePath().toString())// .forExecution()// .listeners(listener)// .build(); LauncherFactory.create().execute(request); return uniqueIds; } private static List selectClasses() { return DiscoverySelectors.selectClasses(TestCase1.class, TestCase2.class, DisabledTestCase.class); } private static Stream findFiles(Path outputDir, String prefix) throws IOException { if (!Files.exists(outputDir)) { return Stream.empty(); } return Files.find(outputDir, 1, // (path, basicFileAttributes) -> (basicFileAttributes.isRegularFile() && path.getFileName().toString().startsWith(prefix))); } private Stream readAllFiles(Path outputDir, String prefix) throws IOException { return findFiles(outputDir, prefix).map(outputFile -> { try { return Files.readAllLines(outputFile); } catch (IOException ex) { throw new UncheckedIOException(ex); } }).flatMap(List::stream); } // ------------------------------------------------------------------------- static class TestCase1 { @Test void passingTest() { } @Test @Disabled("testing") void skippedTest() { } @Test void abortedTest() { abort(); } @Test void failingTest() { fail(); } @TestFactory Stream dynamicTests() { return Stream.of("cat", "dog").map(text -> dynamicTest(text, () -> assertEquals(3, text.length()))); } } static class TestCase2 { @Test void testA() { } @Test void testB() { } } static class TestCase3 { @Test void testC() { } @Test void testD() { } } static class TestCase4 { @Test void testE() { } @Test void testF() { } } @Disabled static class DisabledTestCase { @Nested class Inner { @Test void testG() { } } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/listeners/UnusedTestExecutionListener.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.listeners; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestPlan; public class UnusedTestExecutionListener implements TestExecutionListener { public static boolean called; @Override public void testPlanExecutionStarted(TestPlan testPlan) { called = true; } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/AbortOnFailureLauncherDiscoveryListenerTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.listeners.discovery; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncher; import static org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners.abortOnFailure; import org.junit.jupiter.api.Test; import org.junit.platform.commons.JUnitException; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.fakes.TestEngineStub; class AbortOnFailureLauncherDiscoveryListenerTests { @Test void abortsDiscoveryOnEngineDiscoveryFailure() { var rootCause = new RuntimeException(); var engine = new TestEngineStub("some-engine") { @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { throw rootCause; } }; var request = request() // .listeners(abortOnFailure()) // .selectors(selectUniqueId(UniqueId.forEngine(engine.getId()))) // .build(); var launcher = createLauncher(engine); var exception = assertThrows(JUnitException.class, () -> launcher.discover(request)); assertThat(exception) // .hasMessage("TestEngine with ID 'some-engine' failed to discover tests") // .cause().isSameAs(rootCause); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/CompositeLauncherDiscoveryListenerTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.listeners.discovery; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import java.util.List; import org.junit.jupiter.api.Test; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.SelectorResolutionResult; import org.junit.platform.engine.UniqueId; import org.junit.platform.launcher.EngineDiscoveryResult; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.mockito.InOrder; class CompositeLauncherDiscoveryListenerTests { @Test void callsListenersInReverseOrderForFinishedEvents() { var firstListener = mock(LauncherDiscoveryListener.class, "firstListener"); var secondListener = mock(LauncherDiscoveryListener.class, "secondListener"); var launcherDiscoveryRequest = mock(LauncherDiscoveryRequest.class); var engineId = UniqueId.forEngine("engine"); var engineDiscoveryResult = EngineDiscoveryResult.successful(); var selector = selectUniqueId(engineId); var selectorResolutionResult = SelectorResolutionResult.resolved(); var discoveryIssue = DiscoveryIssue.create(Severity.WARNING, "message"); var composite = new CompositeLauncherDiscoveryListener(List.of(firstListener, secondListener)); composite.launcherDiscoveryStarted(launcherDiscoveryRequest); composite.engineDiscoveryStarted(engineId); composite.selectorProcessed(engineId, selector, selectorResolutionResult); composite.issueEncountered(engineId, discoveryIssue); composite.engineDiscoveryFinished(engineId, engineDiscoveryResult); composite.launcherDiscoveryFinished(launcherDiscoveryRequest); InOrder inOrder = inOrder(firstListener, secondListener); inOrder.verify(firstListener).launcherDiscoveryStarted(launcherDiscoveryRequest); inOrder.verify(secondListener).launcherDiscoveryStarted(launcherDiscoveryRequest); inOrder.verify(firstListener).engineDiscoveryStarted(engineId); inOrder.verify(secondListener).engineDiscoveryStarted(engineId); inOrder.verify(firstListener).selectorProcessed(engineId, selector, selectorResolutionResult); inOrder.verify(secondListener).selectorProcessed(engineId, selector, selectorResolutionResult); inOrder.verify(firstListener).issueEncountered(engineId, discoveryIssue); inOrder.verify(secondListener).issueEncountered(engineId, discoveryIssue); inOrder.verify(secondListener).engineDiscoveryFinished(engineId, engineDiscoveryResult); inOrder.verify(firstListener).engineDiscoveryFinished(engineId, engineDiscoveryResult); inOrder.verify(secondListener).launcherDiscoveryFinished(launcherDiscoveryRequest); inOrder.verify(firstListener).launcherDiscoveryFinished(launcherDiscoveryRequest); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/LoggingLauncherDiscoveryListenerTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.listeners.discovery; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.fakes.FaultyTestEngines.createEngineThatCannotResolveAnything; import static org.junit.platform.fakes.FaultyTestEngines.createEngineThatFailsToResolveAnything; import static org.junit.platform.launcher.LauncherConstants.DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncher; import java.util.logging.Level; import java.util.logging.LogRecord; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.fixtures.TrackLogRecords; import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.fakes.TestEngineStub; @TrackLogRecords class LoggingLauncherDiscoveryListenerTests { @Test void logsWarningOnUnresolvedUniqueIdSelectorWithEnginePrefix(LogRecordListener log) { var engine = createEngineThatCannotResolveAnything("some-engine"); var request = request() // .configurationParameter(DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME, "execution") // .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // .selectors(selectUniqueId(UniqueId.forEngine(engine.getId()))) // .enableImplicitConfigurationParameters(false) // .build(); var launcher = createLauncher(engine); launcher.discover(request); assertThat(log.stream(LoggingLauncherDiscoveryListener.class, Level.WARNING)) // .extracting(LogRecord::getMessage) // .containsExactly( "UniqueIdSelector [uniqueId = [engine:some-engine]] could not be resolved by [engine:some-engine]"); } @Test void logsDebugMessageOnUnresolvedUniqueIdSelectorWithoutEnginePrefix(LogRecordListener log) { var engine = createEngineThatCannotResolveAnything("some-engine"); var request = request() // .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // .selectors(selectUniqueId(UniqueId.forEngine("some-other-engine"))) // .enableImplicitConfigurationParameters(false) // .build(); var launcher = createLauncher(engine); launcher.discover(request); assertThat(log.stream(LoggingLauncherDiscoveryListener.class, Level.FINE)) // .extracting(LogRecord::getMessage) // .containsExactly( "UniqueIdSelector [uniqueId = [engine:some-other-engine]] could not be resolved by [engine:some-engine]"); } @Test void logsErrorOnSelectorResolutionFailure(LogRecordListener log) { var rootCause = new RuntimeException(); var engine = createEngineThatFailsToResolveAnything("some-engine", rootCause); var request = request() // .configurationParameter(DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME, "execution") // .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // .selectors(selectClass(Object.class)) // .enableImplicitConfigurationParameters(false) // .build(); var launcher = createLauncher(engine); launcher.discover(request); assertThat(log.stream(LoggingLauncherDiscoveryListener.class, Level.SEVERE)) // .extracting(LogRecord::getMessage) // .containsExactly( "Resolution of ClassSelector [className = 'java.lang.Object', classLoader = null] by [engine:some-engine] failed"); } @Test void logsErrorOnEngineDiscoveryFailure(LogRecordListener log) { var rootCause = new RuntimeException(); var engine = new TestEngineStub("some-engine") { @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { throw rootCause; } }; var request = request() // .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // .selectors(selectUniqueId(UniqueId.forEngine(engine.getId()))) // .enableImplicitConfigurationParameters(false) // .build(); var launcher = createLauncher(engine); launcher.discover(request); var logRecord = log.stream(LoggingLauncherDiscoveryListener.class, Level.SEVERE).findFirst().get(); assertThat(logRecord.getMessage()).isEqualTo("TestEngine with ID 'some-engine' failed to discover tests"); assertThat(logRecord.getThrown()).isSameAs(rootCause); } @Test void logsTraceMessageOnStartAndEnd(LogRecordListener log) { var engine = new TestEngineStub("some-engine"); var request = request() // .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // .selectors(selectUniqueId(UniqueId.forEngine(engine.getId()))) // .enableImplicitConfigurationParameters(false) // .build(); var launcher = createLauncher(engine); launcher.discover(request); assertThat(log.stream(LoggingLauncherDiscoveryListener.class, Level.FINER)) // .extracting(LogRecord::getMessage) // .containsExactly( // "Test discovery started", // "Engine [engine:some-engine] has started discovering tests", // "Engine [engine:some-engine] has finished discovering tests", // "Test discovery finished"); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/listeners/session/CompositeLauncherSessionListenerTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.listeners.session; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import java.util.List; import org.junit.jupiter.api.Test; import org.junit.platform.launcher.LauncherSession; import org.junit.platform.launcher.LauncherSessionListener; import org.mockito.InOrder; class CompositeLauncherSessionListenerTests { @Test void callsListenersInReverseOrderForClosedEvents() { var firstListener = mock(LauncherSessionListener.class, "firstListener"); var secondListener = mock(LauncherSessionListener.class, "secondListener"); var launcherSession = mock(LauncherSession.class); var composite = new CompositeLauncherSessionListener(List.of(firstListener, secondListener)); composite.launcherSessionOpened(launcherSession); composite.launcherSessionClosed(launcherSession); InOrder inOrder = inOrder(firstListener, secondListener); inOrder.verify(firstListener).launcherSessionOpened(launcherSession); inOrder.verify(secondListener).launcherSessionOpened(launcherSession); inOrder.verify(secondListener).launcherSessionClosed(launcherSession); inOrder.verify(firstListener).launcherSessionClosed(launcherSession); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/ParserErrorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.tagexpression; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.params.provider.Arguments.arguments; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; class ParserErrorTests { private final Parser parser = new Parser(); @Test void emptyExpression() { assertThat(parseErrorFromParsing("")).contains("empty tag expression"); } @Test void missingClosingParenthesis() { assertThat(parseErrorFromParsing("(")).contains("missing closing parenthesis for '(' at index <0>"); assertThat(parseErrorFromParsing("( foo & bar")).contains("missing closing parenthesis for '(' at index <0>"); } @Test void missingOpeningParenthesis() { assertThat(parseErrorFromParsing(")")).contains("missing opening parenthesis for ')' at index <0>"); assertThat(parseErrorFromParsing(" foo | bar)")).contains("missing opening parenthesis for ')' at index <10>"); } @Test void partialUnaryOperator() { assertThat(parseErrorFromParsing("!")).contains("missing rhs operand for '!' at index <0>"); } @Test void partialBinaryOperator() { assertThat(parseErrorFromParsing("& foo")).contains("missing lhs operand for '&' at index <0>"); assertThat(parseErrorFromParsing("foo |")).contains("missing rhs operand for '|' at index <4>"); } @ParameterizedTest @MethodSource("data") void acceptanceTests(String tagExpression, String parseError) { assertThat(parseErrorFromParsing(tagExpression)).contains(parseError); } @SuppressWarnings("unused") private static Stream data() { // @formatter:off return Stream.of( arguments("&", "missing lhs and rhs operand for '&' at index <0>"), arguments("|", "missing lhs and rhs operand for '|' at index <0>"), arguments("| |", "missing lhs and rhs operand for '|' at index <0>"), arguments("!", "missing rhs operand for '!' at index <0>"), arguments("foo bar", "missing operator between 'foo' at index <2> and 'bar' at index <4>"), arguments("foo bar |", "missing rhs operand for '|' at index <8>"), arguments("foo bar | baz", "missing operator between 'foo' at index <2> and '(bar | baz)' at index <4>"), arguments("foo bar &", "missing rhs operand for '&' at index <8>"), arguments("foo & (bar !)", "missing rhs operand for '!' at index <11>"), arguments("( foo & bar ) )", "missing opening parenthesis for ')' at index <14>"), arguments("( ( foo & bar )", "missing closing parenthesis for '(' at index <0>"), arguments("foo & (bar baz) |", "missing operator between 'bar' at index <9> and 'baz' at index <11>"), arguments("foo & (bar baz) &", "missing operator between 'bar' at index <9> and 'baz' at index <11>"), arguments("foo & (bar |baz) &", "missing rhs operand for '&' at index <17>"), arguments("foo | (bar baz) &", "missing rhs operand for '&' at index <16>"), arguments("foo | (bar baz) &quux", "missing operator between 'bar' at index <9> and '(baz & quux)' at index <11>"), arguments("foo & |", "missing rhs operand for '&' at index <4>"), arguments("foo !& bar", "missing rhs operand for '!' at index <4>"), arguments("foo !| bar", "missing rhs operand for '!' at index <4>") ); // @formatter:on } private @Nullable String parseErrorFromParsing(String tagExpression) { try { var parseResult = parser.parse(tagExpression); parseResult.tagExpressionOrThrow(RuntimeException::new); return null; } catch (RuntimeException ex) { return ex.getMessage(); } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/ParserTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.tagexpression; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.params.provider.Arguments.arguments; import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; class ParserTests { private final Parser parser = new Parser(); @Test void notHasHigherPrecedenceThanAnd() { assertThat(tagExpressionParsedFrom("! foo & bar")).hasToString("(!foo & bar)"); } @Test void andHasHigherPrecedenceThanOr() { assertThat(tagExpressionParsedFrom("foo | bar & baz")).hasToString("(foo | (bar & baz))"); } @Test void notIsRightAssociative() { assertThat(tagExpressionParsedFrom("foo &! bar")).hasToString("(foo & !bar)"); } @Test void andIsLeftAssociative() { assertThat(tagExpressionParsedFrom("foo & bar & baz")).hasToString("((foo & bar) & baz)"); } @Test void orIsLeftAssociative() { assertThat(tagExpressionParsedFrom("foo | bar | baz")).hasToString("((foo | bar) | baz)"); } @ParameterizedTest @MethodSource("data") void acceptanceTests(String tagExpression, String expression) { assertThat(tagExpressionParsedFrom(tagExpression)).hasToString(expression); } @SuppressWarnings("unused") private static Stream data() { // @formatter:off return Stream.of( arguments("foo", "foo"), arguments("! foo", "!foo"), arguments("foo & bar", "(foo & bar)"), arguments("foo | bar", "(foo | bar)"), arguments("( ! foo & bar | baz)", "((!foo & bar) | baz)"), arguments("(foo & bar ) | baz & quux", "((foo & bar) | (baz & quux))"), arguments("! foo | bar & ! baz | ! quux | quuz & corge", "(((!foo | (bar & !baz)) | !quux) | (quuz & corge))"), arguments("(foo & bar ) | baz & quux", "((foo & bar) | (baz & quux))"), arguments("foo | bar & baz|quux", "((foo | (bar & baz)) | quux)"), arguments("any()", "any()"), arguments("! none()", "!none()") ); // @formatter:on } private TagExpression tagExpressionParsedFrom(String tagExpression) { return parser.parse(tagExpression).tagExpressionOrThrow( error -> new RuntimeException("[" + tagExpression + "] should be parsable")); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TagExpressionsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.tagexpression; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.junit.platform.engine.TestTag.create; import static org.junit.platform.launcher.tagexpression.TagExpressions.and; import static org.junit.platform.launcher.tagexpression.TagExpressions.any; import static org.junit.platform.launcher.tagexpression.TagExpressions.none; import static org.junit.platform.launcher.tagexpression.TagExpressions.not; import static org.junit.platform.launcher.tagexpression.TagExpressions.or; import static org.junit.platform.launcher.tagexpression.TagExpressions.tag; import java.util.Set; import org.junit.jupiter.api.Test; import org.junit.platform.engine.TestTag; class TagExpressionsTests { private static final TagExpression True = tags -> true; private static final TagExpression False = tags -> false; @Test void tagIsJustATestTag() { assertThat(tag("foo")).hasToString("foo"); } @Test void rejectInvalidTestTags() { assertPreconditionViolationFor(() -> tag("tags with spaces are not allowed"))// .withMessageContaining("tags with spaces are not allowed"); } @Test void tagEvaluation() { var tagExpression = tag("foo"); assertThat(tagExpression.evaluate(Set.of(create("foo")))).isTrue(); assertThat(tagExpression.evaluate(Set.of(create("not_foo")))).isFalse(); } @Test void justConcatenateNot() { assertThat(not(tag("foo"))).hasToString("!foo"); assertThat(not(and(tag("foo"), tag("bar")))).hasToString("!(foo & bar)"); assertThat(not(or(tag("foo"), tag("bar")))).hasToString("!(foo | bar)"); } @Test void notEvaluation() { assertThat(not(True).evaluate(Set.of())).isFalse(); assertThat(not(False).evaluate(Set.of())).isTrue(); } @Test void encloseAndWithParenthesis() { assertThat(and(tag("foo"), tag("bar"))).hasToString("(foo & bar)"); } @Test void andEvaluation() { assertThat(and(True, True).evaluate(Set.of())).isTrue(); assertThat(and(True, False).evaluate(Set.of())).isFalse(); assertThat(and(False, onEvaluateThrow()).evaluate(Set.of())).isFalse(); } @Test void encloseOrWithParenthesis() { assertThat(or(tag("foo"), tag("bar"))).hasToString("(foo | bar)"); } @Test void orEvaluation() { assertThat(or(False, False).evaluate(Set.of())).isFalse(); assertThat(or(True, onEvaluateThrow()).evaluate(Set.of())).isTrue(); assertThat(or(False, True).evaluate(Set.of())).isTrue(); } @Test void anyEvaluation() { assertThat(any().evaluate(Set.of())).isFalse(); assertThat(any().evaluate(Set.of(TestTag.create("foo")))).isTrue(); } @Test void noneEvaluation() { assertThat(none().evaluate(Set.of())).isTrue(); assertThat(none().evaluate(Set.of(TestTag.create("foo")))).isFalse(); } private TagExpression onEvaluateThrow() { return tags -> { throw new RuntimeException("should not be evaluated"); }; } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TokenTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.tagexpression; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import org.junit.jupiter.api.Test; class TokenTests { @Test void startIndexOfTokenString() { assertThat(new Token(0, "!").strippedTokenStartIndex()).isEqualTo(0); assertThat(new Token(0, " !").strippedTokenStartIndex()).isEqualTo(2); assertThat(new Token(7, "!").strippedTokenStartIndex()).isEqualTo(7); } @Test void endIndexExclusive() { assertThat(new Token(0, "!").endIndexExclusive()).isEqualTo(1); assertThat(new Token(0, " !").endIndexExclusive()).isEqualTo(3); assertThat(new Token(7, "!").endIndexExclusive()).isEqualTo(8); } @Test void lastCharacterIndex() { assertThat(new Token(0, "!").lastCharacterIndex()).isEqualTo(0); assertThat(new Token(0, " !").lastCharacterIndex()).isEqualTo(2); assertThat(new Token(7, "!").lastCharacterIndex()).isEqualTo(7); } @Test void concatenateTwoTokens() { var tokens = new Tokenizer().tokenize(" ! foo"); var one = tokens.get(0); var two = tokens.get(1); var joined = one.concatenate(two); assertThat(joined.rawString()).isEqualTo(" ! foo"); assertThat(joined.startIndex()).isEqualTo(0); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TokenizerTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.launcher.tagexpression; import static org.assertj.core.api.Assertions.assertThat; import java.util.List; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; class TokenizerTests { @Test void nullContainsNoTokens() { assertThat(tokenStringsExtractedFrom(null)).isEmpty(); } @Test void removeLeadingAndTrailingSpaces() { assertThat(tokenStringsExtractedFrom(" tag ")).containsExactly("tag"); } @Test void notIsAReservedKeyword() { assertThat(tokenStringsExtractedFrom("! tag")).containsExactly("!", "tag"); assertThat(tokenStringsExtractedFrom("!tag")).containsExactly("!", "tag"); } @Test void andIsAReservedKeyword() { assertThat(tokenStringsExtractedFrom("one & two")).containsExactly("one", "&", "two"); assertThat(tokenStringsExtractedFrom("one&two")).containsExactly("one", "&", "two"); } @Test void orIsAReservedKeyword() { assertThat(tokenStringsExtractedFrom("one | two")).containsExactly("one", "|", "two"); assertThat(tokenStringsExtractedFrom("one|two")).containsExactly("one", "|", "two"); } @Test void anyAndNoneAreReservedKeywords() { assertThat(tokenStringsExtractedFrom("!(any())")).containsExactly("!", "(", "any()", ")"); assertThat(tokenStringsExtractedFrom("!(none())")).containsExactly("!", "(", "none()", ")"); } @Test void discoverBrackets() { assertThat(tokenStringsExtractedFrom("()")).containsExactly("(", ")"); assertThat(tokenStringsExtractedFrom("(tag)")).containsExactly("(", "tag", ")"); assertThat(tokenStringsExtractedFrom("( tag )")).containsExactly("(", "tag", ")"); assertThat(tokenStringsExtractedFrom("( foo &bar)| (baz& qux )")).containsExactly("(", "foo", "&", "bar", ")", "|", "(", "baz", "&", "qux", ")"); } @Test void extractRawStringWithSpaceCharactersBeforeTheToken() { assertThat(rawStringsExtractedFrom("(")).containsExactly("("); assertThat(rawStringsExtractedFrom(" (")).containsExactly(" ("); assertThat(rawStringsExtractedFrom(" ( foo ")).containsExactly(" (", " foo"); assertThat(rawStringsExtractedFrom("(( (( (")).containsExactly("(", "(", " (", "(", " ("); } @Test void extractStartPositionOfRawString() { assertThat(startIndicesExtractedFrom("(")).containsExactly(0); assertThat(startIndicesExtractedFrom(" ( (")).containsExactly(0, 3); assertThat(startIndicesExtractedFrom("foo &!bar")).containsExactly(0, 3, 5, 6); } private Stream startIndicesExtractedFrom(String expression) { return tokensExtractedFrom(expression).map(Token::startIndex); } private Stream rawStringsExtractedFrom(String expression) { return tokensExtractedFrom(expression).map(Token::rawString); } private List tokenStringsExtractedFrom(@Nullable String expression) { return tokensExtractedFrom(expression).map(Token::string).toList(); } private Stream tokensExtractedFrom(@Nullable String expression) { return new Tokenizer().tokenize(expression).stream(); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/IncrementingClock.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.reporting.legacy.xml; import java.time.Clock; import java.time.Duration; import java.time.Instant; import java.time.ZoneId; /** * @since 1.0 */ final class IncrementingClock extends Clock { private final Duration duration; private final ZoneId zone; private int counter; IncrementingClock(int start, Duration duration) { this(start, duration, ZoneId.systemDefault()); } private IncrementingClock(int start, Duration duration, ZoneId zone) { this.counter = start; this.duration = duration; this.zone = zone; } @Override public Instant instant() { return Instant.EPOCH.plus(duration.multipliedBy(counter++)); } @Override public Clock withZone(ZoneId zone) { return new IncrementingClock(counter, duration, zone); } @Override public ZoneId getZone() { return zone; } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListenerTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.reporting.legacy.xml; import static org.assertj.core.api.Assertions.assertThat; import static org.joox.JOOX.$; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assumptions.assumeFalse; import static org.junit.platform.engine.TestExecutionResult.successful; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncher; import static org.junit.platform.launcher.core.OutputDirectoryCreators.dummyOutputDirectoryCreator; import static org.junit.platform.reporting.legacy.xml.XmlReportAssertions.assertValidAccordingToJenkinsSchema; import static org.mockito.Mockito.mock; import java.io.File; import java.io.PrintWriter; import java.io.StringWriter; import java.net.InetAddress; import java.nio.file.Files; import java.nio.file.Path; import java.time.Clock; import java.time.Duration; import java.time.Instant; import java.time.LocalDateTime; import java.time.Year; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import org.joox.Match; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.engine.support.descriptor.EngineDescriptor; import org.junit.platform.engine.support.hierarchical.DemoEngineExecutionContext; import org.junit.platform.engine.support.hierarchical.DemoHierarchicalContainerDescriptor; import org.junit.platform.engine.support.hierarchical.DemoHierarchicalTestDescriptor; import org.junit.platform.engine.support.hierarchical.DemoHierarchicalTestEngine; import org.junit.platform.fakes.TestDescriptorStub; import org.junit.platform.launcher.LauncherConstants; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; import org.opentest4j.AssertionFailedError; /** * Tests for {@link LegacyXmlReportGeneratingListener}. * * @since 1.0 */ class LegacyXmlReportGeneratingListenerTests { @TempDir Path tempDirectory; @Test void writesFileForSingleSucceedingTest() throws Exception { var engine = new DemoHierarchicalTestEngine("dummy"); engine.addTest("succeedingTest", "display<-->Name 😎", () -> { }); executeTests(engine); var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); assertThat(testsuite.attr("name")).isEqualTo("dummy"); assertThat(testsuite.attr("tests", int.class)).isEqualTo(1); assertThat(testsuite.attr("skipped", int.class)).isEqualTo(0); assertThat(testsuite.attr("failures", int.class)).isEqualTo(0); assertThat(testsuite.attr("errors", int.class)).isEqualTo(0); assertThat(testsuite.child("system-out").text()) // .containsSubsequence("unique-id: [engine:dummy]", "display-name: dummy"); var testcase = testsuite.child("testcase"); assertThat(testcase.attr("name")).isEqualTo("display<-->Name 😎"); assertThat(testcase.attr("classname")).isEqualTo("dummy"); assertThat(testcase.child("system-out").text()) // .containsSubsequence("unique-id: [engine:dummy]/[test:succeedingTest]", "display-name: dummy > display<-->Name 😎"); assertThat(testsuite.find("skipped")).isEmpty(); assertThat(testsuite.find("failure")).isEmpty(); assertThat(testsuite.find("error")).isEmpty(); } @Test void writesFileForSingleFailingTest() throws Exception { var engine = new DemoHierarchicalTestEngine("dummy"); engine.addTest("failingTest", () -> fail("expected to fail")); executeTests(engine); var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); assertThat(testsuite.attr("tests", int.class)).isEqualTo(1); assertThat(testsuite.attr("skipped", int.class)).isEqualTo(0); assertThat(testsuite.attr("failures", int.class)).isEqualTo(1); assertThat(testsuite.attr("errors", int.class)).isEqualTo(0); var testcase = testsuite.child("testcase"); assertThat(testcase.attr("name")).isEqualTo("failingTest"); var failure = testcase.child("failure"); assertThat(failure.attr("message")).isEqualTo("expected to fail"); assertThat(failure.attr("type")).isEqualTo(AssertionFailedError.class.getName()); assertThat(failure.text()).containsSubsequence("AssertionFailedError: expected to fail", "\tat"); assertThat(testsuite.find("skipped")).isEmpty(); assertThat(testsuite.find("error")).isEmpty(); } @Test void writesFileForSingleErroneousTest() throws Exception { var engine = new DemoHierarchicalTestEngine("dummy"); engine.addTest("failingTest", () -> { throw new RuntimeException("error occurred"); }); executeTests(engine); var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); assertThat(testsuite.attr("tests", int.class)).isEqualTo(1); assertThat(testsuite.attr("skipped", int.class)).isEqualTo(0); assertThat(testsuite.attr("failures", int.class)).isEqualTo(0); assertThat(testsuite.attr("errors", int.class)).isEqualTo(1); var testcase = testsuite.child("testcase"); assertThat(testcase.attr("name")).isEqualTo("failingTest"); var error = testcase.child("error"); assertThat(error.attr("message")).isEqualTo("error occurred"); assertThat(error.attr("type")).isEqualTo(RuntimeException.class.getName()); assertThat(error.text()).containsSubsequence("RuntimeException: error occurred", "\tat"); assertThat(testsuite.find("skipped")).isEmpty(); assertThat(testsuite.find("failure")).isEmpty(); } @Test void writesFileForSingleSkippedTest() throws Exception { var engine = new DemoHierarchicalTestEngine("dummy"); var testDescriptor = engine.addTest("skippedTest", () -> fail("never called")); testDescriptor.markSkipped("should be skipped"); executeTests(engine); var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); assertThat(testsuite.attr("tests", int.class)).isEqualTo(1); assertThat(testsuite.attr("skipped", int.class)).isEqualTo(1); assertThat(testsuite.attr("failures", int.class)).isEqualTo(0); assertThat(testsuite.attr("errors", int.class)).isEqualTo(0); var testcase = testsuite.child("testcase"); assertThat(testcase.attr("name")).isEqualTo("skippedTest"); assertThat(testcase.child("skipped").text()).isEqualTo("should be skipped"); assertThat(testsuite.find("failure")).isEmpty(); assertThat(testsuite.find("error")).isEmpty(); } @SuppressWarnings("ConstantConditions") @Test void writesFileForSingleAbortedTest() throws Exception { var engine = new DemoHierarchicalTestEngine("dummy"); engine.addTest("abortedTest", () -> assumeFalse(true, "deliberately aborted")); executeTests(engine); var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); assertThat(testsuite.attr("tests", int.class)).isEqualTo(1); assertThat(testsuite.attr("skipped", int.class)).isEqualTo(1); assertThat(testsuite.attr("failures", int.class)).isEqualTo(0); assertThat(testsuite.attr("errors", int.class)).isEqualTo(0); var testcase = testsuite.child("testcase"); assertThat(testcase.attr("name")).isEqualTo("abortedTest"); assertThat(testcase.child("skipped").text()) // .containsSubsequence("TestAbortedException: ", "deliberately aborted", "at "); assertThat(testsuite.find("failure")).isEmpty(); assertThat(testsuite.find("error")).isEmpty(); } @Test void measuresTimesInSeconds() throws Exception { var engine = new DemoHierarchicalTestEngine("dummy"); engine.addTest("firstTest", () -> { }); engine.addTest("secondTest", () -> { }); executeTests(engine, new IncrementingClock(0, Duration.ofMillis(333))); var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); // start end // ----------- ---------- ----------- // engine 0 (1) 1,665 (6) // firstTest 333 (2) 666 (3) // secondTest 999 (4) 1,332 (5) assertThat(testsuite.attr("time", double.class)) // .isEqualTo(1.665); assertThat(testsuite.children("testcase").matchAttr("name", "firstTest").attr("time", double.class)) // .isEqualTo(0.333); assertThat(testsuite.children("testcase").matchAttr("name", "secondTest").attr("time", double.class)) // .isEqualTo(0.333); } @Test void testWithImmeasurableTimeIsOutputCorrectly() throws Exception { var engine = new DemoHierarchicalTestEngine("dummy"); engine.addTest("test", () -> { }); executeTests(engine, Clock.fixed(Instant.EPOCH, ZoneId.systemDefault())); var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); assertThat(testsuite.child("testcase").attr("time")).isEqualTo("0"); } @Test void writesFileForSkippedContainer() throws Exception { var engine = new DemoHierarchicalTestEngine("dummy"); engine.addTest("test", () -> fail("never called")); engine.getEngineDescriptor().markSkipped("should be skipped"); executeTests(engine); var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); assertThat(testsuite.attr("tests", int.class)).isEqualTo(1); assertThat(testsuite.attr("skipped", int.class)).isEqualTo(1); var testcase = testsuite.child("testcase"); assertThat(testcase.attr("name")).isEqualTo("test"); assertThat(testcase.child("skipped").text()).isEqualTo("parent was skipped: should be skipped"); } @Test void writesFileForFailingContainer() throws Exception { var engine = new DemoHierarchicalTestEngine("dummy"); engine.addTest("test", () -> fail("never called")); engine.getEngineDescriptor().setBeforeAllBehavior(() -> fail("failure before all tests")); executeTests(engine); var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); assertThat(testsuite.attr("tests", int.class)).isEqualTo(1); assertThat(testsuite.attr("failures", int.class)).isEqualTo(1); var testcase = testsuite.child("testcase"); assertThat(testcase.attr("name")).isEqualTo("test"); var failure = testcase.child("failure"); assertThat(failure.attr("message")).isEqualTo("failure before all tests"); assertThat(failure.attr("type")).isEqualTo(AssertionFailedError.class.getName()); assertThat(failure.text()).containsSubsequence("AssertionFailedError: failure before all tests", "\tat"); } @Test void writesFileForFailingContainerWithoutTest() throws Exception { var engine = new DemoHierarchicalTestEngine("dummy"); engine.addContainer("failingContainer", () -> { throw new RuntimeException("boom"); }); executeTests(engine); var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); assertThat(testsuite.attr("tests", int.class)).isEqualTo(1); assertThat(testsuite.attr("errors", int.class)).isEqualTo(1); var testcase = testsuite.child("testcase"); assertThat(testcase.attr("name")).isEqualTo("failingContainer"); assertThat(testcase.attr("classname")).isEqualTo("dummy"); var error = testcase.child("error"); assertThat(error.attr("message")).isEqualTo("boom"); assertThat(error.attr("type")).isEqualTo(RuntimeException.class.getName()); assertThat(error.text()).containsSubsequence("RuntimeException: boom", "\tat"); } @Test void writesFileForContainerFailingAfterTest() throws Exception { var engine = new DemoHierarchicalTestEngine("dummy"); var container = engine.addChild("failingContainer", uniqueId -> new DemoHierarchicalContainerDescriptor(uniqueId, "failingContainer", null, null) { @Override public void after(DemoEngineExecutionContext context) { throw new RuntimeException("boom"); } }, "child"); container.addChild(new DemoHierarchicalTestDescriptor(container.getUniqueId().append("test", "someTest"), "someTest", (_, _) -> { })); executeTests(engine); var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); assertThat(testsuite.attr("tests", int.class)).isEqualTo(1); assertThat(testsuite.attr("errors", int.class)).isEqualTo(1); var testcase = testsuite.child("testcase"); assertThat(testcase.attr("name")).isEqualTo("someTest"); assertThat(testcase.attr("classname")).isEqualTo("failingContainer"); var error = testcase.child("error"); assertThat(error.attr("message")).isEqualTo("boom"); assertThat(error.attr("type")).isEqualTo(RuntimeException.class.getName()); assertThat(error.text()).containsSubsequence("RuntimeException: boom", "\tat"); } @Test void writesSystemProperties() throws Exception { var engine = new DemoHierarchicalTestEngine("dummy"); engine.addTest("test", () -> { }); executeTests(engine); var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); var properties = testsuite.child("properties").children("property"); assertThat(properties.matchAttr("name", "file\\.separator").attr("value")).isEqualTo(File.separator); assertThat(properties.matchAttr("name", "path\\.separator").attr("value")).isEqualTo(File.pathSeparator); } @Test void writesHostNameAndTimestamp() throws Exception { var engine = new DemoHierarchicalTestEngine("dummy"); engine.addTest("test", () -> { }); var now = LocalDateTime.parse("2016-01-28T14:02:59.123"); var zone = ZoneId.systemDefault(); executeTests(engine, Clock.fixed(ZonedDateTime.of(now, zone).toInstant(), zone)); var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-dummy.xml")); assertThat(testsuite.attr("hostname")).isEqualTo(InetAddress.getLocalHost().getHostName()); assertThat(testsuite.attr("timestamp")).isEqualTo("2016-01-28T14:02:59"); } @Test void printsExceptionWhenReportsDirCannotBeCreated() throws Exception { var reportsDir = tempDirectory.resolve("dummy.txt"); Files.write(reportsDir, Set.of("content")); var out = new StringWriter(); var listener = new LegacyXmlReportGeneratingListener(reportsDir, new PrintWriter(out)); listener.testPlanExecutionStarted(TestPlan.from(true, Set.of(), mock(), dummyOutputDirectoryCreator())); assertThat(out.toString()).containsSubsequence("Could not create reports directory", "FileAlreadyExistsException", "at "); } @Test void printsExceptionWhenReportCouldNotBeWritten() throws Exception { var engineDescriptor = new EngineDescriptor(UniqueId.forEngine("engine"), "Engine"); var xmlFile = tempDirectory.resolve("TEST-engine.xml"); Files.createDirectories(xmlFile); var out = new StringWriter(); var listener = new LegacyXmlReportGeneratingListener(tempDirectory, new PrintWriter(out)); listener.testPlanExecutionStarted( TestPlan.from(true, Set.of(engineDescriptor), mock(), dummyOutputDirectoryCreator())); listener.executionFinished(TestIdentifier.from(engineDescriptor), successful()); assertThat(out.toString()).containsSubsequence("Could not write XML report", "Exception", "at "); } @Test void writesReportEntriesToSystemOutElement() throws Exception { var engineDescriptor = new EngineDescriptor(UniqueId.forEngine("engine"), "Engine"); var childUniqueId = UniqueId.root("child", "test"); engineDescriptor.addChild(new TestDescriptorStub(childUniqueId, "test")); var testPlan = TestPlan.from(true, Set.of(engineDescriptor), mock(), dummyOutputDirectoryCreator()); var out = new StringWriter(); var listener = new LegacyXmlReportGeneratingListener(tempDirectory, new PrintWriter(out)); listener.testPlanExecutionStarted(testPlan); var testIdentifier = testPlan.getTestIdentifier(childUniqueId); listener.executionStarted(testIdentifier); listener.reportingEntryPublished(testIdentifier, ReportEntry.from("foo", "bar")); Map map = new LinkedHashMap<>(); map.put("bar", "baz"); map.put("qux", "foo"); listener.reportingEntryPublished(testIdentifier, ReportEntry.from(map)); listener.executionFinished(testIdentifier, successful()); listener.executionFinished(testPlan.getTestIdentifier(engineDescriptor.getUniqueId()), successful()); var testsuite = readValidXmlFile(tempDirectory.resolve("TEST-engine.xml")); assertThat(String.join("\n", testsuite.child("testcase").children("system-out").texts())) // .containsSubsequence( // "Report Entry #1 (timestamp: " + Year.now(), "- foo: bar\n", "Report Entry #2 (timestamp: " + Year.now(), "- bar: baz\n", "- qux: foo\n"); } private void executeTests(TestEngine engine) { executeTests(engine, Clock.systemDefaultZone()); } private void executeTests(TestEngine engine, Clock clock) { var out = new PrintWriter(new StringWriter()); var reportListener = new LegacyXmlReportGeneratingListener(tempDirectory.toString(), out, clock); var launcher = createLauncher(engine); launcher.registerTestExecutionListeners(reportListener); var request = request() // .configurationParameter(LauncherConstants.STACKTRACE_PRUNING_ENABLED_PROPERTY_NAME, "false") // .selectors(selectUniqueId(UniqueId.forEngine(engine.getId()))) // .forExecution() // .build(); launcher.execute(request); } private Match readValidXmlFile(Path xmlFile) throws Exception { assertTrue(Files.exists(xmlFile), () -> "File does not exist: " + xmlFile); try (var reader = Files.newBufferedReader(xmlFile)) { var xml = $(reader); assertValidAccordingToJenkinsSchema(xml.document()); return xml; } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportAssertions.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.reporting.legacy.xml; import static org.junit.jupiter.api.Assertions.fail; import javax.xml.XMLConstants; import javax.xml.transform.dom.DOMSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import javax.xml.validation.Validator; import org.w3c.dom.Document; import org.xml.sax.SAXException; /** * @since 1.0 */ class XmlReportAssertions { static void assertValidAccordingToJenkinsSchema(Document document) throws Exception { try { // Schema is thread-safe, Validator is not var validator = CachedSchema.JENKINS.newValidator(); validator.validate(new DOMSource(document)); } catch (SAXException e) { fail("Invalid XML document: " + document, e); } } private enum CachedSchema { JENKINS("/jenkins-junit.xsd"); private final Schema schema; CachedSchema(String resourcePath) { var schemaFile = LegacyXmlReportGeneratingListener.class.getResource(resourcePath); var schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); try { this.schema = schemaFactory.newSchema(schemaFile); } catch (SAXException e) { throw new RuntimeException("Failed to create schema using " + schemaFile, e); } } Validator newValidator() { return schema.newValidator(); } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportDataTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.reporting.legacy.xml; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.platform.engine.TestExecutionResult.failed; import static org.junit.platform.engine.TestExecutionResult.successful; import static org.junit.platform.launcher.core.OutputDirectoryCreators.dummyOutputDirectoryCreator; import static org.mockito.Mockito.mock; import java.time.Clock; import java.util.Set; import org.junit.jupiter.api.Test; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.EngineDescriptor; import org.junit.platform.fakes.TestDescriptorStub; import org.junit.platform.launcher.TestPlan; /** * @since 1.0 */ class XmlReportDataTests { private final ConfigurationParameters configParams = mock(); @Test void resultsOfTestIdentifierWithoutAnyReportedEventsAreEmpty() { var engineDescriptor = new EngineDescriptor(UniqueId.forEngine("engine"), "Engine"); var childUniqueId = UniqueId.root("child", "test"); engineDescriptor.addChild(new TestDescriptorStub(childUniqueId, "test")); var testPlan = TestPlan.from(true, Set.of(engineDescriptor), configParams, dummyOutputDirectoryCreator()); var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); var results = reportData.getResults(testPlan.getTestIdentifier(childUniqueId)); assertThat(results).isEmpty(); } @Test void resultsOfTestIdentifierWithoutReportedEventsContainsOnlyFailureOfAncestor() { var engineDescriptor = new EngineDescriptor(UniqueId.forEngine("engine"), "Engine"); var childUniqueId = UniqueId.root("child", "test"); engineDescriptor.addChild(new TestDescriptorStub(childUniqueId, "test")); var testPlan = TestPlan.from(true, Set.of(engineDescriptor), configParams, dummyOutputDirectoryCreator()); var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); var failureOfAncestor = failed(new RuntimeException("failed!")); reportData.markFinished(testPlan.getTestIdentifier(engineDescriptor.getUniqueId()), failureOfAncestor); var results = reportData.getResults(testPlan.getTestIdentifier(childUniqueId)); assertThat(results).containsExactly(failureOfAncestor); } @Test void resultsOfTestIdentifierWithoutReportedEventsContainsOnlySuccessOfAncestor() { var engineDescriptor = new EngineDescriptor(UniqueId.forEngine("engine"), "Engine"); var childUniqueId = UniqueId.root("child", "test"); engineDescriptor.addChild(new TestDescriptorStub(childUniqueId, "test")); var testPlan = TestPlan.from(true, Set.of(engineDescriptor), configParams, dummyOutputDirectoryCreator()); var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); reportData.markFinished(testPlan.getTestIdentifier(engineDescriptor.getUniqueId()), successful()); var results = reportData.getResults(testPlan.getTestIdentifier(childUniqueId)); assertThat(results).containsExactly(successful()); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportWriterTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.reporting.legacy.xml; import static java.util.stream.Collectors.joining; import static org.assertj.core.api.Assertions.assertThat; import static org.joox.JOOX.$; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import static org.junit.platform.engine.TestExecutionResult.failed; import static org.junit.platform.engine.TestExecutionResult.successful; import static org.junit.platform.launcher.LauncherConstants.STDERR_REPORT_ENTRY_KEY; import static org.junit.platform.launcher.LauncherConstants.STDOUT_REPORT_ENTRY_KEY; import static org.junit.platform.launcher.core.OutputDirectoryCreators.dummyOutputDirectoryCreator; import static org.junit.platform.reporting.legacy.xml.XmlReportAssertions.assertValidAccordingToJenkinsSchema; import static org.junit.platform.reporting.legacy.xml.XmlReportWriter.ILLEGAL_CHARACTER_REPLACEMENT; import static org.mockito.Mockito.mock; import java.io.StringReader; import java.io.StringWriter; import java.io.Writer; import java.time.Clock; import java.util.Map; import java.util.Set; import java.util.stream.IntStream; import java.util.stream.Stream; import org.joox.Match; import org.jspecify.annotations.NullMarked; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.util.SetSystemProperty; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.engine.support.descriptor.EngineDescriptor; import org.junit.platform.fakes.TestDescriptorStub; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; /** * @since 1.0 */ class XmlReportWriterTests { private final ConfigurationParameters configParams = mock(); private EngineDescriptor engineDescriptor = new EngineDescriptor(UniqueId.forEngine("engine"), "Engine"); @Test void writesTestsuiteElementsWithoutTestcaseElementsWithoutAnyTests() throws Exception { var testPlan = TestPlan.from(true, Set.of(engineDescriptor), configParams, dummyOutputDirectoryCreator()); var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); var testsuite = writeXmlReport(testPlan, reportData); assertValidAccordingToJenkinsSchema(testsuite.document()); assertThat(testsuite.document().getDocumentElement().getTagName()).isEqualTo("testsuite"); assertThat(testsuite.attr("name")).isEqualTo("Engine"); assertThat(testsuite.attr("tests", int.class)).isEqualTo(0); assertThat(testsuite.find("testcase")).isEmpty(); } @Test void writesReportEntry() throws Exception { var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); var testDescriptor = new TestDescriptorStub(uniqueId, "successfulTest"); engineDescriptor.addChild(testDescriptor); var testPlan = TestPlan.from(true, Set.of(engineDescriptor), configParams, dummyOutputDirectoryCreator()); var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); reportData.addReportEntry(TestIdentifier.from(testDescriptor), ReportEntry.from("myKey", "myValue")); reportData.markFinished(testPlan.getTestIdentifier(uniqueId), successful()); var testsuite = writeXmlReport(testPlan, reportData); assertValidAccordingToJenkinsSchema(testsuite.document()); assertThat(String.join("\n", testsuite.find("system-out").texts())) // .containsSubsequence("Report Entry #1 (timestamp: ", "- myKey: myValue"); } @Test void writesCapturedOutput() throws Exception { var classDescriptor = new TestDescriptorStub(engineDescriptor.getUniqueId().append("class", "SomeClass"), "SomeClass"); engineDescriptor.addChild(classDescriptor); var testDescriptor = new TestDescriptorStub(classDescriptor.getUniqueId().append("test", "successfulTest"), "successfulTest"); classDescriptor.addChild(testDescriptor); var testPlan = TestPlan.from(true, Set.of(engineDescriptor), configParams, dummyOutputDirectoryCreator()); var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); var reportEntry = ReportEntry.from(Map.of( // STDOUT_REPORT_ENTRY_KEY, "normal output", // STDERR_REPORT_ENTRY_KEY, "error output", // "foo", "bar")); reportData.addReportEntry(TestIdentifier.from(testDescriptor), reportEntry); reportData.addReportEntry(TestIdentifier.from(testDescriptor), ReportEntry.from(Map.of("baz", "qux"))); reportData.markFinished(testPlan.getTestIdentifier(testDescriptor.getUniqueId()), successful()); var testsuite = writeXmlReport(testPlan, reportData); assertValidAccordingToJenkinsSchema(testsuite.document()); assertThat(testsuite.find("system-out").text(0)) // .containsSubsequence("unique-id: [engine:engine]/[class:SomeClass]/[test:successfulTest]", "display-name: Engine > SomeClass > successfulTest"); assertThat(testsuite.find("system-out").text(1)) // .containsSubsequence("Report Entry #1 (timestamp: ", "- foo: bar", "Report Entry #2 (timestamp: ", "- baz: qux"); assertThat(testsuite.find("system-out").text(2).strip()) // .isEqualTo("normal output"); assertThat(testsuite.find("system-err").text().strip()) // .isEqualTo("error output"); } @Test void writesEmptySkippedElementForSkippedTestWithoutReason() throws Exception { var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); engineDescriptor.addChild(new TestDescriptorStub(uniqueId, "skippedTest")); var testPlan = TestPlan.from(true, Set.of(engineDescriptor), configParams, dummyOutputDirectoryCreator()); var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); reportData.markSkipped(testPlan.getTestIdentifier(uniqueId), null); var testsuite = writeXmlReport(testPlan, reportData); assertValidAccordingToJenkinsSchema(testsuite.document()); var testcase = testsuite.child("testcase"); assertThat(testcase.attr("name")).isEqualTo("skippedTest"); var skipped = testcase.child("skipped"); assertThat(skipped.size()).isEqualTo(1); assertThat(skipped.children()).isEmpty(); } @Test @NullMarked void writesEmptyErrorElementForFailedTestWithoutCause() throws Exception { engineDescriptor = new EngineDescriptor(UniqueId.forEngine("myEngineId"), "Fancy Engine") { @Override public String getLegacyReportingName() { return "myEngine"; } }; var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); engineDescriptor.addChild(new TestDescriptorStub(uniqueId, "some fancy name") { @Override public String getLegacyReportingName() { return "failedTest"; } }); var testPlan = TestPlan.from(true, Set.of(engineDescriptor), configParams, dummyOutputDirectoryCreator()); var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); reportData.markFinished(testPlan.getTestIdentifier(uniqueId), failed(null)); var testsuite = writeXmlReport(testPlan, reportData); assertValidAccordingToJenkinsSchema(testsuite.document()); var testcase = testsuite.child("testcase"); assertThat(testcase.attr("name")).isEqualTo("failedTest"); assertThat(testcase.attr("classname")).isEqualTo("myEngine"); var error = testcase.child("error"); assertThat(error.size()).isEqualTo(1); assertThat(error.children()).isEmpty(); } @Test void omitsMessageAttributeForFailedTestWithThrowableWithoutMessage() throws Exception { var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); engineDescriptor.addChild(new TestDescriptorStub(uniqueId, "failedTest")); var testPlan = TestPlan.from(true, Set.of(engineDescriptor), configParams, dummyOutputDirectoryCreator()); var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); reportData.markFinished(testPlan.getTestIdentifier(uniqueId), failed(new NullPointerException())); var testsuite = writeXmlReport(testPlan, reportData); assertValidAccordingToJenkinsSchema(testsuite.document()); var error = testsuite.find("error"); assertThat(error.attr("type")).isEqualTo("java.lang.NullPointerException"); assertThat(error.attr("message")).isNull(); } @Test void writesValidXmlEvenIfExceptionMessageContainsCData() throws Exception { var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); engineDescriptor.addChild(new TestDescriptorStub(uniqueId, "test")); var testPlan = TestPlan.from(true, Set.of(engineDescriptor), configParams, dummyOutputDirectoryCreator()); var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); var assertionError = new AssertionError(""); reportData.markFinished(testPlan.getTestIdentifier(uniqueId), failed(assertionError)); var testsuite = writeXmlReport(testPlan, reportData); assertValidAccordingToJenkinsSchema(testsuite.document()); assertThat(testsuite.find("failure").attr("message")).isEqualTo(""); } @Test @SetSystemProperty(key = "foo.bar", value = "\1") void escapesInvalidCharactersInSystemPropertiesAndExceptionMessages() throws Exception { var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); engineDescriptor.addChild(new TestDescriptorStub(uniqueId, "test")); var testPlan = TestPlan.from(true, Set.of(engineDescriptor), configParams, dummyOutputDirectoryCreator()); var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); var assertionError = new AssertionError("expected: but was: "); reportData.markFinished(testPlan.getTestIdentifier(uniqueId), failed(assertionError)); Match testsuite = writeXmlReport(testPlan, reportData); assertValidAccordingToJenkinsSchema(testsuite.document()); assertThat(testsuite.find("property").matchAttr("name", "foo\\.bar").attr("value")) // .isEqualTo(String.valueOf(ILLEGAL_CHARACTER_REPLACEMENT)); var failure = testsuite.find("failure"); assertThat(failure.attr("message")) // .isEqualTo("expected: but was: "); assertThat(failure.text()) // .contains("AssertionError: expected: but was: "); } @ParameterizedTest(name = "[{index}]") @MethodSource("stringPairs") void replacesIllegalCharacters(String input, String output) { assertEquals(output, XmlReportWriter.replaceIllegalCharacters(input)); } @Test void writesValidXmlForExceptionMessagesContainingLineBreaks() throws Exception { var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); engineDescriptor.addChild(new TestDescriptorStub(uniqueId, "test")); var testPlan = TestPlan.from(true, Set.of(engineDescriptor), configParams, dummyOutputDirectoryCreator()); var allWhitespaceCharacters = IntStream.range(0, 0x10000) // .filter(Character::isWhitespace) // .filter(XmlReportWriter::isAllowedXmlCharacter) // .mapToObj(Character::toString) // .collect(joining()); var message = "a" + allWhitespaceCharacters + " b<&>"; var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); var assertionError = new AssertionError(message); reportData.markFinished(testPlan.getTestIdentifier(uniqueId), failed(assertionError)); var testsuite = writeXmlReport(testPlan, reportData); assertValidAccordingToJenkinsSchema(testsuite.document()); var attributeValue = testsuite.find("failure").attr("message"); assertThat(attributeValue).isEqualTo(message); } static Stream stringPairs() { return Stream.of( // arguments("\0", String.valueOf(ILLEGAL_CHARACTER_REPLACEMENT)), // arguments("\1", String.valueOf(ILLEGAL_CHARACTER_REPLACEMENT)), // arguments("\t", "\t"), // arguments("\r", "\r"), // arguments("\n", "\n"), // arguments("\u001f", String.valueOf(ILLEGAL_CHARACTER_REPLACEMENT)), // arguments("✅", "✅"), // arguments(" ", " "), // arguments("foo!", "foo!"), // arguments("\uD801\uDC00", "\uD801\uDC00") // ); } private Match writeXmlReport(TestPlan testPlan, XmlReportData reportData) throws Exception { var out = new StringWriter(); writeXmlReport(testPlan, reportData, out); return $(new StringReader(out.toString())); } private void writeXmlReport(TestPlan testPlan, XmlReportData reportData, Writer out) throws Exception { new XmlReportWriter(reportData).writeXmlReport(getOnlyElement(testPlan.getRoots()), out); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/reporting/open/xml/JUnitContributorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.reporting.open.xml; import static org.assertj.core.api.Assertions.assertThat; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.opentest4j.reporting.tooling.core.htmlreport.DefaultHtmlReportWriter; class JUnitContributorTests { @Test void contributesJUnitSpecificMetadata(@TempDir Path tempDir) throws Exception { var xmlFile = Files.writeString(tempDir.resolve("report.xml"), """ [engine:dummy] dummy CONTAINER [engine:dummy]/[test:method] method() TEST """); var htmlReport = tempDir.resolve("report.html"); new DefaultHtmlReportWriter().writeHtmlReport(List.of(xmlFile), htmlReport); assertThat(htmlReport).content() // .contains("JUnit metadata") // .contains("Type").contains("CONTAINER") // .contains("Unique ID").contains("[engine:dummy]") // .contains("Legacy reporting name").contains("dummy") // .contains("Type").contains("TEST") // .contains("Unique ID").contains("[engine:dummy]/[test:method]") // .contains("Legacy reporting name").contains("method()"); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListenerTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.reporting.open.xml; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.launcher.LauncherConstants.CAPTURE_STDERR_PROPERTY_NAME; import static org.junit.platform.launcher.LauncherConstants.CAPTURE_STDOUT_PROPERTY_NAME; import static org.junit.platform.launcher.LauncherConstants.OUTPUT_DIR_PROPERTY_NAME; import static org.junit.platform.launcher.LauncherConstants.OUTPUT_DIR_UNIQUE_NUMBER_PLACEHOLDER; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncher; import static org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener.ENABLED_PROPERTY_NAME; import static org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener.GIT_ENABLED_PROPERTY_NAME; import static org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener.SOCKET_PROPERTY_NAME; import static org.junit.platform.reporting.testutil.FileUtils.findPath; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; import java.util.Map; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.engine.support.hierarchical.DemoHierarchicalTestEngine; import org.junit.platform.tests.process.ProcessResult; import org.junit.platform.tests.process.ProcessStarter; import org.opentest4j.reporting.schema.Namespace; import org.opentest4j.reporting.tooling.core.validator.DefaultValidator; import org.opentest4j.reporting.tooling.core.validator.ValidationResult; import org.xmlunit.assertj3.XmlAssert; import org.xmlunit.placeholder.PlaceholderDifferenceEvaluator; /** * Tests for {@link OpenTestReportGeneratingListener}. * * @since 1.9 */ class OpenTestReportGeneratingListenerTests { private static final Map NAMESPACE_CONTEXT = Map.of( // "core", Namespace.REPORTING_CORE.getUri(), // "e", Namespace.REPORTING_EVENTS.getUri(), // "git", Namespace.REPORTING_GIT.getUri() // ); private PrintStream originalOut; private PrintStream originalErr; @BeforeEach void wrapSystemPrintStreams() { // Work around nesting check in org.junit.platform.launcher.core.StreamInterceptor originalOut = System.out; System.setOut(new PrintStream(System.out)); originalErr = System.err; System.setErr(new PrintStream(System.err)); } @AfterEach void restoreSystemPrintStreams() { System.setOut(originalOut); System.setErr(originalErr); } @Test void writesValidXmlReport(@TempDir Path tempDirectory) throws Exception { var engine = new DemoHierarchicalTestEngine("dummy"); engine.addTest("failingTest", "display<-->Name 😎", (context, descriptor) -> { try { var listener = context.request().getEngineExecutionListener(); listener.reportingEntryPublished(descriptor, ReportEntry.from("key", "value")); listener.fileEntryPublished(descriptor, FileEntry.from( Files.writeString(tempDirectory.resolve("test.txt"), "Hello, world!"), "text/plain")); System.out.println("Hello, stdout!"); System.err.println("Hello, stderr!"); } catch (Throwable t) { throw ExceptionUtils.throwAsUncheckedException(t); } fail("failure message"); }); executeTests(tempDirectory, engine, tempDirectory.resolve("junit-" + OUTPUT_DIR_UNIQUE_NUMBER_PLACEHOLDER)); var xmlFile = findPath(tempDirectory, "glob:**/open-test-report.xml"); assertThat(tempDirectory.relativize(xmlFile).toString()) // .matches("junit-\\d+[/\\\\]open-test-report.xml"); assertThat(validate(xmlFile)).isEmpty(); var expected = """ ${xmlunit.ignore} ${xmlunit.ignore} ${xmlunit.ignore} ${xmlunit.ignore} ${xmlunit.ignore} ${xmlunit.ignore} [engine:dummy] dummy CONTAINER [engine:dummy]/[test:failingTest] display<-->Name 😎 TEST value ${xmlunit.matchesRegex(org\\.opentest4j\\.AssertionFailedError: failure message)} """; XmlAssert.assertThat(xmlFile).and(expected) // .withDifferenceEvaluator(new PlaceholderDifferenceEvaluator()) // .ignoreWhitespace() // .areIdentical(); } @ParameterizedTest @ValueSource(strings = { "https://github.com/junit-team/junit-framework.git", "git@github.com:junit-team/junit-framework.git" }) void includesGitInfoWhenEnabled(String originUrl, @TempDir Path tempDirectory) throws Exception { assumeTrue(tryExecGit(tempDirectory, "--version").exitCode() == 0, "git not installed"); execGit(tempDirectory, "init", "--initial-branch=my_branch"); execGit(tempDirectory, "remote", "add", "origin", originUrl); Files.writeString(tempDirectory.resolve("README.md"), "Hello, world!"); execGit(tempDirectory, "add", "."); execGit(tempDirectory, "config", "user.name", "Alice"); execGit(tempDirectory, "config", "user.email", "alice@example.org"); execGit(tempDirectory, "commit", "--no-gpg-sign", "-m", "Initial commit"); var engine = new DemoHierarchicalTestEngine("dummy"); executeTests(tempDirectory, engine, tempDirectory.resolve("junit-reports")); var xmlFile = findPath(tempDirectory, "glob:**/open-test-report.xml"); assertThat(validate(xmlFile)).isEmpty(); assertThatXml(xmlFile) // .doesNotHaveXPath("/e:events/core:infrastructure/git:repository"); assertThatXml(xmlFile) // .doesNotHaveXPath("/e:events/core:infrastructure/git:branch"); assertThatXml(xmlFile) // .doesNotHaveXPath("/e:events/core:infrastructure/git:commit"); assertThatXml(xmlFile) // .doesNotHaveXPath("/e:events/core:infrastructure/git:status"); executeTests(tempDirectory, engine, tempDirectory.resolve("junit-reports"), Map.of(GIT_ENABLED_PROPERTY_NAME, "true")); assertThat(validate(xmlFile)).isEmpty(); assertThatXml(xmlFile) // .valueByXPath("/e:events/core:infrastructure/git:repository/@originUrl") // .isEqualTo(originUrl); assertThatXml(xmlFile) // .valueByXPath("/e:events/core:infrastructure/git:branch") // .isEqualTo("my_branch"); var commitHash = execGit(tempDirectory, "rev-parse", "--verify", "HEAD").stdOut().strip(); assertThatXml(xmlFile) // .valueByXPath("/e:events/core:infrastructure/git:commit") // .isEqualTo(commitHash); assertThatXml(xmlFile) // .valueByXPath("/e:events/core:infrastructure/git:status/@clean") // .isEqualTo(false); assertThatXml(xmlFile) // .valueByXPath("/e:events/core:infrastructure/git:status") // .startsWith("?? junit-reports"); } @ParameterizedTest @CsvSource(textBlock = """ https://foo:bar@github.com/junit-team/junit-framework.git, https://***@github.com/junit-team/junit-framework.git https://token@github.com/junit-team/junit-framework.git, https://***@github.com/junit-team/junit-framework.git foo@github.com:junit-team/junit-framework.git, ***@github.com:junit-team/junit-framework.git ssh://foo@github.com:junit-team/junit-framework.git, ssh://***@github.com:junit-team/junit-framework.git git@github.com:junit-team/junit-framework.git, git@github.com:junit-team/junit-framework.git ssh://git@github.com:junit-team/junit-framework.git, ssh://git@github.com:junit-team/junit-framework.git """) void stripsCredentialsFromOriginUrl(String configuredUrl, String reportedUrl, @TempDir Path tempDirectory) throws Exception { assumeTrue(tryExecGit(tempDirectory, "--version").exitCode() == 0, "git not installed"); execGit(tempDirectory, "init", "--initial-branch=my_branch"); execGit(tempDirectory, "remote", "add", "origin", configuredUrl); var engine = new DemoHierarchicalTestEngine("dummy"); executeTests(tempDirectory, engine, tempDirectory.resolve("junit-reports"), Map.of(GIT_ENABLED_PROPERTY_NAME, "true")); var xmlFile = findPath(tempDirectory, "glob:**/open-test-report.xml"); assertThat(validate(xmlFile)).isEmpty(); assertThatXml(xmlFile) // .valueByXPath("/e:events/core:infrastructure/git:repository/@originUrl") // .isEqualTo(reportedUrl); } @Test void writesXmlReportToSocket(@TempDir Path tempDirectory) throws Exception { var engine = new DemoHierarchicalTestEngine("dummy"); engine.addTest("test1", "Test 1", (context, descriptor) -> { // Simple test }); // Start a server socket to receive the XML var builder = new StringBuilder(); try (var serverSocket = new ServerSocket(0, 50, InetAddress.getLoopbackAddress())) { // Use any available port int port = serverSocket.getLocalPort(); // Start a daemon thread to accept the connection and read the XML Thread serverThread = new Thread(() -> { try (Socket clientSocket = serverSocket.accept(); var reader = new BufferedReader( new InputStreamReader(clientSocket.getInputStream(), StandardCharsets.UTF_8))) { String line; while ((line = reader.readLine()) != null) { builder.append(line).append("\n"); } } catch (Exception e) { fail(e); } }); serverThread.setDaemon(true); serverThread.start(); // Execute tests with socket configuration executeTests(tempDirectory, engine, tempDirectory.resolve("junit-reports"), Map.of(SOCKET_PROPERTY_NAME, String.valueOf(port))); // Wait for the server to receive the data assertThat(serverThread.join(Duration.ofSeconds(10))).isTrue(); // Verify XML was received var expected = """ ${xmlunit.ignore} ${xmlunit.ignore} ${xmlunit.ignore} ${xmlunit.ignore} ${xmlunit.ignore} ${xmlunit.ignore} [engine:dummy] dummy CONTAINER [engine:dummy]/[test:test1] Test 1 TEST """; XmlAssert.assertThat(builder.toString()).and(expected) // .withDifferenceEvaluator(new PlaceholderDifferenceEvaluator()) // .ignoreWhitespace() // .areIdentical(); } } private static XmlAssert assertThatXml(Path xmlFile) { return XmlAssert.assertThat(xmlFile) // .withNamespaceContext(NAMESPACE_CONTEXT); } private static ProcessResult execGit(Path workingDir, String... arguments) throws InterruptedException { var result = tryExecGit(workingDir, arguments); assertEquals(0, result.exitCode(), "git " + String.join(" ", arguments) + " failed"); return result; } private static ProcessResult tryExecGit(Path workingDir, String... arguments) throws InterruptedException { System.out.println("$ git " + String.join(" ", arguments)); return new ProcessStarter() // .executable(Path.of("git")) // .putEnvironment("GIT_CONFIG_GLOBAL", "/dev/null") // https://git-scm.com/docs/git#Documentation/git.txt-GITCONFIGGLOBAL .workingDir(workingDir) // .addArguments(arguments) // .startAndWait(); } private ValidationResult validate(Path xmlFile) throws URISyntaxException { var catalogUri = requireNonNull(getClass().getResource("catalog.xml")).toURI(); return new DefaultValidator(catalogUri).validate(xmlFile); } private static void executeTests(Path tempDirectory, TestEngine engine, Path outputDir) { executeTests(tempDirectory, engine, outputDir, Map.of()); } private static void executeTests(Path tempDirectory, TestEngine engine, Path outputDir, Map extraConfigurationParameters) { var request = request() // .selectors(selectUniqueId(UniqueId.forEngine(engine.getId()))) // .enableImplicitConfigurationParameters(false) // .configurationParameter(ENABLED_PROPERTY_NAME, String.valueOf(true)) // .configurationParameter(CAPTURE_STDOUT_PROPERTY_NAME, String.valueOf(true)) // .configurationParameter(CAPTURE_STDERR_PROPERTY_NAME, String.valueOf(true)) // .configurationParameter(OUTPUT_DIR_PROPERTY_NAME, outputDir.toString()) // .configurationParameters(extraConfigurationParameters) // .forExecution() // .listeners(new OpenTestReportGeneratingListener(tempDirectory)) // .build(); createLauncher(engine).execute(request); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/BeforeAndAfterSuiteTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Named.named; import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.suite.engine.SuiteEngineDescriptor.ENGINE_ID; import static org.junit.platform.testkit.engine.EventConditions.container; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.EventConditions.test; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.suppressed; import java.util.ArrayList; import java.util.function.Predicate; import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.suite.api.AfterSuite; import org.junit.platform.suite.api.BeforeSuite; import org.junit.platform.suite.engine.testcases.StatefulTestCase; import org.junit.platform.suite.engine.testsuites.LifecycleMethodsSuites.FailingAfterSuite; import org.junit.platform.suite.engine.testsuites.LifecycleMethodsSuites.FailingBeforeAndAfterSuite; import org.junit.platform.suite.engine.testsuites.LifecycleMethodsSuites.FailingBeforeSuite; import org.junit.platform.suite.engine.testsuites.LifecycleMethodsSuites.NonStaticAfterSuite; import org.junit.platform.suite.engine.testsuites.LifecycleMethodsSuites.NonStaticBeforeSuite; import org.junit.platform.suite.engine.testsuites.LifecycleMethodsSuites.NonVoidAfterSuite; import org.junit.platform.suite.engine.testsuites.LifecycleMethodsSuites.NonVoidBeforeSuite; import org.junit.platform.suite.engine.testsuites.LifecycleMethodsSuites.ParameterAcceptingAfterSuite; import org.junit.platform.suite.engine.testsuites.LifecycleMethodsSuites.ParameterAcceptingBeforeSuite; import org.junit.platform.suite.engine.testsuites.LifecycleMethodsSuites.PrivateAfterSuite; import org.junit.platform.suite.engine.testsuites.LifecycleMethodsSuites.PrivateBeforeSuite; import org.junit.platform.suite.engine.testsuites.LifecycleMethodsSuites.SeveralFailingBeforeAndAfterSuite; import org.junit.platform.suite.engine.testsuites.LifecycleMethodsSuites.SubclassWithBeforeAndAfterSuite; import org.junit.platform.suite.engine.testsuites.LifecycleMethodsSuites.SuccessfulBeforeAndAfterSuite; import org.junit.platform.testkit.engine.EngineExecutionResults; import org.junit.platform.testkit.engine.EngineTestKit; /** * Integration tests that verify support for {@link BeforeSuite} and {@link AfterSuite}, * in the {@link SuiteTestEngine}. * * @since 1.11 */ public class BeforeAndAfterSuiteTests { @BeforeEach void setUp() { StatefulTestCase.callSequence = new ArrayList<>(); } @Test void successfulBeforeAndAfterSuite() { // @formatter:off executeSuite(SuccessfulBeforeAndAfterSuite.class) .allEvents() .assertStatistics(stats -> stats.started(7).finished(7).succeeded(6).failed(1)) .assertThatEvents() .haveExactly(1, event(test(StatefulTestCase.Test1.class.getName()), finishedSuccessfully())) .haveExactly(1, event(test(StatefulTestCase.Test2.class.getName()), finishedWithFailure())); assertThat(StatefulTestCase.callSequence).containsExactly( "beforeSuiteMethod", "test1", "test2", "afterSuiteMethod" ); // @formatter:on } @Test void beforeAndAfterSuiteInheritance() { // @formatter:off executeSuite(SubclassWithBeforeAndAfterSuite.class) .allEvents() .assertStatistics(stats -> stats.started(7).finished(7).succeeded(6).failed(1)); assertThat(StatefulTestCase.callSequence).containsExactly( "superclassBeforeSuiteMethod", "subclassBeforeSuiteMethod", "test1", "test2", "subclassAfterSuiteMethod", "superclassAfterSuiteMethod" ); // @formatter:on } @Test void failingBeforeSuite() { // @formatter:off executeSuite(FailingBeforeSuite.class) .allEvents() .assertStatistics(stats -> stats.started(2).finished(2).succeeded(1).failed(1)) .assertThatEvents() .haveExactly(1, event( container(FailingBeforeSuite.class), finishedWithFailure(instanceOf(RuntimeException.class), message("Exception thrown by @BeforeSuite method")))); assertThat(StatefulTestCase.callSequence).containsExactly( "beforeSuiteMethod", "afterSuiteMethod" ); // @formatter:on } @Test void failingAfterSuite() { // @formatter:off executeSuite(FailingAfterSuite.class) .allEvents() .assertStatistics(stats -> stats.started(7).finished(7).succeeded(5).failed(2)) .assertThatEvents() .haveExactly(1, event( container(FailingAfterSuite.class), finishedWithFailure(instanceOf(RuntimeException.class), message("Exception thrown by @AfterSuite method")))); assertThat(StatefulTestCase.callSequence).containsExactly( "beforeSuiteMethod", "test1", "test2", "afterSuiteMethod" ); // @formatter:on } @Test void failingBeforeAndAfterSuite() { // @formatter:off executeSuite(FailingBeforeAndAfterSuite.class) .allEvents() .assertStatistics(stats -> stats.started(2).finished(2).succeeded(1).failed(1)) .assertThatEvents() .haveExactly(1, event( container(FailingBeforeAndAfterSuite.class), finishedWithFailure(instanceOf(RuntimeException.class), message("Exception thrown by @BeforeSuite method"), suppressed(0, instanceOf(RuntimeException.class), message("Exception thrown by @AfterSuite method"))))); assertThat(StatefulTestCase.callSequence).containsExactly( "beforeSuiteMethod", "afterSuiteMethod" ); // @formatter:on } @Test void severalFailingBeforeAndAfterSuite() { // @formatter:off executeSuite(SeveralFailingBeforeAndAfterSuite.class) .allEvents() .assertStatistics(stats -> stats.started(2).finished(2).succeeded(1).failed(1)) .assertThatEvents() .haveExactly(1, event( container(SeveralFailingBeforeAndAfterSuite.class), finishedWithFailure(instanceOf(RuntimeException.class), message("Exception thrown by @BeforeSuite method"), suppressed(0, instanceOf(RuntimeException.class), message("Exception thrown by @AfterSuite method")), suppressed(1, instanceOf(RuntimeException.class), message("Exception thrown by @AfterSuite method"))))); assertThat(StatefulTestCase.callSequence).containsExactly( "beforeSuiteMethod", "afterSuiteMethod", "afterSuiteMethod" ); // @formatter:on } @ParameterizedTest(name = "{0}") @MethodSource void invalidBeforeOrAfterSuiteMethod(Class testSuiteClass, Predicate failureMessagePredicate) { var results = engineWithSelectedSuite(testSuiteClass).discover(); var issue = getOnlyElement(results.getDiscoveryIssues()); assertThat(issue.severity()).isEqualTo(Severity.ERROR); assertThat(issue.message()).matches(failureMessagePredicate); assertThat(issue.source()).containsInstanceOf(org.junit.platform.engine.support.descriptor.MethodSource.class); } private static Stream invalidBeforeOrAfterSuiteMethod() { return Stream.of( invalidBeforeOrAfterSuiteCase(NonVoidBeforeSuite.class, "@BeforeSuite method", "must not return a value."), invalidBeforeOrAfterSuiteCase(ParameterAcceptingBeforeSuite.class, "@BeforeSuite method", "must not accept parameters."), invalidBeforeOrAfterSuiteCase(NonStaticBeforeSuite.class, "@BeforeSuite method", "must be static."), invalidBeforeOrAfterSuiteCase(PrivateBeforeSuite.class, "@BeforeSuite method", "must not be private."), invalidBeforeOrAfterSuiteCase(NonVoidAfterSuite.class, "@AfterSuite method", "must not return a value."), invalidBeforeOrAfterSuiteCase(ParameterAcceptingAfterSuite.class, "@AfterSuite method", "must not accept parameters."), invalidBeforeOrAfterSuiteCase(NonStaticAfterSuite.class, "@AfterSuite method", "must be static."), invalidBeforeOrAfterSuiteCase(PrivateAfterSuite.class, "@AfterSuite method", "must not be private.")); } private static Arguments invalidBeforeOrAfterSuiteCase(Class suiteClass, String failureMessageStart, String failureMessageEnd) { return arguments(named(suiteClass.getSimpleName(), suiteClass), expectedMessage(failureMessageStart, failureMessageEnd)); } private static Predicate expectedMessage(String failureMessageStart, String failureMessageEnd) { return message -> message.startsWith(failureMessageStart) && message.endsWith(failureMessageEnd); } private static EngineExecutionResults executeSuite(Class suiteClass) { return engineWithSelectedSuite(suiteClass).execute(); } private static EngineTestKit.Builder engineWithSelectedSuite(Class suiteClass) { return EngineTestKit.engine(ENGINE_ID).selectors(selectClass(suiteClass)); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteEngineTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TemporaryClasspathExecutor.withAdditionalClasspathRoot; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.launcher.TagFilter.excludeTags; import static org.junit.platform.launcher.core.OutputDirectoryCreators.hierarchicalOutputDirectoryCreator; import static org.junit.platform.suite.engine.SuiteEngineDescriptor.ENGINE_ID; import static org.junit.platform.testkit.engine.EventConditions.container; import static org.junit.platform.testkit.engine.EventConditions.displayName; import static org.junit.platform.testkit.engine.EventConditions.engine; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.EventConditions.skippedWithReason; import static org.junit.platform.testkit.engine.EventConditions.started; import static org.junit.platform.testkit.engine.EventConditions.test; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.nio.file.Path; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.FilterResult; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.platform.engine.support.descriptor.MethodSource; import org.junit.platform.engine.support.store.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.LauncherConstants; import org.junit.platform.launcher.PostDiscoveryFilter; import org.junit.platform.launcher.core.NamespacedHierarchicalStoreProviders; import org.junit.platform.suite.api.AfterSuite; import org.junit.platform.suite.api.BeforeSuite; import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; import org.junit.platform.suite.engine.testcases.ConfigurationSensitiveTestCase; import org.junit.platform.suite.engine.testcases.DynamicTestsTestCase; import org.junit.platform.suite.engine.testcases.ErroneousTestCase; import org.junit.platform.suite.engine.testcases.JUnit4TestsTestCase; import org.junit.platform.suite.engine.testcases.MultipleTestsTestCase; import org.junit.platform.suite.engine.testcases.SingleTestTestCase; import org.junit.platform.suite.engine.testcases.TaggedTestTestCase; import org.junit.platform.suite.engine.testsuites.AbstractSuite; import org.junit.platform.suite.engine.testsuites.BlankSuiteDisplayNameSuite; import org.junit.platform.suite.engine.testsuites.ConfigurationSuite; import org.junit.platform.suite.engine.testsuites.CyclicSuite; import org.junit.platform.suite.engine.testsuites.DynamicSuite; import org.junit.platform.suite.engine.testsuites.EmptyCyclicSuite; import org.junit.platform.suite.engine.testsuites.EmptyDynamicTestSuite; import org.junit.platform.suite.engine.testsuites.EmptyDynamicTestWithFailIfNoTestFalseSuite; import org.junit.platform.suite.engine.testsuites.EmptyTestCaseSuite; import org.junit.platform.suite.engine.testsuites.EmptyTestCaseWithFailIfNoTestFalseSuite; import org.junit.platform.suite.engine.testsuites.ErroneousTestSuite; import org.junit.platform.suite.engine.testsuites.InheritedSuite; import org.junit.platform.suite.engine.testsuites.MultiEngineSuite; import org.junit.platform.suite.engine.testsuites.MultipleSuite; import org.junit.platform.suite.engine.testsuites.NestedSuite; import org.junit.platform.suite.engine.testsuites.SelectByIdentifierSuite; import org.junit.platform.suite.engine.testsuites.SelectClassesSuite; import org.junit.platform.suite.engine.testsuites.SelectMethodsSuite; import org.junit.platform.suite.engine.testsuites.SelectorProcessingErrorTestSuite; import org.junit.platform.suite.engine.testsuites.SuiteDisplayNameSuite; import org.junit.platform.suite.engine.testsuites.SuiteSuite; import org.junit.platform.suite.engine.testsuites.SuiteWithErroneousTestSuite; import org.junit.platform.suite.engine.testsuites.ThreePartCyclicSuite; import org.junit.platform.suite.engine.testsuites.WhitespaceSuiteDisplayNameSuite; import org.junit.platform.testkit.engine.EngineTestKit; /** * @since 1.8 */ class SuiteEngineTests { @TempDir private Path outputDir; @ParameterizedTest @ValueSource(classes = { SelectClassesSuite.class, InheritedSuite.class }) void selectClasses(Class suiteClass) { // @formatter:off EngineTestKit.Builder testKit = EngineTestKit.engine(ENGINE_ID) .selectors(selectClass(suiteClass)) .outputDirectoryCreator(hierarchicalOutputDirectoryCreator(outputDir)); assertThat(testKit.discover().getDiscoveryIssues()) .isEmpty(); testKit .execute() .testEvents() .assertThatEvents() .haveExactly(1, event(test(suiteClass.getName()), finishedSuccessfully())) .haveExactly(1, event(test(SingleTestTestCase.class.getName()), finishedSuccessfully())); // @formatter:on } @Test void selectMethods() { // @formatter:off var testKit = EngineTestKit.engine(ENGINE_ID) .selectors(selectClass(SelectMethodsSuite.class)); assertThat(testKit.discover().getDiscoveryIssues()) .isEmpty(); testKit .execute() .testEvents() .assertThatEvents() .haveExactly(1, event(test(MultipleTestsTestCase.class.getName(), "test()"), finishedSuccessfully())) .doNotHave(event(test(MultipleTestsTestCase.class.getName(), "test2()"))); // @formatter:on } @Test void suiteDisplayName() { // @formatter:off EngineTestKit.engine(ENGINE_ID) .selectors(selectClass(SuiteDisplayNameSuite.class)) .execute() .allEvents() .assertThatEvents() .haveExactly(1, event(container(displayName("Suite Display Name")), finishedSuccessfully())); // @formatter:on } @Test void abstractSuiteIsNotExecuted() { // @formatter:off var testKit = EngineTestKit.engine(ENGINE_ID) .selectors(selectClass(AbstractSuite.class)); assertThat(testKit.discover().getDiscoveryIssues()) .isEmpty(); testKit .execute() .testEvents() .assertThatEvents() .isEmpty(); // @formatter:on } @Test void privateSuiteIsNotExecuted() { // @formatter:off var message = "@Suite class '%s' must not be private. It will not be executed." .formatted(PrivateSuite.class.getName()); var issue = DiscoveryIssue.builder(Severity.WARNING, message) .source(ClassSource.from(PrivateSuite.class)) .build(); var testKit = EngineTestKit.engine(ENGINE_ID) .selectors(selectClass(PrivateSuite.class)); assertThat(testKit.discover().getDiscoveryIssues()) .containsExactly(issue); testKit .execute() .testEvents() .assertThatEvents() .isEmpty(); // @formatter:on } @Test void abstractPrivateSuiteIsNotExecuted() { // @formatter:off var testKit = EngineTestKit.engine(ENGINE_ID) .selectors(selectClass(AbstractPrivateSuite.class)); assertThat(testKit.discover().getDiscoveryIssues()) .isEmpty(); testKit .execute() .testEvents() .assertThatEvents() .isEmpty(); // @formatter:on } @ParameterizedTest @ValueSource(classes = { InnerSuite.class, AbstractInnerSuite.class }) void innerSuiteIsNotExecuted(Class suiteClass) { // @formatter:off var message = "@Suite class '%s' must not be an inner class. Did you forget to declare it static? It will not be executed." .formatted(suiteClass.getName()); var issue = DiscoveryIssue.builder(Severity.WARNING, message) .source(ClassSource.from(suiteClass)) .build(); var testKit = EngineTestKit.engine(ENGINE_ID) .selectors(selectClass(suiteClass)); assertThat(testKit.discover().getDiscoveryIssues()) .containsExactly(issue); testKit .execute() .testEvents() .assertThatEvents() .isEmpty(); // @formatter:on } @Test void localSuiteIsNotExecuted() { @Suite @SelectClasses(names = "org.junit.platform.suite.engine.testcases.SingleTestTestCase") class LocalSuite { } // @formatter:off var message = "@Suite class '%s' must not be a local class. It will not be executed." .formatted(LocalSuite.class.getName()); var issue = DiscoveryIssue.builder(Severity.WARNING, message) .source(ClassSource.from(LocalSuite.class)) .build(); var testKit = EngineTestKit.engine(ENGINE_ID) .selectors(selectClass(LocalSuite.class)); assertThat(testKit.discover().getDiscoveryIssues()) .containsExactly(issue); testKit .execute() .testEvents() .assertThatEvents() .isEmpty(); // @formatter:on } @Test void anonymousSuiteIsNotExecuted() { var object = new Object() { }; // @formatter:off var testKit = EngineTestKit.engine(ENGINE_ID) .selectors(selectClass(object.getClass())); assertThat(testKit.discover().getDiscoveryIssues()) .isEmpty(); testKit .execute() .testEvents() .assertThatEvents() .isEmpty(); // @formatter:on } @Test void nestedSuiteIsNotExecuted() { // @formatter:off EngineTestKit.engine(ENGINE_ID) .selectors(selectClass(NestedSuite.class)) .execute() .testEvents() .assertThatEvents() .isEmpty(); // @formatter:on } @Test void dynamicSuite() { // @formatter:off EngineTestKit.engine(ENGINE_ID) .selectors(selectClass(DynamicSuite.class)) .execute() .testEvents() .assertThatEvents() .haveExactly(2, event(test(DynamicTestsTestCase.class.getName()), finishedSuccessfully())); // @formatter:on } @Test void suiteSuite() { // @formatter:off EngineTestKit.engine(ENGINE_ID) .selectors(selectClass(SuiteSuite.class)) .outputDirectoryCreator(hierarchicalOutputDirectoryCreator(outputDir)) .execute() .testEvents() .assertThatEvents() .haveExactly(1, event(test(SuiteSuite.class.getName()), finishedSuccessfully())) .haveExactly(1, event(test(SelectClassesSuite.class.getName()), finishedSuccessfully())) .haveExactly(1, event(test(SingleTestTestCase.class.getName()), finishedSuccessfully())); // @formatter:on } @Test void multipleSuites() { // @formatter:off var testKit = EngineTestKit.engine(ENGINE_ID) .selectors( selectClass(SelectClassesSuite.class), selectClass(MultipleSuite.class) ) .outputDirectoryCreator(hierarchicalOutputDirectoryCreator(outputDir)); assertThat(testKit.discover().getDiscoveryIssues()) .isEmpty(); testKit .execute() .testEvents() .assertThatEvents() .haveExactly(1, event(test(SelectClassesSuite.class.getName()), finishedSuccessfully())) .haveExactly(2, event(test(MultipleSuite.class.getName()), finishedSuccessfully())); // @formatter:on } @Test void multipleSuitesWithMemoryCleanupEnabled() { // @formatter:off var testKit = EngineTestKit.engine(ENGINE_ID) .configurationParameter(LauncherConstants.MEMORY_CLEANUP_ENABLED_PROPERTY_NAME, "true") // .selectors( selectClass(SelectClassesSuite.class), selectClass(MultipleSuite.class) ) .outputDirectoryCreator(hierarchicalOutputDirectoryCreator(outputDir)); assertThat(testKit.discover().getDiscoveryIssues()) .isEmpty(); testKit .execute() .testEvents() .assertThatEvents() .haveExactly(1, event(test(SelectClassesSuite.class.getName()), finishedSuccessfully())) .haveExactly(2, event(test(MultipleSuite.class.getName()), finishedSuccessfully())); // @formatter:on } @Test void selectClassesByUniqueId() { // @formatter:off UniqueId uniqId = UniqueId.forEngine(ENGINE_ID) .append(SuiteTestDescriptor.SEGMENT_TYPE, SelectClassesSuite.class.getName()); EngineTestKit.Builder builder = EngineTestKit.engine(ENGINE_ID) .selectors(selectUniqueId(uniqId)); builder.outputDirectoryCreator(hierarchicalOutputDirectoryCreator(outputDir)) .execute() .testEvents() .assertThatEvents() .haveExactly(1, event(test(SelectClassesSuite.class.getName()), finishedSuccessfully())) .haveExactly(1, event(test(SingleTestTestCase.class.getName()), finishedSuccessfully())); // @formatter:on } @Test void selectMethodInTestPlanByUniqueId() { // @formatter:off UniqueId uniqueId = UniqueId.forEngine(ENGINE_ID) .append(SuiteTestDescriptor.SEGMENT_TYPE, MultipleSuite.class.getName()) .append("engine", JupiterEngineDescriptor.ENGINE_ID) .append(ClassTestDescriptor.SEGMENT_TYPE, MultipleTestsTestCase.class.getName()) .append(TestMethodTestDescriptor.SEGMENT_TYPE, "test()"); EngineTestKit.engine(ENGINE_ID) .selectors(selectUniqueId(uniqueId)) .execute() .testEvents() .assertThatEvents() .haveExactly(1, event(test(MultipleSuite.class.getName()), finishedSuccessfully())) .haveExactly(1, event(test(MultipleTestsTestCase.class.getName()), finishedSuccessfully())); // @formatter:on } @Test void selectSuiteByUniqueId() { // @formatter:off UniqueId uniqueId = UniqueId.forEngine(ENGINE_ID) .append(SuiteTestDescriptor.SEGMENT_TYPE, MultipleSuite.class.getName()); EngineTestKit.engine(ENGINE_ID) .selectors(selectUniqueId(uniqueId)) .execute() .testEvents() .assertThatEvents() .haveExactly(2, event(test(MultipleSuite.class.getName()), finishedSuccessfully())) .haveExactly(2, event(test(MultipleTestsTestCase.class.getName()), finishedSuccessfully())); // @formatter:on } @Test void selectMethodAndSuiteInTestPlanByUniqueId() { // @formatter:off UniqueId uniqueId = UniqueId.forEngine(ENGINE_ID) .append(SuiteTestDescriptor.SEGMENT_TYPE, MultipleSuite.class.getName()) .append("engine", JupiterEngineDescriptor.ENGINE_ID) .append(ClassTestDescriptor.SEGMENT_TYPE, MultipleTestsTestCase.class.getName()) .append(TestMethodTestDescriptor.SEGMENT_TYPE, "test()"); EngineTestKit.Builder builder = EngineTestKit.engine(ENGINE_ID) .selectors(selectUniqueId(uniqueId)) .selectors(selectClass(SelectClassesSuite.class)); builder.outputDirectoryCreator(hierarchicalOutputDirectoryCreator(outputDir)) .execute() .testEvents() .assertThatEvents() .haveExactly(1, event(test(SelectClassesSuite.class.getName()), finishedSuccessfully())) .haveExactly(1, event(test(SingleTestTestCase.class.getName()), finishedSuccessfully())) .haveExactly(1, event(test(MultipleSuite.class.getName()), finishedSuccessfully())) .haveExactly(1, event(test(MultipleTestsTestCase.class.getName()), finishedSuccessfully())); // @formatter:on } @Test void selectMethodsInTestPlanByUniqueId() { // @formatter:off UniqueId uniqueId = UniqueId.forEngine(ENGINE_ID) .append(SuiteTestDescriptor.SEGMENT_TYPE, MultipleSuite.class.getName()) .append("engine", JupiterEngineDescriptor.ENGINE_ID) .append(ClassTestDescriptor.SEGMENT_TYPE, MultipleTestsTestCase.class.getName()) .append(TestMethodTestDescriptor.SEGMENT_TYPE, "test()"); UniqueId uniqueId2 = UniqueId.forEngine(ENGINE_ID) .append(SuiteTestDescriptor.SEGMENT_TYPE, MultipleSuite.class.getName()) .append("engine", JupiterEngineDescriptor.ENGINE_ID) .append(ClassTestDescriptor.SEGMENT_TYPE, MultipleTestsTestCase.class.getName()) .append(TestMethodTestDescriptor.SEGMENT_TYPE, "test2()"); EngineTestKit.engine(ENGINE_ID) .selectors(selectUniqueId(uniqueId)) .selectors(selectUniqueId(uniqueId2)) .execute() .testEvents() .assertThatEvents() .haveExactly(2, event(test(MultipleSuite.class.getName()), finishedSuccessfully())) .haveExactly(2, event(test(MultipleTestsTestCase.class.getName()), finishedSuccessfully())); // @formatter:on } @Test void selectConfigurationSensitiveMethodsInTestPlanByUniqueId() { // @formatter:off var uniqueId1 = UniqueId.forEngine(ENGINE_ID) .append(SuiteTestDescriptor.SEGMENT_TYPE, ConfigurationSuite.class.getName()) .append("engine", JupiterEngineDescriptor.ENGINE_ID) .append(ClassTestDescriptor.SEGMENT_TYPE, ConfigurationSensitiveTestCase.class.getName()) .append(TestMethodTestDescriptor.SEGMENT_TYPE, "test1()"); var uniqueId2 = UniqueId.forEngine(ENGINE_ID) .append(SuiteTestDescriptor.SEGMENT_TYPE, ConfigurationSuite.class.getName()) .append("engine", JupiterEngineDescriptor.ENGINE_ID) .append(ClassTestDescriptor.SEGMENT_TYPE, ConfigurationSensitiveTestCase.class.getName()) .append(TestMethodTestDescriptor.SEGMENT_TYPE, "test2()"); EngineTestKit.engine(ENGINE_ID) .selectors( selectUniqueId(uniqueId1), selectUniqueId(uniqueId2) ) .execute() .testEvents() .assertThatEvents() .haveExactly(1, event(test(ConfigurationSuite.class.getName(), "test1()"), finishedSuccessfully())) .haveExactly(1, event(test(ConfigurationSuite.class.getName(), "test2()"), finishedSuccessfully())); // @formatter:on } @Test void postDiscoveryCanRemoveTestDescriptorsInSuite() { // @formatter:off PostDiscoveryFilter postDiscoveryFilter = testDescriptor -> testDescriptor.getSource() .filter(MethodSource.class::isInstance) .map(MethodSource.class::cast) .filter(classSource -> SingleTestTestCase.class.equals(classSource.getJavaClass())) .map(classSource -> FilterResult.excluded("Was a test in SimpleTest")) .orElseGet(() -> FilterResult.included("Was not a test in SimpleTest")); EngineTestKit.engine(ENGINE_ID) .selectors(selectClass(SelectClassesSuite.class)) .filters(postDiscoveryFilter) .execute() .testEvents() .assertThatEvents() .isEmpty(); // @formatter:on } @Test void emptySuiteFails() { // @formatter:off EngineTestKit.engine(ENGINE_ID) .selectors(selectClass(EmptyTestCaseSuite.class)) .execute() .containerEvents() .assertThatEvents() .haveExactly(1, event(container(EmptyTestCaseSuite.class), finishedWithFailure(instanceOf(NoTestsDiscoveredException.class)))); // @formatter:on } @Test void emptySuitePassesWhenFailIfNoTestIsFalse() { // @formatter:off EngineTestKit.engine(ENGINE_ID) .selectors(selectClass(EmptyTestCaseWithFailIfNoTestFalseSuite.class)) .execute() .containerEvents() .assertThatEvents() .haveExactly(1, event(engine(), finishedSuccessfully())); // @formatter:on } @Test void emptyDynamicSuiteFails() { // @formatter:off EngineTestKit.engine(ENGINE_ID) .selectors(selectClass(EmptyDynamicTestSuite.class)) .execute() .containerEvents() .assertThatEvents() .haveExactly(1, event(container(EmptyDynamicTestSuite.class), finishedWithFailure(instanceOf(NoTestsDiscoveredException.class)))); // @formatter:on } @Test void emptyDynamicSuitePassesWhenFailIfNoTestIsFalse() { // @formatter:off EngineTestKit.engine(ENGINE_ID) .selectors(selectClass(EmptyDynamicTestWithFailIfNoTestFalseSuite.class)) .execute() .allEvents() .assertThatEvents() .haveAtLeastOne(event(container(EmptyDynamicTestWithFailIfNoTestFalseSuite.class), finishedSuccessfully())); // @formatter:on } @Test void pruneAfterPostDiscoveryFilters() { // @formatter:off EngineTestKit.engine(ENGINE_ID) .selectors(selectClass(MultiEngineSuite.class)) .filters(excludeTags("excluded")) .execute() .allEvents() .assertThatEvents() .haveExactly(1, event(test(JUnit4TestsTestCase.class.getName()), finishedSuccessfully())) .doNotHave(test(TaggedTestTestCase.class.getName())) .doNotHave(container("junit-jupiter")); // @formatter:on } @Test void cyclicSuite() { // @formatter:off var expectedUniqueId = UniqueId.forEngine(ENGINE_ID) .append(SuiteTestDescriptor.SEGMENT_TYPE, CyclicSuite.class.getName()) .appendEngine(ENGINE_ID) .append(SuiteTestDescriptor.SEGMENT_TYPE, CyclicSuite.class.getName()); var message = "The suite configuration of [%s] resulted in a cycle [%s] and will not be discovered a second time." .formatted(CyclicSuite.class.getName(), expectedUniqueId); var issue = DiscoveryIssue.builder(Severity.INFO, message) .source(ClassSource.from(CyclicSuite.class)) .build(); EngineTestKit.Builder builder = EngineTestKit.engine(ENGINE_ID) .selectors(selectClass(CyclicSuite.class)); var testKit = builder.outputDirectoryCreator(hierarchicalOutputDirectoryCreator(outputDir)); assertThat(testKit.discover().getDiscoveryIssues()) .containsExactly(issue); testKit .execute() .allEvents() .assertThatEvents() .haveExactly(1, event(test(SingleTestTestCase.class.getName()), finishedSuccessfully())); // @formatter:on } @Test void emptyCyclicSuite() { // @formatter:off EngineTestKit.engine(ENGINE_ID) .selectors(selectClass(EmptyCyclicSuite.class)) .execute() .allEvents() .assertThatEvents() .haveExactly(1, event(container(EmptyCyclicSuite.class), finishedWithFailure(message( "Suite [org.junit.platform.suite.engine.testsuites.EmptyCyclicSuite] did not discover any tests" )))); // @formatter:on } @Test void threePartCyclicSuite() { // @formatter:off EngineTestKit.Builder builder = EngineTestKit.engine(ENGINE_ID) .selectors(selectClass(ThreePartCyclicSuite.PartA.class)); builder.outputDirectoryCreator(hierarchicalOutputDirectoryCreator(outputDir)) .execute() .allEvents() .assertThatEvents() .haveExactly(1, event(test(SingleTestTestCase.class.getName()), finishedSuccessfully())); // @formatter:on } @Test void selectByIdentifier() { // @formatter:off EngineTestKit.Builder builder = EngineTestKit.engine(ENGINE_ID) .selectors(selectClass(SelectByIdentifierSuite.class)); builder.outputDirectoryCreator(hierarchicalOutputDirectoryCreator(outputDir)) .execute() .testEvents() .assertThatEvents() .haveExactly(1, event(test(SelectByIdentifierSuite.class.getName()), finishedSuccessfully())) .haveExactly(1, event(test(SingleTestTestCase.class.getName()), finishedSuccessfully())); // @formatter:on } @Test void passesOutputDirectoryCreatorToEnginesInSuite() { // @formatter:off EngineTestKit.Builder builder = EngineTestKit.engine(ENGINE_ID) .selectors(selectClass(SelectClassesSuite.class)); builder.outputDirectoryCreator(hierarchicalOutputDirectoryCreator(outputDir)) .execute() .testEvents() .assertThatEvents() .haveExactly(1, event(test(SingleTestTestCase.class.getName()), finishedSuccessfully())); // @formatter:on assertThat(outputDir).isDirectoryRecursivelyContaining("glob:**/test.txt"); } @Test void discoveryIssueOfNestedTestEnginesAreReported() throws Exception { // @formatter:off var testKit = EngineTestKit.engine(ENGINE_ID) .selectors(selectClass(SuiteWithErroneousTestSuite.class)); var discoveryIssues = testKit.discover().getDiscoveryIssues(); assertThat(discoveryIssues).hasSize(1); var issue = discoveryIssues.getFirst(); assertThat(issue.message()) // .startsWith("[junit-jupiter] @BeforeAll method") // .endsWith(" (via @Suite %s > %s).".formatted(SuiteWithErroneousTestSuite.class.getName(), ErroneousTestSuite.class.getName())); var method = ErroneousTestCase.class.getDeclaredMethod("nonStaticLifecycleMethod"); assertThat(issue.source()).contains(MethodSource.from(method)); testKit .execute() .testEvents() .assertThatEvents() .isEmpty(); // @formatter:on } @Test void selectorProcessingFailuresAreReported() { withAdditionalClasspathRoot("error-engine/", () -> { var testKit = EngineTestKit.engine(ENGINE_ID).selectors( selectClass(SelectorProcessingErrorTestSuite.class)); var discoveryIssues = testKit.discover().getDiscoveryIssues(); assertThat(discoveryIssues).hasSize(1); var issue = discoveryIssues.getFirst(); assertThat(issue.message()) // .startsWith("[selector-error-engine] ErrorSelector[message=simulatedError] resolution failed") // .endsWith(" (via @Suite %s).".formatted(SelectorProcessingErrorTestSuite.class.getName())); }); } @Test void suiteEnginePassesRequestLevelStoreToSuiteTestDescriptors() { UniqueId engineId = UniqueId.forEngine(SuiteEngineDescriptor.ENGINE_ID); SuiteEngineDescriptor engineDescriptor = new SuiteEngineDescriptor(engineId); SuiteTestDescriptor mockDescriptor = mock(SuiteTestDescriptor.class); engineDescriptor.addChild(mockDescriptor); EngineExecutionListener listener = mock(EngineExecutionListener.class); NamespacedHierarchicalStore requestLevelStore = NamespacedHierarchicalStoreProviders.dummyNamespacedHierarchicalStore(); var cancellationToken = CancellationToken.create(); ExecutionRequest request = mock(); when(request.getRootTestDescriptor()).thenReturn(engineDescriptor); when(request.getEngineExecutionListener()).thenReturn(listener); when(request.getStore()).thenReturn(requestLevelStore); when(request.getCancellationToken()).thenReturn(cancellationToken); new SuiteTestEngine().execute(request); verify(mockDescriptor).execute(same(listener), same(requestLevelStore), same(cancellationToken)); } @Test void reportsSuiteClassAsSkippedWhenCancelledBeforeExecution() { CancellingSuite.cancellationToken = CancellationToken.create(); try { var testKit = EngineTestKit.engine(ENGINE_ID) // .selectors(selectClass(CancellingSuite.class), selectClass(SelectMethodsSuite.class)) // .cancellationToken(CancellingSuite.cancellationToken); var results = testKit.execute(); results.allEvents() // .assertStatistics(stats -> stats.started(3).succeeded(2).aborted(1).skipped(2)) // .assertEventsMatchLooselyInOrder( // event(container(CancellingSuite.class), started()), // event(container(SingleTestTestCase.class), skippedWithReason("Execution cancelled")), // event(container(CancellingSuite.class), finishedSuccessfully()), // event(container(SelectMethodsSuite.class), skippedWithReason("Execution cancelled")) // ); } finally { CancellingSuite.cancellationToken = null; } } @Test void reportsChildrenOfEnginesInSuiteAsSkippedWhenCancelledDuringExecution() { CancellingSuite.cancellationToken = CancellationToken.create(); try { var testKit = EngineTestKit.engine(ENGINE_ID) // .selectors(selectClass(CancellingSuite.class)) // .cancellationToken(CancellingSuite.cancellationToken); var results = testKit.execute(); results.allEvents().assertThatEvents() // .haveExactly(1, event(container(SingleTestTestCase.class), skippedWithReason("Execution cancelled"))).haveExactly(0, event(test(), started())); assertThat(CancellingSuite.afterCalled) // .describedAs("@AfterSuite method was called") // .isTrue(); } finally { CancellingSuite.cancellationToken = null; } } @Test void blankSuiteDisplayNameGeneratesWarning() { var expectedMessage = "@SuiteDisplayName on %s must be declared with a non-blank value.".formatted( BlankSuiteDisplayNameSuite.class.getName()); var expectedIssue = DiscoveryIssue.builder(Severity.WARNING, expectedMessage).source( ClassSource.from(BlankSuiteDisplayNameSuite.class)).build(); var testKit = EngineTestKit.engine(ENGINE_ID).selectors(selectClass(BlankSuiteDisplayNameSuite.class)); assertThat(testKit.discover().getDiscoveryIssues()).contains(expectedIssue); } @Test void whitespaceSuiteDisplayNameGeneratesWarning() { var expectedMessage = "@SuiteDisplayName on %s must be declared with a non-blank value.".formatted( WhitespaceSuiteDisplayNameSuite.class.getName()); var expectedIssue = DiscoveryIssue.builder(Severity.WARNING, expectedMessage).source( ClassSource.from(WhitespaceSuiteDisplayNameSuite.class)).build(); var testKit = EngineTestKit.engine(ENGINE_ID).selectors(selectClass(WhitespaceSuiteDisplayNameSuite.class)); assertThat(testKit.discover().getDiscoveryIssues()).contains(expectedIssue); } @Test void validSuiteDisplayNameGeneratesNoWarning() { var testKit = EngineTestKit.engine(ENGINE_ID).selectors(selectClass(SuiteDisplayNameSuite.class)); assertThat(testKit.discover().getDiscoveryIssues()) // .noneMatch(issue -> issue.message().contains("@SuiteDisplayName")); } // ----------------------------------------------------------------------------------------------------------------- static class CancellingSuite extends SelectClassesSuite { static @Nullable CancellationToken cancellationToken; static boolean afterCalled; @BeforeSuite static void beforeSuite() { CancellingSuite.afterCalled = false; requireNonNull(cancellationToken).cancel(); } @AfterSuite static void afterSuite() { afterCalled = true; } } @Suite @SelectClasses(SingleTestTestCase.class) abstract private static class AbstractPrivateSuite { } @Suite @SelectClasses(SingleTestTestCase.class) private static class PrivateSuite { } @Suite @SelectClasses(names = "org.junit.platform.suite.engine.testcases.SingleTestTestCase") abstract class AbstractInnerSuite { } @Suite @SelectClasses(names = "org.junit.platform.suite.engine.testcases.SingleTestTestCase") class InnerSuite { } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteLauncherDiscoveryRequestBuilderTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine; import static java.util.Map.entry; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.junit.platform.suite.engine.SuiteLauncherDiscoveryRequestBuilder.request; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.net.URI; import java.nio.file.Path; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.regex.Pattern; import java.util.stream.Stream; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.engine.JupiterTestEngine; import org.junit.platform.commons.util.CollectionUtils; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestTag; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.discovery.ClassNameFilter; import org.junit.platform.engine.discovery.ClassSelector; import org.junit.platform.engine.discovery.ClasspathResourceSelector; import org.junit.platform.engine.discovery.DirectorySelector; import org.junit.platform.engine.discovery.DiscoverySelectors; import org.junit.platform.engine.discovery.FilePosition; import org.junit.platform.engine.discovery.FileSelector; import org.junit.platform.engine.discovery.MethodSelector; import org.junit.platform.engine.discovery.ModuleSelector; import org.junit.platform.engine.discovery.PackageNameFilter; import org.junit.platform.engine.discovery.PackageSelector; import org.junit.platform.engine.discovery.UriSelector; import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; import org.junit.platform.launcher.EngineFilter; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.PostDiscoveryFilter; import org.junit.platform.suite.api.ConfigurationParameter; import org.junit.platform.suite.api.ConfigurationParametersResource; import org.junit.platform.suite.api.DisableParentConfigurationParameters; import org.junit.platform.suite.api.ExcludeClassNamePatterns; import org.junit.platform.suite.api.ExcludeEngines; import org.junit.platform.suite.api.ExcludePackages; import org.junit.platform.suite.api.ExcludeTags; import org.junit.platform.suite.api.IncludeClassNamePatterns; import org.junit.platform.suite.api.IncludeEngines; import org.junit.platform.suite.api.IncludePackages; import org.junit.platform.suite.api.IncludeTags; import org.junit.platform.suite.api.Select; import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.SelectClasspathResource; import org.junit.platform.suite.api.SelectDirectories; import org.junit.platform.suite.api.SelectFile; import org.junit.platform.suite.api.SelectMethod; import org.junit.platform.suite.api.SelectModules; import org.junit.platform.suite.api.SelectPackages; import org.junit.platform.suite.api.SelectUris; class SuiteLauncherDiscoveryRequestBuilderTests { SuiteLauncherDiscoveryRequestBuilder builder = request(); @Test void configurationParameter() { @ConfigurationParameter(key = "com.example", value = "*") class Suite { } LauncherDiscoveryRequest request = builder.applyConfigurationParametersFromSuite(Suite.class).build(); ConfigurationParameters configuration = request.getConfigurationParameters(); Optional parameter = configuration.get("com.example"); assertEquals(Optional.of("*"), parameter); } @Test void configurationParametersResource() { @ConfigurationParametersResource("config-test.properties") class Suite { } LauncherDiscoveryRequest request = builder.applyConfigurationParametersFromSuite(Suite.class).build(); ConfigurationParameters configuration = request.getConfigurationParameters(); Optional parameter = configuration.get("com.example.prop.first"); assertEquals(Optional.of("first value"), parameter); } @Test void configurationParametersResources() { @ConfigurationParametersResource("config-test.properties") @ConfigurationParametersResource("config-test-override.properties") class Suite { } LauncherDiscoveryRequest request = builder.applyConfigurationParametersFromSuite(Suite.class).build(); ConfigurationParameters configuration = request.getConfigurationParameters(); Optional parameterOne = configuration.get("com.example.prop.first"); assertEquals(Optional.of("first value from override file"), parameterOne); Optional parameterTwo = configuration.get("com.example.prop.second"); assertEquals(Optional.of("second value"), parameterTwo); } @Test void configurationParametersResource_explicitParametersTakePrecedence() { @ConfigurationParametersResource("config-test.properties") @ConfigurationParameter(key = "com.example.prop.first", value = "first value from explicit parameter") class Suite { } LauncherDiscoveryRequest request = builder.applyConfigurationParametersFromSuite(Suite.class).build(); ConfigurationParameters configuration = request.getConfigurationParameters(); Optional parameterOne = configuration.get("com.example.prop.first"); assertEquals(Optional.of("first value from explicit parameter"), parameterOne); Optional parameterTwo = configuration.get("com.example.prop.second"); assertEquals(Optional.of("second value"), parameterTwo); } @Test void excludeClassNamePatterns() { class TestCase { } @ExcludeClassNamePatterns("^.*TestCase$") class Suite { } LauncherDiscoveryRequest request = builder.applySelectorsAndFiltersFromSuite(Suite.class).build(); List filters = request.getFiltersByType(ClassNameFilter.class); assertTrue(exactlyOne(filters).apply(TestCase.class.getName()).excluded()); } @Test void excludeEngines() { @ExcludeEngines("junit-jupiter") class Suite { } LauncherDiscoveryRequest request = builder.applySelectorsAndFiltersFromSuite(Suite.class).build(); List filters = request.getEngineFilters(); assertTrue(exactlyOne(filters).apply(new JupiterTestEngine()).excluded()); } @Test void excludePackages() { @ExcludePackages("com.example.testcases") class Suite { } LauncherDiscoveryRequest request = builder.applySelectorsAndFiltersFromSuite(Suite.class).build(); List filters = request.getFiltersByType(PackageNameFilter.class); assertTrue(exactlyOne(filters).apply("com.example.testcases").excluded()); } @Test void excludeTags() { @ExcludeTags("test-tag") class Suite { } LauncherDiscoveryRequest request = builder.applySelectorsAndFiltersFromSuite(Suite.class).build(); List filters = request.getPostDiscoveryFilters(); TestDescriptor testDescriptor = new StubAbstractTestDescriptor(); assertTrue(exactlyOne(filters).apply(testDescriptor).excluded()); } @Test void includeClassNamePatterns() { class TestCase { } @IncludeClassNamePatterns("^.*TestCase$") class Suite { } LauncherDiscoveryRequest request = builder.applySelectorsAndFiltersFromSuite(Suite.class).build(); List filters = request.getFiltersByType(ClassNameFilter.class); assertTrue(exactlyOne(filters).apply(TestCase.class.getName()).included()); assertTrue(exactlyOne(filters).apply(Suite.class.getName()).excluded()); } @Test void filtersOnStandardClassNamePatternsWhenIncludeClassNamePatternsIsOmitted() { class Suite { } LauncherDiscoveryRequest request = builder.applySelectorsAndFiltersFromSuite(Suite.class).build(); assertTrue(request.getFiltersByType(ClassNameFilter.class).isEmpty()); } @Test void filtersOnStandardClassNamePatternsWhenIncludeClassNamePatternsIsOmittedUnlessDisabled() { class ExampleTest { } class Suite { } // @formatter:off LauncherDiscoveryRequest request = builder .filterStandardClassNamePatterns() .applySelectorsAndFiltersFromSuite(Suite.class) .build(); // @formatter:on List filters = request.getFiltersByType(ClassNameFilter.class); assertTrue(exactlyOne(filters).apply(ExampleTest.class.getName()).included()); assertTrue(exactlyOne(filters).apply(Suite.class.getName()).excluded()); } @Test void includeEngines() { @IncludeEngines("junit-jupiter") class Suite { } LauncherDiscoveryRequest request = builder.applySelectorsAndFiltersFromSuite(Suite.class).build(); List filters = request.getEngineFilters(); assertTrue(exactlyOne(filters).apply(new JupiterTestEngine()).included()); } @Test void includePackages() { @IncludePackages("com.example.testcases") class Suite { } LauncherDiscoveryRequest request = builder.applySelectorsAndFiltersFromSuite(Suite.class).build(); List filters = request.getFiltersByType(PackageNameFilter.class); assertTrue(exactlyOne(filters).apply("com.example.testcases").included()); } @Test void includeTags() { @IncludeTags("test-tag") class Suite { } LauncherDiscoveryRequest request = builder.applySelectorsAndFiltersFromSuite(Suite.class).build(); List filters = request.getPostDiscoveryFilters(); TestDescriptor testDescriptor = new StubAbstractTestDescriptor(); assertTrue(exactlyOne(filters).apply(testDescriptor).included()); } @Test void selectClassesByReference() { class TestCase { } @SelectClasses(TestCase.class) class Suite { } LauncherDiscoveryRequest request = builder.applySelectorsAndFiltersFromSuite(Suite.class).build(); List selectors = request.getSelectorsByType(ClassSelector.class); assertFalse(selectors.isEmpty()); assertEquals(TestCase.class, exactlyOne(selectors).getJavaClass()); } @Test void selectClassesByName() { @SelectClasses(names = "org.junit.platform.suite.engine.SuiteLauncherDiscoveryRequestBuilderTests$NonLocalTestCase") class Suite { } LauncherDiscoveryRequest request = builder.applySelectorsAndFiltersFromSuite(Suite.class).build(); List selectors = request.getSelectorsByType(ClassSelector.class); assertEquals(NonLocalTestCase.class, exactlyOne(selectors).getJavaClass()); } @Test void selectClassesWithoutReferencesOrNames() { @SelectClasses class Suite { } assertPreconditionViolationFor(() -> builder.applySelectorsAndFiltersFromSuite(Suite.class))// .withMessageMatching("@SelectClasses on class \\[" + Pattern.quote(SuiteLauncherDiscoveryRequestBuilderTests.class.getName()) + "\\$\\d+Suite] must declare at least one class reference or name"); } static class NonLocalTestCase { } @TestFactory Stream selectOneMethodWithNoParameters() { @SelectMethod("org.junit.platform.suite.engine.SuiteLauncherDiscoveryRequestBuilderTests$NoParameterTestCase#testMethod") class SuiteA { } @SelectMethod(type = NoParameterTestCase.class, name = "testMethod") class SuiteB { } @SelectMethod(typeName = "org.junit.platform.suite.engine.SuiteLauncherDiscoveryRequestBuilderTests$NoParameterTestCase", name = "testMethod") class SuiteC { } return Stream.of(SuiteA.class, SuiteB.class, SuiteC.class) // .map(suiteClass -> dynamicTest(suiteClass.getSimpleName(), () -> { LauncherDiscoveryRequest request = request().applySelectorsAndFiltersFromSuite(suiteClass).build(); List selectors = request.getSelectorsByType(MethodSelector.class); assertEquals(DiscoverySelectors.selectMethod(NoParameterTestCase.class, "testMethod"), exactlyOne(selectors)); })); } static class NoParameterTestCase { @SuppressWarnings("unused") void testMethod() { } } @TestFactory Stream selectOneMethodWithOneParameter() { @SelectMethod("org.junit.platform.suite.engine.SuiteLauncherDiscoveryRequestBuilderTests$OneParameterTestCase#testMethod(int)") class SuiteA { } @SelectMethod(type = OneParameterTestCase.class, name = "testMethod", parameterTypeNames = "int") class SuiteB { } @SelectMethod(type = OneParameterTestCase.class, name = "testMethod", parameterTypes = int.class) class SuiteC { } @SelectMethod(typeName = "org.junit.platform.suite.engine.SuiteLauncherDiscoveryRequestBuilderTests$OneParameterTestCase", name = "testMethod", parameterTypeNames = "int") class SuiteD { } @SelectMethod(typeName = "org.junit.platform.suite.engine.SuiteLauncherDiscoveryRequestBuilderTests$OneParameterTestCase", name = "testMethod", parameterTypes = int.class) class SuiteE { } return Stream.of(SuiteA.class, SuiteB.class, SuiteC.class, SuiteD.class, SuiteE.class) // .map(suiteClass -> dynamicTest(suiteClass.getSimpleName(), () -> { LauncherDiscoveryRequest request = request().applySelectorsAndFiltersFromSuite(suiteClass).build(); List selectors = request.getSelectorsByType(MethodSelector.class); assertEquals(DiscoverySelectors.selectMethod(OneParameterTestCase.class, "testMethod", "int"), exactlyOne(selectors)); })); } static class OneParameterTestCase { @SuppressWarnings("unused") void testMethod(int i) { } } @Test void selectTwoMethodsWithTwoParameters() { @SuppressWarnings("unused") class TestClass { void firstTestMethod(int i, String j) { } void secondTestMethod(boolean i, float j) { } } @SelectMethod(type = TestClass.class, name = "firstTestMethod", parameterTypeNames = "int, java.lang.String") @SelectMethod(type = TestClass.class, name = "secondTestMethod", parameterTypes = { boolean.class, float.class }) class Suite { } LauncherDiscoveryRequest request = builder.applySelectorsAndFiltersFromSuite(Suite.class).build(); List selectors = request.getSelectorsByType(MethodSelector.class); assertEquals(2, selectors.size()); assertEquals(DiscoverySelectors.selectMethod(TestClass.class, "firstTestMethod", "int, java.lang.String"), selectors.get(0)); assertEquals(DiscoverySelectors.selectMethod(TestClass.class, "secondTestMethod", "boolean, float"), selectors.get(1)); } @TestFactory Stream selectMethodCausesExceptionOnInvalidUsage() { @SelectMethod(value = "irrelevant", type = NoParameterTestCase.class) class ValueAndType { } @SelectMethod(value = "SomeClass#someMethod", typeName = "org.junit.platform.suite.engine.SuiteLauncherDiscoveryRequestBuilderTests$NoParameterTestCase") class ValueAndTypeName { } @SelectMethod(value = "SomeClass#someMethod", name = "testMethod") class ValueAndMethodName { } @SelectMethod(value = "SomeClass#someMethod", parameterTypes = int.class) class ValueAndParameterTypes { } @SelectMethod(value = "SomeClass#someMethod", parameterTypeNames = "int") class ValueAndParameterTypeNames { } @SelectMethod(type = NoParameterTestCase.class) class MissingMethodName { } @SelectMethod(name = "testMethod", type = NoParameterTestCase.class, typeName = "org.junit.platform.suite.engine.SuiteLauncherDiscoveryRequestBuilderTests$NoParameterTestCase") class TypeAndTypeName { } @SelectMethod(name = "testMethod", parameterTypes = int.class, parameterTypeNames = "int") class ParameterTypesAndParameterTypeNames { } var expectedFailureMessages = Map.ofEntries( // entry(ValueAndType.class, "type must not be set in conjunction with fully qualified method name"), // entry(ValueAndTypeName.class, "type name must not be set in conjunction with fully qualified method name"), // entry(ValueAndMethodName.class, "method name must not be set in conjunction with fully qualified method name"), // entry(ValueAndParameterTypes.class, "parameter types must not be set in conjunction with fully qualified method name"), // entry(ValueAndParameterTypeNames.class, "parameter type names must not be set in conjunction with fully qualified method name"), // entry(MissingMethodName.class, "method name must not be blank"), // entry(TypeAndTypeName.class, "either type name or type must be set but not both"), // entry(ParameterTypesAndParameterTypeNames.class, "either parameter type names or parameter types must be set but not both") // ); return expectedFailureMessages.entrySet().stream() // .map(entry -> { Class suiteClassName = entry.getKey(); var expectedFailureMessage = entry.getValue(); return dynamicTest(suiteClassName.getSimpleName(), () -> { assertPreconditionViolationFor( () -> request().applySelectorsAndFiltersFromSuite(suiteClassName))// .withMessage("@SelectMethod on class [" + suiteClassName.getName() + "]: " + expectedFailureMessage); }); }); } @Test void selectClasspathResource() { @SelectClasspathResource("com.example.testcases") class Suite { } LauncherDiscoveryRequest request = builder.applySelectorsAndFiltersFromSuite(Suite.class).build(); List selectors = request.getSelectorsByType(ClasspathResourceSelector.class); assertEquals("com.example.testcases", exactlyOne(selectors).getClasspathResourceName()); } @Test void selectClasspathResourcePosition() { @SelectClasspathResource(value = "com.example.testcases", line = 42) @SelectClasspathResource(value = "com.example.testcases", line = 14, column = 15) class Suite { } LauncherDiscoveryRequest request = builder.applySelectorsAndFiltersFromSuite(Suite.class).build(); List selectors = request.getSelectorsByType(ClasspathResourceSelector.class); assertEquals(Optional.of(FilePosition.from(42)), selectors.get(0).getPosition()); assertEquals(Optional.of(FilePosition.from(14, 15)), selectors.get(1).getPosition()); } @Test void ignoreClasspathResourcePosition() { @SelectClasspathResource(value = "com.example.testcases", line = -1) @SelectClasspathResource(value = "com.example.testcases", column = 12) @SelectClasspathResource(value = "com.example.testcases", line = 42, column = -12) class Suite { } LauncherDiscoveryRequest request = builder.applySelectorsAndFiltersFromSuite(Suite.class).build(); List selectors = request.getSelectorsByType(ClasspathResourceSelector.class); assertEquals(Optional.empty(), selectors.get(0).getPosition()); assertEquals(Optional.empty(), selectors.get(1).getPosition()); assertEquals(Optional.of(FilePosition.from(42)), selectors.get(2).getPosition()); } @Test void selectDirectories() { @SelectDirectories("path/to/root") class Suite { } LauncherDiscoveryRequest request = builder.applySelectorsAndFiltersFromSuite(Suite.class).build(); List selectors = request.getSelectorsByType(DirectorySelector.class); assertEquals(Path.of("path/to/root"), exactlyOne(selectors).getPath()); } @Test void selectDirectoriesFiltersEmptyPaths() { @SelectDirectories("") class Suite { } LauncherDiscoveryRequest request = builder.applySelectorsAndFiltersFromSuite(Suite.class).build(); assertTrue(request.getSelectorsByType(DirectorySelector.class).isEmpty()); } @Test void selectFile() { @SelectFile("path/to/root") class Suite { } LauncherDiscoveryRequest request = builder.applySelectorsAndFiltersFromSuite(Suite.class).build(); List selectors = request.getSelectorsByType(FileSelector.class); assertEquals(Path.of("path/to/root"), exactlyOne(selectors).getPath()); } @Test void selectFilePosition() { @SelectFile(value = "path/to/root", line = 42) @SelectFile(value = "path/to/root", line = 14, column = 15) class Suite { } LauncherDiscoveryRequest request = builder.applySelectorsAndFiltersFromSuite(Suite.class).build(); List selectors = request.getSelectorsByType(FileSelector.class); assertEquals(Optional.of(FilePosition.from(42)), selectors.get(0).getPosition()); assertEquals(Optional.of(FilePosition.from(14, 15)), selectors.get(1).getPosition()); } @Test void ignoreInvalidFilePosition() { @SelectFile(value = "path/to/root", line = -1) @SelectFile(value = "path/to/root", column = 12) @SelectFile(value = "path/to/root", line = 42, column = -12) class Suite { } LauncherDiscoveryRequest request = builder.applySelectorsAndFiltersFromSuite(Suite.class).build(); List selectors = request.getSelectorsByType(FileSelector.class); assertEquals(Optional.empty(), selectors.get(0).getPosition()); assertEquals(Optional.empty(), selectors.get(1).getPosition()); assertEquals(Optional.of(FilePosition.from(42)), selectors.get(2).getPosition()); } @Test void selectModules() { @SelectModules("com.example.testcases") class Suite { } LauncherDiscoveryRequest request = builder.applySelectorsAndFiltersFromSuite(Suite.class).build(); List selectors = request.getSelectorsByType(ModuleSelector.class); assertEquals("com.example.testcases", exactlyOne(selectors).getModuleName()); } @Test void selectUris() { @SelectUris("path/to/root") class Suite { } LauncherDiscoveryRequest request = builder.applySelectorsAndFiltersFromSuite(Suite.class).build(); List selectors = request.getSelectorsByType(UriSelector.class); assertEquals(URI.create("path/to/root"), exactlyOne(selectors).getUri()); } @Test void selectUrisFiltersEmptyUris() { @SelectUris("") class Suite { } LauncherDiscoveryRequest request = builder.applySelectorsAndFiltersFromSuite(Suite.class).build(); assertTrue(request.getSelectorsByType(UriSelector.class).isEmpty()); } @Test void selectPackages() { @SelectPackages("com.example.testcases") class Suite { } LauncherDiscoveryRequest request = builder.applySelectorsAndFiltersFromSuite(Suite.class).build(); List selectors = request.getSelectorsByType(PackageSelector.class); assertEquals("com.example.testcases", exactlyOne(selectors).getPackageName()); } @SelectPackages("com.example.testcases") @Retention(RetentionPolicy.RUNTIME) @interface Meta { } @Test void metaAnnotations() { @Meta class Suite { } LauncherDiscoveryRequest request = builder.applySelectorsAndFiltersFromSuite(Suite.class).build(); List pSelectors = request.getSelectorsByType(PackageSelector.class); assertEquals("com.example.testcases", exactlyOne(pSelectors).getPackageName()); } @Test void enableParentConfigurationParametersByDefault() { class Suite { } // @formatter:off var configuration = new ParentConfigurationParameters("parent", "parent parameters were used"); var request = builder.applyConfigurationParametersFromSuite(Suite.class) .parentConfigurationParameters(configuration) .build(); // @formatter:on var configurationParameters = request.getConfigurationParameters(); assertEquals(Optional.of("parent parameters were used"), configurationParameters.get("parent")); } @Test void disableParentConfigurationParameters() { @DisableParentConfigurationParameters class Suite { } // @formatter:off var configuration = new ParentConfigurationParameters("parent", "parent parameters were used"); var request = builder.applyConfigurationParametersFromSuite(Suite.class) .parentConfigurationParameters(configuration) .build(); // @formatter:on var configurationParameters = request.getConfigurationParameters(); assertEquals(Optional.empty(), configurationParameters.get("parent")); } @Test void selectByIdentifier() { // @formatter:off @Select({ "class:org.junit.platform.suite.engine.SuiteLauncherDiscoveryRequestBuilderTests$NonLocalTestCase", "method:org.junit.platform.suite.engine.SuiteLauncherDiscoveryRequestBuilderTests$NoParameterTestCase#testMethod" }) // @formatter:on class Suite { } LauncherDiscoveryRequest request = builder.applySelectorsAndFiltersFromSuite(Suite.class).build(); List classSelectors = request.getSelectorsByType(ClassSelector.class); assertEquals(NonLocalTestCase.class, exactlyOne(classSelectors).getJavaClass()); List methodSelectors = request.getSelectorsByType(MethodSelector.class); // @formatter:off assertThat(exactlyOne(methodSelectors)) .extracting(MethodSelector::getJavaClass, MethodSelector::getMethodName) .containsExactly(NoParameterTestCase.class, "testMethod"); // @formatter:on } private static T exactlyOne(List list) { return CollectionUtils.getOnlyElement(list); } private static class StubAbstractTestDescriptor extends AbstractTestDescriptor { StubAbstractTestDescriptor() { super(UniqueId.forEngine("test"), "stub"); } @Override public Type getType() { return Type.CONTAINER; } @Override public Set getTags() { return Set.of(TestTag.create("test-tag")); } } private static class ParentConfigurationParameters implements ConfigurationParameters { private final Map map; ParentConfigurationParameters(String key, String value) { this.map = Map.of(key, value); } @Override public Optional get(String key) { return Optional.ofNullable(map.get(key)); } @Override public Optional getBoolean(String key) { return Optional.empty(); } @Override public Set keySet() { return Set.of(); } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteTestDescriptorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.mockito.Mockito.mock; import java.util.Collections; import java.util.Optional; import java.util.Set; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestReporter; import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.OutputDirectoryCreator; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter; import org.junit.platform.launcher.core.OutputDirectoryCreators; import org.junit.platform.suite.api.Suite; import org.junit.platform.suite.engine.testcases.SingleTestTestCase; import org.junit.platform.suite.engine.testsuites.SelectClassesSuite; /** * @since 1.8 */ class SuiteTestDescriptorTests { final UniqueId engineId = UniqueId.forEngine(SuiteEngineDescriptor.ENGINE_ID); final UniqueId suiteId = engineId.append(SuiteTestDescriptor.SEGMENT_TYPE, "test"); final UniqueId jupiterEngineId = suiteId.append("engine", JupiterEngineDescriptor.ENGINE_ID); final UniqueId testClassId = jupiterEngineId.append(ClassTestDescriptor.SEGMENT_TYPE, SingleTestTestCase.class.getName()); final UniqueId methodId = testClassId.append(TestMethodTestDescriptor.SEGMENT_TYPE, "test(%s)".formatted(TestReporter.class.getName())); final ConfigurationParameters configurationParameters = new EmptyConfigurationParameters(); final OutputDirectoryCreator outputDirectoryCreator = OutputDirectoryCreators.dummyOutputDirectoryCreator(); final DiscoveryIssueReporter discoveryIssueReporter = DiscoveryIssueReporter.forwarding(mock(), engineId); final SuiteTestDescriptor suite = new SuiteTestDescriptor(suiteId, TestSuite.class, configurationParameters, outputDirectoryCreator, mock(), discoveryIssueReporter); @Test void suiteIsEmptyBeforeDiscovery() { suite.addDiscoveryRequestFrom(SelectClassesSuite.class); assertThat(suite.getChildren()).isEmpty(); } @Test void suiteDiscoversTestsFromClass() { suite.addDiscoveryRequestFrom(SelectClassesSuite.class); suite.discover(); assertThat(suite.getDescendants()).map(TestDescriptor::getUniqueId)// .containsExactly(jupiterEngineId, testClassId, methodId); } @Test void suiteDiscoversTestsFromUniqueId() { suite.addDiscoveryRequestFrom(methodId); suite.discover(); assertThat(suite.getDescendants()).map(TestDescriptor::getUniqueId)// .containsExactly(jupiterEngineId, testClassId, methodId); } @Test void discoveryPlanCanNotBeModifiedAfterDiscovery() { suite.addDiscoveryRequestFrom(SelectClassesSuite.class); suite.discover(); assertAll(// () -> assertPreconditionViolationFor(() -> suite.addDiscoveryRequestFrom(SelectClassesSuite.class))// .withMessage("discovery request cannot be modified after discovery"), () -> assertPreconditionViolationFor(() -> suite.addDiscoveryRequestFrom(methodId))// .withMessage("discovery request cannot be modified after discovery")); } @Test void suiteMayRegisterTests() { assertThat(suite.mayRegisterTests()).isTrue(); } @Suite static class TestSuite { } private static class EmptyConfigurationParameters implements ConfigurationParameters { @Override public Optional get(String key) { return Optional.empty(); } @Override public Optional getBoolean(String key) { return Optional.empty(); } @Override public Set keySet() { return Collections.emptySet(); } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/error/ErrorSelector.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.error; import org.junit.platform.engine.DiscoverySelector; record ErrorSelector(String message) implements DiscoverySelector { } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/error/ErrorSelectorIdentifierParser.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.error; import java.util.Optional; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.DiscoverySelectorIdentifier; import org.junit.platform.engine.discovery.DiscoverySelectorIdentifierParser; public class ErrorSelectorIdentifierParser implements DiscoverySelectorIdentifierParser { @Override public String getPrefix() { return "error"; } @Override public Optional parse(DiscoverySelectorIdentifier identifier, Context context) { return Optional.of(new ErrorSelector(identifier.getValue())); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/error/SelectorProcessingErrorCausingEngine.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.error; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.SelectorResolutionResult; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.fakes.TestEngineStub; public class SelectorProcessingErrorCausingEngine extends TestEngineStub { @Override public String getId() { return "selector-error-engine"; } @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { var engineDescriptor = super.discover(discoveryRequest, uniqueId); var errorSelectors = discoveryRequest.getSelectorsByType(ErrorSelector.class); if (!errorSelectors.isEmpty()) { var selector = errorSelectors.getFirst(); var failure = SelectorResolutionResult.failed(new RuntimeException(selector.message())); discoveryRequest.getDiscoveryListener().selectorProcessed(engineDescriptor.getUniqueId(), selector, failure); } return engineDescriptor; } @Override public void execute(ExecutionRequest request) { throw new RuntimeException("should not be called"); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/error/package-info.java ================================================ @NullMarked package org.junit.platform.suite.engine.error; import org.jspecify.annotations.NullMarked; ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/ConfigurationSensitiveTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testcases; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.MethodOrderer.MethodName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; /** * @since 1.11 */ @TestMethodOrder(MethodName.class) public class ConfigurationSensitiveTestCase { boolean shared; @Test void test1() { shared = true; } @Test void test2() { // This will fail unless the test instance lifecycle is set to per_class, // which is configured via @ConfigurationParameter in ConfigurationSuite. assertTrue(shared); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/DynamicTestsTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testcases; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import java.util.stream.Stream; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.TestFactory; /** * @since 1.8 */ public class DynamicTestsTestCase { @TestFactory Stream dynamicTests() { return Stream.of(// dynamicTest("Add test", () -> assertEquals(2, Math.addExact(1, 1))), dynamicTest("Multiply Test", () -> assertEquals(4, Math.multiplyExact(2, 2)))// ); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/EmptyDynamicTestsTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testcases; import java.util.stream.Stream; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.TestFactory; /** * @since 1.9 */ public class EmptyDynamicTestsTestCase { @TestFactory Stream dynamicTests() { return Stream.empty(); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/EmptyTestTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testcases; /** * @since 1.9 */ public class EmptyTestTestCase { } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/ErroneousTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testcases; import static org.junit.jupiter.api.Assertions.fail; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; public class ErroneousTestCase { @SuppressWarnings({ "JUnitMalformedDeclaration", "unused" }) @BeforeAll void nonStaticLifecycleMethod() { fail("should not be called"); } @Test void name() { fail("should not be called"); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/JUnit4TestsTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testcases; import org.junit.Test; public class JUnit4TestsTestCase { @Test public void aTest() { } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/MultipleTestsTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testcases; import org.junit.jupiter.api.Test; /** * @since 1.8 */ public class MultipleTestsTestCase { @Test void test() { } @Test void test2() { } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/SingleTestTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testcases; import java.nio.file.Files; import org.junit.jupiter.api.MediaType; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestReporter; /** * @since 1.8 */ public class SingleTestTestCase { @Test void test(TestReporter testReporter) { testReporter.publishFile("test.txt", MediaType.TEXT_PLAIN_UTF_8, file -> Files.writeString(file, "test")); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/StatefulTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testcases; import static org.junit.jupiter.api.Assertions.fail; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.Test; /** * @since 1.11 */ public class StatefulTestCase { public static List callSequence = new ArrayList<>(); @SuppressWarnings("NewClassNamingConvention") public static class Test1 { @Test void statefulTest() { callSequence.add("test1"); } } @SuppressWarnings("NewClassNamingConvention") public static class Test2 { @Test void statefulTest() { callSequence.add("test2"); fail("This is a failing test"); } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/TaggedTestTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testcases; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; public class TaggedTestTestCase { @Test @Tag("excluded") public void test() { } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/AbstractSuite.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testsuites; import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; import org.junit.platform.suite.engine.testcases.SingleTestTestCase; /** * @since 1.8 */ @Suite @SelectClasses(SingleTestTestCase.class) public abstract class AbstractSuite { } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/BlankSuiteDisplayNameSuite.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testsuites; import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; import org.junit.platform.suite.api.SuiteDisplayName; import org.junit.platform.suite.engine.testcases.SingleTestTestCase; /** * Test suite with blank @SuiteDisplayName to verify validation. * * @since 5.14 */ @Suite @SelectClasses(SingleTestTestCase.class) @SuiteDisplayName("") public class BlankSuiteDisplayNameSuite { } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/ConfigurationSuite.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testsuites; import static org.junit.jupiter.api.Constants.DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME; import org.junit.platform.suite.api.ConfigurationParameter; import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; import org.junit.platform.suite.engine.testcases.ConfigurationSensitiveTestCase; @Suite @ConfigurationParameter(key = DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME, value = "per_class") @SelectClasses(ConfigurationSensitiveTestCase.class) public class ConfigurationSuite { } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/CyclicSuite.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testsuites; import org.junit.platform.suite.api.IncludeClassNamePatterns; import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; import org.junit.platform.suite.engine.testcases.SingleTestTestCase; /** * @since 1.8 */ @Suite @IncludeClassNamePatterns(".*") @SelectClasses({ CyclicSuite.class, SingleTestTestCase.class }) public class CyclicSuite { } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/DynamicSuite.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testsuites; import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; import org.junit.platform.suite.engine.testcases.DynamicTestsTestCase; /** * @since 1.8 */ @Suite @SelectClasses(DynamicTestsTestCase.class) public class DynamicSuite { } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyCyclicSuite.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testsuites; import org.junit.platform.suite.api.IncludeClassNamePatterns; import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; /** * @since 1.9.2 */ @Suite @IncludeClassNamePatterns(".*") @SelectClasses(EmptyCyclicSuite.class) public class EmptyCyclicSuite { } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyDynamicTestSuite.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testsuites; import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; import org.junit.platform.suite.engine.testcases.EmptyDynamicTestsTestCase; /** * @since 1.9 */ @Suite @SelectClasses(EmptyDynamicTestsTestCase.class) public class EmptyDynamicTestSuite { } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyDynamicTestWithFailIfNoTestFalseSuite.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testsuites; import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; import org.junit.platform.suite.engine.testcases.EmptyDynamicTestsTestCase; /** * @since 1.9 */ @Suite(failIfNoTests = false) @SelectClasses(EmptyDynamicTestsTestCase.class) public class EmptyDynamicTestWithFailIfNoTestFalseSuite { } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyTestCaseSuite.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testsuites; import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; import org.junit.platform.suite.engine.testcases.EmptyTestTestCase; /** * @since 1.9 */ @Suite @SelectClasses(EmptyTestTestCase.class) public class EmptyTestCaseSuite { } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyTestCaseWithFailIfNoTestFalseSuite.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testsuites; import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; import org.junit.platform.suite.engine.testcases.EmptyTestTestCase; /** * @since 1.9 */ @Suite(failIfNoTests = false) @SelectClasses(EmptyTestTestCase.class) public class EmptyTestCaseWithFailIfNoTestFalseSuite { } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/ErroneousTestSuite.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testsuites; import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; import org.junit.platform.suite.engine.testcases.ErroneousTestCase; @Suite @SelectClasses(ErroneousTestCase.class) public class ErroneousTestSuite { } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/InheritedSuite.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testsuites; public class InheritedSuite extends AbstractSuite { } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/LifecycleMethodsSuites.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testsuites; import static org.junit.jupiter.api.Assertions.fail; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.junit.platform.suite.api.AfterSuite; import org.junit.platform.suite.api.BeforeSuite; import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; import org.junit.platform.suite.engine.BeforeAndAfterSuiteTests; import org.junit.platform.suite.engine.testcases.StatefulTestCase; /** * Test suites used in {@link BeforeAndAfterSuiteTests}. * * @since 1.11 */ @SuppressWarnings("NewClassNamingConvention") public class LifecycleMethodsSuites { @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Suite @SelectClasses({ StatefulTestCase.Test1.class, StatefulTestCase.Test2.class }) private @interface TestSuite { } @TestSuite public static class SuccessfulBeforeAndAfterSuite { @BeforeSuite static void setUp() { StatefulTestCase.callSequence.add("beforeSuiteMethod"); } @AfterSuite static void tearDown() { StatefulTestCase.callSequence.add("afterSuiteMethod"); } } @TestSuite public static class FailingBeforeSuite { @BeforeSuite static void setUp() { StatefulTestCase.callSequence.add("beforeSuiteMethod"); throw new RuntimeException("Exception thrown by @BeforeSuite method"); } @AfterSuite static void tearDown() { StatefulTestCase.callSequence.add("afterSuiteMethod"); } } @TestSuite public static class FailingAfterSuite { @BeforeSuite static void setUp() { StatefulTestCase.callSequence.add("beforeSuiteMethod"); } @AfterSuite static void tearDown() { StatefulTestCase.callSequence.add("afterSuiteMethod"); throw new RuntimeException("Exception thrown by @AfterSuite method"); } } @TestSuite public static class FailingBeforeAndAfterSuite { @BeforeSuite static void setUp() { StatefulTestCase.callSequence.add("beforeSuiteMethod"); throw new RuntimeException("Exception thrown by @BeforeSuite method"); } @AfterSuite static void tearDown() { StatefulTestCase.callSequence.add("afterSuiteMethod"); throw new RuntimeException("Exception thrown by @AfterSuite method"); } } @TestSuite public static class SeveralFailingBeforeAndAfterSuite { @BeforeSuite static void setUp1() { StatefulTestCase.callSequence.add("beforeSuiteMethod"); throw new RuntimeException("Exception thrown by @BeforeSuite method"); } @BeforeSuite static void setUp2() { StatefulTestCase.callSequence.add("beforeSuiteMethod"); throw new RuntimeException("Exception thrown by @BeforeSuite method"); } @AfterSuite static void tearDown1() { StatefulTestCase.callSequence.add("afterSuiteMethod"); throw new RuntimeException("Exception thrown by @AfterSuite method"); } @AfterSuite static void tearDown2() { StatefulTestCase.callSequence.add("afterSuiteMethod"); throw new RuntimeException("Exception thrown by @AfterSuite method"); } } @TestSuite public static class SuperclassWithBeforeAndAfterSuite { @BeforeSuite static void setUp() { StatefulTestCase.callSequence.add("superclassBeforeSuiteMethod"); } @AfterSuite static void tearDown() { StatefulTestCase.callSequence.add("superclassAfterSuiteMethod"); } } public static class SubclassWithBeforeAndAfterSuite extends SuperclassWithBeforeAndAfterSuite { @BeforeSuite static void setUp() { StatefulTestCase.callSequence.add("subclassBeforeSuiteMethod"); } @AfterSuite static void tearDown() { StatefulTestCase.callSequence.add("subclassAfterSuiteMethod"); } } @TestSuite public static class NonVoidBeforeSuite { @BeforeSuite static String nonVoidBeforeSuite() { fail("Should not be called"); return ""; } } @TestSuite public static class ParameterAcceptingBeforeSuite { @BeforeSuite static void parameterAcceptingBeforeSuite(String param) { fail("Should not be called"); } } @TestSuite public static class NonStaticBeforeSuite { @BeforeSuite void nonStaticBeforeSuite() { fail("Should not be called"); } } @TestSuite public static class PrivateBeforeSuite { @BeforeSuite private static void privateBeforeSuite() { fail("Should not be called"); } } @TestSuite public static class NonVoidAfterSuite { @AfterSuite static String nonVoidAfterSuite() { fail("Should not be called"); return ""; } } @TestSuite public static class ParameterAcceptingAfterSuite { @AfterSuite static void parameterAcceptingAfterSuite(String param) { fail("Should not be called"); } } @TestSuite public static class NonStaticAfterSuite { @AfterSuite void nonStaticAfterSuite() { fail("Should not be called"); } } @TestSuite public static class PrivateAfterSuite { @AfterSuite private static void privateAfterSuite() { fail("Should not be called"); } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/MultiEngineSuite.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testsuites; import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; import org.junit.platform.suite.engine.testcases.JUnit4TestsTestCase; import org.junit.platform.suite.engine.testcases.TaggedTestTestCase; @Suite @SelectClasses({ JUnit4TestsTestCase.class, TaggedTestTestCase.class }) public class MultiEngineSuite { } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/MultipleSuite.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testsuites; import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; import org.junit.platform.suite.engine.testcases.MultipleTestsTestCase; /** * @since 1.8 */ @Suite @SelectClasses(MultipleTestsTestCase.class) public class MultipleSuite { } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/NestedSuite.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testsuites; import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; import org.junit.platform.suite.engine.testcases.MultipleTestsTestCase; import org.junit.platform.suite.engine.testcases.SingleTestTestCase; /** * @since 1.8 */ public class NestedSuite { @Suite @SelectClasses(SingleTestTestCase.class) static class Jupiter { } @Suite @SelectClasses(MultipleTestsTestCase.class) static class Tagged { } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SelectByIdentifierSuite.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testsuites; import org.junit.platform.suite.api.IncludeClassNamePatterns; import org.junit.platform.suite.api.Select; import org.junit.platform.suite.api.Suite; /** * @since 1.11 */ @Suite @IncludeClassNamePatterns(".*") @Select("class:org.junit.platform.suite.engine.testcases.SingleTestTestCase") public class SelectByIdentifierSuite { } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SelectClassesSuite.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testsuites; import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; import org.junit.platform.suite.engine.testcases.SingleTestTestCase; /** * @since 1.8 */ @Suite @SelectClasses(SingleTestTestCase.class) public class SelectClassesSuite { } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SelectMethodsSuite.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testsuites; import org.junit.platform.suite.api.SelectMethod; import org.junit.platform.suite.api.Suite; import org.junit.platform.suite.engine.testcases.MultipleTestsTestCase; /** * @since 1.10 */ @Suite @SelectMethod(type = MultipleTestsTestCase.class, name = "test") public class SelectMethodsSuite { } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SelectorProcessingErrorTestSuite.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testsuites; import org.junit.platform.suite.api.IncludeEngines; import org.junit.platform.suite.api.Select; import org.junit.platform.suite.api.Suite; @Suite @IncludeEngines("selector-error-engine") @Select("error:simulatedError") public class SelectorProcessingErrorTestSuite { } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SuiteDisplayNameSuite.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testsuites; import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; import org.junit.platform.suite.api.SuiteDisplayName; import org.junit.platform.suite.engine.testcases.SingleTestTestCase; /** * @since 1.8 */ @Suite @SelectClasses(SingleTestTestCase.class) @SuiteDisplayName("Suite Display Name") public class SuiteDisplayNameSuite { } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SuiteSuite.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testsuites; import org.junit.platform.suite.api.IncludeClassNamePatterns; import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; /** * @since 1.8 */ @Suite @IncludeClassNamePatterns(".*") @SelectClasses(SelectClassesSuite.class) public class SuiteSuite { } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SuiteWithErroneousTestSuite.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testsuites; import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; @Suite @SelectClasses(ErroneousTestSuite.class) public class SuiteWithErroneousTestSuite { } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/ThreePartCyclicSuite.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testsuites; import org.junit.platform.suite.api.IncludeClassNamePatterns; import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; import org.junit.platform.suite.engine.testcases.SingleTestTestCase; /** * @since 1.9.2 */ public class ThreePartCyclicSuite { @Suite @IncludeClassNamePatterns(".*") @SelectClasses({ PartB.class }) public static class PartA { } @Suite @IncludeClassNamePatterns(".*") @SelectClasses({ PartC.class }) public static class PartB { } @Suite @IncludeClassNamePatterns(".*") @SelectClasses({ PartB.class, SingleTestTestCase.class }) public static class PartC { } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/WhitespaceSuiteDisplayNameSuite.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.suite.engine.testsuites; import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; import org.junit.platform.suite.api.SuiteDisplayName; import org.junit.platform.suite.engine.testcases.SingleTestTestCase; /** * Test suite with whitespace-only @SuiteDisplayName to verify validation. * * @since 5.14 */ @Suite @SelectClasses(SingleTestTestCase.class) @SuiteDisplayName(" ") public class WhitespaceSuiteDisplayNameSuite { } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/testkit/engine/EngineDiscoveryResultsIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.testkit.engine; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import org.jspecify.annotations.NullMarked; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedClass; import org.junit.jupiter.params.provider.EnumSource; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.platform.fakes.TestEngineStub; import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; @ParameterizedClass @EnumSource record EngineDiscoveryResultsIntegrationTests(TestKitApi testKit) { @Test void returnsEngineDescriptor() { var results = testKit.discover("junit-jupiter", selectClass(TestCase.class)); assertThat(results.getEngineDescriptor().getDisplayName()).isEqualTo("JUnit Jupiter"); assertThat(getOnlyElement(results.getEngineDescriptor().getChildren()).getSource()) // .contains(ClassSource.from(TestCase.class)); } @Test @NullMarked void collectsDiscoveryIssues() { var issue = DiscoveryIssue.create(Severity.WARNING, "warning"); var testEngine = new TestEngineStub() { @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { var listener = discoveryRequest.getDiscoveryListener(); listener.issueEncountered(uniqueId, issue); return super.discover(discoveryRequest, uniqueId); } }; var results = testKit.discover(testEngine); assertThat(results.getDiscoveryIssues()).containsExactly(issue); } static class TestCase { @Test void test() { } } enum TestKitApi { STATIC_METHOD { @Override EngineDiscoveryResults discover(String engineId, DiscoverySelector selector) { return EngineTestKit.discover(engineId, newRequest().selectors(selector).build()); } @Override EngineDiscoveryResults discover(TestEngine testEngine) { return EngineTestKit.discover(testEngine, newRequest().build()); } private static LauncherDiscoveryRequestBuilder newRequest() { return request().enableImplicitConfigurationParameters(false); } }, FLUENT_API { @Override EngineDiscoveryResults discover(String engineId, DiscoverySelector selector) { return EngineTestKit.engine(engineId).selectors(selector).discover(); } @Override EngineDiscoveryResults discover(TestEngine testEngine) { return EngineTestKit.engine(testEngine).discover(); } }; @SuppressWarnings("SameParameterValue") abstract EngineDiscoveryResults discover(String engineId, DiscoverySelector selector); abstract EngineDiscoveryResults discover(TestEngine testEngine); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/testkit/engine/EngineTestKitTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.testkit.engine; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.mockito.ArgumentCaptor.forClass; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockConstruction; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.Optional; import java.util.function.UnaryOperator; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.util.SetSystemProperty; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.engine.support.descriptor.EngineDescriptor; import org.junit.platform.engine.support.store.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.core.EngineExecutionOrchestrator; import org.mockito.ArgumentCaptor; import org.mockito.MockedConstruction; @SetSystemProperty(key = EngineTestKitTests.KEY, value = "from system property") class EngineTestKitTests { static final String KEY = "org.junit.platform.testkit.engine.EngineTestKitTests"; @AfterEach void resetSystemProperty() { System.clearProperty(KEY); } @Test void ignoresImplicitConfigurationParametersByDefault() { var value = executeExampleTestCaseAndCollectValue(builder -> builder); assertThat(value).isEmpty(); } @Test @SuppressWarnings("unchecked") void verifyRequestLevelStoreIsUsedInExecution() { TestEngine testEngine = mock(TestEngine.class); when(testEngine.getId()).thenReturn("test-engine"); LauncherDiscoveryRequest request = mock(LauncherDiscoveryRequest.class); when(request.getConfigurationParameters()).thenReturn(mock()); when(request.getDiscoveryListener()).thenReturn(LauncherDiscoveryListener.NOOP); try (MockedConstruction mockedConstruction = mockConstruction( EngineExecutionOrchestrator.class)) { EngineTestKit.execute(testEngine, request); assertThat(mockedConstruction.constructed()).isNotEmpty(); EngineExecutionOrchestrator mockOrchestrator = mockedConstruction.constructed().getFirst(); ArgumentCaptor> storeCaptor = forClass( NamespacedHierarchicalStore.class); verify(mockOrchestrator).execute(any(), any(), any(), storeCaptor.capture(), eq(CancellationToken.disabled())); assertNotNull(storeCaptor.getValue(), "Request level store should be passed to execute"); } } @ParameterizedTest @CsvSource({ "true, from system property", "false," }) void usesImplicitConfigurationParametersWhenEnabled(boolean enabled, String expectedValue) { var value = executeExampleTestCaseAndCollectValue( builder -> builder.enableImplicitConfigurationParameters(enabled)); assertThat(value).isEqualTo(Optional.ofNullable(expectedValue)); } @Test void cancellationTokenIsPassedToEngines() { TestEngine testEngine = mock(TestEngine.class); when(testEngine.getId()).thenReturn("test-engine"); when(testEngine.discover(any(), any())).thenReturn( new EngineDescriptor(UniqueId.forEngine("test-engine"), "Engine")); var cancellationToken = CancellationToken.create(); EngineTestKit.engine(testEngine).cancellationToken(cancellationToken).execute(); var executionRequest = forClass(ExecutionRequest.class); verify(testEngine).execute(executionRequest.capture()); assertThat(executionRequest.getValue().getCancellationToken()).isSameAs(cancellationToken); } private Optional executeExampleTestCaseAndCollectValue(UnaryOperator configuration) { return configuration.apply(EngineTestKit.engine("junit-jupiter")) // .selectors(selectClass(ExampleTestCase.class)) // .execute() // .allEvents() // .reportingEntryPublished() // .map(event -> event.getPayload(ReportEntry.class).orElseThrow()) // .map(ReportEntry::getKeyValuePairs) // .map(entries -> entries.get(KEY)) // .findFirst(); } static class ExampleTestCase { @RegisterExtension BeforeEachCallback callback = context -> context.getConfigurationParameter(KEY) // .ifPresent(value -> context.publishReportEntry(KEY, value)); @Test void test() { } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/testkit/engine/EventsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.testkit.engine; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.platform.testkit.engine.EventConditions.engine; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.EventConditions.skippedWithReason; import static org.junit.platform.testkit.engine.EventConditions.started; import java.util.List; import org.assertj.core.error.AssertJMultipleFailuresError; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.EngineDescriptor; import org.opentest4j.AssertionFailedError; class EventsTests { TestDescriptor engineDescriptor = new EngineDescriptor(UniqueId.forEngine("e1"), "engine"); List list = List.of(Event.executionStarted(engineDescriptor), Event.executionSkipped(engineDescriptor, "reason1"), Event.executionSkipped(engineDescriptor, "reason2"), Event.executionFinished(engineDescriptor, TestExecutionResult.successful())); Events events = new Events(list, "test"); @Test @DisplayName("assertEventsMatchExactly: all events in order -> match") void assertEventsMatchExactlyMatchesAllEventsInOrder() { events.assertEventsMatchExactly( // event(engine(), started()), // event(engine(), skippedWithReason("reason1")), // event(engine(), skippedWithReason("reason2")), // event(engine(), finishedSuccessfully()) // ); } @Test @DisplayName("assertEventsMatchLoosely: all events in order -> match") void assertEventsMatchLooselyMatchesAllEventsInOrder() { events.assertEventsMatchLoosely( // event(engine(), started()), // event(engine(), skippedWithReason("reason1")), // event(engine(), skippedWithReason("reason2")), // event(engine(), finishedSuccessfully()) // ); } @Test @DisplayName("assertEventsMatchLoosely: all events in wrong order -> match") void assertEventsMatchLooselyMatchesAllEventsInWrongOrder() { events.assertEventsMatchLoosely( // event(engine(), skippedWithReason("reason2")), // event(engine(), finishedSuccessfully()), // event(engine(), skippedWithReason("reason1")), // event(engine(), started()) // ); } @Test @DisplayName("assertEventsMatchLoosely: tailing subset -> match") void assertEventsMatchLooselyMatchesATailingSubset() { events.assertEventsMatchLoosely( // event(engine(), skippedWithReason("reason1")), // event(engine(), finishedSuccessfully()) // ); } @Test @DisplayName("assertEventsMatchLoosely: starting subset -> match") void assertEventsMatchLooselyMatchesAStartingSubset() { events.assertEventsMatchLoosely( // event(engine(), started()), // event(engine(), skippedWithReason("reason1")) // ); } @Test @DisplayName("assertEventsMatchLoosely: subset in wrong order -> match") void assertEventsMatchLooselyMatchesASubsetInWrongOrder() { events.assertEventsMatchLoosely( // event(engine(), skippedWithReason("reason1")), // event(engine(), started()) // ); } @Test @DisplayName("assertEventsMatchLoosely: only last event -> match") void assertEventsMatchLooselyMatchesTheLastEventAlone() { events.assertEventsMatchLoosely( // event(engine(), finishedSuccessfully()) // ); } @Test @DisplayName("assertEventsMatchLoosely: only first event -> match") void assertEventsMatchLooselyMatchesTheFirstEventAlone() { events.assertEventsMatchLoosely( // event(engine(), started()) // ); } @Test @DisplayName("assertEventsMatchLoosely: only bad events -> fails") void assertEventsMatchLooselyWithBadConditionsOnlyFails() { Executable willFail = () -> events.assertEventsMatchLoosely( // event(engine(), finishedWithFailure()), // event(engine(), skippedWithReason("other")) // ); var error = assertThrows(AssertJMultipleFailuresError.class, willFail); var failures = error.getFailures(); assertEquals(2, failures.size()); assertEquals(AssertionError.class, failures.get(0).getClass()); assertEquals(AssertionError.class, failures.get(1).getClass()); } @Test @DisplayName("assertEventsMatchLoosely: one matching and one bad event -> fails") void assertEventsMatchLooselyWithOneMatchingAndOneBadConditionFailsPartly() { Executable willFail = () -> events.assertEventsMatchLoosely( // event(engine(), started()), // event(engine(), finishedWithFailure()) // ); var error = assertThrows(AssertJMultipleFailuresError.class, willFail); var failures = error.getFailures(); assertEquals(1, failures.size()); assertEquals(AssertionError.class, failures.getFirst().getClass()); } @Test @DisplayName("assertEventsMatchLooselyInOrder: all events in order -> match") void assertEventsMatchLooselyInOrderMatchesAllEventsInOrder() { events.assertEventsMatchLooselyInOrder( // event(engine(), started()), // event(engine(), skippedWithReason("reason1")), // event(engine(), skippedWithReason("reason2")), // event(engine(), finishedSuccessfully()) // ); } @Test @DisplayName("assertEventsMatchLooselyInOrder: all events in wrong order -> fail") void assertEventsMatchLooselyInOrderWithAllEventsInWrongOrderFails() { Executable willFail = () -> events.assertEventsMatchLooselyInOrder( // event(engine(), skippedWithReason("reason2")), // event(engine(), finishedSuccessfully()), // event(engine(), skippedWithReason("reason1")), // event(engine(), started()) // ); var error = assertThrows(AssertionFailedError.class, willFail); assertThat(error).hasMessageContaining("Conditions are not in the correct order."); } @Test @DisplayName("assertEventsMatchLooselyInOrder: tailing subset in order -> match") void assertEventsMatchLooselyInOrderMatchesATailingSubset() { events.assertEventsMatchLooselyInOrder( // event(engine(), skippedWithReason("reason1")), // event(engine(), finishedSuccessfully()) // ); } @Test @DisplayName("assertEventsMatchLooselyInOrder: starting subset in order -> match") void assertEventsMatchLooselyInOrderMatchesAStartingSubset() { events.assertEventsMatchLooselyInOrder( // event(engine(), started()), // event(engine(), skippedWithReason("reason1")) // ); } @Test @DisplayName("assertEventsMatchLooselyInOrder: subset in wrong order -> fail") void assertEventsMatchLooselyInOrderWithASubsetInWrongOrderFails() { Executable willFail = () -> events.assertEventsMatchLooselyInOrder( // event(engine(), skippedWithReason("reason1")), // event(engine(), started()) // ); var error = assertThrows(AssertionFailedError.class, willFail); assertThat(error).hasMessageContaining("Conditions are not in the correct order."); } @Test @DisplayName("assertEventsMatchLooselyInOrder: last event alone -> match") void assertEventsMatchLooselyInOrderMatchesTheLastEventAlone() { events.assertEventsMatchLooselyInOrder( // event(engine(), finishedSuccessfully()) // ); } @Test @DisplayName("assertEventsMatchLooselyInOrder: first event alone -> match") void assertEventsMatchLooselyInOrderMatchesTheFirstEventAlone() { events.assertEventsMatchLooselyInOrder( // event(engine(), started()) // ); } @Test @DisplayName("assertEventsMatchLooselyInOrder: bad events only -> fail") void assertEventsMatchLooselyInOrderWithBadConditionsOnlyFails() { Executable willFail = () -> events.assertEventsMatchLooselyInOrder( // event(engine(), finishedWithFailure()), // event(engine(), skippedWithReason("other")) // ); var error = assertThrows(AssertJMultipleFailuresError.class, willFail); var failures = error.getFailures(); assertEquals(2, failures.size()); assertEquals(AssertionError.class, failures.get(0).getClass()); assertEquals(AssertionError.class, failures.get(1).getClass()); } @Test @DisplayName("assertEventsMatchLooselyInOrder: one matching event and one bad event -> fail") void assertEventsMatchLooselyInOrderWithOneMatchingAndOneBadConditionFailsPartly() { Executable willFail = () -> events.assertEventsMatchLooselyInOrder( // event(engine(), started()), // event(engine(), finishedWithFailure()) // ); var error = assertThrows(AssertJMultipleFailuresError.class, willFail); assertEquals(1, error.getFailures().size()); } @Test @DisplayName("assertEventsMatchLooselyInOrder: first and last event in order -> match") void assertEventsMatchLooselyInOrderMatchesFirstAndLastEventInOrder() { events.assertEventsMatchLooselyInOrder( // event(engine(), started()), // event(engine(), finishedSuccessfully()) // ); } @Test @DisplayName("assertEventsMatchLooselyInOrder: second and last event in bad order -> fail") void assertEventsMatchLooselyInOrderWithSecondAndLastEventInBadOrderFails() { Executable willFail = () -> events.assertEventsMatchLooselyInOrder( // event(engine(), finishedSuccessfully()), // event(engine(), skippedWithReason("reason1")) // ); var error = assertThrows(AssertionFailedError.class, willFail); assertThat(error).hasMessageContaining("Conditions are not in the correct order."); } @Test @DisplayName("assertEventsMatchLooselyInOrder: too many events -> fail") void assertEventsMatchLooselyInOrderWithTooManyEventsFails() { Executable willFail = () -> events.assertEventsMatchLooselyInOrder( // event(engine(), finishedSuccessfully()), // event(engine(), finishedSuccessfully()), // event(engine(), finishedSuccessfully()), // event(engine(), finishedSuccessfully()), // event(engine(), finishedSuccessfully()), // event(engine(), finishedSuccessfully())); var error = assertThrows(AssertionError.class, willFail); assertThat(error).hasMessageEndingWith("to be less than or equal to 4 but was 6"); } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/testkit/engine/ExecutionsIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.testkit.engine; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import static org.junit.jupiter.api.Assumptions.abort; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; /** * Integration tests for {@link Executions}. * * @since 1.4 */ class ExecutionsIntegrationTests { @Test void executionsFromSkippedTestEvents() { var testEvents = getTestEvents(); // We expect 1 for both of the following cases, since an Execution can // be created for a "skipped event even if "started" and "finished" events // are filtered out. assertThat(testEvents.executions().skipped().count()).isEqualTo(1); assertThat(testEvents.skipped().executions().count()).isEqualTo(1); } @Test @SuppressWarnings("removal") void executionsFromStartedTestEvents() { var testEvents = getTestEvents(); // We expect 3 if the executions are created BEFORE filtering out "finished" events. assertThat(testEvents.executions().started().count()).isEqualTo(3); // We expect 0 if the executions are created AFTER filtering out "finished" events. assertThat(testEvents.started().executions().count()).isEqualTo(0); } @Test void executionsFromFinishedTestEvents() { var testEvents = getTestEvents(); // We expect 3 if the executions are created BEFORE filtering out "started" events. assertThat(testEvents.executions().finished().count()).isEqualTo(3); // We expect 0 if the executions are created AFTER filtering out "started" events. assertThat(testEvents.finished().executions().count()).isEqualTo(0); } @Test void executionsFromSucceededTestEvents() { var testEvents = getTestEvents(); // We expect 1 if the executions are created BEFORE filtering out "finished" events. assertThat(testEvents.executions().succeeded().count()).isEqualTo(1); // We expect 0 if the executions are created AFTER filtering out "finished" events. assertThat(testEvents.succeeded().executions().count()).isEqualTo(0); } @Test void executionsFromAbortedTestEvents() { var testEvents = getTestEvents(); // We expect 1 if the executions are created BEFORE filtering out "started" events. assertThat(testEvents.executions().aborted().count()).isEqualTo(1); // We expect 0 if the executions are created AFTER filtering out "started" events. assertThat(testEvents.aborted().executions().count()).isEqualTo(0); } @Test void executionsFromFailedTestEvents() { var testEvents = getTestEvents(); // We expect 1 if the executions are created BEFORE filtering out "started" events. assertThat(testEvents.executions().failed().count()).isEqualTo(1); // We expect 0 if the executions are created AFTER filtering out "started" events. assertThat(testEvents.failed().executions().count()).isEqualTo(0); } private Events getTestEvents() { return EngineTestKit.engine("junit-jupiter")// .selectors(selectClass(ExampleTestCase.class))// .execute()// .testEvents()// .assertStatistics(stats -> stats.skipped(1).started(3).succeeded(1).aborted(1).failed(1)); } static class ExampleTestCase { @Test @Disabled void skippedTest() { } @Test void succeedingTest() { } @Test void abortedTest() { abort(); } @Test void failingTest() { fail("Boom!"); } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/testkit/engine/NestedContainerEventConditionTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.testkit.engine; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullFor; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.testkit.engine.EventConditions.container; import static org.junit.platform.testkit.engine.EventConditions.displayName; import static org.junit.platform.testkit.engine.EventConditions.engine; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; import static org.junit.platform.testkit.engine.EventConditions.nestedContainer; import static org.junit.platform.testkit.engine.EventConditions.started; import static org.junit.platform.testkit.engine.EventConditions.test; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.HashMap; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.testkit.engine.NestedContainerEventConditionTests.ATestCase.BTestCase; import org.junit.platform.testkit.engine.NestedContainerEventConditionTests.ATestCase.BTestCase.CTestCase; /** * @since 1.6 */ class NestedContainerEventConditionTests { @SuppressWarnings("DataFlowIssue") @Test void preconditions() { assertPreconditionViolationNotNullFor("Class", () -> nestedContainer(null)); assertPreconditionViolationFor(() -> nestedContainer(NestedContainerEventConditionTests.class))// .withMessage(NestedContainerEventConditionTests.class.getName() + " must be a nested class"); } @Test void nestedContainerChecksSuppliedClassAndAllEnclosingClasses() { var uniqueId = UniqueId.root("top-level", getClass().getName())// .append("nested", ATestCase.class.getSimpleName())// .append("nested", BTestCase.class.getSimpleName())// .append("nested", CTestCase.class.getSimpleName()); var event = createEvent(uniqueId); var condition = nestedContainer(HashMap.Entry.class); assertThat(condition.matches(event)).isFalse(); assertThat(condition.toString()).contains(// "is a container", "with uniqueId substring 'Map'", "with uniqueId substring 'Entry'"); condition = nestedContainer(ATestCase.BTestCase.CTestCase.class); assertThat(condition.matches(event)).isTrue(); assertThat(condition.toString()).contains(// "is a container", "with uniqueId substring 'NestedContainerEventConditionTests'", "with uniqueId substring 'ATestCase'", "with uniqueId substring 'BTestCase'", "with uniqueId substring 'CTestCase'"); } private Event createEvent(UniqueId uniqueId) { var testDescriptor = mock(TestDescriptor.class); when(testDescriptor.isContainer()).thenReturn(true); when(testDescriptor.getUniqueId()).thenReturn(uniqueId); var event = mock(Event.class); when(event.getTestDescriptor()).thenReturn(testDescriptor); return event; } @Test void usingNestedContainerCorrectly() { assertDoesNotThrow(() -> container(ATestCase.class)); assertDoesNotThrow(() -> nestedContainer(ATestCase.class)); assertDoesNotThrow(() -> container(ATestCase.BTestCase.class)); assertDoesNotThrow(() -> nestedContainer(ATestCase.BTestCase.class)); assertDoesNotThrow(() -> container(ATestCase.BTestCase.CTestCase.class)); assertDoesNotThrow(() -> nestedContainer(ATestCase.BTestCase.CTestCase.class)); assertDoesNotThrow(() -> container(NestedContainerEventConditionTests.class)); } @Test void eventConditionsForMultipleLevelsOfNestedClasses() { // @formatter:off EngineTestKit.engine("junit-jupiter") .selectors(selectClass(ATestCase.class)) .execute() .allEvents() .assertEventsMatchExactly( event(engine(), started()), event(container(ATestCase.class), started()), event(test("test_a"), started()), event(test("test_a"), finishedSuccessfully()), event(nestedContainer(ATestCase.BTestCase.class, displayName("Test case B")), started()), event(test("test_b"), started()), event(test("test_b"), finishedSuccessfully()), event(nestedContainer(ATestCase.BTestCase.CTestCase.class), started()), event(test("test_c"), started()), event(test("test_c"), finishedSuccessfully()), event(nestedContainer(ATestCase.BTestCase.CTestCase.class), finishedSuccessfully()), event(nestedContainer(ATestCase.BTestCase.class, displayName("Test case B")), finishedSuccessfully()), event(container(ATestCase.class), finishedSuccessfully()), event(engine(), finishedSuccessfully()) ); // @formatter:on } static class ATestCase { @Test void test_a() { } @Nested @DisplayName("Test case B") class BTestCase { @Test void test_b() { } @Nested class CTestCase { @Test void test_c() { } } } } } ================================================ FILE: platform-tests/src/test/java/org/junit/platform/testkit/engine/TestExecutionResultConditionsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.testkit.engine; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationFor; import static org.junit.platform.commons.test.PreconditionAssertions.assertPreconditionViolationNotNullFor; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.rootCause; import org.assertj.core.api.Condition; import org.junit.jupiter.api.Test; /** * Tests for {@link TestExecutionResultConditions}. * * @since 1.11 */ class TestExecutionResultConditionsTests { private static final String EXPECTED = "expected"; private static final String UNEXPECTED = "unexpected"; private static final Condition rootCauseCondition = rootCause(message(EXPECTED)); @Test void rootCauseFailsForNullThrowable() { assertPreconditionViolationNotNullFor("Throwable", () -> rootCauseCondition.matches(null)); } @Test void rootCauseFailsForThrowableWithoutCause() { Throwable throwable = new Throwable(); assertPreconditionViolationFor(() -> rootCauseCondition.matches(throwable))// .withMessage("Throwable does not have a cause"); } @Test void rootCauseMatchesForDirectCauseWithExpectedMessage() { RuntimeException cause = new RuntimeException(EXPECTED); Throwable throwable = new Throwable(cause); assertThat(rootCauseCondition.matches(throwable)).isTrue(); } @Test void rootCauseDoesNotMatchForDirectCauseWithDifferentMessage() { RuntimeException cause = new RuntimeException(UNEXPECTED); Throwable throwable = new Throwable(cause); assertThat(rootCauseCondition.matches(throwable)).isFalse(); } @Test void rootCauseMatchesForRootCauseWithExpectedMessage() { RuntimeException rootCause = new RuntimeException(EXPECTED); RuntimeException intermediateCause = new RuntimeException("intermediate cause", rootCause); Throwable throwable = new Throwable(intermediateCause); assertThat(rootCauseCondition.matches(throwable)).isTrue(); } @Test void rootCauseDoesNotMatchForRootCauseWithDifferentMessage() { RuntimeException rootCause = new RuntimeException(UNEXPECTED); RuntimeException intermediateCause = new RuntimeException("intermediate cause", rootCause); Throwable throwable = new Throwable(intermediateCause); assertThat(rootCauseCondition.matches(throwable)).isFalse(); } @Test void rootCauseMatchesForRootCauseWithExpectedMessageAndSingleLevelRecursiveCauseChain() { RuntimeException rootCause = new RuntimeException(EXPECTED); Throwable throwable = new Throwable(rootCause); rootCause.initCause(throwable); assertThat(rootCauseCondition.matches(throwable)).isTrue(); } @Test void rootCauseDoesNotMatchForRootCauseWithDifferentMessageAndSingleLevelRecursiveCauseChain() { RuntimeException rootCause = new RuntimeException(UNEXPECTED); Throwable throwable = new Throwable(rootCause); rootCause.initCause(throwable); assertThat(rootCauseCondition.matches(throwable)).isFalse(); } @Test void rootCauseMatchesForRootCauseWithExpectedMessageAndDoubleLevelRecursiveCauseChain() { RuntimeException rootCause = new RuntimeException(EXPECTED); Exception intermediateCause = new Exception("intermediate cause", rootCause); Throwable throwable = new Throwable(intermediateCause); rootCause.initCause(throwable); assertThat(rootCauseCondition.matches(throwable)).isTrue(); } @Test void rootCauseDoesNotMatchForRootCauseWithDifferentMessageAndDoubleLevelRecursiveCauseChain() { RuntimeException rootCause = new RuntimeException(UNEXPECTED); Exception intermediateCause = new Exception("intermediate cause", rootCause); Throwable throwable = new Throwable(intermediateCause); rootCause.initCause(throwable); assertThat(rootCauseCondition.matches(throwable)).isFalse(); } } ================================================ FILE: platform-tests/src/test/kotlin/org/junit/platform/commons/util/KotlinReflectionUtilsTests.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package org.junit.platform.commons.util import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.junit.platform.commons.support.ModifierSupport import kotlin.coroutines.Continuation class KotlinReflectionUtilsTests { @Test fun recognizesSuspendFunction() { val method = OpenTestMethodTestCase::class.java.getDeclaredMethod( "test", Continuation::class.java ) assertFalse(ModifierSupport.isStatic(method)) assertFalse(method.isSynthetic) assertTrue(KotlinReflectionUtils.isKotlinSuspendingFunction(method)) } @Test fun doesNotRecognizeSyntheticMethodAsSuspendFunction() { val method = OpenTestMethodTestCase::class.java.getDeclaredMethod( "test\$suspendImpl", OpenTestMethodTestCase::class.java, Continuation::class.java ) assertTrue(ModifierSupport.isStatic(method)) assertTrue(method.isSynthetic) assertFalse(KotlinReflectionUtils.isKotlinSuspendingFunction(method)) } @Suppress("JUnitMalformedDeclaration") open class OpenTestMethodTestCase { @Test open suspend fun test() { } } } ================================================ FILE: platform-tests/src/test/resources/META-INF/services/org.junit.platform.engine.discovery.DiscoverySelectorIdentifierParser ================================================ org.junit.platform.suite.engine.error.ErrorSelectorIdentifierParser ================================================ FILE: platform-tests/src/test/resources/config-test-override.properties ================================================ # Used in tests, don't delete me com.example.prop.first=first value from override file ================================================ FILE: platform-tests/src/test/resources/config-test.properties ================================================ # Used in tests, don't delete me com.example.prop.first=first value com.example.prop.second=second value com.example.prop.third=third value ================================================ FILE: platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-flat-ascii.out.txt ================================================ Test execution started. Number of static tests: 1 Started: JUnit Jupiter ([engine:junit-jupiter]) Started: Basic ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]) Started: .oO fancy display name Oo. ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:changeDisplayName()]) Finished: .oO fancy display name Oo. ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:changeDisplayName()]) Finished: Basic ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]) Finished: JUnit Jupiter ([engine:junit-jupiter]) Test execution finished. Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-flat-unicode.out.txt ================================================ Test execution started. Number of static tests: 1 Started: JUnit Jupiter ([engine:junit-jupiter]) Started: Basic ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]) Started: .oO fancy display name Oo. ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:changeDisplayName()]) Finished: .oO fancy display name Oo. ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:changeDisplayName()]) Finished: Basic ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]) Finished: JUnit Jupiter ([engine:junit-jupiter]) Test execution finished. Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-none-ascii.out.txt ================================================ ================================================ FILE: platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-none-unicode.out.txt ================================================ ================================================ FILE: platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-summary-ascii.out.txt ================================================ Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-summary-unicode.out.txt ================================================ Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-testfeed-ascii.out.txt ================================================ JUnit Jupiter > Basic > .oO fancy display name Oo. :: STARTED JUnit Jupiter > Basic > .oO fancy display name Oo. :: SUCCESSFUL Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-testfeed-unicode.out.txt ================================================ JUnit Jupiter > Basic > .oO fancy display name Oo. :: STARTED JUnit Jupiter > Basic > .oO fancy display name Oo. :: SUCCESSFUL Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-tree-ascii.out.txt ================================================ . '-- JUnit Jupiter [OK] '-- Basic [OK] '-- .oO fancy display name Oo. [OK] Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-tree-unicode.out.txt ================================================ ╷ └─ JUnit Jupiter ✔ └─ Basic ✔ └─ .oO fancy display name Oo. ✔ Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-verbose-ascii.out.txt ================================================ Test plan execution started. Number of static tests: 1 . +-- JUnit Jupiter | +-- Basic | | +-- .oO fancy display name Oo. | | | tags: [] | | | uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:changeDisplayName()] | | | parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase] | | | source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$BasicTestCase', methodName = 'changeDisplayName', methodParameterTypes = ''] \| \| \| duration: [\d]+ ms | | | status: [OK] SUCCESSFUL \| '-- Basic finished after [\d]+ ms\. '-- JUnit Jupiter finished after [\d]+ ms\. Test plan execution finished. Number of all tests: 1 Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/basic/Basic-changeDisplayName-verbose-unicode.out.txt ================================================ Test plan execution started. Number of static tests: 1 ╷ ├─ JUnit Jupiter │ ├─ Basic │ │ ├─ .oO fancy display name Oo. │ │ │ tags: [] │ │ │ uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:changeDisplayName()] │ │ │ parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase] │ │ │ source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$BasicTestCase', methodName = 'changeDisplayName', methodParameterTypes = ''] │ │ │ duration: [\d]+ ms │ │ │ status: ✔ SUCCESSFUL │ └─ Basic finished after [\d]+ ms\. └─ JUnit Jupiter finished after [\d]+ ms\. Test plan execution finished. Number of all tests: 1 Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/basic/Basic-empty-flat-ascii.out.txt ================================================ Test execution started. Number of static tests: 1 Started: JUnit Jupiter ([engine:junit-jupiter]) Started: Basic ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]) Started: empty() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:empty()]) Finished: empty() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:empty()]) Finished: Basic ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]) Finished: JUnit Jupiter ([engine:junit-jupiter]) Test execution finished. Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/basic/Basic-empty-flat-unicode.out.txt ================================================ Test execution started. Number of static tests: 1 Started: JUnit Jupiter ([engine:junit-jupiter]) Started: Basic ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]) Started: empty() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:empty()]) Finished: empty() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:empty()]) Finished: Basic ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]) Finished: JUnit Jupiter ([engine:junit-jupiter]) Test execution finished. Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/basic/Basic-empty-none-ascii.out.txt ================================================ ================================================ FILE: platform-tests/src/test/resources/console/details/basic/Basic-empty-none-unicode.out.txt ================================================ ================================================ FILE: platform-tests/src/test/resources/console/details/basic/Basic-empty-summary-ascii.out.txt ================================================ Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/basic/Basic-empty-summary-unicode.out.txt ================================================ Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/basic/Basic-empty-testfeed-ascii.out.txt ================================================ JUnit Jupiter > Basic > empty() :: STARTED JUnit Jupiter > Basic > empty() :: SUCCESSFUL Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/basic/Basic-empty-testfeed-unicode.out.txt ================================================ JUnit Jupiter > Basic > empty() :: STARTED JUnit Jupiter > Basic > empty() :: SUCCESSFUL Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/basic/Basic-empty-tree-ascii.out.txt ================================================ . '-- JUnit Jupiter [OK] '-- Basic [OK] '-- empty() [OK] Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/basic/Basic-empty-tree-unicode.out.txt ================================================ ╷ └─ JUnit Jupiter ✔ └─ Basic ✔ └─ empty() ✔ Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/basic/Basic-empty-verbose-ascii.out.txt ================================================ Test plan execution started. Number of static tests: 1 . +-- JUnit Jupiter | +-- Basic | | +-- empty() | | | tags: [] | | | uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:empty()] | | | parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase] | | | source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$BasicTestCase', methodName = 'empty', methodParameterTypes = ''] \| \| \| duration: [\d]+ ms | | | status: [OK] SUCCESSFUL \| '-- Basic finished after [\d]+ ms\. '-- JUnit Jupiter finished after [\d]+ ms\. Test plan execution finished. Number of all tests: 1 Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/basic/Basic-empty-verbose-unicode.out.txt ================================================ Test plan execution started. Number of static tests: 1 ╷ ├─ JUnit Jupiter │ ├─ Basic │ │ ├─ empty() │ │ │ tags: [] │ │ │ uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase]/[method:empty()] │ │ │ parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$BasicTestCase] │ │ │ source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$BasicTestCase', methodName = 'empty', methodParameterTypes = ''] │ │ │ duration: [\d]+ ms │ │ │ status: ✔ SUCCESSFUL │ └─ Basic finished after [\d]+ ms\. └─ JUnit Jupiter finished after [\d]+ ms\. Test plan execution finished. Number of all tests: 1 Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-flat-ascii.out.txt ================================================ Test execution started. Number of static tests: 1 Started: JUnit Jupiter ([engine:junit-jupiter]) Started: Fail ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]) Started: failWithMultiLineMessage() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithMultiLineMessage()]) Finished: failWithMultiLineMessage() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithMultiLineMessage()]) => Exception: org.opentest4j.AssertionFailedError: multi line fail message >> S T A C K T R A C E >> Finished: Fail ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]) Finished: JUnit Jupiter ([engine:junit-jupiter]) Test execution finished. Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 1 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-flat-unicode.out.txt ================================================ Test execution started. Number of static tests: 1 Started: JUnit Jupiter ([engine:junit-jupiter]) Started: Fail ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]) Started: failWithMultiLineMessage() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithMultiLineMessage()]) Finished: failWithMultiLineMessage() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithMultiLineMessage()]) => Exception: org.opentest4j.AssertionFailedError: multi line fail message >> S T A C K T R A C E >> Finished: Fail ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]) Finished: JUnit Jupiter ([engine:junit-jupiter]) Test execution finished. Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 1 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-none-ascii.out.txt ================================================ Failures (1): JUnit Jupiter:Fail:failWithMultiLineMessage() MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithMultiLineMessage', methodParameterTypes = ''] => org.opentest4j.AssertionFailedError: multi line fail message >> STACKTRACE >> Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 1 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-none-unicode.out.txt ================================================ Failures (1): JUnit Jupiter:Fail:failWithMultiLineMessage() MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithMultiLineMessage', methodParameterTypes = ''] => org.opentest4j.AssertionFailedError: multi line fail message >> STACKTRACE >> Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 1 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-summary-ascii.out.txt ================================================ Failures (1): JUnit Jupiter:Fail:failWithMultiLineMessage() MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithMultiLineMessage', methodParameterTypes = ''] => org.opentest4j.AssertionFailedError: multi line fail message >> STACKTRACE >> Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 1 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-summary-unicode.out.txt ================================================ Failures (1): JUnit Jupiter:Fail:failWithMultiLineMessage() MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithMultiLineMessage', methodParameterTypes = ''] => org.opentest4j.AssertionFailedError: multi line fail message >> STACKTRACE >> Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 1 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-testfeed-ascii.out.txt ================================================ JUnit Jupiter > Fail > failWithMultiLineMessage() :: STARTED JUnit Jupiter > Fail > failWithMultiLineMessage() :: FAILED org.opentest4j.AssertionFailedError: multi line fail message >> STACKTRACE >> Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 1 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-testfeed-unicode.out.txt ================================================ JUnit Jupiter > Fail > failWithMultiLineMessage() :: STARTED JUnit Jupiter > Fail > failWithMultiLineMessage() :: FAILED org.opentest4j.AssertionFailedError: multi line fail message >> STACKTRACE >> Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 1 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-tree-ascii.out.txt ================================================ . '-- JUnit Jupiter [OK] '-- Fail [OK] '-- failWithMultiLineMessage() [X] multi line fail message Failures (1): JUnit Jupiter:Fail:failWithMultiLineMessage() MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithMultiLineMessage', methodParameterTypes = ''] => org.opentest4j.AssertionFailedError: multi line fail message >> STACKTRACE >> Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 1 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-tree-unicode.out.txt ================================================ ╷ └─ JUnit Jupiter ✔ └─ Fail ✔ └─ failWithMultiLineMessage() ✘ multi line fail message Failures (1): JUnit Jupiter:Fail:failWithMultiLineMessage() MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithMultiLineMessage', methodParameterTypes = ''] => org.opentest4j.AssertionFailedError: multi line fail message >> STACKTRACE >> Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 1 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-verbose-ascii.out.txt ================================================ Test plan execution started. Number of static tests: 1 . +-- JUnit Jupiter | +-- Fail | | +-- failWithMultiLineMessage() | | | tags: [] | | | uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithMultiLineMessage()] | | | parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase] | | | source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithMultiLineMessage', methodParameterTypes = ''] | | | caught: org.opentest4j.AssertionFailedError: multi | | | line | | | fail | | | message >> S T A C K T R A C E >> \| \| \| duration: [\d]+ ms | | | status: [X] FAILED \| '-- Fail finished after [\d]+ ms\. '-- JUnit Jupiter finished after [\d]+ ms\. Test plan execution finished. Number of all tests: 1 Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 1 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/fail/Fail-failWithMultiLineMessage-verbose-unicode.out.txt ================================================ Test plan execution started. Number of static tests: 1 ╷ ├─ JUnit Jupiter │ ├─ Fail │ │ ├─ failWithMultiLineMessage() │ │ │ tags: [] │ │ │ uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithMultiLineMessage()] │ │ │ parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase] │ │ │ source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithMultiLineMessage', methodParameterTypes = ''] │ │ │ caught: org.opentest4j.AssertionFailedError: multi │ │ │ line │ │ │ fail │ │ │ message >> S T A C K T R A C E >> │ │ │ duration: [\d]+ ms │ │ │ status: ✘ FAILED │ └─ Fail finished after [\d]+ ms\. └─ JUnit Jupiter finished after [\d]+ ms\. Test plan execution finished. Number of all tests: 1 Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 1 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-flat-ascii.out.txt ================================================ Test execution started. Number of static tests: 1 Started: JUnit Jupiter ([engine:junit-jupiter]) Started: Fail ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]) Started: failWithSingleLineMessage() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithSingleLineMessage()]) Finished: failWithSingleLineMessage() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithSingleLineMessage()]) => Exception: org.opentest4j.AssertionFailedError: single line fail message >> S T A C K T R A C E >> Finished: Fail ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]) Finished: JUnit Jupiter ([engine:junit-jupiter]) Test execution finished. Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 1 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-flat-unicode.out.txt ================================================ Test execution started. Number of static tests: 1 Started: JUnit Jupiter ([engine:junit-jupiter]) Started: Fail ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]) Started: failWithSingleLineMessage() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithSingleLineMessage()]) Finished: failWithSingleLineMessage() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithSingleLineMessage()]) => Exception: org.opentest4j.AssertionFailedError: single line fail message >> S T A C K T R A C E >> Finished: Fail ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]) Finished: JUnit Jupiter ([engine:junit-jupiter]) Test execution finished. Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 1 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-none-ascii.out.txt ================================================ Failures (1): JUnit Jupiter:Fail:failWithSingleLineMessage() MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithSingleLineMessage', methodParameterTypes = ''] => org.opentest4j.AssertionFailedError: single line fail message >> STACKTRACE >> Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 1 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-none-unicode.out.txt ================================================ Failures (1): JUnit Jupiter:Fail:failWithSingleLineMessage() MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithSingleLineMessage', methodParameterTypes = ''] => org.opentest4j.AssertionFailedError: single line fail message >> STACKTRACE >> Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 1 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-summary-ascii.out.txt ================================================ Failures (1): JUnit Jupiter:Fail:failWithSingleLineMessage() MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithSingleLineMessage', methodParameterTypes = ''] => org.opentest4j.AssertionFailedError: single line fail message >> STACKTRACE >> Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 1 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-summary-unicode.out.txt ================================================ Failures (1): JUnit Jupiter:Fail:failWithSingleLineMessage() MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithSingleLineMessage', methodParameterTypes = ''] => org.opentest4j.AssertionFailedError: single line fail message >> STACKTRACE >> Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 1 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-testfeed-ascii.out.txt ================================================ JUnit Jupiter > Fail > failWithSingleLineMessage() :: STARTED JUnit Jupiter > Fail > failWithSingleLineMessage() :: FAILED org.opentest4j.AssertionFailedError: single line fail message >> STACKTRACE >> Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 1 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-testfeed-unicode.out.txt ================================================ JUnit Jupiter > Fail > failWithSingleLineMessage() :: STARTED JUnit Jupiter > Fail > failWithSingleLineMessage() :: FAILED org.opentest4j.AssertionFailedError: single line fail message >> STACKTRACE >> Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 1 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-tree-ascii.out.txt ================================================ . '-- JUnit Jupiter [OK] '-- Fail [OK] '-- failWithSingleLineMessage() [X] single line fail message Failures (1): JUnit Jupiter:Fail:failWithSingleLineMessage() MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithSingleLineMessage', methodParameterTypes = ''] => org.opentest4j.AssertionFailedError: single line fail message >> STACKTRACE >> Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 1 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-tree-unicode.out.txt ================================================ ╷ └─ JUnit Jupiter ✔ └─ Fail ✔ └─ failWithSingleLineMessage() ✘ single line fail message Failures (1): JUnit Jupiter:Fail:failWithSingleLineMessage() MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithSingleLineMessage', methodParameterTypes = ''] => org.opentest4j.AssertionFailedError: single line fail message >> STACKTRACE >> Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 1 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-verbose-ascii.out.txt ================================================ Test plan execution started. Number of static tests: 1 . +-- JUnit Jupiter | +-- Fail | | +-- failWithSingleLineMessage() | | | tags: [] | | | uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithSingleLineMessage()] | | | parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase] | | | source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithSingleLineMessage', methodParameterTypes = ''] | | | caught: org.opentest4j.AssertionFailedError: single line fail message >> S T A C K T R A C E >> \| \| \| duration: [\d]+ ms | | | status: [X] FAILED \| '-- Fail finished after [\d]+ ms\. '-- JUnit Jupiter finished after [\d]+ ms\. Test plan execution finished. Number of all tests: 1 Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 1 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/fail/Fail-failWithSingleLineMessage-verbose-unicode.out.txt ================================================ Test plan execution started. Number of static tests: 1 ╷ ├─ JUnit Jupiter │ ├─ Fail │ │ ├─ failWithSingleLineMessage() │ │ │ tags: [] │ │ │ uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase]/[method:failWithSingleLineMessage()] │ │ │ parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$FailTestCase] │ │ │ source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$FailTestCase', methodName = 'failWithSingleLineMessage', methodParameterTypes = ''] │ │ │ caught: org.opentest4j.AssertionFailedError: single line fail message >> S T A C K T R A C E >> │ │ │ duration: [\d]+ ms │ │ │ status: ✘ FAILED │ └─ Fail finished after [\d]+ ms\. └─ JUnit Jupiter finished after [\d]+ ms\. Test plan execution finished. Number of all tests: 1 Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 1 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-flat-ascii.out.txt ================================================ Test execution started. Number of static tests: 1 Started: JUnit Jupiter ([engine:junit-jupiter]) Started: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) Started: reportMultiEntriesWithMultiMappings(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)]) Reported: reportMultiEntriesWithMultiMappings(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)]) => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, user name = 'dk38', award year = '1974'\] Reported: reportMultiEntriesWithMultiMappings(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)]) => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, single = 'mapping'\] Reported: reportMultiEntriesWithMultiMappings(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)]) => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, user name = 'st77', award year = '1977', last seen = '2001'\] Finished: reportMultiEntriesWithMultiMappings(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)]) Finished: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) Finished: JUnit Jupiter ([engine:junit-jupiter]) Test execution finished. Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-flat-unicode.out.txt ================================================ Test execution started. Number of static tests: 1 Started: JUnit Jupiter ([engine:junit-jupiter]) Started: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) Started: reportMultiEntriesWithMultiMappings(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)]) Reported: reportMultiEntriesWithMultiMappings(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)]) => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, user name = 'dk38', award year = '1974'\] Reported: reportMultiEntriesWithMultiMappings(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)]) => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, single = 'mapping'\] Reported: reportMultiEntriesWithMultiMappings(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)]) => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, user name = 'st77', award year = '1977', last seen = '2001'\] Finished: reportMultiEntriesWithMultiMappings(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)]) Finished: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) Finished: JUnit Jupiter ([engine:junit-jupiter]) Test execution finished. Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-none-ascii.out.txt ================================================ ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-none-unicode.out.txt ================================================ ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-summary-ascii.out.txt ================================================ Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-summary-unicode.out.txt ================================================ Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-testfeed-ascii.out.txt ================================================ JUnit Jupiter > Report > reportMultiEntriesWithMultiMappings(TestReporter) :: STARTED JUnit Jupiter > Report > reportMultiEntriesWithMultiMappings(TestReporter) :: SUCCESSFUL Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-testfeed-unicode.out.txt ================================================ JUnit Jupiter > Report > reportMultiEntriesWithMultiMappings(TestReporter) :: STARTED JUnit Jupiter > Report > reportMultiEntriesWithMultiMappings(TestReporter) :: SUCCESSFUL Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-tree-ascii.out.txt ================================================ . '-- JUnit Jupiter [OK] '-- Report [OK] '-- reportMultiEntriesWithMultiMappings(TestReporter) [OK] ....-..-..T..:...* user name = `dk38` award year = `1974` ....-..-..T..:...* single = `mapping` ....-..-..T..:...* user name = `st77` award year = `1977` last seen = `2001` Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-tree-unicode.out.txt ================================================ ╷ └─ JUnit Jupiter ✔ └─ Report ✔ └─ reportMultiEntriesWithMultiMappings(TestReporter) ✔ ....-..-..T..:...* user name = `dk38` award year = `1974` ....-..-..T..:...* single = `mapping` ....-..-..T..:...* user name = `st77` award year = `1977` last seen = `2001` Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-verbose-ascii.out.txt ================================================ Test plan execution started. Number of static tests: 1 . +-- JUnit Jupiter | +-- Report | | +-- reportMultiEntriesWithMultiMappings(TestReporter) | | | tags: [] | | | uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)] | | | parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase] | | | source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$ReportTestCase', methodName = 'reportMultiEntriesWithMultiMappings', methodParameterTypes = 'org.junit.jupiter.api.TestReporter'] \| \| \| reports: ReportEntry \[timestamp = ....-..-..T..:...*, user name = 'dk38', award year = '1974'\] \| \| \| reports: ReportEntry \[timestamp = ....-..-..T..:...*, single = 'mapping'\] \| \| \| reports: ReportEntry \[timestamp = ....-..-..T..:...*, user name = 'st77', award year = '1977', last seen = '2001'\] \| \| \| duration: [\d]+ ms | | | status: [OK] SUCCESSFUL \| '-- Report finished after [\d]+ ms\. '-- JUnit Jupiter finished after [\d]+ ms\. Test plan execution finished. Number of all tests: 1 Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithMultiMappings-verbose-unicode.out.txt ================================================ Test plan execution started. Number of static tests: 1 ╷ ├─ JUnit Jupiter │ ├─ Report │ │ ├─ reportMultiEntriesWithMultiMappings(TestReporter) │ │ │ tags: [] │ │ │ uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithMultiMappings(org.junit.jupiter.api.TestReporter)] │ │ │ parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase] │ │ │ source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$ReportTestCase', methodName = 'reportMultiEntriesWithMultiMappings', methodParameterTypes = 'org.junit.jupiter.api.TestReporter'] │ │ │ reports: ReportEntry \[timestamp = ....-..-..T..:...*, user name = 'dk38', award year = '1974'\] │ │ │ reports: ReportEntry \[timestamp = ....-..-..T..:...*, single = 'mapping'\] │ │ │ reports: ReportEntry \[timestamp = ....-..-..T..:...*, user name = 'st77', award year = '1977', last seen = '2001'\] │ │ │ duration: [\d]+ ms │ │ │ status: ✔ SUCCESSFUL │ └─ Report finished after [\d]+ ms\. └─ JUnit Jupiter finished after [\d]+ ms\. Test plan execution finished. Number of all tests: 1 Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-flat-ascii.out.txt ================================================ Test execution started. Number of static tests: 1 Started: JUnit Jupiter ([engine:junit-jupiter]) Started: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) Started: reportMultiEntriesWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithSingleMapping(org.junit.jupiter.api.TestReporter)]) Reported: reportMultiEntriesWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithSingleMapping(org.junit.jupiter.api.TestReporter)]) => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, foo = 'bar'\] Reported: reportMultiEntriesWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithSingleMapping(org.junit.jupiter.api.TestReporter)]) => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, far = 'boo'\] Finished: reportMultiEntriesWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithSingleMapping(org.junit.jupiter.api.TestReporter)]) Finished: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) Finished: JUnit Jupiter ([engine:junit-jupiter]) Test execution finished. Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-flat-unicode.out.txt ================================================ Test execution started. Number of static tests: 1 Started: JUnit Jupiter ([engine:junit-jupiter]) Started: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) Started: reportMultiEntriesWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithSingleMapping(org.junit.jupiter.api.TestReporter)]) Reported: reportMultiEntriesWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithSingleMapping(org.junit.jupiter.api.TestReporter)]) => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, foo = 'bar'\] Reported: reportMultiEntriesWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithSingleMapping(org.junit.jupiter.api.TestReporter)]) => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, far = 'boo'\] Finished: reportMultiEntriesWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithSingleMapping(org.junit.jupiter.api.TestReporter)]) Finished: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) Finished: JUnit Jupiter ([engine:junit-jupiter]) Test execution finished. Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-none-ascii.out.txt ================================================ ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-none-unicode.out.txt ================================================ ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-summary-ascii.out.txt ================================================ Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-summary-unicode.out.txt ================================================ Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-testfeed-ascii.out.txt ================================================ JUnit Jupiter > Report > reportMultiEntriesWithSingleMapping(TestReporter) :: STARTED JUnit Jupiter > Report > reportMultiEntriesWithSingleMapping(TestReporter) :: SUCCESSFUL Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-testfeed-unicode.out.txt ================================================ JUnit Jupiter > Report > reportMultiEntriesWithSingleMapping(TestReporter) :: STARTED JUnit Jupiter > Report > reportMultiEntriesWithSingleMapping(TestReporter) :: SUCCESSFUL Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-tree-ascii.out.txt ================================================ . '-- JUnit Jupiter [OK] '-- Report [OK] '-- reportMultiEntriesWithSingleMapping(TestReporter) [OK] ....-..-..T..:...* foo = `bar` ....-..-..T..:...* far = `boo` Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-tree-unicode.out.txt ================================================ ╷ └─ JUnit Jupiter ✔ └─ Report ✔ └─ reportMultiEntriesWithSingleMapping(TestReporter) ✔ ....-..-..T..:...* foo = `bar` ....-..-..T..:...* far = `boo` Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-verbose-ascii.out.txt ================================================ Test plan execution started. Number of static tests: 1 . +-- JUnit Jupiter | +-- Report | | +-- reportMultiEntriesWithSingleMapping(TestReporter) | | | tags: [] | | | uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithSingleMapping(org.junit.jupiter.api.TestReporter)] | | | parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase] | | | source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$ReportTestCase', methodName = 'reportMultiEntriesWithSingleMapping', methodParameterTypes = 'org.junit.jupiter.api.TestReporter'] \| \| \| reports: ReportEntry \[timestamp = ....-..-..T..:...*, foo = 'bar'\] \| \| \| reports: ReportEntry \[timestamp = ....-..-..T..:...*, far = 'boo'\] \| \| \| duration: [\d]+ ms | | | status: [OK] SUCCESSFUL \| '-- Report finished after [\d]+ ms\. '-- JUnit Jupiter finished after [\d]+ ms\. Test plan execution finished. Number of all tests: 1 Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultiEntriesWithSingleMapping-verbose-unicode.out.txt ================================================ Test plan execution started. Number of static tests: 1 ╷ ├─ JUnit Jupiter │ ├─ Report │ │ ├─ reportMultiEntriesWithSingleMapping(TestReporter) │ │ │ tags: [] │ │ │ uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultiEntriesWithSingleMapping(org.junit.jupiter.api.TestReporter)] │ │ │ parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase] │ │ │ source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$ReportTestCase', methodName = 'reportMultiEntriesWithSingleMapping', methodParameterTypes = 'org.junit.jupiter.api.TestReporter'] │ │ │ reports: ReportEntry \[timestamp = ....-..-..T..:...*, foo = 'bar'\] │ │ │ reports: ReportEntry \[timestamp = ....-..-..T..:...*, far = 'boo'\] │ │ │ duration: [\d]+ ms │ │ │ status: ✔ SUCCESSFUL │ └─ Report finished after [\d]+ ms\. └─ JUnit Jupiter finished after [\d]+ ms\. Test plan execution finished. Number of all tests: 1 Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-flat-ascii.out.txt ================================================ Test execution started. Number of static tests: 1 Started: JUnit Jupiter ([engine:junit-jupiter]) Started: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) Started: reportMultipleMessages(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultipleMessages(org.junit.jupiter.api.TestReporter)]) Reported: reportMultipleMessages(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultipleMessages(org.junit.jupiter.api.TestReporter)]) => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'foo'\] Reported: reportMultipleMessages(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultipleMessages(org.junit.jupiter.api.TestReporter)]) => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'bar'\] Finished: reportMultipleMessages(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultipleMessages(org.junit.jupiter.api.TestReporter)]) Finished: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) Finished: JUnit Jupiter ([engine:junit-jupiter]) Test execution finished. Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-flat-unicode.out.txt ================================================ Test execution started. Number of static tests: 1 Started: JUnit Jupiter ([engine:junit-jupiter]) Started: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) Started: reportMultipleMessages(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultipleMessages(org.junit.jupiter.api.TestReporter)]) Reported: reportMultipleMessages(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultipleMessages(org.junit.jupiter.api.TestReporter)]) => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'foo'\] Reported: reportMultipleMessages(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultipleMessages(org.junit.jupiter.api.TestReporter)]) => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'bar'\] Finished: reportMultipleMessages(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultipleMessages(org.junit.jupiter.api.TestReporter)]) Finished: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) Finished: JUnit Jupiter ([engine:junit-jupiter]) Test execution finished. Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-none-ascii.out.txt ================================================ ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-none-unicode.out.txt ================================================ ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-summary-ascii.out.txt ================================================ Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-summary-unicode.out.txt ================================================ Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-testfeed-ascii.out.txt ================================================ JUnit Jupiter > Report > reportMultipleMessages(TestReporter) :: STARTED JUnit Jupiter > Report > reportMultipleMessages(TestReporter) :: SUCCESSFUL Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-testfeed-unicode.out.txt ================================================ JUnit Jupiter > Report > reportMultipleMessages(TestReporter) :: STARTED JUnit Jupiter > Report > reportMultipleMessages(TestReporter) :: SUCCESSFUL Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-tree-ascii.out.txt ================================================ . '-- JUnit Jupiter [OK] '-- Report [OK] '-- reportMultipleMessages(TestReporter) [OK] ....-..-..T..:...* value = `foo` ....-..-..T..:...* value = `bar` Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-tree-unicode.out.txt ================================================ ╷ └─ JUnit Jupiter ✔ └─ Report ✔ └─ reportMultipleMessages(TestReporter) ✔ ....-..-..T..:...* value = `foo` ....-..-..T..:...* value = `bar` Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-verbose-ascii.out.txt ================================================ Test plan execution started. Number of static tests: 1 . +-- JUnit Jupiter | +-- Report | | +-- reportMultipleMessages(TestReporter) | | | tags: [] | | | uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultipleMessages(org.junit.jupiter.api.TestReporter)] | | | parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase] | | | source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$ReportTestCase', methodName = 'reportMultipleMessages', methodParameterTypes = 'org.junit.jupiter.api.TestReporter'] \| \| \| reports: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'foo'\] \| \| \| reports: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'bar'\] \| \| \| duration: [\d]+ ms | | | status: [OK] SUCCESSFUL \| '-- Report finished after [\d]+ ms\. '-- JUnit Jupiter finished after [\d]+ ms\. Test plan execution finished. Number of all tests: 1 Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportMultipleMessages-verbose-unicode.out.txt ================================================ Test plan execution started. Number of static tests: 1 ╷ ├─ JUnit Jupiter │ ├─ Report │ │ ├─ reportMultipleMessages(TestReporter) │ │ │ tags: [] │ │ │ uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportMultipleMessages(org.junit.jupiter.api.TestReporter)] │ │ │ parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase] │ │ │ source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$ReportTestCase', methodName = 'reportMultipleMessages', methodParameterTypes = 'org.junit.jupiter.api.TestReporter'] │ │ │ reports: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'foo'\] │ │ │ reports: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'bar'\] │ │ │ duration: [\d]+ ms │ │ │ status: ✔ SUCCESSFUL │ └─ Report finished after [\d]+ ms\. └─ JUnit Jupiter finished after [\d]+ ms\. Test plan execution finished. Number of all tests: 1 Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-flat-ascii.out.txt ================================================ Test execution started. Number of static tests: 1 Started: JUnit Jupiter ([engine:junit-jupiter]) Started: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) Started: reportSingleEntryWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleEntryWithSingleMapping(org.junit.jupiter.api.TestReporter)]) Reported: reportSingleEntryWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleEntryWithSingleMapping(org.junit.jupiter.api.TestReporter)]) => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, foo = 'bar'\] Finished: reportSingleEntryWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleEntryWithSingleMapping(org.junit.jupiter.api.TestReporter)]) Finished: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) Finished: JUnit Jupiter ([engine:junit-jupiter]) Test execution finished. Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-flat-unicode.out.txt ================================================ Test execution started. Number of static tests: 1 Started: JUnit Jupiter ([engine:junit-jupiter]) Started: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) Started: reportSingleEntryWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleEntryWithSingleMapping(org.junit.jupiter.api.TestReporter)]) Reported: reportSingleEntryWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleEntryWithSingleMapping(org.junit.jupiter.api.TestReporter)]) => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, foo = 'bar'\] Finished: reportSingleEntryWithSingleMapping(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleEntryWithSingleMapping(org.junit.jupiter.api.TestReporter)]) Finished: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) Finished: JUnit Jupiter ([engine:junit-jupiter]) Test execution finished. Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-none-ascii.out.txt ================================================ ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-none-unicode.out.txt ================================================ ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-summary-ascii.out.txt ================================================ Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-summary-unicode.out.txt ================================================ Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-testfeed-ascii.out.txt ================================================ JUnit Jupiter > Report > reportSingleEntryWithSingleMapping(TestReporter) :: STARTED JUnit Jupiter > Report > reportSingleEntryWithSingleMapping(TestReporter) :: SUCCESSFUL Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-testfeed-unicode.out.txt ================================================ JUnit Jupiter > Report > reportSingleEntryWithSingleMapping(TestReporter) :: STARTED JUnit Jupiter > Report > reportSingleEntryWithSingleMapping(TestReporter) :: SUCCESSFUL Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-tree-ascii.out.txt ================================================ . '-- JUnit Jupiter [OK] '-- Report [OK] '-- reportSingleEntryWithSingleMapping(TestReporter) [OK] ....-..-..T..:...* foo = `bar` Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-tree-unicode.out.txt ================================================ ╷ └─ JUnit Jupiter ✔ └─ Report ✔ └─ reportSingleEntryWithSingleMapping(TestReporter) ✔ ....-..-..T..:...* foo = `bar` Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-verbose-ascii.out.txt ================================================ Test plan execution started. Number of static tests: 1 . +-- JUnit Jupiter | +-- Report | | +-- reportSingleEntryWithSingleMapping(TestReporter) | | | tags: [] | | | uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleEntryWithSingleMapping(org.junit.jupiter.api.TestReporter)] | | | parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase] | | | source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$ReportTestCase', methodName = 'reportSingleEntryWithSingleMapping', methodParameterTypes = 'org.junit.jupiter.api.TestReporter'] \| \| \| reports: ReportEntry \[timestamp = ....-..-..T..:...*, foo = 'bar'\] \| \| \| duration: [\d]+ ms | | | status: [OK] SUCCESSFUL \| '-- Report finished after [\d]+ ms\. '-- JUnit Jupiter finished after [\d]+ ms\. Test plan execution finished. Number of all tests: 1 Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportSingleEntryWithSingleMapping-verbose-unicode.out.txt ================================================ Test plan execution started. Number of static tests: 1 ╷ ├─ JUnit Jupiter │ ├─ Report │ │ ├─ reportSingleEntryWithSingleMapping(TestReporter) │ │ │ tags: [] │ │ │ uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleEntryWithSingleMapping(org.junit.jupiter.api.TestReporter)] │ │ │ parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase] │ │ │ source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$ReportTestCase', methodName = 'reportSingleEntryWithSingleMapping', methodParameterTypes = 'org.junit.jupiter.api.TestReporter'] │ │ │ reports: ReportEntry \[timestamp = ....-..-..T..:...*, foo = 'bar'\] │ │ │ duration: [\d]+ ms │ │ │ status: ✔ SUCCESSFUL │ └─ Report finished after [\d]+ ms\. └─ JUnit Jupiter finished after [\d]+ ms\. Test plan execution finished. Number of all tests: 1 Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-flat-ascii.out.txt ================================================ Test execution started. Number of static tests: 1 Started: JUnit Jupiter ([engine:junit-jupiter]) Started: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) Started: reportSingleMessage(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleMessage(org.junit.jupiter.api.TestReporter)]) Reported: reportSingleMessage(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleMessage(org.junit.jupiter.api.TestReporter)]) => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'foo'\] Finished: reportSingleMessage(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleMessage(org.junit.jupiter.api.TestReporter)]) Finished: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) Finished: JUnit Jupiter ([engine:junit-jupiter]) Test execution finished. Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-flat-unicode.out.txt ================================================ Test execution started. Number of static tests: 1 Started: JUnit Jupiter ([engine:junit-jupiter]) Started: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) Started: reportSingleMessage(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleMessage(org.junit.jupiter.api.TestReporter)]) Reported: reportSingleMessage(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleMessage(org.junit.jupiter.api.TestReporter)]) => Reported values: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'foo'\] Finished: reportSingleMessage(TestReporter) ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleMessage(org.junit.jupiter.api.TestReporter)]) Finished: Report ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]) Finished: JUnit Jupiter ([engine:junit-jupiter]) Test execution finished. Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-none-ascii.out.txt ================================================ ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-none-unicode.out.txt ================================================ ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-summary-ascii.out.txt ================================================ Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-summary-unicode.out.txt ================================================ Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-testfeed-ascii.out.txt ================================================ JUnit Jupiter > Report > reportSingleMessage(TestReporter) :: STARTED JUnit Jupiter > Report > reportSingleMessage(TestReporter) :: SUCCESSFUL Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-testfeed-unicode.out.txt ================================================ JUnit Jupiter > Report > reportSingleMessage(TestReporter) :: STARTED JUnit Jupiter > Report > reportSingleMessage(TestReporter) :: SUCCESSFUL Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-tree-ascii.out.txt ================================================ . '-- JUnit Jupiter [OK] '-- Report [OK] '-- reportSingleMessage(TestReporter) [OK] ....-..-..T..:...* value = `foo` Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-tree-unicode.out.txt ================================================ ╷ └─ JUnit Jupiter ✔ └─ Report ✔ └─ reportSingleMessage(TestReporter) ✔ ....-..-..T..:...* value = `foo` Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-verbose-ascii.out.txt ================================================ Test plan execution started. Number of static tests: 1 . +-- JUnit Jupiter | +-- Report | | +-- reportSingleMessage(TestReporter) | | | tags: [] | | | uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleMessage(org.junit.jupiter.api.TestReporter)] | | | parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase] | | | source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$ReportTestCase', methodName = 'reportSingleMessage', methodParameterTypes = 'org.junit.jupiter.api.TestReporter'] \| \| \| reports: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'foo'\] \| \| \| duration: [\d]+ ms | | | status: [OK] SUCCESSFUL \| '-- Report finished after [\d]+ ms\. '-- JUnit Jupiter finished after [\d]+ ms\. Test plan execution finished. Number of all tests: 1 Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/report/Report-reportSingleMessage-verbose-unicode.out.txt ================================================ Test plan execution started. Number of static tests: 1 ╷ ├─ JUnit Jupiter │ ├─ Report │ │ ├─ reportSingleMessage(TestReporter) │ │ │ tags: [] │ │ │ uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase]/[method:reportSingleMessage(org.junit.jupiter.api.TestReporter)] │ │ │ parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$ReportTestCase] │ │ │ source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$ReportTestCase', methodName = 'reportSingleMessage', methodParameterTypes = 'org.junit.jupiter.api.TestReporter'] │ │ │ reports: ReportEntry \[timestamp = ....-..-..T..:...*, value = 'foo'\] │ │ │ duration: [\d]+ ms │ │ │ status: ✔ SUCCESSFUL │ └─ Report finished after [\d]+ ms\. └─ JUnit Jupiter finished after [\d]+ ms\. Test plan execution finished. Number of all tests: 1 Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 0 tests skipped ] [ 1 tests started ] [ 0 tests aborted ] [ 1 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-flat-ascii.out.txt ================================================ Test execution started. Number of static tests: 1 Started: JUnit Jupiter ([engine:junit-jupiter]) Started: Skip ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]) Skipped: skipWithMultiLineMessage() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]/[method:skipWithMultiLineMessage()]) => Reason: multi line fail message Finished: Skip ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]) Finished: JUnit Jupiter ([engine:junit-jupiter]) Test execution finished. Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 1 tests skipped ] [ 0 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-flat-unicode.out.txt ================================================ Test execution started. Number of static tests: 1 Started: JUnit Jupiter ([engine:junit-jupiter]) Started: Skip ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]) Skipped: skipWithMultiLineMessage() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]/[method:skipWithMultiLineMessage()]) => Reason: multi line fail message Finished: Skip ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]) Finished: JUnit Jupiter ([engine:junit-jupiter]) Test execution finished. Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 1 tests skipped ] [ 0 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-none-ascii.out.txt ================================================ ================================================ FILE: platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-none-unicode.out.txt ================================================ ================================================ FILE: platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-summary-ascii.out.txt ================================================ Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 1 tests skipped ] [ 0 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-summary-unicode.out.txt ================================================ Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 1 tests skipped ] [ 0 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-testfeed-ascii.out.txt ================================================ JUnit Jupiter > Skip > skipWithMultiLineMessage() :: SKIPPED Reason: multi line fail message Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 1 tests skipped ] [ 0 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-testfeed-unicode.out.txt ================================================ JUnit Jupiter > Skip > skipWithMultiLineMessage() :: SKIPPED Reason: multi line fail message Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 1 tests skipped ] [ 0 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-tree-ascii.out.txt ================================================ . '-- JUnit Jupiter [OK] '-- Skip [OK] '-- skipWithMultiLineMessage() [S] multi line fail message Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 1 tests skipped ] [ 0 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-tree-unicode.out.txt ================================================ ╷ └─ JUnit Jupiter ✔ └─ Skip ✔ └─ skipWithMultiLineMessage() ↷ multi line fail message Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 1 tests skipped ] [ 0 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-verbose-ascii.out.txt ================================================ Test plan execution started. Number of static tests: 1 . +-- JUnit Jupiter | +-- Skip | | +-- skipWithMultiLineMessage() | | | tags: [] | | | uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]/[method:skipWithMultiLineMessage()] | | | parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase] | | | source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$SkipTestCase', methodName = 'skipWithMultiLineMessage', methodParameterTypes = ''] | | | reason: multi | | | line | | | fail | | | message | | | status: [S] SKIPPED \| '-- Skip finished after [\d]+ ms\. '-- JUnit Jupiter finished after [\d]+ ms\. Test plan execution finished. Number of all tests: 1 Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 1 tests skipped ] [ 0 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/skip/Skip-skipWithMultiLineMessage-verbose-unicode.out.txt ================================================ Test plan execution started. Number of static tests: 1 ╷ ├─ JUnit Jupiter │ ├─ Skip │ │ ├─ skipWithMultiLineMessage() │ │ │ tags: [] │ │ │ uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]/[method:skipWithMultiLineMessage()] │ │ │ parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase] │ │ │ source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$SkipTestCase', methodName = 'skipWithMultiLineMessage', methodParameterTypes = ''] │ │ │ reason: multi │ │ │ line │ │ │ fail │ │ │ message │ │ │ status: ↷ SKIPPED │ └─ Skip finished after [\d]+ ms\. └─ JUnit Jupiter finished after [\d]+ ms\. Test plan execution finished. Number of all tests: 1 Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 1 tests skipped ] [ 0 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-flat-ascii.out.txt ================================================ Test execution started. Number of static tests: 1 Started: JUnit Jupiter ([engine:junit-jupiter]) Started: Skip ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]) Skipped: skipWithSingleLineReason() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]/[method:skipWithSingleLineReason()]) => Reason: single line skip reason Finished: Skip ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]) Finished: JUnit Jupiter ([engine:junit-jupiter]) Test execution finished. Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 1 tests skipped ] [ 0 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-flat-unicode.out.txt ================================================ Test execution started. Number of static tests: 1 Started: JUnit Jupiter ([engine:junit-jupiter]) Started: Skip ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]) Skipped: skipWithSingleLineReason() ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]/[method:skipWithSingleLineReason()]) => Reason: single line skip reason Finished: Skip ([engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]) Finished: JUnit Jupiter ([engine:junit-jupiter]) Test execution finished. Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 1 tests skipped ] [ 0 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-none-ascii.out.txt ================================================ ================================================ FILE: platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-none-unicode.out.txt ================================================ ================================================ FILE: platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-summary-ascii.out.txt ================================================ Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 1 tests skipped ] [ 0 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-summary-unicode.out.txt ================================================ Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 1 tests skipped ] [ 0 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-testfeed-ascii.out.txt ================================================ JUnit Jupiter > Skip > skipWithSingleLineReason() :: SKIPPED Reason: single line skip reason Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 1 tests skipped ] [ 0 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-testfeed-unicode.out.txt ================================================ JUnit Jupiter > Skip > skipWithSingleLineReason() :: SKIPPED Reason: single line skip reason Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 1 tests skipped ] [ 0 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-tree-ascii.out.txt ================================================ . '-- JUnit Jupiter [OK] '-- Skip [OK] '-- skipWithSingleLineReason() [S] single line skip reason Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 1 tests skipped ] [ 0 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-tree-unicode.out.txt ================================================ ╷ └─ JUnit Jupiter ✔ └─ Skip ✔ └─ skipWithSingleLineReason() ↷ single line skip reason Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 1 tests skipped ] [ 0 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-verbose-ascii.out.txt ================================================ Test plan execution started. Number of static tests: 1 . +-- JUnit Jupiter | +-- Skip | | +-- skipWithSingleLineReason() | | | tags: [] | | | uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]/[method:skipWithSingleLineReason()] | | | parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase] | | | source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$SkipTestCase', methodName = 'skipWithSingleLineReason', methodParameterTypes = ''] | | | reason: single line skip reason | | | status: [S] SKIPPED \| '-- Skip finished after [\d]+ ms\. '-- JUnit Jupiter finished after [\d]+ ms\. Test plan execution finished. Number of all tests: 1 Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 1 tests skipped ] [ 0 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/console/details/skip/Skip-skipWithSingleLineReason-verbose-unicode.out.txt ================================================ Test plan execution started. Number of static tests: 1 ╷ ├─ JUnit Jupiter │ ├─ Skip │ │ ├─ skipWithSingleLineReason() │ │ │ tags: [] │ │ │ uniqueId: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase]/[method:skipWithSingleLineReason()] │ │ │ parent: [engine:junit-jupiter]/[class:org.junit.platform.console.ConsoleDetailsTests$SkipTestCase] │ │ │ source: MethodSource [className = 'org.junit.platform.console.ConsoleDetailsTests$SkipTestCase', methodName = 'skipWithSingleLineReason', methodParameterTypes = ''] │ │ │ reason: single line skip reason │ │ │ status: ↷ SKIPPED │ └─ Skip finished after [\d]+ ms\. └─ JUnit Jupiter finished after [\d]+ ms\. Test plan execution finished. Number of all tests: 1 Test run finished after [\d]+ ms [ 2 containers found ] [ 0 containers skipped ] [ 2 containers started ] [ 0 containers aborted ] [ 2 containers successful ] [ 0 containers failed ] [ 1 tests found ] [ 1 tests skipped ] [ 0 tests started ] [ 0 tests aborted ] [ 0 tests successful ] [ 0 tests failed ] ================================================ FILE: platform-tests/src/test/resources/default-package.resource ================================================ This file was unintentionally left blank. ================================================ FILE: platform-tests/src/test/resources/do_not_delete_me.txt ================================================ I am used by tests, so... Do NOT delete me! ================================================ FILE: platform-tests/src/test/resources/error-engine/META-INF/services/org.junit.platform.engine.TestEngine ================================================ org.junit.platform.suite.engine.error.SelectorProcessingErrorCausingEngine ================================================ FILE: platform-tests/src/test/resources/intercepted-testservices/META-INF/services/org.junit.platform.engine.TestEngine ================================================ org.junit.platform.launcher.InterceptedTestEngine ================================================ FILE: platform-tests/src/test/resources/intercepted-testservices/META-INF/services/org.junit.platform.launcher.LauncherSessionListener ================================================ org.junit.platform.launcher.InterceptorInjectedLauncherSessionListener ================================================ FILE: platform-tests/src/test/resources/jenkins-junit.xsd ================================================ ================================================ FILE: platform-tests/src/test/resources/junit-platform.properties ================================================ junit.jupiter.extensions.autodetection.enabled=true junit.platform.stacktrace.pruning.enabled=false ================================================ FILE: platform-tests/src/test/resources/log4j2-test.xml ================================================ ================================================ FILE: platform-tests/src/test/resources/modules-2500/foo/Foo.java ================================================ package foo; public class Foo {} ================================================ FILE: platform-tests/src/test/resources/modules-2500/foo/module-info.java ================================================ module foo { exports foo; } ================================================ FILE: platform-tests/src/test/resources/modules-2500/foo.bar/FooBar.java ================================================ package foo.bar; public class FooBar {} ================================================ FILE: platform-tests/src/test/resources/modules-2500/foo.bar/module-info.java ================================================ open module foo.bar { requires foo; } ================================================ FILE: platform-tests/src/test/resources/org/junit/platform/commons/example.resource ================================================ This file was unintentionally left blank. ================================================ FILE: platform-tests/src/test/resources/org/junit/platform/commons/other-example.resource ================================================ This file was unintentionally left blank. ================================================ FILE: platform-tests/src/test/resources/test-junit-platform.properties ================================================ org.junit.platform.launcher.core.LauncherConfigurationParametersTests = from config file ================================================ FILE: platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener ================================================ org.junit.platform.launcher.TestLauncherDiscoveryListener ================================================ FILE: platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.LauncherInterceptor ================================================ org.junit.platform.launcher.TestLauncherInterceptor1 org.junit.platform.launcher.TestLauncherInterceptor2 ================================================ FILE: platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.LauncherSessionListener ================================================ org.junit.platform.launcher.TestLauncherSessionListener ================================================ FILE: platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.PostDiscoveryFilter ================================================ org.junit.platform.launcher.TestPostDiscoveryTagFilter ================================================ FILE: platform-tests/src/test/resources/testservices/META-INF/services/org.junit.platform.launcher.TestExecutionListener ================================================ org.junit.platform.launcher.listeners.NoopTestExecutionListener org.junit.platform.launcher.listeners.UnusedTestExecutionListener org.junit.platform.launcher.listeners.AnotherUnusedTestExecutionListener ================================================ FILE: platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts ================================================ import com.gradle.develocity.agent.gradle.internal.test.TestDistributionConfigurationInternal import junitbuild.extensions.capitalized import junitbuild.extensions.dependencyProject import junitbuild.extensions.javaModuleName import net.ltgt.gradle.errorprone.errorprone import org.gradle.api.tasks.PathSensitivity.RELATIVE import org.gradle.kotlin.dsl.support.listFilesOrdered import java.time.Duration plugins { id("junitbuild.build-parameters") id("junitbuild.kotlin-library-conventions") id("junitbuild.testing-conventions") } javaLibrary { mainJavaVersion = JavaVersion.VERSION_25 } spotless { java { target(files(project.java.sourceSets.map { it.allJava }), "projects/**/*.java") fileTree("projects/junit-start") { include("**/*.java") }.forEach { file -> suppressLintsFor { // due to compact source files and module imports path = file.toRelativeString(project.projectDir).replace('\\', '/') } } } format("moduleAndPackageInfo") { target("projects/**/module-info.java", "projects/**/package-info.java") } kotlin { target("projects/**/*.kt") } format("projects") { target("projects/**/*.gradle.kts", "projects/**/*.md") trimTrailingWhitespace() endWithNewline() } } val thirdPartyJars = configurations.dependencyScope("thirdPartyJars") val thirdPartyJarsClasspath = configurations.resolvable("thirdPartyJarsClasspath") { extendsFrom(thirdPartyJars.get()) } val antJars = configurations.dependencyScope("antJars") val antJarsClasspath = configurations.resolvable("antJarsClasspath") { extendsFrom(antJars.get()) } val mavenDistribution = configurations.dependencyScope("mavenDistribution") val mavenDistributionClasspath = configurations.resolvable("mavenDistributionClasspath") { extendsFrom(mavenDistribution.get()) } val modularProjects: List by rootProject dependencies { implementation(libs.commons.io) { because("moving/deleting directory trees") } implementation(projects.platformTests) { capabilities { requireFeature("process-starter") } } implementation(projects.junitJupiterApi) { because("it uses the OS enum to support Windows") } thirdPartyJars(libs.junit4) { exclude(group = "org.hamcrest") } thirdPartyJars(libs.assertj) thirdPartyJars(libs.apiguardian) thirdPartyJars(libs.fastcsv) thirdPartyJars(libs.hamcrest) thirdPartyJars(libs.jimfs) thirdPartyJars(libs.jspecify) thirdPartyJars(kotlin("stdlib")) thirdPartyJars(kotlin("reflect")) thirdPartyJars(libs.kotlinx.coroutines.core) thirdPartyJars(libs.opentest4j) thirdPartyJars(libs.openTestReporting.events) thirdPartyJars(libs.openTestReporting.tooling.spi) thirdPartyJars(libs.picocli) antJars(platform(projects.junitBom)) antJars(libs.bundles.ant) antJars(projects.junitPlatformConsoleStandalone) antJars(projects.junitPlatformLauncher) antJars(projects.junitPlatformReporting) mavenDistribution(libs.maven) { artifact { classifier = "bin" type = "zip" isTransitive = false } } } val mavenDistributionDir = layout.buildDirectory.dir("maven-distribution") val unzipMavenDistribution by tasks.registering(Sync::class) { from(zipTree(mavenDistributionClasspath.flatMap { d -> d.elements.map { e -> e.single() } })) into(mavenDistributionDir) } val normalizeMavenRepo by tasks.registering(Sync::class) { val mavenizedProjects: List by rootProject val tempRepoDir: File by rootProject val tempRepoName: String by rootProject // All maven-aware projects must be published to the local temp repository (mavenizedProjects + dependencyProject(projects.junitBom)) .map { project -> project.tasks.named("publishAllPublicationsTo${tempRepoName.capitalized()}Repository") } .forEach { dependsOn(it) } from(tempRepoDir) { exclude("**/maven-metadata.xml*") exclude("**/*.md5") exclude("**/*.sha*") exclude("**/*.module") } from(tempRepoDir) { include("**/*.module") val regex = "\"(sha\\d+|md5|size)\": (?:\".+\"|\\d+)(,)?".toRegex() filter { line -> regex.replace(line, "\"normalized-$1\": \"normalized-value\"$2") } } rename("(.*\\W)\\d{8}\\.\\d{6}-\\d+(\\W.*)", "$1SNAPSHOT$2") into(layout.buildDirectory.dir("normalized-repo")) } val archUnit by testing.suites.registering(JvmTestSuite::class) { dependencies { implementation(libs.archunit) { because("checking the architecture") } implementation(libs.apiguardian) { because("we validate that public classes are annotated") } implementation(libs.jspecify) { because("we validate that packages are annotated") } implementation(libs.assertj) runtimeOnly.bundle(libs.bundles.log4j) modularProjects.forEach { implementation(project(it.path)) } } targets { all { testTask.configure { useJUnitPlatform() (options as JUnitPlatformOptions).apply { includeEngines("archunit") excludeEngines("junit-jupiter") } develocity { testRetry.maxRetries = 0 testDistribution.enabled = false predictiveTestSelection.enabled = false } } } } } tasks.compileJava { options.errorprone { disableAllChecks = true } } tasks.named("checkstyle${archUnit.name.capitalized()}").configure { config = resources.text.fromFile(checkstyle.configDirectory.file("checkstyleTest.xml")) } tasks.check { dependsOn(archUnit) } val test by testing.suites.getting(JvmTestSuite::class) { dependencies { implementation(libs.bndlib) { because("parsing OSGi metadata") } runtimeOnly(libs.slf4j.julBinding) { because("provide appropriate SLF4J binding") } implementation(libs.ant) { because("we reference Ant's main class") } implementation.bundle(libs.bundles.xmlunit) implementation(testFixtures(projects.junitJupiterApi)) implementation(testFixtures(projects.junitPlatformReporting)) implementation(libs.snapshotTests.junit5) implementation(libs.snapshotTests.xml) } targets { all { testTask.configure { shouldRunAfter(archUnit) // Opt-out via system property: '-Dplatform.tooling.support.tests.enabled=false' enabled = System.getProperty("platform.tooling.support.tests.enabled")?.toBoolean() ?: true // The following if-block is necessary since Gradle will otherwise // always publish all mavenizedProjects even if this "test" task // is not executed. if (enabled) { dependsOn(normalizeMavenRepo) jvmArgumentProviders += MavenRepo(project, normalizeMavenRepo.map { it.destinationDir }) } environment.remove("JAVA_TOOL_OPTIONS") jvmArgumentProviders += JarPath(project, thirdPartyJarsClasspath.get(), "thirdPartyJars") jvmArgumentProviders += JarPath(project, antJarsClasspath.get(), "antJars") jvmArgumentProviders += MavenDistribution(project, unzipMavenDistribution, mavenDistributionDir) systemProperty("junit.modules", modularProjects.map { it.javaModuleName }.joinToString(",")) jvmArgumentProviders += CommandLineArgumentProvider { modularProjects.map { "-Djunit.moduleSourcePath.${it.javaModuleName}=${it.sourceSets["main"].allJava.sourceDirectories.filter { it.exists() }.asPath}" } } inputs.apply { dir("projects").withPathSensitivity(RELATIVE) file("${rootDir}/gradle.properties").withPathSensitivity(RELATIVE) file("${rootDir}/settings.gradle.kts").withPathSensitivity(RELATIVE) file("${rootDir}/gradlew").withPathSensitivity(RELATIVE) file("${rootDir}/gradlew.bat").withPathSensitivity(RELATIVE) dir("${rootDir}/gradle/wrapper").withPathSensitivity(RELATIVE) dir("${rootDir}/documentation/src/main").withPathSensitivity(RELATIVE) dir("${rootDir}/documentation/src/test").withPathSensitivity(RELATIVE) } // Disable capturing output since parallel execution is enabled and output of // external processes happens on non-test threads which can't reliably be // attributed to the test that started the process. systemProperty("junit.platform.output.capture.stdout", "false") systemProperty("junit.platform.output.capture.stderr", "false") develocity { testDistribution { requirements.add("jdk=17") this as TestDistributionConfigurationInternal preferredMaxDuration = Duration.ofMillis(500) } } jvmArgumentProviders += JavaHomeDir(project, 17, develocity.testDistribution.enabled) val gradleJavaVersion = JavaVersion.current().majorVersion.toInt() jvmArgumentProviders += JavaHomeDir(project, gradleJavaVersion, develocity.testDistribution.enabled) systemProperty("gradle.java.version", gradleJavaVersion) } } } } class MavenRepo(project: Project, @get:Internal val repoDir: Provider) : CommandLineArgumentProvider { // Track jars and non-jars separately to benefit from runtime classpath normalization // which ignores timestamp manifest attributes. @InputFiles @Classpath val jarFiles: ConfigurableFileTree = project.fileTree(repoDir) { include("**/*.jar") } @InputFiles @PathSensitive(RELATIVE) val nonJarFiles: ConfigurableFileTree = project.fileTree(repoDir) { exclude("**/*.jar") } override fun asArguments() = listOf("-Dmaven.repo=${repoDir.get().absolutePath}") } class JavaHomeDir(project: Project, @Input val version: Int, testDistributionEnabled: Provider) : CommandLineArgumentProvider { @Internal val javaLauncher: Property = project.objects.property() .value(project.provider { try { project.javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(version) }.get() } catch (e: Exception) { null } }) @Internal val enabled: Property = project.objects.property().convention(testDistributionEnabled.map { !it }) override fun asArguments(): List { if (!enabled.get()) { return emptyList() } val metadata = javaLauncher.map { it.metadata } val javaHome = metadata.map { it.installationPath.asFile.absolutePath }.orNull return javaHome?.let { listOf("-Djava.home.$version=$it") } ?: emptyList() } } class JarPath(project: Project, configuration: Configuration, @Input val key: String = configuration.name) : CommandLineArgumentProvider { @get:Classpath val files: ConfigurableFileCollection = project.objects.fileCollection().from(configuration) override fun asArguments() = listOf("-D${key}=${files.asPath}") } class MavenDistribution(project: Project, sourceTask: TaskProvider<*>, distributionDir: Provider) : CommandLineArgumentProvider { @InputDirectory @PathSensitive(RELATIVE) val mavenDistribution: DirectoryProperty = project.objects.directoryProperty() .fileProvider(project.files(distributionDir).builtBy(sourceTask).elements.map { it.single().asFile.listFilesOrdered().single() }) override fun asArguments() = listOf("-DmavenDistribution=${mavenDistribution.get().asFile.absolutePath}") } ================================================ FILE: platform-tooling-support-tests/projects/graalvm-starter/build.gradle.kts ================================================ plugins { java id("org.graalvm.buildtools.native") } val junitVersion: String by project dependencies { testImplementation("org.junit.jupiter:junit-jupiter:$junitVersion") testImplementation("junit:junit:4.13.2") testImplementation("org.junit.platform:junit-platform-suite:$junitVersion") testRuntimeOnly("org.junit.vintage:junit-vintage-engine:$junitVersion") testRuntimeOnly("org.junit.platform:junit-platform-reporting:$junitVersion") } tasks.withType().configureEach { options.release = 21 } tasks.test { useJUnitPlatform { includeEngines("junit-platform-suite") } val outputDir = reports.junitXml.outputLocation jvmArgumentProviders += CommandLineArgumentProvider { listOf( "-Djunit.platform.reporting.open.xml.enabled=true", "-Djunit.platform.reporting.output.dir=${outputDir.get().asFile.absolutePath}" ) } } val initializeAtBuildTime = mapOf( // These need to be added to native-build-tools "5.14.1" to listOf( // https://github.com/graalvm/native-build-tools/pull/794 "org.junit.jupiter.engine.discovery.MethodSegmentResolver" ), "6.1" to listOf( // https://github.com/graalvm/native-build-tools/pull/867 "org.junit.platform.launcher.core.DiscoveryIssueReportingDiscoveryListener", ), ) graalvmNative { metadataRepository { enabled = false } binaries { named("test") { buildArgs.add("-H:+ReportExceptionStackTraces") val classNames = initializeAtBuildTime.values.flatten() if (classNames.isNotEmpty()) { buildArgs.add("--initialize-at-build-time=${classNames.joinToString(",")}") } } } } ================================================ FILE: platform-tooling-support-tests/projects/graalvm-starter/settings.gradle.kts ================================================ pluginManagement { plugins { // TODO Check if classes can be removed from `initializeAtBuildTime` in build.gradle.kts when upgrading id("org.graalvm.buildtools.native") version "1.1.0" } repositories { mavenCentral() gradlePluginPortal() maven(url = "https://raw.githubusercontent.com/graalvm/native-build-tools/snapshots") { mavenContent { snapshotsOnly() } } } } plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } dependencyResolutionManagement { repositories { repositories { maven { url = uri(file(System.getProperty("maven.repo"))) } mavenCentral() maven(url = "https://raw.githubusercontent.com/graalvm/native-build-tools/snapshots") { mavenContent { snapshotsOnly() } } } } } rootProject.name = "graalvm-starter" ================================================ FILE: platform-tooling-support-tests/projects/graalvm-starter/src/main/java/com/example/project/Calculator.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package com.example.project; public class Calculator { public int add(int a, int b) { return a + b; } } ================================================ FILE: platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/CalculatorParameterizedClassTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package com.example.project; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.Parameter; import org.junit.jupiter.params.ParameterizedClass; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; @ParameterizedClass @ValueSource(ints = { 1, 2 }) class CalculatorParameterizedClassTests { @Parameter int i; @ParameterizedTest @ValueSource(ints = { 1, 2 }) void parameterizedTest(int j) { Calculator calculator = new Calculator(); assertEquals(i + j, calculator.add(i, j)); } @Nested @ParameterizedClass @ValueSource(ints = { 1, 2 }) @Disabled("https://github.com/junit-team/junit-framework/issues/4440") class Inner { final int j; Inner(int j) { this.j = j; } @Test void regularTest() { Calculator calculator = new Calculator(); assertEquals(i + j, calculator.add(i, j)); } } } ================================================ FILE: platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/CalculatorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package com.example.project; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; class CalculatorTests { @Test @DisplayName("1 + 1 = 2") void addsTwoNumbers() { Calculator calculator = new Calculator(); assertEquals(2, calculator.add(1, 1), "1 + 1 should equal 2"); } @ParameterizedTest(name = "{0} + {1} = {2}", quoteTextArguments = false) @CsvSource({ // "0, 1, 1", // "1, 2, 3", // "49, 51, 100", // "1, 100, 101" // }) void add(int first, int second, int expectedResult) { Calculator calculator = new Calculator(); assertEquals(expectedResult, calculator.add(first, second), () -> first + " + " + second + " should equal " + expectedResult); } } ================================================ FILE: platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/ClassLevelAnnotationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package com.example.project; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.IndicativeSentencesGeneration; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledInNativeImage; @EnabledInNativeImage @IndicativeSentencesGeneration(generator = DisplayNameGenerator.ReplaceUnderscores.class) class ClassLevelAnnotationTests { @Nested class Inner { @Test void test() { } } } ================================================ FILE: platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/GraalvmSuite.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package com.example.project; import org.junit.platform.suite.api.*; @Suite @SelectPackages("com.example.project") public class GraalvmSuite { } ================================================ FILE: platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/VintageTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package com.example.project; import org.junit.Test; public class VintageTests { @Test public void test() { } } ================================================ FILE: platform-tooling-support-tests/projects/graalvm-starter/src/test/resources/junit-platform.properties ================================================ junit.platform.stacktrace.pruning.enabled=false ================================================ FILE: platform-tooling-support-tests/projects/gradle-kotlin-extensions/build.gradle.kts ================================================ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 import org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_1 plugins { kotlin("jvm") version "2.3.21" } repositories { maven { url = uri(file(System.getProperty("maven.repo"))) } mavenCentral() } val junitVersion: String by project dependencies { testImplementation(kotlin("stdlib")) testImplementation("org.junit.jupiter:junit-jupiter:$junitVersion") testRuntimeOnly("org.junit.platform:junit-platform-launcher:$junitVersion") } java { toolchain { languageVersion = JavaLanguageVersion.of(17) } } tasks.withType().configureEach { compilerOptions { jvmTarget = JVM_17 apiVersion = KOTLIN_2_1 languageVersion = KOTLIN_2_1 freeCompilerArgs.addAll("-Xskip-prerelease-check") } } tasks.test { useJUnitPlatform() testLogging { events("passed", "skipped", "failed") } } ================================================ FILE: platform-tooling-support-tests/projects/gradle-kotlin-extensions/gradle.properties ================================================ org.gradle.java.installations.fromEnv=JDK17 ================================================ FILE: platform-tooling-support-tests/projects/gradle-kotlin-extensions/settings.gradle.kts ================================================ plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } rootProject.name = "gradle-kotlin-extensions" ================================================ FILE: platform-tooling-support-tests/projects/gradle-kotlin-extensions/src/test/kotlin/com/example/project/ExtensionFunctionsTests.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package com.example.project import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertAll import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.fail import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.aggregator.ArgumentsAccessor import org.junit.jupiter.params.aggregator.get import org.junit.jupiter.params.provider.CsvSource import org.opentest4j.AssertionFailedError class ExtensionFunctionsTests { @Test fun `assertDoesNotThrow() and assertAll`() { assertDoesNotThrow { assertAll(setOf {}) } assertDoesNotThrow("message") { assertAll("header", setOf {}) } assertDoesNotThrow({ "message" }) { assertAll({}) assertAll("header", {}) } } @Test fun `fail() and assertThrows`() { assertThrows { fail("message") } assertThrows("message") { fail { "message" } } assertThrows({ "message" }) { fail(IllegalArgumentException()) } } @ParameterizedTest @CsvSource("1") fun accessor(accessor: ArgumentsAccessor) { val value = accessor.get(0) assertEquals(1, value) } } ================================================ FILE: platform-tooling-support-tests/projects/gradle-missing-engine/build.gradle.kts ================================================ plugins { java } val junitVersion: String by project repositories { maven { url = uri(file(System.getProperty("maven.repo"))) } mavenCentral() } dependencies { testImplementation("org.junit.jupiter:junit-jupiter-api:$junitVersion") { exclude(group = "org.junit.jupiter", module = "junit-jupiter-engine") } testRuntimeOnly("org.junit.platform:junit-platform-launcher:$junitVersion")} java { toolchain { languageVersion = JavaLanguageVersion.of(17) } } tasks.test { useJUnitPlatform() } ================================================ FILE: platform-tooling-support-tests/projects/gradle-missing-engine/gradle.properties ================================================ org.gradle.java.installations.fromEnv=JDK17 ================================================ FILE: platform-tooling-support-tests/projects/gradle-missing-engine/settings.gradle.kts ================================================ plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } rootProject.name = "gradle-missing-engine" ================================================ FILE: platform-tooling-support-tests/projects/gradle-missing-engine/src/test/java/FooTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ import static org.junit.jupiter.api.Assertions.fail; import org.junit.jupiter.api.Test; class FooTests { @Test void test() { fail("This test must not be executed!"); } } ================================================ FILE: platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-api.expected.txt ================================================ org.junit.jupiter.api@${version} jar:file:.+/junit-jupiter-api-\d.+\.jar..module-info\.class exports org.junit.jupiter.api exports org.junit.jupiter.api.condition exports org.junit.jupiter.api.extension exports org.junit.jupiter.api.extension.support exports org.junit.jupiter.api.function exports org.junit.jupiter.api.io exports org.junit.jupiter.api.parallel exports org.junit.jupiter.api.util requires java.base mandated requires kotlin.stdlib static requires org.apiguardian.api static transitive requires org.jspecify static transitive requires org.junit.platform.commons transitive requires org.opentest4j transitive qualified exports org.junit.jupiter.api.timeout to org.junit.jupiter.engine qualified opens org.junit.jupiter.api.condition to org.junit.platform.commons qualified opens org.junit.jupiter.api.util to org.junit.platform.commons ================================================ FILE: platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-engine.expected.txt ================================================ org.junit.jupiter.engine@${version} jar:file:.+/junit-jupiter-engine-\d.+\.jar..module-info\.class requires java.base mandated requires org.apiguardian.api static requires org.jspecify static transitive requires org.junit.jupiter.api requires org.junit.platform.commons requires org.junit.platform.engine requires org.opentest4j uses org.junit.jupiter.api.extension.Extension provides org.junit.platform.engine.TestEngine with org.junit.jupiter.engine.JupiterTestEngine qualified opens org.junit.jupiter.engine.extension to org.junit.platform.commons contains org.junit.jupiter.engine ================================================ FILE: platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-migrationsupport.expected.txt ================================================ org.junit.jupiter.migrationsupport@${version} jar:file:.+/junit-jupiter-migrationsupport-\d.+\.jar..module-info\.class exports org.junit.jupiter.migrationsupport exports org.junit.jupiter.migrationsupport.conditions exports org.junit.jupiter.migrationsupport.rules exports org.junit.jupiter.migrationsupport.rules.adapter exports org.junit.jupiter.migrationsupport.rules.member requires java.base mandated requires junit transitive requires org.apiguardian.api static transitive requires org.jspecify static transitive requires org.junit.jupiter.api transitive requires org.junit.platform.commons ================================================ FILE: platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-params.expected.txt ================================================ org.junit.jupiter.params@${version} jar:file:.+/junit-jupiter-params-\d.+\.jar..module-info\.class exports org.junit.jupiter.params exports org.junit.jupiter.params.aggregator exports org.junit.jupiter.params.converter exports org.junit.jupiter.params.provider exports org.junit.jupiter.params.support requires java.base mandated requires org.apiguardian.api static transitive requires org.jspecify static transitive requires org.junit.jupiter.api transitive requires org.junit.platform.commons transitive qualified opens org.junit.jupiter.params to org.junit.platform.commons qualified opens org.junit.jupiter.params.converter to org.junit.platform.commons qualified opens org.junit.jupiter.params.provider to org.junit.platform.commons ================================================ FILE: platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter.expected.txt ================================================ org.junit.jupiter@${version} jar:file:.+/junit-jupiter-\d.+\.jar..module-info\.class requires java.base mandated requires org.junit.jupiter.api transitive requires org.junit.jupiter.engine transitive requires org.junit.jupiter.params transitive ================================================ FILE: platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt ================================================ org.junit.platform.commons@${version} jar:file:.+/junit-platform-commons-\d.+\.jar..module-info\.class exports org.junit.platform.commons exports org.junit.platform.commons.annotation exports org.junit.platform.commons.function exports org.junit.platform.commons.io exports org.junit.platform.commons.support exports org.junit.platform.commons.support.conversion exports org.junit.platform.commons.support.scanning requires java.base mandated requires java.logging requires java.management requires kotlin.reflect static requires kotlin.stdlib static requires kotlinx.coroutines.core static requires org.apiguardian.api static transitive requires org.jspecify static transitive uses org.junit.platform.commons.support.scanning.ClasspathScanner qualified exports org.junit.platform.commons.logging to org.junit.jupiter.api org.junit.jupiter.engine org.junit.jupiter.migrationsupport org.junit.jupiter.params org.junit.platform.console org.junit.platform.engine org.junit.platform.launcher org.junit.platform.reporting org.junit.platform.suite.api org.junit.platform.suite.engine org.junit.platform.testkit org.junit.vintage.engine qualified exports org.junit.platform.commons.util to org.junit.jupiter.api org.junit.jupiter.engine org.junit.jupiter.migrationsupport org.junit.jupiter.params org.junit.platform.console org.junit.platform.engine org.junit.platform.launcher org.junit.platform.reporting org.junit.platform.suite.api org.junit.platform.suite.engine org.junit.platform.testkit org.junit.vintage.engine ================================================ FILE: platform-tooling-support-tests/projects/jar-describe-module/junit-platform-console.expected.txt ================================================ org.junit.platform.console@${version} jar:file:.+/junit-platform-console-\d.+\.jar..module-info\.class requires java.base mandated requires org.apiguardian.api static requires org.jspecify static transitive requires org.junit.platform.commons requires org.junit.platform.engine requires org.junit.platform.launcher requires org.junit.platform.reporting provides java.util.spi.ToolProvider with org.junit.platform.console.ConsoleLauncherToolProvider qualified exports org.junit.platform.console.output to org.junit.start contains org.junit.platform.console contains org.junit.platform.console.command contains org.junit.platform.console.options contains org.junit.platform.console.shadow.picocli main-class org.junit.platform.console.ConsoleLauncher ================================================ FILE: platform-tooling-support-tests/projects/jar-describe-module/junit-platform-engine.expected.txt ================================================ org.junit.platform.engine@${version} jar:file:.+/junit-platform-engine-\d.+\.jar..module-info\.class exports org.junit.platform.engine exports org.junit.platform.engine.discovery exports org.junit.platform.engine.reporting exports org.junit.platform.engine.support.config exports org.junit.platform.engine.support.descriptor exports org.junit.platform.engine.support.discovery exports org.junit.platform.engine.support.hierarchical exports org.junit.platform.engine.support.store requires java.base mandated requires org.apiguardian.api static transitive requires org.jspecify static transitive requires org.junit.platform.commons transitive requires org.opentest4j transitive uses org.junit.platform.engine.discovery.DiscoverySelectorIdentifierParser provides org.junit.platform.engine.discovery.DiscoverySelectorIdentifierParser with org.junit.platform.engine.discovery.ClassSelector$IdentifierParser org.junit.platform.engine.discovery.ClasspathResourceSelector$IdentifierParser org.junit.platform.engine.discovery.ClasspathRootSelector$IdentifierParser org.junit.platform.engine.discovery.DirectorySelector$IdentifierParser org.junit.platform.engine.discovery.FileSelector$IdentifierParser org.junit.platform.engine.discovery.IterationSelector$IdentifierParser org.junit.platform.engine.discovery.MethodSelector$IdentifierParser org.junit.platform.engine.discovery.ModuleSelector$IdentifierParser org.junit.platform.engine.discovery.NestedClassSelector$IdentifierParser org.junit.platform.engine.discovery.NestedMethodSelector$IdentifierParser org.junit.platform.engine.discovery.PackageSelector$IdentifierParser org.junit.platform.engine.discovery.UniqueIdSelector$IdentifierParser org.junit.platform.engine.discovery.UriSelector$IdentifierParser ================================================ FILE: platform-tooling-support-tests/projects/jar-describe-module/junit-platform-launcher.expected.txt ================================================ org.junit.platform.launcher@${version} jar:file:.+/junit-platform-launcher-\d.+\.jar..module-info\.class exports org.junit.platform.launcher exports org.junit.platform.launcher.core exports org.junit.platform.launcher.listeners exports org.junit.platform.launcher.listeners.discovery requires java.base mandated requires java.logging transitive requires jdk.jfr static requires org.apiguardian.api static transitive requires org.jspecify static transitive requires org.junit.platform.commons transitive requires org.junit.platform.engine transitive uses org.junit.platform.engine.TestEngine uses org.junit.platform.launcher.LauncherDiscoveryListener uses org.junit.platform.launcher.LauncherInterceptor uses org.junit.platform.launcher.LauncherSessionListener uses org.junit.platform.launcher.PostDiscoveryFilter uses org.junit.platform.launcher.TestExecutionListener provides org.junit.platform.launcher.TestExecutionListener with org.junit.platform.launcher.listeners.UniqueIdTrackingListener ================================================ FILE: platform-tooling-support-tests/projects/jar-describe-module/junit-platform-reporting.expected.txt ================================================ org.junit.platform.reporting@${version} jar:file:.+/junit-platform-reporting-\d.+\.jar..module-info\.class exports org.junit.platform.reporting.legacy exports org.junit.platform.reporting.legacy.xml exports org.junit.platform.reporting.open.xml requires java.base mandated requires java.xml requires org.apiguardian.api static transitive requires org.jspecify static transitive requires org.junit.platform.commons requires org.junit.platform.engine transitive requires org.junit.platform.launcher transitive requires org.opentest4j.reporting.tooling.spi provides org.junit.platform.launcher.TestExecutionListener with org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener provides org.opentest4j.reporting.tooling.spi.htmlreport.Contributor with org.junit.platform.reporting.open.xml.JUnitContributor ================================================ FILE: platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-api.expected.txt ================================================ org.junit.platform.suite.api@${version} jar:file:.+/junit-platform-suite-api-\d.+\.jar..module-info\.class exports org.junit.platform.suite.api requires java.base mandated requires org.apiguardian.api static transitive requires org.jspecify static transitive requires org.junit.platform.commons transitive ================================================ FILE: platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-engine.expected.txt ================================================ org.junit.platform.suite.engine@${version} jar:file:.+/junit-platform-suite-engine-\d.+\.jar..module-info\.class requires java.base mandated requires org.apiguardian.api static requires org.jspecify static transitive requires org.junit.platform.commons requires org.junit.platform.engine requires org.junit.platform.launcher requires org.junit.platform.suite.api provides org.junit.platform.engine.TestEngine with org.junit.platform.suite.engine.SuiteTestEngine contains org.junit.platform.suite.engine ================================================ FILE: platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite.expected.txt ================================================ org.junit.platform.suite@${version} jar:file:.+/junit-platform-suite-\d.+\.jar..module-info\.class requires java.base mandated requires org.junit.platform.suite.api transitive requires org.junit.platform.suite.engine transitive ================================================ FILE: platform-tooling-support-tests/projects/jar-describe-module/junit-platform-testkit.expected.txt ================================================ org.junit.platform.testkit@${version} jar:file:.+/junit-platform-testkit-\d.+\.jar..module-info\.class exports org.junit.platform.testkit.engine requires java.base mandated requires org.apiguardian.api static transitive requires org.assertj.core transitive requires org.jspecify static transitive requires org.junit.platform.commons requires org.junit.platform.engine transitive requires org.junit.platform.launcher transitive requires org.opentest4j transitive uses org.junit.platform.engine.TestEngine ================================================ FILE: platform-tooling-support-tests/projects/jar-describe-module/junit-start.expected.txt ================================================ org.junit.start@${version} jar:file:.+/junit-start-\d.+\.jar..module-info\.class exports org.junit.start requires java.base mandated requires org.apiguardian.api static transitive requires org.jspecify static transitive requires org.junit.jupiter transitive requires org.junit.platform.console requires org.junit.platform.launcher ================================================ FILE: platform-tooling-support-tests/projects/jar-describe-module/junit-vintage-engine.expected.txt ================================================ org.junit.vintage.engine@${version} jar:file:.+/junit-vintage-engine-\d.+\.jar..module-info\.class requires java.base mandated requires junit requires org.apiguardian.api static requires org.jspecify static transitive requires org.junit.platform.engine provides org.junit.platform.engine.TestEngine with org.junit.vintage.engine.VintageTestEngine contains org.junit.vintage.engine ================================================ FILE: platform-tooling-support-tests/projects/junit-start/compact/JUnitRun.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ import module org.junit.start; void main() { JUnit.run(); } @Test void addition() { Assertions.assertEquals(2, 1 + 1, "Addition error detected!"); } ================================================ FILE: platform-tooling-support-tests/projects/junit-start/compact/JUnitRunClass.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ import module org.junit.start; void main() { JUnit.run(getClass()); } @Test void substraction() { Assertions.assertEquals(2, 3 - 1, "Subtraction error detected!"); } ================================================ FILE: platform-tooling-support-tests/projects/junit-start/modular/module-info.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ open module m { requires org.junit.start; } ================================================ FILE: platform-tooling-support-tests/projects/junit-start/modular/p/JUnitRunModule.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package p; import module org.junit.start; class JUnitRunModule { void main() { JUnit.run(getClass().getModule()); } } ================================================ FILE: platform-tooling-support-tests/projects/junit-start/modular/p/MultiplicationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package p; import module org.junit.jupiter.api; class MultiplicationTests { @Test void multiplication() { Assertions.assertEquals(4, 2 * 2, "Multiplication error detected!"); } } ================================================ FILE: platform-tooling-support-tests/projects/jupiter-starter/build.gradle.kts ================================================ plugins { java } val junitVersion: String by project repositories { maven { url = uri(file(System.getProperty("maven.repo"))) } mavenCentral() } dependencies { testImplementation("org.junit.jupiter:junit-jupiter:$junitVersion") testRuntimeOnly("org.junit.platform:junit-platform-reporting:$junitVersion") } java { toolchain { languageVersion = JavaLanguageVersion.of(System.getProperty("java.toolchain.version")) } } tasks.test { useJUnitPlatform() testLogging { events("passed", "skipped", "failed", "standardOut") exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL } reports { html.required = true } val outputDir = reports.junitXml.outputLocation jvmArgumentProviders += CommandLineArgumentProvider { listOf( "-Djunit.platform.reporting.open.xml.enabled=true", "-Djunit.platform.reporting.output.dir=${outputDir.get().asFile.absolutePath}" ) } } ================================================ FILE: platform-tooling-support-tests/projects/jupiter-starter/build.xml ================================================ ================================================ FILE: platform-tooling-support-tests/projects/jupiter-starter/gradle.properties ================================================ org.gradle.java.installations.fromEnv=JDK17 ================================================ FILE: platform-tooling-support-tests/projects/jupiter-starter/pom.xml ================================================ 4.0.0 com.example maven-starter 1.0-SNAPSHOT UTF-8 17 ${junit.version} org.junit.platform junit-platform-commons ${junit.platform.commons.version} test org.junit.jupiter junit-jupiter test org.junit.platform junit-platform-reporting test org.junit junit-bom ${junit.version} pom import maven-compiler-plugin 3.15.0 maven-surefire-plugin 3.5.5 junit.platform.reporting.open.xml.enabled = true junit.platform.reporting.output.dir = target/surefire-reports local-temp file://${maven.repo} true ignore true ignore snapshots-repo ${snapshot.repo.url} true false ================================================ FILE: platform-tooling-support-tests/projects/jupiter-starter/settings.gradle.kts ================================================ plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } rootProject.name = "gradle-starter" ================================================ FILE: platform-tooling-support-tests/projects/jupiter-starter/src/main/java/com/example/project/Calculator.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package com.example.project; public class Calculator { public int add(int a, int b) { return a + b; } } ================================================ FILE: platform-tooling-support-tests/projects/jupiter-starter/src/test/java/com/example/project/CalculatorParameterizedClassTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package com.example.project; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.Parameter; import org.junit.jupiter.params.ParameterizedClass; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; @ParameterizedClass @ValueSource(ints = { 1, 2 }) class CalculatorParameterizedClassTests { @Parameter int i; @ParameterizedTest @ValueSource(ints = { 1, 2 }) void parameterizedTest(int j) { Calculator calculator = new Calculator(); assertEquals(i + j, calculator.add(i, j)); } @Nested @ParameterizedClass @ValueSource(ints = { 1, 2 }) class Inner { final int j; Inner(int j) { this.j = j; } @Test void regularTest() { Calculator calculator = new Calculator(); assertEquals(i + j, calculator.add(i, j)); } } } ================================================ FILE: platform-tooling-support-tests/projects/jupiter-starter/src/test/java/com/example/project/CalculatorTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package com.example.project; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; class CalculatorTests { @BeforeAll static void printJavaVersion() { System.out.println("Using Java version: " + System.getProperty("java.specification.version")); } @Test @DisplayName("1 + 1 = 2") void addsTwoNumbers() { Calculator calculator = new Calculator(); assertEquals(2, calculator.add(1, 1), "1 + 1 should equal 2"); } @ParameterizedTest(name = "{0} + {1} = {2}", quoteTextArguments = false) @CsvSource({ // "0, 1, 1", // "1, 2, 3", // "49, 51, 100", // "1, 100, 101" // }) void add(int first, int second, int expectedResult) { Calculator calculator = new Calculator(); assertEquals(expectedResult, calculator.add(first, second), () -> first + " + " + second + " should equal " + expectedResult); } } ================================================ FILE: platform-tooling-support-tests/projects/jupiter-starter/src/test/resources/junit-platform.properties ================================================ junit.jupiter.testclass.order.default = \ org.junit.jupiter.api.ClassOrderer$ClassName junit.platform.stacktrace.pruning.enabled = false ================================================ FILE: platform-tooling-support-tests/projects/kotlin-coroutines/build.gradle.kts ================================================ plugins { kotlin("jvm") version "2.3.21" } val junitVersion: String by project repositories { maven { url = uri(file(System.getProperty("maven.repo"))) } mavenCentral() } dependencies { testImplementation("org.junit.jupiter:junit-jupiter:$junitVersion") testRuntimeOnly("org.junit.platform:junit-platform-launcher") if (!project.hasProperty("withoutKotlinReflect")) { testImplementation(kotlin("reflect")) } if (!project.hasProperty("withoutKotlinxCoroutines")) { testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0") } } java { toolchain { languageVersion = JavaLanguageVersion.of(17) } } tasks.test { useJUnitPlatform() testLogging { exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL } systemProperty("junit.platform.stacktrace.pruning.enabled", "false") } ================================================ FILE: platform-tooling-support-tests/projects/kotlin-coroutines/settings.gradle.kts ================================================ plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } rootProject.name = "kotlin-coroutines" ================================================ FILE: platform-tooling-support-tests/projects/kotlin-coroutines/src/test/kotlin/com/example/project/SuspendFunctionTests.kt ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package com.example.project import org.junit.jupiter.api.Test import org.junit.jupiter.api.fail class SuspendFunctionTests { @Test suspend fun test() { fail("expected") } } ================================================ FILE: platform-tooling-support-tests/projects/maven-surefire-compatibility/pom.xml ================================================ 4.0.0 com.example maven-surefire-compatibility 1.0-SNAPSHOT UTF-8 17 org.junit.jupiter junit-jupiter test org.junit junit-bom ${junit.version} pom import maven-compiler-plugin 3.15.0 maven-surefire-plugin ${surefire.version} junit.platform.listeners.uid.tracking.enabled = true local-temp file://${maven.repo} true ignore true ignore ================================================ FILE: platform-tooling-support-tests/projects/maven-surefire-compatibility/src/test/java/com/example/project/DummyTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package com.example.project; import org.junit.jupiter.api.Test; class DummyTests { @Test void test() { } } ================================================ FILE: platform-tooling-support-tests/projects/memory-cleanup/src/OneMillionTests.java ================================================ /* * Copyright 2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import java.util.stream.IntStream; import java.util.stream.Stream; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.TestFactory; class OneMillionTests { @TestFactory Stream tests() { return IntStream.range(0, 1_000_000) // .mapToObj(i -> dynamicTest("test " + i, () -> { assertTrue(i + 1 < 1_000_000); // fail last test })); } } ================================================ FILE: platform-tooling-support-tests/projects/reflection-tests/build.gradle.kts ================================================ plugins { java } val junitVersion: String by project repositories { maven { url = uri(file(System.getProperty("maven.repo"))) } mavenCentral() } dependencies { testImplementation("org.junit.jupiter:junit-jupiter:$junitVersion") testImplementation("org.junit.jupiter:junit-jupiter-engine:$junitVersion") testRuntimeOnly("org.junit.platform:junit-platform-launcher:$junitVersion") } java { toolchain { languageVersion = JavaLanguageVersion.of(17) } } tasks.test { useJUnitPlatform() testLogging { events("failed", "standardOut") } reports { html.required = true } } ================================================ FILE: platform-tooling-support-tests/projects/reflection-tests/gradle.properties ================================================ org.gradle.java.installations.fromEnv=JDK17 ================================================ FILE: platform-tooling-support-tests/projects/reflection-tests/settings.gradle.kts ================================================ plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } rootProject.name = "reflection-tests" ================================================ FILE: platform-tooling-support-tests/projects/reflection-tests/src/test/java/ReflectionTestCase.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package standalone; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import java.util.Arrays; import java.util.stream.Stream; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DynamicNode; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor; import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; import org.junit.jupiter.engine.descriptor.JupiterTestDescriptor; import org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor; import org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor; import org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor; import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor; import org.junit.jupiter.engine.descriptor.TestTemplateInvocationTestDescriptor; import org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor; class ReflectionTestCase { @BeforeAll static void printJavaVersion() { System.out.println("Using Java version: " + System.getProperty("java.specification.version")); } @TestFactory Stream canReadParameters() { return Stream.of(JupiterTestDescriptor.class, ClassBasedTestDescriptor.class, ClassTestDescriptor.class, MethodBasedTestDescriptor.class, TestMethodTestDescriptor.class, TestTemplateTestDescriptor.class, TestTemplateInvocationTestDescriptor.class, TestFactoryTestDescriptor.class, NestedClassTestDescriptor.class) // .map(descriptorClass -> dynamicContainer(descriptorClass.getSimpleName(), Arrays.stream(descriptorClass.getDeclaredMethods()) // .map(method -> dynamicTest(method.getName(), () -> assertDoesNotThrow(method::getParameters))))); } } ================================================ FILE: platform-tooling-support-tests/projects/standalone/expected-err.txt ================================================ .+ org.junit.platform.launcher.core.ServiceLoaderRegistry load .+ Loaded LauncherInterceptor instances: .. .+ org.junit.platform.launcher.core.ServiceLoaderRegistry load .+ Loaded LauncherSessionListener instances: .. .+ org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry loadTestEngines .+ Discovered TestEngines: - junit-platform-suite .+ - junit-jupiter .+ - junit-vintage .+ .+ org.junit.platform.launcher.core.ServiceLoaderRegistry load .+ Loaded PostDiscoveryFilter instances: .. .+ org.junit.platform.launcher.core.ServiceLoaderRegistry load .+ Loaded LauncherDiscoveryListener instances: .. .+ org.junit.platform.launcher.core.ServiceLoaderRegistry load .+ Loaded TestExecutionListener instances: .+ \Q(excluded classes: [])\E .+ org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry loadTestEngines .+ Discovered TestEngines: - junit-platform-suite .+ - junit-jupiter .+ - junit-vintage .+ >> LOGGER >> INFO: TestEngine with ID 'junit-vintage' encountered a non-critical issue during test discovery: (1) [INFO] The JUnit Vintage engine is deprecated and should only be used temporarily while migrating tests to JUnit Jupiter or another testing framework with native JUnit Platform support. ================================================ FILE: platform-tooling-support-tests/projects/standalone/expected-out.txt ================================================ >> JAVA VERSION + TREE >> Failures (2): >> STACKTRACE >> Test run finished after \d+ ms [ 11 containers found ] [ 0 containers skipped ] [ 11 containers started ] [ 0 containers aborted ] [ 11 containers successful ] [ 0 containers failed ] [ 10 tests found ] [ 2 tests skipped ] [ 8 tests started ] [ 1 tests aborted ] [ 5 tests successful ] [ 2 tests failed ] ================================================ FILE: platform-tooling-support-tests/projects/standalone/logging.properties ================================================ handlers=java.util.logging.ConsoleHandler java.util.logging.ConsoleHandler.level=CONFIG org.junit.level=CONFIG ================================================ FILE: platform-tooling-support-tests/projects/standalone/src/other/OtherwiseNotReferencedClass.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package other; public class OtherwiseNotReferencedClass { } ================================================ FILE: platform-tooling-support-tests/projects/standalone/src/standalone/JupiterIntegration.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package standalone; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; class JupiterIntegration { @Test void successful() { Runtime.getRuntime().addShutdownHook(new Thread(() -> { new other.OtherwiseNotReferencedClass(); })); } @Test @Disabled("integration-test-disabled") void disabled() { } @Test void abort() { Assumptions.assumeTrue(false, "integration-test-abort"); } @Test void fail() { Assertions.fail("integration-test-fail"); } } ================================================ FILE: platform-tooling-support-tests/projects/standalone/src/standalone/JupiterParamsIntegration.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package standalone; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; class JupiterParamsIntegration { @ParameterizedTest(name = "[{index}] argument={0}", quoteTextArguments = false) @ValueSource(strings = "test") void parameterizedTest(String argument) { assertEquals("test", argument); } } ================================================ FILE: platform-tooling-support-tests/projects/standalone/src/standalone/SuiteIntegration.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package standalone; import org.junit.jupiter.api.Test; import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; @Suite @SelectClasses(SuiteIntegration.SingleTestContainer.class) class SuiteIntegration { static class SingleTestContainer { @Test void successful() { } } } ================================================ FILE: platform-tooling-support-tests/projects/standalone/src/standalone/VintageIntegration.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package standalone; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.junit.runners.MethodSorters.NAME_ASCENDING; import org.junit.FixMethodOrder; import org.junit.Ignore; import org.junit.Test; @FixMethodOrder(NAME_ASCENDING) public class VintageIntegration { @Test @Ignore("integr4tion test") public void ignored() { fail("this test should be ignored"); } @Test public void succ3ssful() { assertEquals(3, 1 + 2); } @Test public void f4il() { fail("f4iled"); } } ================================================ FILE: platform-tooling-support-tests/projects/vintage/build.gradle.kts ================================================ import org.gradle.api.tasks.testing.logging.TestLogEvent plugins { java } repositories { maven { url = uri(file(System.getProperty("maven.repo"))) } mavenCentral() } val junitVersion: String by project dependencies { val junit4Version = System.getProperty("junit4Version", "4.12") testImplementation("junit:junit:$junit4Version") testImplementation("org.junit.vintage:junit-vintage-engine:$junitVersion") { exclude(group = "junit") because("we want to override it to test against different versions") } testRuntimeOnly("org.junit.platform:junit-platform-launcher:$junitVersion") } java { toolchain { languageVersion = JavaLanguageVersion.of(17) } } tasks.test { useJUnitPlatform() testLogging { events = setOf(TestLogEvent.STARTED, TestLogEvent.PASSED, TestLogEvent.FAILED) afterSuite(KotlinClosure2({ _, result -> result.exception?.printStackTrace(System.out) })) } } ================================================ FILE: platform-tooling-support-tests/projects/vintage/gradle.properties ================================================ org.gradle.java.installations.fromEnv=JDK17 ================================================ FILE: platform-tooling-support-tests/projects/vintage/pom.xml ================================================ 4.0.0 com.example vintage 1.0-SNAPSHOT UTF-8 17 org.junit.vintage junit-vintage-engine ${junit.version} test junit junit junit junit ${junit4Version} test maven-compiler-plugin 3.15.0 maven-surefire-plugin 3.5.5 local-temp file://${maven.repo} true ignore true ignore ================================================ FILE: platform-tooling-support-tests/projects/vintage/settings.gradle.kts ================================================ plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } rootProject.name = "vintage" ================================================ FILE: platform-tooling-support-tests/projects/vintage/src/test/java/DefaultPackageTest.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ import com.example.vintage.VintageTest; import org.junit.Ignore; /** * Reproducer for https://github.com/junit-team/junit-framework/issues/4076 */ @Ignore public class DefaultPackageTest extends VintageTest { void packagePrivateMethod() { } } ================================================ FILE: platform-tooling-support-tests/projects/vintage/src/test/java/com/example/vintage/VintageTest.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package com.example.vintage; import static org.junit.Assert.*; import org.junit.Test; public class VintageTest { void packagePrivateMethod() { } @Test public void success() { // pass } @Test public void failure() { fail("expected to fail"); } } ================================================ FILE: platform-tooling-support-tests/src/archUnit/java/platform/tooling/support/tests/ArchUnitTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support.tests; import static com.tngtech.archunit.base.DescribedPredicate.and; import static com.tngtech.archunit.base.DescribedPredicate.describe; import static com.tngtech.archunit.base.DescribedPredicate.not; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.ANONYMOUS_CLASSES; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.TOP_LEVEL_CLASSES; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.resideInAPackage; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.simpleName; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.simpleNameEndingWith; import static com.tngtech.archunit.core.domain.JavaMember.Predicates.declaredIn; import static com.tngtech.archunit.core.domain.JavaModifier.PUBLIC; import static com.tngtech.archunit.core.domain.properties.CanBeAnnotated.Predicates.annotatedWith; import static com.tngtech.archunit.core.domain.properties.HasModifiers.Predicates.modifier; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameContaining; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameStartingWith; import static com.tngtech.archunit.lang.conditions.ArchConditions.onlyBeAccessedByClassesThat; import static com.tngtech.archunit.lang.conditions.ArchPredicates.are; import static com.tngtech.archunit.lang.conditions.ArchPredicates.have; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.members; import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices; import static org.apiguardian.api.API.Status.DEPRECATED; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; import java.lang.annotation.Annotation; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.util.Arrays; import java.util.Objects; import java.util.function.BiPredicate; import java.util.stream.Stream; import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.domain.JavaPackage; import com.tngtech.archunit.core.domain.PackageMatcher; import com.tngtech.archunit.core.domain.properties.HasAnnotations; import com.tngtech.archunit.core.domain.properties.HasSourceCodeLocation; import com.tngtech.archunit.junit.AnalyzeClasses; import com.tngtech.archunit.junit.ArchTest; import com.tngtech.archunit.lang.ArchCondition; import com.tngtech.archunit.lang.ArchRule; import com.tngtech.archunit.library.GeneralCodingRules; import org.apiguardian.api.API; import org.jspecify.annotations.NullMarked; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.TestReporter; import org.junit.platform.commons.support.scanning.ClasspathScanner; import org.junit.platform.engine.OutputDirectoryCreator; import org.junit.platform.engine.TestDescriptor; @AnalyzeClasses(packages = { "org.junit.platform", "org.junit.jupiter", "org.junit.vintage" }) class ArchUnitTests { @SuppressWarnings("unused") @ArchTest private final ArchRule allClassesAreInJUnitPackage = classes() // .should().haveNameMatching("org\\.junit\\..+"); @SuppressWarnings("unused") @ArchTest private final ArchRule allPublicTopLevelTypesHaveApiAnnotations = classes() // .that(have(modifier(PUBLIC))) // .and(TOP_LEVEL_CLASSES) // .and(not(ANONYMOUS_CLASSES)) // .and(not(describe("are Kotlin SAM type implementations", simpleName("")))) // .and(not(describe("are Kotlin-generated classes that contain only top-level functions", simpleNameEndingWith("Kt")))) // .and(notShadowed()) // .should().beAnnotatedWith(API.class); @SuppressWarnings("unused") @ArchTest // Consistency of @Documented and @Inherited is checked by the compiler but not for @Retention and @Target private final ArchRule repeatableAnnotationsShouldHaveMatchingContainerAnnotations = classes() // .that(nameStartingWith("org.junit.")) // .and().areAnnotations() // .and().areAnnotatedWith(Repeatable.class) // .should(haveContainerAnnotationWithSameRetentionPolicy()) // .andShould(haveContainerAnnotationWithSameTargetTypes()); private final DescribedPredicate jupiterAssertions = name(Assertions.class.getName()) // .or(name(Assumptions.class.getName())).or(name("org.junit.jupiter.api.AssertionsKt")); @SuppressWarnings("unused") @ArchTest // https://github.com/junit-team/junit-framework/issues/4604 private final ArchRule jupiterAssertionsShouldBeSelfContained = classes().that(jupiterAssertions) // .should(onlyBeAccessedByClassesThat(jupiterAssertions)); @SuppressWarnings("unused") @ArchTest private final ArchRule deprecatedAnnotationOnMembersShouldBeDeclaredConsistently = members() // .that(annotatedWith(Deprecated.class)) // .or(haveApiAnnotationWithDeprecatedStatus()) // .and(declaredIn(notShadowed())) // .should(haveBothAnnotationsWithMatchingSinceAttributes()); @SuppressWarnings("unused") @ArchTest private final ArchRule deprecatedAnnotationOnClassesShouldBeDeclaredConsistently = classes() // .that(annotatedWith(Deprecated.class)) // .or(haveApiAnnotationWithDeprecatedStatus()) // .and(notShadowed()) // .should(haveBothAnnotationsWithMatchingSinceAttributes()); @ArchTest void packagesShouldBeNullMarked(JavaClasses classes) { var exclusions = Stream.of( // "..shadow.." // ).map(PackageMatcher::of).toList(); var subpackages = Stream.of("org.junit.platform", "org.junit.jupiter", "org.junit.vintage") // .map(classes::getPackage) // .flatMap(rootPackage -> rootPackage.getSubpackagesInTree().stream()) // .filter(pkg -> exclusions.stream().noneMatch(it -> it.matches(pkg.getName()))) // .filter(pkg -> !pkg.getClasses().isEmpty()) // .toList(); assertThat(subpackages).isNotEmpty(); var violations = subpackages.stream() // .filter(pkg -> !pkg.isAnnotatedWith(NullMarked.class)) // .map(JavaPackage::getName) // .sorted(); assertThat(violations).describedAs("The following packages are missing the @NullMarked annotation").isEmpty(); } @ArchTest void allAreIn(JavaClasses classes) { // about 928 classes found in all jars assertTrue(classes.size() > 800, "expected more than 800 classes, got: " + classes.size()); } @ArchTest void freeOfGroupCycles(JavaClasses classes) { slices().matching("org.junit.(*)..").should().beFreeOfCycles().check(classes); } @ArchTest @SuppressWarnings("removal") void freeOfPackageCycles(JavaClasses classes) throws Exception { slices().matching("org.junit.(**)").should().beFreeOfCycles() // // Ignore shadowed packages .ignoreDependency(nameContaining(".shadow."), nameContaining(".shadow.")) // // https://github.com/junit-team/junit-framework/issues/4886 .ignoreDependency(TestReporter.class, org.junit.jupiter.api.extension.MediaType.class) // // https://github.com/junit-team/junit-framework/issues/4885 .ignoreDependency(ClasspathScanner.class, org.junit.platform.commons.support.Resource.class) // // https://github.com/junit-team/junit-framework/issues/4919 .ignoreDependency(org.junit.jupiter.params.support.ParameterInfo.class, org.junit.jupiter.params.ParameterInfo.class) // https://github.com/junit-team/junit-framework/issues/4923 .ignoreDependency(org.junit.platform.engine.reporting.OutputDirectoryProvider.class, OutputDirectoryCreator.class) // .ignoreDependency(Class.forName("org.junit.platform.engine.reporting.OutputDirectoryProviderAdapter"), OutputDirectoryCreator.class) // .ignoreDependency(Class.forName("org.junit.platform.engine.reporting.OutputDirectoryProviderAdapter"), TestDescriptor.class) // .check(classes); } @ArchTest void avoidJavaUtilLogging(JavaClasses classes) { // LoggerFactory.java:80 -> sets field LoggerFactory$DelegatingLogger.julLogger var subset = classes.that(are(not(name("org.junit.platform.commons.logging.LoggerFactory$DelegatingLogger")))); GeneralCodingRules.NO_CLASSES_SHOULD_USE_JAVA_UTIL_LOGGING.check(subset); } @ArchTest void avoidThrowingGenericExceptions(JavaClasses classes) { // LoggerFactory.java:155 -> new Throwable() var subset = classes.that(are(not( name("org.junit.platform.commons.logging.LoggerFactory$DelegatingLogger").or(nameContaining(".shadow."))))); GeneralCodingRules.NO_CLASSES_SHOULD_THROW_GENERIC_EXCEPTIONS.check(subset); } @ArchTest void avoidAccessingStandardStreams(JavaClasses classes) { // ConsoleLauncher, StreamInterceptor, Picocli et al... var subset = classes // .that(are(not(name("org.junit.platform.console.ConsoleLauncher")))) // .that(are(not(name("org.junit.platform.console.command.ConsoleTestExecutor")))) // .that(are(not(name("org.junit.platform.launcher.core.StreamInterceptor")))) // .that(are(not(name("org.junit.platform.testkit.engine.Events")))) // .that(are(not(name("org.junit.platform.testkit.engine.Executions")))) // //The PreInterruptThreadDumpPrinter writes to StdOut by contract to dump threads .that(are(not(name("org.junit.jupiter.engine.extension.PreInterruptThreadDumpPrinter")))) // .that(are(not(nameContaining(".shadow.")))); GeneralCodingRules.NO_CLASSES_SHOULD_ACCESS_STANDARD_STREAMS.check(subset); } private static ArchCondition haveContainerAnnotationWithSameRetentionPolicy() { return ArchCondition.from(new RepeatableAnnotationPredicate<>(Retention.class, (expectedTarget, actualTarget) -> expectedTarget.value() == actualTarget.value())); } private static ArchCondition haveContainerAnnotationWithSameTargetTypes() { return ArchCondition.from(new RepeatableAnnotationPredicate<>(Target.class, (expectedTarget, actualTarget) -> Arrays.equals(expectedTarget.value(), actualTarget.value()))); } private static & HasSourceCodeLocation> ArchCondition haveBothAnnotationsWithMatchingSinceAttributes() { return ArchCondition.from( // DescribedPredicate.and( // annotatedWith(Deprecated.class), haveApiAnnotationWithDeprecatedStatus(), // haveSinceAttributeMatchingApiAnnotation())); } private static & HasSourceCodeLocation> DescribedPredicate haveApiAnnotationWithDeprecatedStatus() { return and(annotatedWith(API.class), describe("status() is DEPRECATED", element -> element.getAnnotationOfType(API.class).status() == DEPRECATED)); } private static & HasSourceCodeLocation> DescribedPredicate haveSinceAttributeMatchingApiAnnotation() { return describe("@API(since) equals @Deprecated(since)", element -> { var deprecatedAnnotation = element.getAnnotationOfType(Deprecated.class); var apiAnnotation = element.getAnnotationOfType(API.class); return deprecatedAnnotation.since() != null // && Objects.equals(deprecatedAnnotation.since(), apiAnnotation.since()); }); } private static DescribedPredicate notShadowed() { return not(describe("are shadowed", resideInAPackage("..shadow.."))); } private static class RepeatableAnnotationPredicate extends DescribedPredicate { private final Class annotationType; private final BiPredicate predicate; RepeatableAnnotationPredicate(Class annotationType, BiPredicate predicate) { super("have identical @%s annotation as container annotation", annotationType.getSimpleName()); this.annotationType = annotationType; this.predicate = predicate; } @Override public boolean test(JavaClass annotationClass) { var containerAnnotationClass = (JavaClass) annotationClass.getAnnotationOfType( Repeatable.class.getName()).get("value").orElseThrow(); var expectedAnnotation = annotationClass.tryGetAnnotationOfType(annotationType); var actualAnnotation = containerAnnotationClass.tryGetAnnotationOfType(annotationType); return expectedAnnotation.map(expectedTarget -> actualAnnotation // .map(actualTarget -> predicate.test(expectedTarget, actualTarget)) // .orElse(false)) // .orElse(actualAnnotation.isEmpty()); } } } ================================================ FILE: platform-tooling-support-tests/src/main/java/platform/tooling/support/Helper.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Stream; /** * @since 1.3 */ public class Helper { private static final Path ROOT = Path.of(".."); private static final Path GRADLE_PROPERTIES = ROOT.resolve("gradle.properties"); private static final Path SETTINGS_GRADLE = ROOT.resolve("settings.gradle.kts"); private static final Properties gradleProperties = new Properties(); static { try { gradleProperties.load(Files.newInputStream(GRADLE_PROPERTIES)); } catch (Exception e) { throw new AssertionError("loading gradle.properties failed", e); } } public static String version() { return gradleProperties.getProperty("version"); } public static List loadModuleDirectoryNames() { var moduleLinePattern = Pattern.compile("include\\(\"(.+)\"\\)"); try (var stream = Files.lines(SETTINGS_GRADLE)) { return stream.map(moduleLinePattern::matcher) // .filter(Matcher::matches) // .map(matcher -> matcher.group(1)) // .filter(name -> name.startsWith("junit-")) // .filter(name -> !"junit-bom".equals(name)) // .filter(name -> !"junit-platform-console-standalone".equals(name)).toList(); } catch (Exception e) { throw new AssertionError("loading module directory names failed: " + SETTINGS_GRADLE); } } public static Optional getJavaHome(int version) { // First, try various system sources... var sources = Stream.of( // System.getProperty("java.home." + version), // System.getProperty("java." + version), // System.getProperty("jdk.home." + version), // System.getProperty("jdk." + version), // System.getenv("JAVA_HOME_" + version), // System.getenv("JAVA_" + version), // System.getenv("JDK" + version) // ); return sources.filter(Objects::nonNull).findFirst().map(Path::of); } private Helper() { } } ================================================ FILE: platform-tooling-support-tests/src/main/java/platform/tooling/support/MavenRepo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support; import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.function.Predicate; import java.util.stream.Stream; public class MavenRepo { private MavenRepo() { } public static Path dir() { var candidates = Stream.of(Path.of("../build/repo")); var candidate = candidates.filter(Files::isDirectory).findFirst().orElse(Path.of("build/repo")); return Path.of(System.getProperty("maven.repo", candidate.toString())); } public static Path jar(String artifactId) { return artifact(artifactId, fileName -> fileName.endsWith(".jar") // && !fileName.endsWith("-sources.jar") // && !fileName.endsWith("-javadoc.jar")); } public static Path gradleModuleMetadata(String artifactId) { return artifact(artifactId, fileName -> fileName.endsWith(".module")); } public static Path pom(String artifactId) { return artifact(artifactId, fileName -> fileName.endsWith(".pom")); } private static Path artifact(String artifactId, Predicate fileNamePredicate) { var parentDir = dir() // .resolve(groupId(artifactId).replace('.', File.separatorChar)) // .resolve(artifactId) // .resolve(Helper.version()); try (var files = Files.list(parentDir)) { return files.filter( file -> fileNamePredicate.test(file.getFileName().toString())).findFirst().orElseThrow(); } catch (IOException e) { throw new UncheckedIOException(e); } } private static String groupId(String artifactId) { if (artifactId.startsWith("junit-jupiter")) { return "org.junit.jupiter"; } if (artifactId.startsWith("junit-platform")) { return "org.junit.platform"; } if (artifactId.startsWith("junit-vintage")) { return "org.junit.vintage"; } return "org.junit"; } } ================================================ FILE: platform-tooling-support-tests/src/main/java/platform/tooling/support/ProcessStarters.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support; import java.nio.file.Path; import java.util.Optional; import org.junit.jupiter.api.condition.OS; import org.junit.platform.tests.process.ProcessStarter; import org.opentest4j.TestAbortedException; public class ProcessStarters { public static ProcessStarter java() { return javaCommand(currentJdkHome(), "java"); } public static Path currentJdkHome() { var executable = ProcessHandle.current().info().command().map(Path::of).orElseThrow(); // path element count is 3 or higher: "/bin/java[.exe]" return executable.getParent().getParent().toAbsolutePath(); } public static ProcessStarter java(Path javaHome) { return javaCommand(javaHome, "java"); } public static ProcessStarter javaCommand(String commandName) { return javaCommand(currentJdkHome(), commandName); } public static ProcessStarter javaCommand(Path javaHome, String commandName) { return new ProcessStarter() // .executable(javaHome.resolve("bin").resolve(commandName)) // .putEnvironment("JAVA_HOME", javaHome); } public static ProcessStarter gradlew() { return new ProcessStarter() // .executable(Path.of("..").resolve(windowsOrOtherExecutable("gradlew.bat", "gradlew")).toAbsolutePath()) // .putEnvironment("JAVA_HOME", getGradleJavaHome().orElseThrow(TestAbortedException::new)) // .addArguments("-PjunitVersion=" + Helper.version()); } public static ProcessStarter maven(Path javaHome) { return new ProcessStarter() // .executable(Path.of(System.getProperty("mavenDistribution")).resolve("bin").resolve( windowsOrOtherExecutable("mvn.cmd", "mvn")).toAbsolutePath()) // .putEnvironment("JAVA_HOME", javaHome) // .addArguments("-Djunit.version=" + Helper.version()); } private static String windowsOrOtherExecutable(String cmdOrExe, String other) { return OS.current() == OS.WINDOWS ? cmdOrExe : other; } public static Optional getGradleJavaHome() { return Helper.getJavaHome(getGradleJavaVersion()); } public static int getGradleJavaVersion() { return Integer.parseInt(System.getProperty("gradle.java.version")); } private ProcessStarters() { } } ================================================ FILE: platform-tooling-support-tests/src/main/java/platform/tooling/support/ThirdPartyJars.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.stream.Stream; public class ThirdPartyJars { private ThirdPartyJars() { } public static void copy(Path targetDir, String group, String artifact) { Path source = find(group, artifact); copy(source, targetDir); } public static void copyAll(Path targetDir) { thirdPartyJars().forEach(path -> copy(path, targetDir)); } private static void copy(Path source, Path targetDir) { try { Files.copy(source, targetDir.resolve(source.getFileName()), REPLACE_EXISTING); } catch (IOException e) { throw new UncheckedIOException("Failed to copy %s to %s".formatted(source, targetDir), e); } } public static Path find(String group, String artifact) { return thirdPartyJars() // .filter(it -> it.toAbsolutePath().toString().replace(File.separator, "/").contains( "/" + group + "/" + artifact + "/")) // .findFirst() // .orElseThrow(() -> new AssertionError("Failed to find JAR file for " + group + ":" + artifact)); } private static Stream thirdPartyJars() { return Stream.of(System.getProperty("thirdPartyJars").split(File.pathSeparator)) // .map(Path::of); } } ================================================ FILE: platform-tooling-support-tests/src/main/java/platform/tooling/support/package-info.java ================================================ /** * Tooling support helper. * * @since 1.3 */ package platform.tooling.support; ================================================ FILE: platform-tooling-support-tests/src/test/java/platform/tooling/support/HelperTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertLinesMatch; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.nio.file.Files; import java.util.List; import java.util.Optional; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; @Order(Integer.MAX_VALUE) class HelperTests { @Test void loadModuleDirectoryNames() { assertLinesMatch(List.of( // "junit-jupiter", // "junit-jupiter-api", // "junit-jupiter-engine", // "junit-jupiter-migrationsupport", // "junit-jupiter-params", // "junit-start", // "junit-platform-commons", // "junit-platform-console", // "junit-platform-engine", // "junit-platform-launcher", // "junit-platform-reporting", // "junit-platform-suite", // "junit-platform-suite-api", // "junit-platform-suite-engine", // "junit-platform-testkit", // "junit-vintage-engine"// ), Helper.loadModuleDirectoryNames()); } @Test void version() { assertNotNull(Helper.version()); } @Test void nonExistingJdkVersionYieldsAnEmptyOptional() { assertEquals(Optional.empty(), Helper.getJavaHome(-1)); } @ParameterizedTest @ValueSource(ints = 17) void checkJavaHome(int version) { var home = Helper.getJavaHome(version); assumeTrue(home.isPresent(), "No 'jdk' element found in Maven toolchain for: " + version); assertTrue(Files.isDirectory(home.get())); } } ================================================ FILE: platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/AntStarterTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support.tests; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertLinesMatch; import static platform.tooling.support.tests.Projects.copyToWorkspace; import static platform.tooling.support.tests.XmlAssertions.verifyContainsExpectedStartedOpenTestReport; import java.nio.file.Path; import java.util.List; import de.skuzzle.test.snapshots.Snapshot; import de.skuzzle.test.snapshots.junit5.EnableSnapshotTests; import org.apache.tools.ant.Main; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.io.TempDir; import org.junit.platform.tests.process.OutputFiles; import platform.tooling.support.ProcessStarters; /** * @since 1.3 */ @EnableSnapshotTests //@SnapshotTestOptions(alwaysPersistActualResult = true) class AntStarterTests { @Test @Timeout(60) void ant_starter(@TempDir Path workspace, @FilePrefix("ant") OutputFiles outputFiles, Snapshot snapshot) throws Exception { var result = ProcessStarters.java() // .workingDir(copyToWorkspace(Projects.JUPITER_STARTER, workspace)) // .addArguments("-cp", System.getProperty("antJars"), Main.class.getName()) // .redirectOutput(outputFiles) // .startAndWait(); assertEquals(0, result.exitCode()); assertEquals("", result.stdErr(), "error log isn't empty"); assertLinesMatch(List.of(">> HEAD >>", // "test.junit.launcher:", // ">>>>", // "\\[junitlauncher\\] Tests run: 8, Failures: 0, Aborted: 0, Skipped: 0, Time elapsed: .+ sec", // "\\[junitlauncher\\] Running com.example.project.CalculatorTests", // "\\[junitlauncher\\] Tests run: 5, Failures: 0, Aborted: 0, Skipped: 0, Time elapsed: .+ sec", // ">>>>", // "test.console.launcher:", // ">>>>", // " \\[java\\] Test run finished after [\\d]+ ms", // ">>>>", // " \\[java\\] \\[ 13 tests successful \\]", // " \\[java\\] \\[ 0 tests failed \\]", // ">> TAIL >>"), // result.stdOutLines()); var testResultsDir = workspace.resolve("build/test-report"); verifyContainsExpectedStartedOpenTestReport(testResultsDir, snapshot); } } ================================================ FILE: platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/FilePrefix.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support.tests; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.junit.jupiter.api.extension.ExtendWith; @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @ExtendWith(OutputAttachingExtension.class) @interface FilePrefix { String value(); } ================================================ FILE: platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support.tests; import static java.util.concurrent.TimeUnit.MINUTES; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static platform.tooling.support.tests.Projects.copyToWorkspace; import java.nio.file.Path; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.junit.jupiter.api.extension.DisabledOnOpenJ9; import org.junit.jupiter.api.io.TempDir; import org.junit.platform.tests.process.OutputFiles; import platform.tooling.support.MavenRepo; import platform.tooling.support.ProcessStarters; /** * @since 1.9.1 */ @Order(Integer.MIN_VALUE) @DisabledOnOpenJ9 @EnabledIfEnvironmentVariable(named = "GRAALVM_HOME", matches = ".+") class GraalVmStarterTests { @Test @Timeout(value = 10, unit = MINUTES) void runsTestsInNativeImage(@TempDir Path workspace, @FilePrefix("gradle") OutputFiles outputFiles) throws Exception { var result = ProcessStarters.gradlew() // .workingDir(copyToWorkspace(Projects.GRAALVM_STARTER, workspace)) // .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // .addArguments("javaToolchains", "nativeTest", "--no-daemon", "--stacktrace", "--no-build-cache", "--warning-mode=fail", "--refresh-dependencies") // .redirectOutput(outputFiles) // .startAndWait(); assertEquals(0, result.exitCode()); assertThat(result.stdOutLines()) // .anyMatch(line -> line.contains("CalculatorTests > 1 + 1 = 2 SUCCESSFUL")) // .anyMatch(line -> line.contains("CalculatorTests > 1 + 100 = 101 SUCCESSFUL")) // .anyMatch(line -> line.contains( "ClassLevelAnnotationTests$Inner > ClassLevelAnnotationTests, Inner, test SUCCESSFUL")) // .anyMatch( line -> line.contains("com.example.project.CalculatorParameterizedClassTests > [1] 1 SUCCESSFUL")) // .anyMatch( line -> line.contains("com.example.project.CalculatorParameterizedClassTests > [2] 2 SUCCESSFUL")) // .anyMatch(line -> line.contains("com.example.project.VintageTests > test SUCCESSFUL")) // .anyMatch(line -> line.contains("BUILD SUCCESSFUL")); } } ================================================ FILE: platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleKotlinExtensionsTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support.tests; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static platform.tooling.support.tests.Projects.copyToWorkspace; import java.nio.file.Path; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.platform.tests.process.OutputFiles; import org.opentest4j.TestAbortedException; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; import platform.tooling.support.ProcessStarters; /** * @since 1.3 */ class GradleKotlinExtensionsTests { @Test void gradle_wrapper(@TempDir Path workspace, @FilePrefix("gradle") OutputFiles outputFiles) throws Exception { var result = ProcessStarters.gradlew() // .workingDir(copyToWorkspace(Projects.GRADLE_KOTLIN_EXTENSIONS, workspace)) // .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // .addArguments("build", "--no-daemon", "--stacktrace", "--no-build-cache", "--warning-mode=fail") // .putEnvironment("JDK17", Helper.getJavaHome(17).orElseThrow(TestAbortedException::new).toString()) // .redirectOutput(outputFiles) // .startAndWait(); assertEquals(0, result.exitCode(), "result=" + result); assertTrue(result.stdOut().lines().anyMatch(line -> line.contains("BUILD SUCCESSFUL"))); } } ================================================ FILE: platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleMissingEngineTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support.tests; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static platform.tooling.support.tests.Projects.copyToWorkspace; import java.nio.file.Path; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.platform.tests.process.OutputFiles; import org.opentest4j.TestAbortedException; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; import platform.tooling.support.ProcessStarters; /** * @since 1.3 */ class GradleMissingEngineTests { @Test void gradle_wrapper(@TempDir Path workspace, @FilePrefix("gradle") OutputFiles outputFiles) throws Exception { var result = ProcessStarters.gradlew() // .workingDir(copyToWorkspace(Projects.GRADLE_MISSING_ENGINE, workspace)) // .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // .addArguments("build", "--no-daemon", "--stacktrace", "--no-build-cache", "--warning-mode=fail") // .putEnvironment("JDK17", Helper.getJavaHome(17).orElseThrow(TestAbortedException::new).toString()) // .redirectOutput(outputFiles).startAndWait(); assertEquals(1, result.exitCode()); assertThat(result.stdErr()) // .contains("FAILURE: Build failed with an exception.") // .contains("Cannot create Launcher without at least one TestEngine"); } } ================================================ FILE: platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleModuleFileTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support.tests; import static org.junit.jupiter.api.Assertions.assertLinesMatch; import java.nio.file.Files; import java.util.List; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import platform.tooling.support.MavenRepo; /** * @since 1.6 */ @Order(Integer.MAX_VALUE) class GradleModuleFileTests { @Test void jupiterAggregatorGradleModuleMetadataVariants() throws Exception { var expected = List.of(">> HEAD >>", // "{", // " \"formatVersion\": \"1.1\",", // " \"component\": {", // " \"group\": \"org.junit.jupiter\",", // " \"module\": \"junit-jupiter\",", // ">> VERSION >>", // " \"attributes\": {", // ">> STATUS >>", // " }", // " },", // ">> CREATED_BY >>", // " \"variants\": [", // " {", // " \"name\": \"apiElements\",", // " \"attributes\": {", // " \"org.gradle.category\": \"library\",", // " \"org.gradle.dependency.bundling\": \"external\",", // " \"org.gradle.jvm.version\": 17,", // " \"org.gradle.libraryelements\": \"jar\",", // " \"org.gradle.usage\": \"java-api\"", // " },", // " \"dependencies\": [", // " {", // " \"group\": \"org.junit\",", // " \"module\": \"junit-bom\",", // " \"version\": {", // ">> VERSION >>", // " },", // " \"attributes\": {", // " \"org.gradle.category\": \"platform\"", // " },", // " \"endorseStrictVersions\": true", // " },", // " {", // " \"group\": \"org.junit.jupiter\",", // " \"module\": \"junit-jupiter-api\",", // " \"version\": {", // ">> VERSION >>", // " }", // " },", // " {", // " \"group\": \"org.junit.jupiter\",", // " \"module\": \"junit-jupiter-params\",", // " \"version\": {", // ">> VERSION >>", // " }", // " }", // " ],", // " \"files\": [", // " {", // ">> JAR_FILE_DETAILS >>", // " }", // " ]", // " },", // " {", // " \"name\": \"runtimeElements\",", // " \"attributes\": {", // " \"org.gradle.category\": \"library\",", // " \"org.gradle.dependency.bundling\": \"external\",", // " \"org.gradle.jvm.version\": 17,", // " \"org.gradle.libraryelements\": \"jar\",", // " \"org.gradle.usage\": \"java-runtime\"", // " },", // " \"dependencies\": [", // " {", // " \"group\": \"org.junit.jupiter\",", // " \"module\": \"junit-jupiter-engine\",", // " \"version\": {", // ">> VERSION >>", // " }", // " },", // " {", // " \"group\": \"org.junit\",", // " \"module\": \"junit-bom\",", // " \"version\": {", // ">> VERSION >>", // " },", // " \"attributes\": {", // " \"org.gradle.category\": \"platform\"", // " },", // " \"endorseStrictVersions\": true", // " },", // " {", // " \"group\": \"org.junit.jupiter\",", // " \"module\": \"junit-jupiter-api\",", // " \"version\": {", // ">> VERSION >>", // " }", // " },", // " {", // " \"group\": \"org.junit.jupiter\",", // " \"module\": \"junit-jupiter-params\",", // " \"version\": {", // ">> VERSION >>", // " }", // " }", // " ],", // " \"files\": [", // " {", // ">> JAR_FILE_DETAILS >>", // " }", // " ]", // " },", // " {", // " \"name\": \"javadocElements\",", // " \"attributes\": {", // " \"org.gradle.category\": \"documentation\",", // " \"org.gradle.dependency.bundling\": \"external\",", // " \"org.gradle.docstype\": \"javadoc\",", // " \"org.gradle.usage\": \"java-runtime\"", // " },", // " \"files\": [", // " {", // ">> JAR_FILE_DETAILS >>", // " }", // " ]", // " },", // " {", // " \"name\": \"sourcesElements\",", // " \"attributes\": {", // " \"org.gradle.category\": \"documentation\",", // " \"org.gradle.dependency.bundling\": \"external\",", // " \"org.gradle.docstype\": \"sources\",", // " \"org.gradle.usage\": \"java-runtime\"", // " },", // " \"files\": [", // " {", // ">> JAR_FILE_DETAILS >>", // " }", // " ]", // " }", // " ]", // "}"); assertLinesMatch(expected, Files.readAllLines(MavenRepo.gradleModuleMetadata("junit-jupiter"))); } } ================================================ FILE: platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleStarterTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support.tests; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static platform.tooling.support.tests.Projects.copyToWorkspace; import static platform.tooling.support.tests.XmlAssertions.verifyContainsExpectedStartedOpenTestReport; import java.nio.file.Path; import de.skuzzle.test.snapshots.Snapshot; import de.skuzzle.test.snapshots.junit5.EnableSnapshotTests; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.platform.tests.process.OutputFiles; import org.junit.platform.tests.process.ProcessResult; import org.opentest4j.TestAbortedException; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; import platform.tooling.support.ProcessStarters; /** * @since 1.3 */ @EnableSnapshotTests //@SnapshotTestOptions(alwaysPersistActualResult = true) class GradleStarterTests { @TempDir Path workspace; @BeforeEach void prepareWorkspace() throws Exception { copyToWorkspace(Projects.JUPITER_STARTER, workspace); } @Test void buildJupiterStarterProject(@FilePrefix("gradle") OutputFiles outputFiles, Snapshot snapshot) throws Exception { var result = runGradle(outputFiles, "build"); assertThat(result.stdOut()) // .contains( // "CalculatorParameterizedClassTests > [1] i = 1 > parameterizedTest(int)", // "CalculatorParameterizedClassTests > [1] i = 1 > Inner > [1] 1 > regularTest() PASSED", // "CalculatorParameterizedClassTests > [1] i = 1 > Inner > [2] 2 > regularTest() PASSED", // "CalculatorParameterizedClassTests > [2] i = 2 > parameterizedTest(int)", // "CalculatorParameterizedClassTests > [2] i = 2 > Inner > [1] 1 > regularTest() PASSED", // "CalculatorParameterizedClassTests > [2] i = 2 > Inner > [2] 2 > regularTest() PASSED", // "Using Java version: 17", // "CalculatorTests > 1 + 1 = 2 PASSED", // "CalculatorTests > add(int, int, int) > 0 + 1 = 1 PASSED", // "CalculatorTests > add(int, int, int) > 1 + 2 = 3 PASSED", // "CalculatorTests > add(int, int, int) > 49 + 51 = 100 PASSED", // "CalculatorTests > add(int, int, int) > 1 + 100 = 101 PASSED" // ); var testResultsDir = workspace.resolve("build/test-results/test"); verifyContainsExpectedStartedOpenTestReport(testResultsDir, snapshot); } @Test void runOnlyOneMethodInClassTemplate(@FilePrefix("gradle") OutputFiles outputFiles) throws Exception { var result = runGradle(outputFiles, "test", "--tests", "CalculatorParameterized*.regular*"); assertThat(result.stdOut()) // .contains( // "CalculatorParameterizedClassTests > [1] i = 1 > Inner > [1] 1 > regularTest() PASSED", // "CalculatorParameterizedClassTests > [1] i = 1 > Inner > [2] 2 > regularTest() PASSED", // "CalculatorParameterizedClassTests > [2] i = 2 > Inner > [1] 1 > regularTest() PASSED", // "CalculatorParameterizedClassTests > [2] i = 2 > Inner > [2] 2 > regularTest() PASSED" // ) // .doesNotContain("parameterizedTest(int)", "CalculatorTests"); result = runGradle(outputFiles, "test", "--tests", "*ParameterizedClassTests.parameterized*"); assertThat(result.stdOut()) // .contains( // "CalculatorParameterizedClassTests > [1] i = 1 > parameterizedTest(int)", // "CalculatorParameterizedClassTests > [2] i = 2 > parameterizedTest(int)" // ) // .doesNotContain("regularTest()", "CalculatorTests"); } private ProcessResult runGradle(OutputFiles outputFiles, String... extraArgs) throws InterruptedException { var result = ProcessStarters.gradlew() // .workingDir(workspace) // .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // .addArguments("-Djava.toolchain.version=17") // .addArguments("--stacktrace", "--no-build-cache", "--warning-mode=fail") // .addArguments(extraArgs).putEnvironment("JDK17", Helper.getJavaHome(17).orElseThrow(TestAbortedException::new).toString()) // .redirectOutput(outputFiles) // .startAndWait(); assertEquals(0, result.exitCode()); assertTrue(result.stdOut().lines().anyMatch(line -> line.contains("BUILD SUCCESSFUL"))); return result; } } ================================================ FILE: platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JUnitStartTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support.tests; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static platform.tooling.support.tests.Projects.copyToWorkspace; import java.nio.file.Files; import java.nio.file.Path; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledOnJre; import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.io.TempDir; import org.junit.platform.tests.process.OutputFiles; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; import platform.tooling.support.ProcessStarters; import platform.tooling.support.ThirdPartyJars; /** * @since 6.1 */ class JUnitStartTests { @TempDir static Path workspace; @BeforeAll static void prepareLocalLibraryDirectoryWithJUnitModules() throws Exception { copyToWorkspace(Projects.JUNIT_START, workspace); var lib = workspace.resolve("lib"); try { Files.createDirectories(lib); try (var directoryStream = Files.newDirectoryStream(lib, "*.jar")) { for (Path jarFile : directoryStream) { Files.delete(jarFile); } } for (var module : Helper.loadModuleDirectoryNames()) { if (module.startsWith("junit-platform") || module.startsWith("junit-jupiter") || module.equals("junit-start")) { if (module.equals("junit-jupiter-migrationsupport")) continue; if (module.startsWith("junit-platform-suite")) continue; if (module.equals("junit-platform-testkit")) continue; var jar = MavenRepo.jar(module); Files.copy(jar, lib.resolve(module + ".jar")); } } ThirdPartyJars.copy(lib, "org.apiguardian", "apiguardian-api"); ThirdPartyJars.copy(lib, "org.jspecify", "jspecify"); ThirdPartyJars.copy(lib, "org.opentest4j", "opentest4j"); ThirdPartyJars.copy(lib, "org.opentest4j.reporting", "open-test-reporting-tooling-spi"); } catch (Exception e) { throw new AssertionError("Preparing local library folder failed", e); } } @Test @EnabledOnJre(JRE.JAVA_25) void junitRun(@FilePrefix("junit-run") OutputFiles outputFiles) throws Exception { var result = ProcessStarters.java() // .workingDir(workspace) // .addArguments("--module-path", "lib") // relative to workspace .addArguments("--add-modules", "org.junit.start") // configure root module .addArguments("compact/JUnitRun.java") // leverage Java's source mode .redirectOutput(outputFiles) // .startAndWait(); assertEquals(0, result.exitCode()); assertTrue(result.stdOut().contains("addition()"), result.stdOut()); } @Test @EnabledOnJre(JRE.JAVA_25) void junitRunClass(@FilePrefix("junit-run-class") OutputFiles outputFiles) throws Exception { var result = ProcessStarters.java() // .workingDir(workspace) // .addArguments("--module-path", "lib") // relative to workspace .addArguments("--add-modules", "org.junit.start") // configure root module .addArguments("compact/JUnitRunClass.java") // leverage Java's source mode .redirectOutput(outputFiles) // .startAndWait(); assertEquals(0, result.exitCode()); assertTrue(result.stdOut().contains("substraction()"), result.stdOut()); } @Test @EnabledOnJre(JRE.JAVA_25) void junitRunModule(@FilePrefix("junit-run-module") OutputFiles outputFiles) throws Exception { var result = ProcessStarters.java() // .workingDir(workspace) // .putEnvironment("NO_COLOR", "1") // --disable-ansi-colors .addArguments("--module-path", "lib") // relative to workspace .addArguments("modular/p/JUnitRunModule.java") // leverage Java's source mode .redirectOutput(outputFiles) // .startAndWait(); assertEquals(0, result.exitCode()); assertTrue(result.stdOut().contains("multiplication()"), result.stdOut()); } } ================================================ FILE: platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarContainsManifestFirstTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support.tests; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.fail; import java.io.FileInputStream; import java.nio.file.Files; import java.util.jar.JarInputStream; import org.junit.jupiter.api.Order; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import platform.tooling.support.MavenRepo; /** * @since 1.8 */ @Order(Integer.MAX_VALUE) class JarContainsManifestFirstTests { @ParameterizedTest(quoteTextArguments = false) @MethodSource("platform.tooling.support.Helper#loadModuleDirectoryNames") void manifestFirst(String module) throws Exception { var modulePath = MavenRepo.jar(module); if (Files.notExists(modulePath)) { fail("No such file: " + modulePath); } // JarInputStream expects the META-INF/MANIFEST.MF to be at the start of the JAR archive try (var jarInputStream = new JarInputStream(new FileInputStream(modulePath.toFile()))) { assertNotNull(jarInputStream.getManifest(), "MANIFEST.MF should be available via JarInputStream"); } } } ================================================ FILE: platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarDescribeModuleTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support.tests; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertLinesMatch; import static org.junit.jupiter.api.Assertions.assertTrue; import static platform.tooling.support.tests.Projects.getSourceDirectory; import java.lang.module.ModuleFinder; import java.nio.file.Files; import org.junit.jupiter.api.Order; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.junit.platform.tests.process.OutputFiles; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; import platform.tooling.support.ProcessStarters; /** * @since 1.3 */ @Order(Integer.MAX_VALUE) class JarDescribeModuleTests { @ParameterizedTest(quoteTextArguments = false) @MethodSource("platform.tooling.support.Helper#loadModuleDirectoryNames") void describeModule(String module, @FilePrefix("jar") OutputFiles outputFiles) throws Exception { var sourceDirectory = getSourceDirectory(Projects.JAR_DESCRIBE_MODULE); var modulePath = MavenRepo.jar(module); var result = ProcessStarters.javaCommand("jar") // .workingDir(sourceDirectory) // .addArguments("--describe-module", "--file", modulePath.toAbsolutePath().toString()) // .redirectOutput(outputFiles) // .startAndWait(); assertEquals(0, result.exitCode()); assertEquals("", result.stdErr(), "error log isn't empty"); var expectedLines = replaceVersionPlaceholders( Files.readString(sourceDirectory.resolve(module + ".expected.txt")).strip()); assertLinesMatch(expectedLines.lines().toList(), result.stdOut().strip().lines().toList()); } @ParameterizedTest(quoteTextArguments = false) @MethodSource("platform.tooling.support.Helper#loadModuleDirectoryNames") void packageNamesStartWithNameOfTheModule(String module) { var modulePath = MavenRepo.jar(module); var moduleDescriptor = ModuleFinder.of(modulePath).findAll().iterator().next().descriptor(); var moduleName = moduleDescriptor.name(); for (var packageName : moduleDescriptor.packages()) { assertTrue(packageName.startsWith(moduleName)); } } private static String replaceVersionPlaceholders(String line) { line = line.replace("${version}", Helper.version()); return line; } } ================================================ FILE: platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/KotlinCoroutinesTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support.tests; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static platform.tooling.support.tests.Projects.copyToWorkspace; import java.io.IOException; import java.nio.file.Path; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.platform.tests.process.OutputFiles; import org.junit.platform.tests.process.ProcessResult; import org.opentest4j.TestAbortedException; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; import platform.tooling.support.ProcessStarters; /** * @since 6.0 */ @Order(Integer.MIN_VALUE + 1) class KotlinCoroutinesTests { @Test void failsExpectedlyWhenAllOptionalDependenciesArePresent(@TempDir Path workspace, @FilePrefix("gradle") OutputFiles outputFiles) throws Exception { var result = runBuild(workspace, outputFiles); assertEquals(1, result.exitCode(), "result=" + result); assertThat(result.stdOut()).contains("AssertionFailedError: expected"); assertThat(result.stdErr()).contains("BUILD FAILED"); } @Test void failsWithHelpfulErrorMessageWhenKotlinxCoroutinesIsMissing(@TempDir Path workspace, @FilePrefix("gradle") OutputFiles outputFiles) throws Exception { var result = runBuild(workspace, outputFiles, "-PwithoutKotlinxCoroutines"); assertEquals(1, result.exitCode(), "result=" + result); assertThat(result.stdOut()).contains("PreconditionViolationException: Kotlin suspending function " + "[public final java.lang.Object com.example.project.SuspendFunctionTests.test(kotlin.coroutines.Continuation)] " + "requires org.jetbrains.kotlinx:kotlinx-coroutines-core to be on the classpath or module path. " + "Please add a corresponding dependency."); assertThat(result.stdErr()).contains("BUILD FAILED"); } @Test void failsWithHelpfulErrorMessageWhenKotlinReflectIsMissing(@TempDir Path workspace, @FilePrefix("gradle") OutputFiles outputFiles) throws Exception { var result = runBuild(workspace, outputFiles, "-PwithoutKotlinReflect"); assertEquals(1, result.exitCode(), "result=" + result); assertThat(result.stdOut()).contains("PreconditionViolationException: Kotlin suspending function " + "[public final java.lang.Object com.example.project.SuspendFunctionTests.test(kotlin.coroutines.Continuation)] " + "requires org.jetbrains.kotlin:kotlin-reflect to be on the classpath or module path. " + "Please add a corresponding dependency."); assertThat(result.stdErr()).contains("BUILD FAILED"); } private static ProcessResult runBuild(Path workspace, OutputFiles outputFiles, String... extraArgs) throws InterruptedException, IOException { return ProcessStarters.gradlew() // .workingDir(copyToWorkspace(Projects.KOTLIN_COROUTINES, workspace)) // .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // .addArguments("build", "--no-daemon", "--stacktrace", "--no-build-cache", "--warning-mode=fail") // .addArguments(extraArgs).putEnvironment("JDK17", Helper.getJavaHome(17).orElseThrow(TestAbortedException::new).toString()) // .redirectOutput(outputFiles) // .startAndWait(); } } ================================================ FILE: platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/LocalMavenRepo.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support.tests; import static platform.tooling.support.tests.ManagedResource.Scope.GLOBAL; import static platform.tooling.support.tests.ManagedResource.Scope.PER_CONTEXT; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Comparator; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Namespace; @ManagedResource.Scoped(LocalMavenRepo.ScopeProvider.class) public class LocalMavenRepo implements AutoCloseable { public static class ScopeProvider implements ManagedResource.Scoped.Provider { private static final Namespace NAMESPACE = Namespace.create(LocalMavenRepo.class); @Override public ManagedResource.Scope determineScope(ExtensionContext extensionContext) { var store = extensionContext.getRoot().getStore(NAMESPACE); var fileSystemType = store.computeIfAbsent("tempFileSystemType", key -> { var type = getFileSystemType(Path.of(System.getProperty("java.io.tmpdir"))); extensionContext.getRoot().publishReportEntry("tempFileSystemType", type); return type; }, String.class); // Writing to the same file from multiple Maven processes may fail the build on Windows return "NTFS".equalsIgnoreCase(fileSystemType) ? PER_CONTEXT : GLOBAL; } private static String getFileSystemType(Path path) { try { return Files.getFileStore(path).type(); } catch (IOException e) { throw new UncheckedIOException(e); } } } private final Path tempDir; public LocalMavenRepo() { try { tempDir = Files.createTempDirectory("local-maven-repo-"); } catch (IOException e) { throw new RuntimeException(e); } } public String toCliArgument() { return "-Dmaven.repo.local=" + tempDir; } @Override public void close() throws IOException { try (var files = Files.walk(tempDir)) { files.sorted(Comparator. naturalOrder().reversed()) // .forEach(path -> { try { Files.delete(path); } catch (IOException e) { throw new UncheckedIOException(e); } }); } } } ================================================ FILE: platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ManagedResource.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support.tests; import static java.lang.annotation.RetentionPolicy.RUNTIME; import static org.junit.platform.commons.support.ReflectionSupport.streamFields; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.Target; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Namespace; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.jupiter.api.extension.TestInstancePostProcessor; import org.junit.platform.commons.support.AnnotationSupport; import org.junit.platform.commons.support.HierarchyTraversalMode; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.Preconditions; @Target({ ElementType.PARAMETER, ElementType.FIELD }) @Retention(RUNTIME) @ExtendWith(ManagedResource.Extension.class) public @interface ManagedResource { @Target(ElementType.TYPE) @Retention(RUNTIME) @interface Scoped { Class value(); interface Provider { Scope determineScope(ExtensionContext extensionContext); } } enum Scope { GLOBAL, PER_CONTEXT } class Extension implements ParameterResolver, TestInstancePostProcessor { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return parameterContext.isAnnotated(ManagedResource.class); } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { Class type = parameterContext.getParameter().getType(); return getOrCreateResource(extensionContext, type).get(); } @Override public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { return ExtensionContextScope.TEST_METHOD; } @Override public void postProcessTestInstance(Object testInstance, ExtensionContext extensionContext) { streamFields(testInstance.getClass(), field -> AnnotationSupport.isAnnotated(field, ManagedResource.class), HierarchyTraversalMode.BOTTOM_UP) // .forEach(field -> { try { field.set(testInstance, getOrCreateResource(extensionContext, field.getType()).get()); } catch (IllegalAccessException e) { throw new RuntimeException("Failed to inject resource into field: " + field, e); } }); } @SuppressWarnings("unchecked") private Resource getOrCreateResource(ExtensionContext extensionContext, Class type) { var scope = AnnotationSupport.findAnnotation(type, Scoped.class) // .map(Scoped::value) // .map(ReflectionSupport::newInstance) // .map(provider -> provider.determineScope(extensionContext)) // .orElse(Scope.GLOBAL); var storingContext = switch (scope) { case GLOBAL -> extensionContext.getRoot(); case PER_CONTEXT -> extensionContext; }; return storingContext.getStore(Namespace.GLOBAL) // .computeIfAbsent(type, Resource::new, Resource.class); } } @SuppressWarnings("try") class Resource implements AutoCloseable { private final T value; private Resource(Class type) { Preconditions.condition(AutoCloseable.class.isAssignableFrom(type), () -> "Resource type must implement AutoCloseable: " + type.getName()); this.value = ReflectionSupport.newInstance(type); } private T get() { return value; } @Override public void close() throws Exception { ((AutoCloseable) value).close(); } } } ================================================ FILE: platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ManifestTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support.tests; import static aQute.bnd.osgi.Constants.VERSION_ATTRIBUTE; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import java.util.jar.Attributes; import aQute.bnd.osgi.Domain; import aQute.bnd.osgi.Jar; import aQute.bnd.version.MavenVersion; import org.junit.jupiter.api.Order; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.osgi.framework.Version; import org.osgi.framework.VersionRange; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; /** * @since 1.5 */ @Order(Integer.MAX_VALUE) class ManifestTests { @ParameterizedTest(quoteTextArguments = false) @MethodSource("platform.tooling.support.Helper#loadModuleDirectoryNames") void manifestEntriesAdhereToConventions(String module) throws Exception { var version = Helper.version(); var jarFile = MavenRepo.jar(module).toFile(); try (var jar = new Jar(jarFile)) { var manifest = jar.getManifest(); var attributes = manifest.getMainAttributes(); assertValue(attributes, "Built-By", "JUnit Team"); assertValue(attributes, "Specification-Title", module); assertValue(attributes, "Specification-Version", specificationVersion(version)); assertValue(attributes, "Specification-Vendor", "junit.org"); assertValue(attributes, "Implementation-Title", module); assertValue(attributes, "Implementation-Version", version); assertValue(attributes, "Implementation-Vendor", "junit.org"); assertValue(attributes, "Automatic-Module-Name", null); assertValue(attributes, "Bundle-ManifestVersion", "2"); assertValue(attributes, "Bundle-SymbolicName", module); assertValue(attributes, "Bundle-Version", MavenVersion.parseMavenString(version).getOSGiVersion().toString()); if (module.equals("junit-platform-console")) { assertValue(attributes, "Main-Class", "org.junit.platform.console.ConsoleLauncher"); } var domain = Domain.domain(manifest); domain.getExportPackage().forEach((pkg, attrs) -> { final var stringVersion = attrs.get(VERSION_ATTRIBUTE); assertNotNull(stringVersion); assertDoesNotThrow(() -> new Version(stringVersion)); }); domain.getImportPackage().forEach((pkg, attrs) -> { final var stringVersionRange = attrs.get(VERSION_ATTRIBUTE); if (stringVersionRange == null) { return; } assertDoesNotThrow(() -> new VersionRange(stringVersionRange)); }); } } private static String specificationVersion(String version) { var dash = version.indexOf('-'); if (dash < 0) { return version; } return version.substring(0, dash); } private static void assertValue(Attributes attributes, String name, String expected) { var actual = attributes.getValue(name); assertEquals(expected, actual, "Manifest attribute %s expected to be %s, but is: %s".formatted(name, expected, actual)); } } ================================================ FILE: platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenEnvVars.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support.tests; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.junit.jupiter.api.condition.JRE; final class MavenEnvVars { private static final List FOR_JDK24_AND_LATER = List.of( // "--enable-native-access=ALL-UNNAMED", // https://issues.apache.org/jira/browse/MNG-8248 "--sun-misc-unsafe-memory-access=allow" // https://issues.apache.org/jira/browse/MNG-8399 ); private static final List FOR_JDK26_AND_LATER = List.of( // "--enable-final-field-mutation=ALL-UNNAMED" // https://github.com/junit-team/junit-framework/issues/5173 ); static Map forJre(JRE jre) { var list = new ArrayList(); if (jre.compareTo(JRE.JAVA_24) >= 0) list.addAll(FOR_JDK24_AND_LATER); if (jre == JRE.JAVA_26) { // exclude "leyden" and "valhalla" builds if (Runtime.version().build().orElse(0) >= 25) { list.addAll(FOR_JDK26_AND_LATER); } } if (jre.compareTo(JRE.JAVA_27) >= 0) { list.addAll(FOR_JDK26_AND_LATER); } return list.isEmpty() ? Map.of() : Map.of("MAVEN_OPTS", String.join(" ", list)); } private MavenEnvVars() { } } ================================================ FILE: platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenPomFileTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support.tests; import static org.junit.jupiter.api.Assertions.assertLinesMatch; import java.nio.file.Files; import java.util.List; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import platform.tooling.support.MavenRepo; /** * @since 1.4 */ @Order(Integer.MAX_VALUE) class MavenPomFileTests { @Test void jupiterAggregatorPomDependencies() throws Exception { var expected = List.of(">> HEAD >>", // " ", // " ", // " ", // " org.junit", // " junit-bom", // ">> VERSION >>", // " pom", // " import", // " ", // " ", // " ", // " ", // " ", // " org.junit.jupiter", // " junit-jupiter-api", // ">> VERSION >>", // " compile", // " ", // " ", // " org.junit.jupiter", // " junit-jupiter-params", // ">> VERSION >>", // " compile", // " ", // " ", // " org.junit.jupiter", // " junit-jupiter-engine", // ">> VERSION >>", // " runtime", // " ", // " ", // ">> TAIL >>"); assertLinesMatch(expected, Files.readAllLines(MavenRepo.pom("junit-jupiter"))); } @Test void jupiterAggregatorGradleMetadataMarker() throws Exception { var expected = List.of(">> HEAD >>", // " ", // ">> TAIL >>"); assertLinesMatch(expected, Files.readAllLines(MavenRepo.pom("junit-jupiter"))); } } ================================================ FILE: platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenRepoProxy.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support.tests; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.URI; import java.util.List; import com.sun.net.httpserver.HttpServer; public class MavenRepoProxy implements AutoCloseable { // Forbid downloading JUnit artifacts since we want to use the local ones private static final List FORBIDDEN_PATHS = List.of("/org/junit"); private final HttpServer httpServer; @SuppressWarnings("unused") public MavenRepoProxy() throws IOException { this(0); } @SuppressWarnings("unused") public MavenRepoProxy(int port) throws IOException { this("https://central.sonatype.com/repository/maven-snapshots", port); } private MavenRepoProxy(String proxiedUrl, int port) throws IOException { httpServer = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), port), 0); httpServer.createContext("/", exchange -> { try (exchange) { switch (exchange.getRequestMethod()) { case "HEAD", "GET" -> { if (FORBIDDEN_PATHS.stream().anyMatch(exchange.getRequestURI().getPath()::startsWith)) { exchange.sendResponseHeaders(404, -1); break; } var redirectUrl = proxiedUrl + exchange.getRequestURI().getPath(); exchange.getResponseHeaders().add("Location", redirectUrl); exchange.sendResponseHeaders(302, -1); } default -> exchange.sendResponseHeaders(405, -1); } } catch (Exception e) { e.printStackTrace(); } }); httpServer.start(); } URI getBaseUri() { var address = httpServer.getAddress(); return URI.create("http://" + address.getAddress().getHostName() + ":" + address.getPort()); } @Override public void close() { httpServer.stop(0); } } ================================================ FILE: platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support.tests; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static platform.tooling.support.tests.Projects.copyToWorkspace; import static platform.tooling.support.tests.XmlAssertions.verifyContainsExpectedStartedOpenTestReport; import java.nio.file.Path; import de.skuzzle.test.snapshots.Snapshot; import de.skuzzle.test.snapshots.junit5.EnableSnapshotTests; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.platform.tests.process.OutputFiles; import org.junit.platform.tests.process.ProcessResult; import org.opentest4j.TestAbortedException; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; import platform.tooling.support.ProcessStarters; /** * @since 1.3 */ @EnableSnapshotTests //@SnapshotTestOptions(alwaysPersistActualResult = true) class MavenStarterTests { @ManagedResource LocalMavenRepo localMavenRepo; @ManagedResource MavenRepoProxy mavenRepoProxy; @TempDir Path workspace; @BeforeEach void prepareWorkspace() throws Exception { copyToWorkspace(Projects.JUPITER_STARTER, workspace); } @Test void verifyJupiterStarterProject(@FilePrefix("maven") OutputFiles outputFiles, Snapshot snapshot) throws Exception { var result = runMaven(outputFiles, "verify"); assertThat(result.stdOutLines()).contains("[INFO] Tests run: 13, Failures: 0, Errors: 0, Skipped: 0"); assertThat(result.stdOut()).contains("Using Java version: 17"); var testResultsDir = workspace.resolve("target/surefire-reports"); verifyContainsExpectedStartedOpenTestReport(testResultsDir, snapshot); } @Test void runOnlyOneMethodInClassTemplate(@FilePrefix("maven") OutputFiles outputFiles) throws Exception { var result = runMaven(outputFiles, "test", "-Dtest=CalculatorParameterizedClassTests$Inner#regularTest"); assertThat(result.stdOutLines()) // .doesNotContain("CalculatorTests") // .contains("[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0"); result = runMaven(outputFiles, "test", "-Dtest=CalculatorParameterizedClassTests#parameterizedTest"); assertThat(result.stdOutLines()) // .doesNotContain("CalculatorTests") // .contains("[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0"); } private ProcessResult runMaven(OutputFiles outputFiles, String... extraArgs) throws InterruptedException { var result = ProcessStarters.maven(Helper.getJavaHome(17).orElseThrow(TestAbortedException::new)) // .workingDir(workspace) // .addArguments(localMavenRepo.toCliArgument(), "-Dmaven.repo=" + MavenRepo.dir()) // .addArguments("-Dsnapshot.repo.url=" + mavenRepoProxy.getBaseUri()) // .addArguments("--update-snapshots", "--batch-mode") // .addArguments(extraArgs).redirectOutput(outputFiles) // .startAndWait(); assertEquals(0, result.exitCode()); assertEquals("", result.stdErr()); assertThat(result.stdOutLines()).contains("[INFO] BUILD SUCCESS"); return result; } } ================================================ FILE: platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenSurefireCompatibilityTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support.tests; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static platform.tooling.support.tests.Projects.copyToWorkspace; import java.nio.file.Files; import java.nio.file.Path; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.platform.tests.process.OutputFiles; import org.opentest4j.TestAbortedException; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; import platform.tooling.support.ProcessStarters; /** * @since 1.9.2 */ class MavenSurefireCompatibilityTests { static final String MINIMUM_SUPPORTED_SUREFIRE_VERSION = "3.0.0"; @ManagedResource LocalMavenRepo localMavenRepo; @Test void testMavenSurefireCompatibilityProject(@TempDir Path workspace, @FilePrefix("maven") OutputFiles outputFiles) throws Exception { var result = ProcessStarters.maven(Helper.getJavaHome(17).orElseThrow(TestAbortedException::new)) // .workingDir(copyToWorkspace(Projects.MAVEN_SUREFIRE_COMPATIBILITY, workspace)) // .addArguments(localMavenRepo.toCliArgument(), "-Dmaven.repo=" + MavenRepo.dir()) // .addArguments("-Dsurefire.version=" + MINIMUM_SUPPORTED_SUREFIRE_VERSION) // .addArguments("--update-snapshots", "--batch-mode", "test") // .redirectOutput(outputFiles) // .startAndWait(); assertEquals(0, result.exitCode()); assertEquals("", result.stdErr()); var output = result.stdOutLines(); assertTrue(output.contains("[INFO] BUILD SUCCESS")); assertTrue(output.contains("[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0")); var targetDir = workspace.resolve("target"); try (var stream = Files.list(targetDir)) { assertThat(stream.filter(file -> file.getFileName().toString().startsWith("junit-platform-unique-ids"))) // .hasSize(1); } } } ================================================ FILE: platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MemoryCleanupTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support.tests; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; import static org.junit.platform.launcher.LauncherConstants.MEMORY_CLEANUP_ENABLED_PROPERTY_NAME; import static platform.tooling.support.tests.Projects.copyToWorkspace; import java.nio.file.Path; import java.time.Duration; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.OS; import org.junit.jupiter.api.extension.DisabledOnOpenJ9; import org.junit.jupiter.api.io.TempDir; import org.junit.platform.tests.process.OutputFiles; import org.junit.platform.tests.process.ProcessResult; import platform.tooling.support.MavenRepo; import platform.tooling.support.ProcessStarters; /** * @since 6.1 */ class MemoryCleanupTests { @TempDir Path workspace; @Test @DisabledOnOpenJ9 void runsWithSmallHeapSize(@FilePrefix("javac") OutputFiles javacOutputFiles, @FilePrefix("java") OutputFiles javaOutputFiles) throws Exception { copyToWorkspace(Projects.MEMORY_CLEANUP, workspace); compile(javacOutputFiles); var timeout = Duration.ofSeconds(OS.WINDOWS.isCurrentOs() ? 20 : 10); var result = assertTimeoutPreemptively(timeout, () -> executeWithSmallHeapSize(javaOutputFiles)); assertThat(result).isNotNull(); assertThat(result.exitCode()).isOne(); assertThat(result.stdOut()) // .contains("1000000 tests found") // .contains(" 999999 tests successful") // .contains(" 1 tests failed"); } void compile(OutputFiles javacOutputFiles) throws Exception { var result = ProcessStarters.javaCommand("javac") // .workingDir(workspace) // .addArguments("-Xlint:all") // .addArguments("--release", "17") // .addArguments("-proc:none") // .addArguments("-d", workspace.resolve("bin").toString()) // .addArguments("--class-path", MavenRepo.jar("junit-platform-console-standalone").toString()) // .addArguments(workspace.resolve("src/OneMillionTests.java").toString()) // .redirectOutput(javacOutputFiles) // .startAndWait(); assertThat(result.exitCode()).isZero(); assertThat(result.stdOut()).isEmpty(); assertThat(result.stdErr()).isEmpty(); } private ProcessResult executeWithSmallHeapSize(OutputFiles outputFiles) throws Exception { return ProcessStarters.java() // .workingDir(workspace) // .addArguments("-Xmx16m") // .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone").toString()) // .addArguments("execute") // .addArguments("--scan-class-path") // .addArguments("--disable-banner") // .addArguments("--classpath", "bin") // .addArguments("--details=summary") // .addArguments("--config=%s=%s".formatted(MEMORY_CLEANUP_ENABLED_PROPERTY_NAME, true)) // .redirectOutput(outputFiles) // .startAndWait(); } } ================================================ FILE: platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularCompilationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support.tests; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.platform.tests.process.OutputFiles; import platform.tooling.support.ProcessStarters; import platform.tooling.support.ThirdPartyJars; class ModularCompilationTests { @Test void compileAllJUnitModules(@TempDir Path workspace, @FilePrefix("javac") OutputFiles javacOutputFiles) throws Exception { var lib = Files.createDirectories(workspace.resolve("lib")); ThirdPartyJars.copyAll(lib); var moduleNames = Arrays.asList(System.getProperty("junit.modules").split(",")); var outputDir = workspace.resolve("classes").toAbsolutePath(); var processStarter = ProcessStarters.javaCommand("javac") // .workingDir(workspace) // .addArguments("-d", outputDir.toString()) // .addArguments("-Xlint:all", "-Werror") // .addArguments("-Xlint:-requires-automatic,-requires-transitive-automatic") // JUnit 4 // external modules .addArguments("--module-path", lib.toAbsolutePath().toString()); // source locations in module-specific form moduleNames.forEach( moduleName -> processStarter.addArguments("--module-source-path", moduleSourcePath(moduleName))); var result = processStarter // un-shadow .addArguments("--add-modules", "info.picocli") // .addArguments("--add-reads", "org.junit.platform.console=info.picocli") // .addArguments("--add-modules", "org.opentest4j.reporting.events") // .addArguments("--add-reads", "org.junit.platform.reporting=org.opentest4j.reporting.events") // .addArguments("--add-modules", "de.siegmar.fastcsv") // .addArguments("--add-reads", "org.junit.jupiter.params=de.siegmar.fastcsv") // modules to compile .addArguments("--module", String.join(",", moduleNames)) // .redirectOutput(javacOutputFiles) // .startAndWait(); assertEquals(0, result.exitCode()); assertThat(outputDir).isNotEmptyDirectory(); } static String moduleSourcePath(String moduleName) { return "%s=%s".formatted(moduleName, requireNonNull(System.getProperty("junit.moduleSourcePath." + moduleName))); } } ================================================ FILE: platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularUserGuideTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support.tests; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertLinesMatch; import static org.junit.jupiter.api.Assertions.assertTrue; import static platform.tooling.support.Helper.loadModuleDirectoryNames; import java.io.File; import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.spi.ToolProvider; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.DisabledOnOpenJ9; import org.junit.jupiter.api.io.TempDir; import org.junit.platform.launcher.LauncherConstants; import org.junit.platform.tests.process.OutputFiles; import platform.tooling.support.MavenRepo; import platform.tooling.support.ProcessStarters; import platform.tooling.support.ThirdPartyJars; /** * @since 1.5 */ @DisabledOnOpenJ9 class ModularUserGuideTests { private static final String DOCUMENTATION_MODULE_DESCRIPTOR = """ @SuppressWarnings("removal") open module documentation { exports example.testkit; // just here to ensure documentation example sources are compiled requires org.junit.jupiter.api; requires org.junit.jupiter.migrationsupport; requires org.junit.jupiter.params; requires org.junit.platform.engine; requires org.junit.platform.reporting; requires org.junit.platform.suite; requires org.junit.platform.testkit; // Byte Buddy is used by AssertJ's soft assertions which are used by the Engine Test Kit requires net.bytebuddy; requires java.desktop; requires java.logging; requires java.scripting; requires jdk.httpserver; provides org.junit.platform.launcher.LauncherSessionListener with example.session.GlobalSetupTeardownListener; } """; private static List compile(Path temp, Writer out, Writer err) throws Exception { var documentation = Files.createDirectories(temp.resolve("src/documentation")); Files.writeString(documentation.resolve("module-info.java"), DOCUMENTATION_MODULE_DESCRIPTOR); var args = new ArrayList(); args.add("-Xlint"); // enable all default warnings args.add("-proc:none"); // disable annotation processing args.add("-cp"); args.add(""); // set empty class path, otherwise system property "java.class.path" is read args.add("-d"); args.add(temp.resolve("destination").toString()); var lib = Files.createDirectories(temp.resolve("lib")); ThirdPartyJars.copyAll(lib); loadAllJUnitModules(lib); args.add("--module-path"); args.add(lib.toString()); args.add("--patch-module"); args.add("documentation=" + Path.of("../documentation/src/main/java") + File.pathSeparator + Path.of("../documentation/src/test/java")); args.add("--module-source-path"); args.add(temp.resolve("src").toString()); args.add(documentation.resolve("module-info.java").toString()); try (var walk = Files.walk(Path.of("../documentation/src/test/java"))) { walk.map(Path::toString) // .filter(s -> s.endsWith(".java")) // // TypeError: systemProperty.get is not a function ?!?! .filter(s -> !s.endsWith("ConditionalTestExecutionDemo.java")) // // Don't include command-line tools that "require io.github.classgraph" .filter(s -> !s.contains("tools")).forEach(args::add); } var javac = ToolProvider.findFirst("javac").orElseThrow(); var code = javac.run(new PrintWriter(out), new PrintWriter(err), args.toArray(String[]::new)); assertEquals(0, code, err.toString()); assertTrue(out.toString().isBlank(), out.toString()); return args; } private static void junit(Path temp, OutputFiles outputFiles) throws Exception { var projectDir = Path.of("../documentation").toAbsolutePath(); var result = ProcessStarters.java() // .workingDir(projectDir) // .addArguments("-XX:StartFlightRecording:filename=" + temp.resolve("user-guide.jfr")) // .addArguments("--show-version", "--show-module-resolution") // .addArguments("--module-path", String.join(File.pathSeparator, // temp.resolve("destination").toString(), // temp.resolve("lib").toString() // )) // .addArguments("--add-modules", "documentation") // .addArguments("--patch-module", "documentation=" + projectDir.resolve("src/test/resources")) // .addArguments("--add-modules", "com.google.common") // .addArguments("--module", "org.junit.platform.console") // .addArguments("execute") // .addArguments("--scan-modules") // .addArguments("--config", "enableHttpServer=true") // .addArguments("--config", LauncherConstants.OUTPUT_DIR_PROPERTY_NAME + "=" + temp.resolve("reports")) // .addArguments("--fail-if-no-tests") // .addArguments("--include-classname", ".*Tests") // .addArguments("--include-classname", ".*Demo") // .addArguments("--exclude-tag", "exclude") // .addArguments("--exclude-tag", "exclude") // .redirectOutput(outputFiles) // .startAndWait(); assertEquals(0, result.exitCode()); } @Test void runTestsFromUserGuideWithinModularBoundaries(@TempDir Path temp, @FilePrefix("console-launcher") OutputFiles outputFiles) throws Exception { var out = new StringWriter(); var err = new StringWriter(); var args = compile(temp, out, err); assertTrue(err.toString().isBlank(), () -> err + "\n\n" + String.join("\n", args)); var listing = treeWalk(temp); assertLinesMatch(List.of( // "destination", // ">> CLASSES AND JARS >>", // "src", // "src/documentation", // "src/documentation/module-info.java" // ), listing); junit(temp, outputFiles); } private static void loadAllJUnitModules(Path target) throws Exception { for (var module : loadModuleDirectoryNames()) { var jar = MavenRepo.jar(module); Files.copy(jar, target.resolve(jar.getFileName())); } } private static List treeWalk(Path root) { var lines = new ArrayList(); treeWalk(root, lines::add); return lines; } private static void treeWalk(Path root, Consumer out) { try (var stream = Files.walk(root)) { stream.map(root::relativize) // .map(path -> path.toString().replace('\\', '/')) // .sorted().filter(Predicate.not(String::isEmpty)) // .forEach(out); } catch (Exception e) { throw new Error("Walking tree failed: " + root, e); } } } ================================================ FILE: platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/OutputAttachingExtension.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support.tests; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.Comparator; import org.junit.jupiter.api.MediaType; import org.junit.jupiter.api.extension.AfterTestExecutionCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Namespace; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.platform.tests.process.OutputFiles; import org.junit.platform.tests.process.ProcessStarter; class OutputAttachingExtension implements ParameterResolver, AfterTestExecutionCallback { private static final Namespace NAMESPACE = Namespace.create(OutputAttachingExtension.class); private static final MediaType MEDIA_TYPE = MediaType.create("text", "plain", ProcessStarter.OUTPUT_ENCODING); @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return parameterContext.isAnnotated(FilePrefix.class); } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { var outputDir = extensionContext.getStore(NAMESPACE).computeIfAbsent("outputDir", __ -> { try { return new OutputDir(Files.createTempDirectory("output")); } catch (Exception e) { throw new ParameterResolutionException("Failed to create temp directory", e); } }, OutputDir.class); var prefix = parameterContext.findAnnotation(FilePrefix.class) // .map(FilePrefix::value) // .orElseThrow(); return outputDir.toOutputFiles(prefix); } @Override public void afterTestExecution(ExtensionContext context) throws Exception { var outputDir = context.getStore(NAMESPACE).get("outputDir", OutputDir.class); if (outputDir != null) { try (var stream = Files.list(outputDir.root()).filter(Files::isRegularFile).sorted()) { stream.filter(OutputAttachingExtension::notEmpty).forEach(file -> { var fileName = file.getFileName().toString(); context.publishFile(fileName, MEDIA_TYPE, target -> Files.copy(file, target, StandardCopyOption.REPLACE_EXISTING)); }); } } } private static boolean notEmpty(Path file) { try { return Files.size(file) > 0; } catch (IOException e) { throw new UncheckedIOException(e); } } @SuppressWarnings("try") record OutputDir(Path root) implements AutoCloseable { @Override public void close() throws Exception { try (var stream = Files.walk(root).sorted(Comparator. naturalOrder().reversed())) { stream.forEach(path -> { try { Files.delete(path); } catch (IOException e) { throw new UncheckedIOException("Failed to delete " + path, e); } }); } } private OutputFiles toOutputFiles(String prefix) { return new OutputFiles(root.resolve(prefix + "-stdout.txt"), root.resolve(prefix + "-stderr.txt")); } } } ================================================ FILE: platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/Projects.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support.tests; import java.io.IOException; import java.nio.file.Path; import org.apache.commons.io.file.PathUtils; public class Projects { public static final String GRAALVM_STARTER = "graalvm-starter"; public static final String GRADLE_KOTLIN_EXTENSIONS = "gradle-kotlin-extensions"; public static final String GRADLE_MISSING_ENGINE = "gradle-missing-engine"; public static final String JAR_DESCRIBE_MODULE = "jar-describe-module"; public static final String JUNIT_START = "junit-start"; public static final String JUPITER_STARTER = "jupiter-starter"; public static final String KOTLIN_COROUTINES = "kotlin-coroutines"; public static final String MAVEN_SUREFIRE_COMPATIBILITY = "maven-surefire-compatibility"; public static final String MEMORY_CLEANUP = "memory-cleanup"; public static final String REFLECTION_TESTS = "reflection-tests"; public static final String STANDALONE = "standalone"; public static final String VINTAGE = "vintage"; private Projects() { } static Path copyToWorkspace(String project, Path workspace) throws IOException { PathUtils.copyDirectory(getSourceDirectory(project), workspace); return workspace; } static Path getSourceDirectory(String project) { return Path.of("projects").resolve(project); } } ================================================ FILE: platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ReflectionCompatibilityTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support.tests; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static platform.tooling.support.tests.Projects.copyToWorkspace; import java.nio.file.Path; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.platform.tests.process.OutputFiles; import org.opentest4j.TestAbortedException; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; import platform.tooling.support.ProcessStarters; /** * @since 1.11 */ class ReflectionCompatibilityTests { @Test void gradle_wrapper(@TempDir Path workspace, @FilePrefix("gradle") OutputFiles outputFiles) throws Exception { var result = ProcessStarters.gradlew() // .workingDir(copyToWorkspace(Projects.REFLECTION_TESTS, workspace)) // .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // .addArguments("build", "--no-daemon", "--stacktrace", "--no-build-cache", "--warning-mode=fail") // .putEnvironment("JDK17", Helper.getJavaHome(17).orElseThrow(TestAbortedException::new).toString()) // .redirectOutput(outputFiles) // .startAndWait(); assertEquals(0, result.exitCode()); assertTrue(result.stdOut().lines().anyMatch(line -> line.contains("BUILD SUCCESSFUL"))); assertThat(result.stdOut()).contains("Using Java version: 17"); } } ================================================ FILE: platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support.tests; import static java.util.stream.Collectors.joining; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertLinesMatch; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static platform.tooling.support.tests.Projects.copyToWorkspace; import static platform.tooling.support.tests.Projects.getSourceDirectory; import java.io.File; import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.parallel.Execution; import org.junit.platform.tests.process.OutputFiles; import org.junit.platform.tests.process.ProcessResult; import org.opentest4j.TestAbortedException; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; import platform.tooling.support.ProcessStarters; import platform.tooling.support.ThirdPartyJars; /** * @since 1.4 */ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) @Execution(CONCURRENT) class StandaloneTests { @TempDir static Path workspace; @BeforeAll static void prepareWorkspace() throws IOException { copyToWorkspace(Projects.STANDALONE, workspace); } @Test void jarFileWithoutCompiledModuleDescriptorClass() throws Exception { var jar = MavenRepo.jar("junit-platform-console-standalone"); var name = "module-info.class"; var found = new ArrayList(); try (var fileSystem = FileSystems.newFileSystem(jar)) { for (var rootDirectory : fileSystem.getRootDirectories()) { try (var stream = Files.walk(rootDirectory)) { stream.filter(path -> path.getNameCount() > 0) // skip root entry .filter(path -> path.getFileName().toString().equals(name)).forEach(found::add); } } } assertTrue(found.isEmpty(), jar + " must not contain any " + name + " files: " + found); } @Test void listAllObservableEngines(@FilePrefix("java") OutputFiles outputFiles) throws Exception { var result = ProcessStarters.java() // .workingDir(getSourceDirectory(Projects.STANDALONE)) // .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone").toString()) // .addArguments("engines", "--disable-ansi-colors", "--disable-banner") // .redirectOutput(outputFiles) // .startAndWait(); assertEquals(0, result.exitCode()); assertLinesMatch(""" junit-jupiter (org.junit.jupiter:junit-jupiter-engine:%1$s) junit-platform-suite (org.junit.platform:junit-platform-suite-engine:%1$s) junit-vintage (org.junit.vintage:junit-vintage-engine:%1$s) """.formatted(Helper.version()).lines(), // result.stdOut().lines()); } @Test void printVersionViaJar(@FilePrefix("java") OutputFiles outputFiles) throws Exception { var result = ProcessStarters.java() // .workingDir(getSourceDirectory(Projects.STANDALONE)) // .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone").toString()) // .addArguments("--version", "--disable-ansi-colors") // .putEnvironment("CLICOLOR_FORCE", "1") // enable ANSI colors by default (see https://picocli.info/#_heuristics_for_enabling_ansi) .redirectOutput(outputFiles) // .startAndWait(); assertEquals(0, result.exitCode()); var version = Helper.version(); assertLinesMatch(""" JUnit Platform Console Launcher %s JVM: .* OS: .* """.formatted(version).lines(), // result.stdOut().lines()); } @Test void printVersionViaModule(@FilePrefix("java") OutputFiles outputFiles) throws Exception { var junitJars = Stream.of("junit-platform-console", "junit-platform-reporting", "junit-platform-engine", "junit-platform-launcher", "junit-platform-commons") // .map(MavenRepo::jar); var thirdPartyJars = Stream.of( // ThirdPartyJars.find("org.opentest4j", "opentest4j"), // ThirdPartyJars.find("org.opentest4j.reporting", "open-test-reporting-tooling-spi") // ); var modulePath = Stream.concat(junitJars, thirdPartyJars) // .map(String::valueOf) // .collect(joining(File.pathSeparator)); var result = ProcessStarters.java() // .workingDir(getSourceDirectory(Projects.STANDALONE)) // .addArguments("--module-path", modulePath) // .addArguments("--module", "org.junit.platform.console") // .addArguments("--version", "--disable-ansi-colors") // .putEnvironment("CLICOLOR_FORCE", "1") // enable ANSI colors by default (see https://picocli.info/#_heuristics_for_enabling_ansi) .redirectOutput(outputFiles) // .startAndWait(); assertEquals(0, result.exitCode()); var version = Helper.version(); assertLinesMatch(""" JUnit Platform Console Launcher %s JVM: .* OS: .* """.formatted(version).lines(), // result.stdOut().lines()); } @Test @Order(1) @Execution(SAME_THREAD) void compile(@FilePrefix("javac") OutputFiles javacOutputFiles) throws Exception { var result = ProcessStarters.javaCommand("javac") // .workingDir(workspace) // .addArguments("-Xlint:-options") // .addArguments("--release", "17") // .addArguments("-proc:none") // .addArguments("-d", workspace.resolve("bin").toString()) // .addArguments("--class-path", MavenRepo.jar("junit-platform-console-standalone").toString()) // .addArguments(workspace.resolve("src/other/OtherwiseNotReferencedClass.java").toString()) // .addArguments(workspace.resolve("src/standalone/JupiterIntegration.java").toString()) // .addArguments(workspace.resolve("src/standalone/JupiterParamsIntegration.java").toString()) // .addArguments(workspace.resolve("src/standalone/SuiteIntegration.java").toString()) // .addArguments(workspace.resolve("src/standalone/VintageIntegration.java").toString()) // .redirectOutput(javacOutputFiles) // .startAndWait(); assertEquals(0, result.exitCode()); assertTrue(result.stdOut().isEmpty()); assertTrue(result.stdErr().isEmpty()); } @Test @Order(2) @Execution(SAME_THREAD) void discoverTree(@FilePrefix("console-launcher") OutputFiles outputFiles) throws Exception { var result = discover(outputFiles, "--details-theme=ascii"); var expected = """ . +-- JUnit Platform Suite | '-- SuiteIntegration | '-- JUnit Jupiter | '-- SuiteIntegration$SingleTestContainer | '-- successful() +-- JUnit Jupiter | +-- JupiterIntegration | | +-- successful() | | +-- fail() | | +-- abort() | | '-- disabled() | +-- SuiteIntegration$SingleTestContainer | | '-- successful() | '-- JupiterParamsIntegration | '-- parameterizedTest(String) '-- JUnit Vintage '-- VintageIntegration +-- f4il +-- ignored '-- succ3ssful [ 11 containers found ] [ 9 tests found ] """.stripIndent(); assertLinesMatch(expected.lines(), result.stdOut().lines()); } @Test @Order(2) @Execution(SAME_THREAD) void discoverFlat(@FilePrefix("console-launcher") OutputFiles outputFiles) throws Exception { var result = discover(outputFiles, "--details=flat"); var expected = """ JUnit Platform Suite ([engine:junit-platform-suite]) SuiteIntegration ([engine:junit-platform-suite]/[suite:standalone.SuiteIntegration]) JUnit Jupiter ([engine:junit-platform-suite]/[suite:standalone.SuiteIntegration]/[engine:junit-jupiter]) SuiteIntegration$SingleTestContainer ([engine:junit-platform-suite]/[suite:standalone.SuiteIntegration]/[engine:junit-jupiter]/[class:standalone.SuiteIntegration$SingleTestContainer]) successful() ([engine:junit-platform-suite]/[suite:standalone.SuiteIntegration]/[engine:junit-jupiter]/[class:standalone.SuiteIntegration$SingleTestContainer]/[method:successful()]) JUnit Jupiter ([engine:junit-jupiter]) JupiterIntegration ([engine:junit-jupiter]/[class:standalone.JupiterIntegration]) successful() ([engine:junit-jupiter]/[class:standalone.JupiterIntegration]/[method:successful()]) fail() ([engine:junit-jupiter]/[class:standalone.JupiterIntegration]/[method:fail()]) abort() ([engine:junit-jupiter]/[class:standalone.JupiterIntegration]/[method:abort()]) disabled() ([engine:junit-jupiter]/[class:standalone.JupiterIntegration]/[method:disabled()]) SuiteIntegration$SingleTestContainer ([engine:junit-jupiter]/[class:standalone.SuiteIntegration$SingleTestContainer]) successful() ([engine:junit-jupiter]/[class:standalone.SuiteIntegration$SingleTestContainer]/[method:successful()]) JupiterParamsIntegration ([engine:junit-jupiter]/[class:standalone.JupiterParamsIntegration]) parameterizedTest(String) ([engine:junit-jupiter]/[class:standalone.JupiterParamsIntegration]/[test-template:parameterizedTest(java.lang.String)]) JUnit Vintage ([engine:junit-vintage]) VintageIntegration ([engine:junit-vintage]/[runner:standalone.VintageIntegration]) f4il ([engine:junit-vintage]/[runner:standalone.VintageIntegration]/[test:f4il(standalone.VintageIntegration)]) ignored ([engine:junit-vintage]/[runner:standalone.VintageIntegration]/[test:ignored(standalone.VintageIntegration)]) succ3ssful ([engine:junit-vintage]/[runner:standalone.VintageIntegration]/[test:succ3ssful(standalone.VintageIntegration)]) [ 11 containers found ] [ 9 tests found ] """.stripIndent(); assertLinesMatch(expected.lines(), result.stdOut().lines()); } @Test @Order(2) @Execution(SAME_THREAD) void discoverVerbose(@FilePrefix("console-launcher") OutputFiles outputFiles) throws Exception { var result = discover(outputFiles, "--details=verbose", "--details-theme=ascii"); var expected = """ +-- JUnit Platform Suite | +-- SuiteIntegration | | +-- JUnit Jupiter | | | +-- SuiteIntegration$SingleTestContainer | | | | +-- successful() | | | | | tags: [] | | | | | uniqueId: [engine:junit-platform-suite]/[suite:standalone.SuiteIntegration]/[engine:junit-jupiter]/[class:standalone.SuiteIntegration$SingleTestContainer]/[method:successful()] | | | | | parent: [engine:junit-platform-suite]/[suite:standalone.SuiteIntegration]/[engine:junit-jupiter]/[class:standalone.SuiteIntegration$SingleTestContainer] | | | | | source: MethodSource [className = 'standalone.SuiteIntegration$SingleTestContainer', methodName = 'successful', methodParameterTypes = ''] | | | '-- SuiteIntegration$SingleTestContainer | | '-- JUnit Jupiter | '-- SuiteIntegration '-- JUnit Platform Suite +-- JUnit Jupiter | +-- JupiterIntegration | | +-- successful() | | | tags: [] | | | uniqueId: [engine:junit-jupiter]/[class:standalone.JupiterIntegration]/[method:successful()] | | | parent: [engine:junit-jupiter]/[class:standalone.JupiterIntegration] | | | source: MethodSource [className = 'standalone.JupiterIntegration', methodName = 'successful', methodParameterTypes = ''] | | +-- fail() | | | tags: [] | | | uniqueId: [engine:junit-jupiter]/[class:standalone.JupiterIntegration]/[method:fail()] | | | parent: [engine:junit-jupiter]/[class:standalone.JupiterIntegration] | | | source: MethodSource [className = 'standalone.JupiterIntegration', methodName = 'fail', methodParameterTypes = ''] | | +-- abort() | | | tags: [] | | | uniqueId: [engine:junit-jupiter]/[class:standalone.JupiterIntegration]/[method:abort()] | | | parent: [engine:junit-jupiter]/[class:standalone.JupiterIntegration] | | | source: MethodSource [className = 'standalone.JupiterIntegration', methodName = 'abort', methodParameterTypes = ''] | | +-- disabled() | | | tags: [] | | | uniqueId: [engine:junit-jupiter]/[class:standalone.JupiterIntegration]/[method:disabled()] | | | parent: [engine:junit-jupiter]/[class:standalone.JupiterIntegration] | | | source: MethodSource [className = 'standalone.JupiterIntegration', methodName = 'disabled', methodParameterTypes = ''] | '-- JupiterIntegration | +-- SuiteIntegration$SingleTestContainer | | +-- successful() | | | tags: [] | | | uniqueId: [engine:junit-jupiter]/[class:standalone.SuiteIntegration$SingleTestContainer]/[method:successful()] | | | parent: [engine:junit-jupiter]/[class:standalone.SuiteIntegration$SingleTestContainer] | | | source: MethodSource [className = 'standalone.SuiteIntegration$SingleTestContainer', methodName = 'successful', methodParameterTypes = ''] | '-- SuiteIntegration$SingleTestContainer | +-- JupiterParamsIntegration | | +-- parameterizedTest(String) | | | tags: [] | | | uniqueId: [engine:junit-jupiter]/[class:standalone.JupiterParamsIntegration]/[test-template:parameterizedTest(java.lang.String)] | | | parent: [engine:junit-jupiter]/[class:standalone.JupiterParamsIntegration] | | | source: MethodSource [className = 'standalone.JupiterParamsIntegration', methodName = 'parameterizedTest', methodParameterTypes = 'java.lang.String'] | '-- JupiterParamsIntegration '-- JUnit Jupiter +-- JUnit Vintage | +-- VintageIntegration | | +-- f4il | | | tags: [] | | | uniqueId: [engine:junit-vintage]/[runner:standalone.VintageIntegration]/[test:f4il(standalone.VintageIntegration)] | | | parent: [engine:junit-vintage]/[runner:standalone.VintageIntegration] | | | source: MethodSource [className = 'standalone.VintageIntegration', methodName = 'f4il', methodParameterTypes = ''] | | +-- ignored | | | tags: [] | | | uniqueId: [engine:junit-vintage]/[runner:standalone.VintageIntegration]/[test:ignored(standalone.VintageIntegration)] | | | parent: [engine:junit-vintage]/[runner:standalone.VintageIntegration] | | | source: MethodSource [className = 'standalone.VintageIntegration', methodName = 'ignored', methodParameterTypes = ''] | | +-- succ3ssful | | | tags: [] | | | uniqueId: [engine:junit-vintage]/[runner:standalone.VintageIntegration]/[test:succ3ssful(standalone.VintageIntegration)] | | | parent: [engine:junit-vintage]/[runner:standalone.VintageIntegration] | | | source: MethodSource [className = 'standalone.VintageIntegration', methodName = 'succ3ssful', methodParameterTypes = ''] | '-- VintageIntegration '-- JUnit Vintage [ 11 containers found ] [ 9 tests found ] """.stripIndent(); assertLinesMatch(expected.lines(), result.stdOut().lines()); } @Test @Order(2) @Execution(SAME_THREAD) void discoverNone(@FilePrefix("console-launcher") OutputFiles outputFiles) throws Exception { var result = discover(outputFiles, "--details=none"); assertThat(result.stdOut()).isEmpty(); } @Test @Order(2) @Execution(SAME_THREAD) void discoverSummary(@FilePrefix("console-launcher") OutputFiles outputFiles) throws Exception { var result = discover(outputFiles, "--details=summary"); var expected = """ [ 11 containers found ] [ 9 tests found ] """.stripIndent(); assertLinesMatch(expected.lines(), result.stdOut().lines()); } @Test @Order(2) @Execution(SAME_THREAD) void discoverTestFeed(@FilePrefix("console-launcher") OutputFiles outputFiles) throws Exception { var result = discover(outputFiles, "--details=testfeed"); var expected = """ JUnit Platform Suite > SuiteIntegration > JUnit Jupiter > SuiteIntegration$SingleTestContainer > successful() JUnit Jupiter > JupiterIntegration > successful() JUnit Jupiter > JupiterIntegration > fail() JUnit Jupiter > JupiterIntegration > abort() JUnit Jupiter > JupiterIntegration > disabled() JUnit Jupiter > SuiteIntegration$SingleTestContainer > successful() JUnit Vintage > VintageIntegration > f4il JUnit Vintage > VintageIntegration > ignored JUnit Vintage > VintageIntegration > succ3ssful [ 11 containers found ] [ 9 tests found ] """.stripIndent(); assertLinesMatch(expected.lines(), result.stdOut().lines()); } private static ProcessResult discover(OutputFiles outputFiles, String... args) throws Exception { var result = ProcessStarters.java() // .workingDir(workspace) // .putEnvironment("NO_COLOR", "1") // --disable-ansi-colors .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone").toString()) // .addArguments("discover") // .addArguments("--scan-class-path") // .addArguments("--disable-banner") // .addArguments("--include-classname", "standalone.*") // .addArguments("--classpath", "bin") // .addArguments(args) // .redirectOutput(outputFiles) // .startAndWait(); assertEquals(0, result.exitCode()); return result; } @Test @Order(3) @Execution(SAME_THREAD) void execute(@FilePrefix("console-launcher") OutputFiles outputFiles) throws Exception { var result = ProcessStarters.java() // .workingDir(workspace) // .putEnvironment("NO_COLOR", "1") // --disable-ansi-colors .addArguments("--show-version") // .addArguments("-enableassertions") // .addArguments("-Djava.util.logging.config.file=logging.properties") // .addArguments("-Djunit.platform.launcher.interceptors.enabled=true") // .addArguments("-Duser.language=en", "-Duser.country=US") // .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone").toString()) // .addArguments("execute") // .addArguments("--scan-class-path") // .addArguments("--disable-banner") // .addArguments("--include-classname", "standalone.*") // .addArguments("--classpath", "bin") // .redirectOutput(outputFiles) // .startAndWait(); assertEquals(1, result.exitCode()); assertOutputOnCurrentJvm(result); } @Test @Order(4) @Execution(SAME_THREAD) void executeOnJava17(@FilePrefix("console-launcher") OutputFiles outputFiles) throws Exception { var javaHome = Helper.getJavaHome(17).orElseThrow(TestAbortedException::new); var result = ProcessStarters.java(javaHome) // .workingDir(workspace) // .addArguments("-showversion") // .addArguments("-enableassertions") // .addArguments("-Djava.util.logging.config.file=logging.properties") // .addArguments("-Djunit.platform.launcher.interceptors.enabled=true") // .addArguments("-Duser.language=en", "-Duser.country=US") // .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone").toString()) // .addArguments("execute") // .addArguments("--scan-class-path") // .addArguments("--disable-banner") // .addArguments("--include-classname", "standalone.*") // .addArguments("--classpath", "bin") // .redirectOutput(outputFiles) // .startAndWait(); assertEquals(1, result.exitCode()); var expectedOutLines = Files.readAllLines(workspace.resolve("expected-out.txt")); var expectedErrLines = getExpectedErrLinesOnJava17(workspace); assertLinesMatch(expectedOutLines, result.stdOutLines()); assertLinesMatch(expectedErrLines, result.stdErrLines()); assertTrue(result.stdErr().contains("junit-jupiter" + " (group ID: org.junit.jupiter, artifact ID: junit-jupiter-engine, version: " + Helper.version())); assertTrue(result.stdErr().contains("junit-vintage" + " (group ID: org.junit.vintage, artifact ID: junit-vintage-engine, version: " + Helper.version())); } @Test @Order(5) @Execution(SAME_THREAD) // https://github.com/junit-team/junit-framework/issues/2600 void executeOnJava17SelectPackage(@FilePrefix("console-launcher") OutputFiles outputFiles) throws Exception { var javaHome = Helper.getJavaHome(17).orElseThrow(TestAbortedException::new); var result = ProcessStarters.java(javaHome) // .workingDir(workspace).addArguments("-showversion") // .addArguments("-enableassertions") // .addArguments("-Djava.util.logging.config.file=logging.properties") // .addArguments("-Djunit.platform.launcher.interceptors.enabled=true") // .addArguments("-Duser.language=en", "-Duser.country=US") // .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone").toString()) // .addArguments("execute") // .addArguments("--select-package", Projects.STANDALONE) // .addArguments("--disable-banner") // .addArguments("--include-classname", "standalone.*") // .addArguments("--classpath", "bin") // .redirectOutput(outputFiles) // .startAndWait(); assertEquals(1, result.exitCode()); var expectedOutLines = Files.readAllLines(workspace.resolve("expected-out.txt")); var expectedErrLines = getExpectedErrLinesOnJava17(workspace); assertLinesMatch(expectedOutLines, result.stdOutLines()); assertLinesMatch(expectedErrLines, result.stdErrLines()); assertTrue(result.stdErr().contains("junit-jupiter" + " (group ID: org.junit.jupiter, artifact ID: junit-jupiter-engine, version: " + Helper.version())); assertTrue(result.stdErr().contains("junit-vintage" + " (group ID: org.junit.vintage, artifact ID: junit-vintage-engine, version: " + Helper.version())); } private static List getExpectedErrLinesOnJava17(Path workspace) throws IOException { var expectedErrLines = new ArrayList(); expectedErrLines.add(">> JAVA VERSION >>"); expectedErrLines.addAll(Files.readAllLines(workspace.resolve("expected-err.txt"))); return expectedErrLines; } @Test @Order(6) @Execution(SAME_THREAD) void executeWithJarredTestClasses(@FilePrefix("jar") OutputFiles jarOutputFiles, @FilePrefix("console-launcher") OutputFiles outputFiles) throws Exception { var jar = workspace.resolve("tests.jar"); var jarResult = ProcessStarters.javaCommand("jar") // .workingDir(workspace) // .addArguments("--create") // .addArguments("--file", jar.toAbsolutePath().toString()) // .addArguments("-C", workspace.resolve("bin").toString(), ".") // .redirectOutput(jarOutputFiles) // .startAndWait(); assertEquals(0, jarResult.exitCode()); var result = ProcessStarters.java() // .workingDir(workspace) // .putEnvironment("NO_COLOR", "1") // --disable-ansi-colors .addArguments("--show-version") // .addArguments("-enableassertions") // .addArguments("-Djava.util.logging.config.file=logging.properties") // .addArguments("-Djunit.platform.launcher.interceptors.enabled=true") // .addArguments("-Duser.language=en", "-Duser.country=US") // .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone").toString()) // .addArguments("execute") // .addArguments("--scan-class-path") // .addArguments("--disable-banner") // .addArguments("--include-classname", "standalone.*") // .addArguments("--classpath", jar.toAbsolutePath().toString()) // .redirectOutput(outputFiles) // .startAndWait(); assertEquals(1, result.exitCode()); assertOutputOnCurrentJvm(result); } private static void assertOutputOnCurrentJvm(ProcessResult result) throws IOException { var expectedOutLines = Files.readAllLines(workspace.resolve("expected-out.txt")); var expectedErrLines = Files.readAllLines(workspace.resolve("expected-err.txt")); assertLinesMatch(expectedOutLines, result.stdOutLines()); var actualErrLines = result.stdErrLines(); if (actualErrLines.getFirst().contains("stty: /dev/tty: No such device or address")) { // Happens intermittently on GitHub Actions on Windows actualErrLines = new ArrayList<>(actualErrLines); actualErrLines.removeFirst(); } assertLinesMatch(expectedErrLines, actualErrLines); assertTrue(result.stdErr().contains("junit-jupiter" + " (group ID: org.junit.jupiter, artifact ID: junit-jupiter-engine, version: " + Helper.version())); assertTrue(result.stdErr().contains("junit-vintage" + " (group ID: org.junit.vintage, artifact ID: junit-vintage-engine, version: " + Helper.version())); } } ================================================ FILE: platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ToolProviderTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support.tests; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertLinesMatch; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.module.ModuleDescriptor; import java.lang.module.ModuleFinder; import java.lang.module.ModuleReference; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.ServiceLoader; import java.util.Set; import java.util.spi.ToolProvider; import java.util.stream.StreamSupport; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.OS; import org.junit.jupiter.api.extension.DisabledOnOpenJ9; import org.junit.jupiter.api.io.TempDir; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; import platform.tooling.support.ThirdPartyJars; /** * @since 1.6 */ @Order(Integer.MAX_VALUE) class ToolProviderTests { @TempDir static Path lib; @BeforeAll static void prepareLocalLibraryDirectoryWithJUnitPlatformModules() { try { Files.createDirectories(lib); try (var directoryStream = Files.newDirectoryStream(lib, "*.jar")) { for (Path jarFile : directoryStream) { Files.delete(jarFile); } } for (var module : Helper.loadModuleDirectoryNames()) { if (module.startsWith("junit-platform")) { var jar = MavenRepo.jar(module); Files.copy(jar, lib.resolve(module + ".jar")); } } ThirdPartyJars.copy(lib, "org.apiguardian", "apiguardian-api"); ThirdPartyJars.copy(lib, "org.opentest4j", "opentest4j"); ThirdPartyJars.copy(lib, "org.opentest4j.reporting", "open-test-reporting-tooling-spi"); } catch (Exception e) { throw new AssertionError("Preparing local library folder failed", e); } } @AfterAll static void triggerReleaseOfFileHandlesOnWindows() throws Exception { if (OS.current() == OS.WINDOWS) { System.gc(); Thread.sleep(1_000); } } @Test void findAndRunJUnitOnTheClassPath() { try (var loader = new URLClassLoader("junit", urls(lib), ClassLoader.getPlatformClassLoader())) { var sl = ServiceLoader.load(ToolProvider.class, loader); var junit = StreamSupport.stream(sl.spliterator(), false).filter(p -> p.name().equals("junit")).findFirst(); assertTrue(junit.isPresent(), "Tool 'junit' not found in: " + lib); assertJUnitPrintsHelpMessage(junit.get()); } catch (IOException e) { throw new AssertionError("Closing URLClassLoader failed: " + e, e); } } @Test @DisabledOnOpenJ9 void findAndRunJUnitOnTheModulePath() { var finder = ModuleFinder.of(lib); var modules = finder.findAll().stream() // .map(ModuleReference::descriptor) // .map(ModuleDescriptor::toNameAndVersion) // .sorted() // .toList(); // modules.forEach(System.out::println); var bootLayer = ModuleLayer.boot(); var configuration = bootLayer.configuration().resolveAndBind(finder, ModuleFinder.of(), Set.of()); var layer = bootLayer.defineModulesWithOneLoader(configuration, ClassLoader.getPlatformClassLoader()); var sl = ServiceLoader.load(layer, ToolProvider.class); var junit = StreamSupport.stream(sl.spliterator(), false).filter(p -> p.name().equals("junit")).findFirst(); assertTrue(junit.isPresent(), "Tool 'junit' not found in modules: " + modules); assertJUnitPrintsHelpMessage(junit.get()); } private static URL[] urls(Path directory) { try (var stream = Files.newDirectoryStream(directory, "*.jar")) { var paths = new ArrayList(); stream.forEach(path -> paths.add(url(path))); return paths.toArray(URL[]::new); } catch (Exception e) { throw new AssertionError("Creating URL[] failed: " + e, e); } } private static URL url(Path path) { try { return path.toUri().toURL(); } catch (MalformedURLException e) { throw new AssertionError("Converting path to URL failed: " + e, e); } } private static void assertJUnitPrintsHelpMessage(ToolProvider junit) { var out = new StringWriter(); var err = new StringWriter(); var code = junit.run(new PrintWriter(out), new PrintWriter(err), "--help"); assertAll(() -> assertLinesMatch(List.of( // ">> USAGE >>", // "Launches the JUnit Platform for test discovery and execution.", // ">> OPTIONS >>"), // out.toString().lines().toList()), // () -> assertEquals("", err.toString()), // () -> assertEquals(0, code, "Expected exit of 0, but got: " + code) // ); } } ================================================ FILE: platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/UnalignedClasspathTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support.tests; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static platform.tooling.support.ProcessStarters.currentJdkHome; import static platform.tooling.support.tests.Projects.copyToWorkspace; import java.nio.file.Path; import java.util.stream.Stream; import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.junit.platform.tests.process.OutputFiles; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; import platform.tooling.support.ProcessStarters; /** * @since 1.3 */ class UnalignedClasspathTests { @ManagedResource LocalMavenRepo localMavenRepo; @ManagedResource MavenRepoProxy mavenRepoProxy; @ParameterizedTest(name = "[{index}] {0}") @MethodSource("javaVersions") @Execution(SAME_THREAD) void verifyErrorMessageForUnalignedClasspath(JRE jre, Path javaHome, @TempDir Path workspace, @FilePrefix("maven") OutputFiles outputFiles) throws Exception { var starter = ProcessStarters.maven(javaHome) // .workingDir(copyToWorkspace(Projects.JUPITER_STARTER, workspace)) // .addArguments(localMavenRepo.toCliArgument(), "-Dmaven.repo=" + MavenRepo.dir()) // .addArguments("-Dsnapshot.repo.url=" + mavenRepoProxy.getBaseUri()) // .addArguments("-Djunit.platform.commons.version=1.11.4") // .addArguments("--update-snapshots", "--batch-mode", "verify") // .putEnvironment(MavenEnvVars.forJre(jre)) // .redirectOutput(outputFiles); var result = starter.startAndWait(); assertEquals(1, result.exitCode()); assertEquals("", result.stdErr()); assertThat(result.stdOutLines()).contains("[INFO] BUILD FAILURE"); assertThat(result.stdOut()) // .contains("The wrapped NoClassDefFoundError is likely caused by the versions of JUnit jars " + "on the classpath/module path not being properly aligned"); } static Stream javaVersions() { return Stream.concat( // Helper.getJavaHome(17).map(path -> Arguments.of(JRE.JAVA_17, path)).stream(), // Stream.of(Arguments.of(JRE.currentJre(), currentJdkHome())) // ); } } ================================================ FILE: platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageGradleIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support.tests; import static org.assertj.core.api.Assertions.assertThat; import static platform.tooling.support.tests.Projects.copyToWorkspace; import java.nio.file.Path; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.tests.process.OutputFiles; import org.junit.platform.tests.process.ProcessResult; import org.opentest4j.TestAbortedException; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; import platform.tooling.support.ProcessStarters; @Order(Integer.MIN_VALUE + 1) class VintageGradleIntegrationTests { @TempDir Path workspace; @Test void unsupportedVersion(@FilePrefix("gradle") OutputFiles outputFiles) throws Exception { var result = run(outputFiles, "4.11"); assertThat(result.exitCode()).isGreaterThan(0); assertThat(result.stdOut()) // .doesNotContain("STARTED") // .contains("Unsupported version of junit:junit: 4.11"); } @ParameterizedTest(name = "{0}", quoteTextArguments = false) @ValueSource(strings = { "4.12", "4.13.2" }) void supportedVersions(String version, @FilePrefix("gradle") OutputFiles outputFiles) throws Exception { var result = run(outputFiles, version); assertThat(result.exitCode()).isGreaterThan(0); assertThat(result.stdOut()) // .contains("VintageTest > success PASSED") // .contains("VintageTest > failure FAILED"); var testResultsDir = workspace.resolve("build/test-results/test"); assertThat(testResultsDir.resolve("TEST-com.example.vintage.VintageTest.xml")).isRegularFile(); } private ProcessResult run(OutputFiles outputFiles, String version) throws Exception { return ProcessStarters.gradlew() // .workingDir(copyToWorkspace(Projects.VINTAGE, workspace)) // .putEnvironment("JDK17", Helper.getJavaHome(17).orElseThrow(TestAbortedException::new).toString()) // .addArguments("build", "--no-daemon", "--stacktrace", "--no-build-cache", "--warning-mode=fail") // .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // .addArguments("-Djunit4Version=" + version) // .redirectOutput(outputFiles) // .startAndWait(); } } ================================================ FILE: platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageMavenIntegrationTests.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support.tests; import static org.assertj.core.api.Assertions.assertThat; import static platform.tooling.support.tests.Projects.copyToWorkspace; import java.nio.file.Path; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.tests.process.OutputFiles; import org.junit.platform.tests.process.ProcessResult; import org.opentest4j.TestAbortedException; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; import platform.tooling.support.ProcessStarters; class VintageMavenIntegrationTests { @ManagedResource LocalMavenRepo localMavenRepo; @TempDir Path workspace; @Test void unsupportedVersion(@FilePrefix("maven") OutputFiles outputFiles) throws Exception { var result = run(outputFiles, "4.11"); assertThat(result.exitCode()).isEqualTo(1); assertThat(result.stdOut()) // .contains("TestEngine with ID 'junit-vintage' failed to discover tests") // .contains("Tests run: 0, Failures: 0, Errors: 0, Skipped: 0"); } @ParameterizedTest(name = "{0}", quoteTextArguments = false) @ValueSource(strings = { "4.12", "4.13.2" }) void supportedVersions(String version, @FilePrefix("maven") OutputFiles outputFiles) throws Exception { var result = run(outputFiles, version); assertThat(result.exitCode()).isGreaterThan(0); assertThat(result.stdOut()) // .contains("Running com.example.vintage.VintageTest") // .contains("VintageTest.failure:") // .contains("Tests run: 2, Failures: 1, Errors: 0, Skipped: 0"); var surefireReportsDir = workspace.resolve("target/surefire-reports"); assertThat(surefireReportsDir.resolve("com.example.vintage.VintageTest.txt")).isRegularFile(); assertThat(surefireReportsDir.resolve("TEST-com.example.vintage.VintageTest.xml")).isRegularFile(); } private ProcessResult run(OutputFiles outputFiles, String version) throws Exception { return ProcessStarters.maven(Helper.getJavaHome(17).orElseThrow(TestAbortedException::new)) // .workingDir(copyToWorkspace(Projects.VINTAGE, workspace)) // .addArguments("clean", "test", "--update-snapshots", "--batch-mode") // .addArguments(localMavenRepo.toCliArgument(), "-Dmaven.repo=" + MavenRepo.dir()) // .addArguments("-Djunit4Version=" + version) // .redirectOutput(outputFiles) // .startAndWait(); } } ================================================ FILE: platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/XmlAssertions.java ================================================ /* * Copyright 2015-2026 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which * accompanies this distribution and is available at * * https://www.eclipse.org/legal/epl-v20.html */ package platform.tooling.support.tests; import static de.skuzzle.test.snapshots.data.xml.XmlSnapshot.xml; import static org.junit.platform.reporting.testutil.FileUtils.findPath; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Map; import java.util.function.UnaryOperator; import java.util.regex.Pattern; import de.skuzzle.test.snapshots.Snapshot; import de.skuzzle.test.snapshots.SnapshotSerializer; import de.skuzzle.test.snapshots.StructuredData; import de.skuzzle.test.snapshots.StructuredDataProvider; class XmlAssertions { static void verifyContainsExpectedStartedOpenTestReport(Path testResultsDir, Snapshot snapshot) throws IOException { var xmlFile = findPath(testResultsDir, "glob:**/open-test-report.xml"); verifyContent(xmlFile, snapshot); } private static void verifyContent(Path xmlFile, Snapshot snapshot) throws IOException { snapshot.named("open-test-report.xml") // .assertThat(Files.readString(xmlFile)) // .as(obfuscated( // xml() // .withXPathNamespaceContext(Map.of( // "c", "https://schemas.opentest4j.org/reporting/core/0.2.0", // "e", "https://schemas.opentest4j.org/reporting/events/0.2.0", // "java", "https://schemas.opentest4j.org/reporting/java/0.2.0" // )) // .withComparisonRules(rules -> rules // .pathAt("//@time").mustMatch( Pattern.compile("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?Z")) // .pathAt("//c:infrastructure/c:hostName/text()").ignore() // .pathAt("//c:infrastructure/c:userName/text()").ignore() // .pathAt("//c:infrastructure/c:operatingSystem/text()").ignore() // .pathAt("//c:infrastructure/c:cpuCores/text()").ignore() // .pathAt("//c:infrastructure/java:javaVersion/text()").ignore() // .pathAt("//c:infrastructure/java:fileEncoding/text()").ignore() // .pathAt("//c:infrastructure/java:heapSize/@max").ignore() // ), // text -> text // .replaceAll(".+?", "obfuscated") // .replaceAll(".+?", "obfuscated") // )) // .matchesSnapshotStructure(); } private static StructuredDataProvider obfuscated(StructuredDataProvider provider, UnaryOperator obfuscator) { return () -> { var structuredData = provider.build(); var snapshotSerializer = obfuscatingSnapshotSerializer(structuredData.snapshotSerializer(), obfuscator); return StructuredData.with(snapshotSerializer, structuredData.structuralAssertions()); }; } private static SnapshotSerializer obfuscatingSnapshotSerializer(SnapshotSerializer delegate, UnaryOperator obfuscator) { return testResult -> { Object obfuscatedTestResult = testResult; if (testResult instanceof String string) { obfuscatedTestResult = obfuscator.apply(string); } return delegate.serialize(obfuscatedTestResult); }; } } ================================================ FILE: platform-tooling-support-tests/src/test/resources/junit-platform.properties ================================================ junit.jupiter.execution.parallel.enabled=true junit.jupiter.execution.parallel.mode.default=concurrent junit.jupiter.execution.parallel.config.executor-service=worker_thread_pool junit.jupiter.execution.parallel.config.strategy=dynamic junit.jupiter.execution.parallel.config.dynamic.factor=0.25 junit.jupiter.execution.parallel.config.dynamic.max-pool-size-factor=1 junit.jupiter.testclass.order.default = \ org.junit.jupiter.api.ClassOrderer$OrderAnnotation junit.jupiter.execution.timeout.default = 3m ================================================ FILE: platform-tooling-support-tests/src/test/resources/log4j2-test.xml ================================================ ================================================ FILE: platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/AntStarterTests_snapshots/open-test-report.xml.snapshot ================================================ dynamic-directory: false snapshot-name: open-test-report.xml snapshot-number: 0 test-class: platform.tooling.support.tests.AntStarterTests test-method: ant_starter obfuscated obfuscated Linux 16 25.0.2 UTF-8 [engine:junit-jupiter] JUnit Jupiter CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests] com.example.project.CalculatorParameterizedClassTests CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1] com.example.project.CalculatorParameterizedClassTests[1] CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[test-template:parameterizedTest(int)] parameterizedTest(int)[1] CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[test-template:parameterizedTest(int)]/[test-template-invocation:#1] parameterizedTest(int)[1][1] TEST [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[test-template:parameterizedTest(int)]/[test-template-invocation:#2] parameterizedTest(int)[1][2] TEST [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[nested-class-template:Inner] com.example.project.CalculatorParameterizedClassTests$Inner[1] CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[nested-class-template:Inner]/[class-template-invocation:#1] com.example.project.CalculatorParameterizedClassTests$Inner[1][1] CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[nested-class-template:Inner]/[class-template-invocation:#1]/[method:regularTest()] regularTest()[1][1] TEST [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[nested-class-template:Inner]/[class-template-invocation:#2] com.example.project.CalculatorParameterizedClassTests$Inner[1][2] CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[nested-class-template:Inner]/[class-template-invocation:#2]/[method:regularTest()] regularTest()[1][2] TEST [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2] com.example.project.CalculatorParameterizedClassTests[2] CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[test-template:parameterizedTest(int)] parameterizedTest(int)[2] CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[test-template:parameterizedTest(int)]/[test-template-invocation:#1] parameterizedTest(int)[2][1] TEST [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[test-template:parameterizedTest(int)]/[test-template-invocation:#2] parameterizedTest(int)[2][2] TEST [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[nested-class-template:Inner] com.example.project.CalculatorParameterizedClassTests$Inner[2] CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[nested-class-template:Inner]/[class-template-invocation:#1] com.example.project.CalculatorParameterizedClassTests$Inner[2][1] CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[nested-class-template:Inner]/[class-template-invocation:#1]/[method:regularTest()] regularTest()[2][1] TEST [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[nested-class-template:Inner]/[class-template-invocation:#2] com.example.project.CalculatorParameterizedClassTests$Inner[2][2] CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[nested-class-template:Inner]/[class-template-invocation:#2]/[method:regularTest()] regularTest()[2][2] TEST [engine:junit-jupiter]/[class:com.example.project.CalculatorTests] com.example.project.CalculatorTests CONTAINER [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[method:addsTwoNumbers()] addsTwoNumbers() TEST [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)] add(int, int, int) CONTAINER [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#1] add(int, int, int)[1] TEST [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#2] add(int, int, int)[2] TEST [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#3] add(int, int, int)[3] TEST [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#4] add(int, int, int)[4] TEST ================================================ FILE: platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/GradleStarterTests_snapshots/open-test-report.xml.snapshot ================================================ dynamic-directory: false snapshot-name: open-test-report.xml snapshot-number: 0 test-class: platform.tooling.support.tests.GradleStarterTests test-method: buildJupiterStarterProject obfuscated obfuscated Linux 16 17.0.18 UTF-8 [engine:junit-jupiter] JUnit Jupiter CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests] com.example.project.CalculatorParameterizedClassTests CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1] com.example.project.CalculatorParameterizedClassTests[1] CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[test-template:parameterizedTest(int)] parameterizedTest(int)[1] CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[test-template:parameterizedTest(int)]/[test-template-invocation:#1] parameterizedTest(int)[1][1] TEST [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[test-template:parameterizedTest(int)]/[test-template-invocation:#2] parameterizedTest(int)[1][2] TEST [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[nested-class-template:Inner] com.example.project.CalculatorParameterizedClassTests$Inner[1] CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[nested-class-template:Inner]/[class-template-invocation:#1] com.example.project.CalculatorParameterizedClassTests$Inner[1][1] CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[nested-class-template:Inner]/[class-template-invocation:#1]/[method:regularTest()] regularTest()[1][1] TEST [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[nested-class-template:Inner]/[class-template-invocation:#2] com.example.project.CalculatorParameterizedClassTests$Inner[1][2] CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[nested-class-template:Inner]/[class-template-invocation:#2]/[method:regularTest()] regularTest()[1][2] TEST [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2] com.example.project.CalculatorParameterizedClassTests[2] CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[test-template:parameterizedTest(int)] parameterizedTest(int)[2] CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[test-template:parameterizedTest(int)]/[test-template-invocation:#1] parameterizedTest(int)[2][1] TEST [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[test-template:parameterizedTest(int)]/[test-template-invocation:#2] parameterizedTest(int)[2][2] TEST [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[nested-class-template:Inner] com.example.project.CalculatorParameterizedClassTests$Inner[2] CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[nested-class-template:Inner]/[class-template-invocation:#1] com.example.project.CalculatorParameterizedClassTests$Inner[2][1] CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[nested-class-template:Inner]/[class-template-invocation:#1]/[method:regularTest()] regularTest()[2][1] TEST [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[nested-class-template:Inner]/[class-template-invocation:#2] com.example.project.CalculatorParameterizedClassTests$Inner[2][2] CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[nested-class-template:Inner]/[class-template-invocation:#2]/[method:regularTest()] regularTest()[2][2] TEST [engine:junit-jupiter]/[class:com.example.project.CalculatorTests] com.example.project.CalculatorTests CONTAINER [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[method:addsTwoNumbers()] addsTwoNumbers() TEST [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)] add(int, int, int) CONTAINER [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#1] add(int, int, int)[1] TEST [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#2] add(int, int, int)[2] TEST [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#3] add(int, int, int)[3] TEST [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#4] add(int, int, int)[4] TEST ================================================ FILE: platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/MavenStarterTests_snapshots/open-test-report.xml.snapshot ================================================ dynamic-directory: false snapshot-name: open-test-report.xml snapshot-number: 0 test-class: platform.tooling.support.tests.MavenStarterTests test-method: verifyJupiterStarterProject obfuscated obfuscated Linux 16 17.0.18 UTF-8 [engine:junit-jupiter] JUnit Jupiter CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests] com.example.project.CalculatorParameterizedClassTests CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1] com.example.project.CalculatorParameterizedClassTests[1] CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[test-template:parameterizedTest(int)] parameterizedTest(int)[1] CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[test-template:parameterizedTest(int)]/[test-template-invocation:#1] parameterizedTest(int)[1][1] TEST [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[test-template:parameterizedTest(int)]/[test-template-invocation:#2] parameterizedTest(int)[1][2] TEST [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[nested-class-template:Inner] com.example.project.CalculatorParameterizedClassTests$Inner[1] CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[nested-class-template:Inner]/[class-template-invocation:#1] com.example.project.CalculatorParameterizedClassTests$Inner[1][1] CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[nested-class-template:Inner]/[class-template-invocation:#1]/[method:regularTest()] regularTest()[1][1] TEST [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[nested-class-template:Inner]/[class-template-invocation:#2] com.example.project.CalculatorParameterizedClassTests$Inner[1][2] CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[nested-class-template:Inner]/[class-template-invocation:#2]/[method:regularTest()] regularTest()[1][2] TEST [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2] com.example.project.CalculatorParameterizedClassTests[2] CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[test-template:parameterizedTest(int)] parameterizedTest(int)[2] CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[test-template:parameterizedTest(int)]/[test-template-invocation:#1] parameterizedTest(int)[2][1] TEST [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[test-template:parameterizedTest(int)]/[test-template-invocation:#2] parameterizedTest(int)[2][2] TEST [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[nested-class-template:Inner] com.example.project.CalculatorParameterizedClassTests$Inner[2] CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[nested-class-template:Inner]/[class-template-invocation:#1] com.example.project.CalculatorParameterizedClassTests$Inner[2][1] CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[nested-class-template:Inner]/[class-template-invocation:#1]/[method:regularTest()] regularTest()[2][1] TEST [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[nested-class-template:Inner]/[class-template-invocation:#2] com.example.project.CalculatorParameterizedClassTests$Inner[2][2] CONTAINER [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[nested-class-template:Inner]/[class-template-invocation:#2]/[method:regularTest()] regularTest()[2][2] TEST [engine:junit-jupiter]/[class:com.example.project.CalculatorTests] com.example.project.CalculatorTests CONTAINER [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[method:addsTwoNumbers()] addsTwoNumbers() TEST [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)] add(int, int, int) CONTAINER [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#1] add(int, int, int)[1] TEST [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#2] add(int, int, int)[2] TEST [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#3] add(int, int, int)[3] TEST [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#4] add(int, int, int)[4] TEST ================================================ FILE: settings.gradle.kts ================================================ import buildparameters.BuildParametersExtension pluginManagement { includeBuild("gradle/plugins") repositories { gradlePluginPortal() } } plugins { id("junitbuild.build-parameters") id("junitbuild.maven-central-publishing") id("junitbuild.settings-conventions") } dependencyResolutionManagement { repositories { mavenCentral() } } val buildParameters = the() val develocityServer = "https://ge.junit.org" val useDevelocityInstance = !gradle.startParameter.isBuildScan develocity { if (useDevelocityInstance) { // Publish to scans.gradle.com when `--scan` is used explicitly server = develocityServer } buildScan { uploadInBackground = !buildParameters.ci publishing { onlyIf { it.isAuthenticated } } obfuscation { if (buildParameters.ci) { username { "github" } } else { hostname { null } ipAddresses { emptyList() } } } if (buildParameters.junit.develocity.testDistribution.enabled) { tag("test-distribution") } } } buildCache { local { isEnabled = !buildParameters.ci } val buildCacheServer = buildParameters.junit.develocity.buildCache.server if (useDevelocityInstance) { remote(develocity.buildCache) { server = buildCacheServer.orNull isPush = buildParameters.junit.develocity.buildCache.pushEnabled } } else { remote { url = uri(buildCacheServer.getOrElse(develocityServer)).resolve("/cache/") } } } includeBuild("gradle/base") rootProject.name = "junit-framework" include("documentation") include("junit-jupiter") include("junit-jupiter-api") include("junit-jupiter-engine") include("junit-jupiter-migrationsupport") include("junit-jupiter-params") include("junit-start") include("junit-platform-commons") include("junit-platform-console") include("junit-platform-console-standalone") include("junit-platform-engine") include("junit-platform-launcher") include("junit-platform-reporting") include("junit-platform-suite") include("junit-platform-suite-api") include("junit-platform-suite-engine") include("junit-platform-testkit") include("junit-vintage-engine") include("jupiter-tests") include("platform-tests") include("platform-tooling-support-tests") include("junit-bom") // check that every subproject has a custom build file // based on the project name rootProject.children.forEach { project -> project.buildFileName = "${project.name}.gradle.kts" require(project.buildFile.isFile) { "${project.buildFile} must exist" } } enableFeaturePreview("STABLE_CONFIGURATION_CACHE") enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")