diff --git a/.github/actions/run-gradle/action.yml b/.github/actions/run-gradle/action.yml index 6265705e57e6..d2c704f5ef4c 100644 --- a/.github/actions/run-gradle/action.yml +++ b/.github/actions/run-gradle/action.yml @@ -17,7 +17,7 @@ runs: distribution: temurin java-version: 21 check-latest: true - - uses: gradle/actions/setup-gradle@473878a77f1b98e2b5ac4af93489d1656a80a5ed # v4 + - uses: gradle/actions/setup-gradle@cc4fc85e6b35bafd578d5ffbc76a5518407e1af0 # v4 with: cache-encryption-key: ${{ inputs.encryptionKey }} - shell: bash diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 73559f7da546..e943e26bc2f6 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -34,7 +34,7 @@ jobs: - name: Check out repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Initialize CodeQL - uses: github/codeql-action/init@ea9e4e37992a54ee68a9622e985e60c8e8f12d9f # v3 + uses: github/codeql-action/init@f09c1c0a94de965c15400f5634aa42fac8fb8f88 # v3 with: languages: ${{ matrix.language }} tools: linked @@ -47,4 +47,4 @@ jobs: -Dscan.tag.CodeQL \ allMainClasses - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@ea9e4e37992a54ee68a9622e985e60c8e8f12d9f # v3 + uses: github/codeql-action/analyze@f09c1c0a94de965c15400f5634aa42fac8fb8f88 # v3 diff --git a/.github/workflows/gradle-dependency-submission.yml b/.github/workflows/gradle-dependency-submission.yml index bab2e20e114e..9c31d8bcbfbd 100644 --- a/.github/workflows/gradle-dependency-submission.yml +++ b/.github/workflows/gradle-dependency-submission.yml @@ -25,4 +25,4 @@ jobs: java-version: 21 check-latest: true - name: Generate and submit dependency graph - uses: gradle/actions/dependency-submission@473878a77f1b98e2b5ac4af93489d1656a80a5ed # v4 + uses: gradle/actions/dependency-submission@cc4fc85e6b35bafd578d5ffbc76a5518407e1af0 # v4 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ed22fcf21543..0db6c7784251 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -39,7 +39,7 @@ jobs: jacocoRootReport \ --no-configuration-cache # Disable configuration cache due to https://github.com/diffplug/spotless/issues/2318 - name: Upload to Codecov.io - uses: codecov/codecov-action@5c47607acb93fed5485fdbf7232e8a31425f672a # v5 + uses: codecov/codecov-action@015f24e6818733317a2da2edd6290ab26238649a # v5 with: token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 839051e5d1d0..ae94503b3b68 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -57,6 +57,6 @@ jobs: # 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@ea9e4e37992a54ee68a9622e985e60c8e8f12d9f # v3 + uses: github/codeql-action/upload-sarif@f09c1c0a94de965c15400f5634aa42fac8fb8f88 # v3 with: sarif_file: results.sarif diff --git a/.gitignore b/.gitignore index aa4aae75cc61..612c29c09076 100644 --- a/.gitignore +++ b/.gitignore @@ -31,5 +31,6 @@ gradle-app.setting coverage.db* .metadata /.sdkmanrc +/.tool-versions checksums* diff --git a/documentation/documentation.gradle.kts b/documentation/documentation.gradle.kts index 2f1cff759cd1..c19537403b9c 100644 --- a/documentation/documentation.gradle.kts +++ b/documentation/documentation.gradle.kts @@ -6,7 +6,7 @@ import junitbuild.javadoc.ModuleSpecificJavadocFileOption import org.asciidoctor.gradle.base.AsciidoctorAttributeProvider import org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask import org.gradle.api.tasks.PathSensitivity.RELATIVE -import java.nio.file.Files +import org.jetbrains.kotlin.incremental.deleteRecursivelyOrThrow plugins { alias(libs.plugins.asciidoctorConvert) @@ -147,7 +147,7 @@ tasks { val consoleLauncherTestReportsDir = project.layout.buildDirectory.dir("console-launcher-test-results") val consoleLauncherTestEventXmlFiles = - files(consoleLauncherTestReportsDir.map { it.asFileTree.matching { include("junit-platform-events-*.xml") } }) + files(consoleLauncherTestReportsDir.map { it.asFileTree.matching { include("**/open-test-report.xml") } }) val consoleLauncherTest by registering(RunConsoleLauncher::class) { args.addAll("execute") @@ -157,7 +157,6 @@ tasks { argumentProviders.add(CommandLineArgumentProvider { listOf( "--reports-dir=${consoleLauncherTestReportsDir.get()}", - "--config=junit.platform.reporting.output.dir=${consoleLauncherTestReportsDir.get()}", ) }) args.addAll("--include-classname", ".*Tests") @@ -166,9 +165,7 @@ tasks { args.addAll("--exclude-tag", "timeout") doFirst { - consoleLauncherTestEventXmlFiles.files.forEach { - Files.delete(it.toPath()) - } + consoleLauncherTestReportsDir.get().asFile.deleteRecursivelyOrThrow() } finalizedBy(generateOpenTestHtmlReport) @@ -176,6 +173,7 @@ tasks { generateOpenTestHtmlReport { mustRunAfter(consoleLauncherTest) + inputs.files(consoleLauncherTestEventXmlFiles).withPathSensitivity(RELATIVE).skipWhenEmpty() argumentProviders += CommandLineArgumentProvider { consoleLauncherTestEventXmlFiles.files.map { it.absolutePath }.toList() } diff --git a/documentation/src/docs/asciidoc/link-attributes.adoc b/documentation/src/docs/asciidoc/link-attributes.adoc index 46da6e279efd..4010e54b7085 100644 --- a/documentation/src/docs/asciidoc/link-attributes.adoc +++ b/documentation/src/docs/asciidoc/link-attributes.adoc @@ -40,6 +40,7 @@ endif::[] :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] +: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] @@ -47,6 +48,7 @@ endif::[] :ModuleSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/ModuleSelector.html[ModuleSelector] :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] +:OutputDirectoryProvider: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/reporting/OutputDirectoryProvider.html[OutputDirectoryProvider] :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] @@ -128,6 +130,7 @@ endif::[] :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 diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.2.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.2.adoc index aecdaa23aac2..0bb4b292e1d9 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.2.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.2.adoc @@ -16,9 +16,9 @@ on GitHub. [[release-notes-5.11.2-junit-platform-bug-fixes]] ==== Bug Fixes -* Fix regression in parallel execution that was introduced in 5.10.4/5.11.1 regarding - global read-write locks. When such a lock was declared on descendants of top-level nodes - in the test tree, such as Cucumber scenarios, test execution failed. +* Fix regression in parallel execution that was introduced in 5.11.1 regarding global + read-write locks. When such a lock was declared on descendants of top-level nodes in the + test tree, such as Cucumber scenarios, test execution failed. [[release-notes-5.11.2-junit-jupiter]] diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc index 92c97ce8dab6..175b2c5c8de2 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc @@ -16,8 +16,8 @@ JUnit repository on GitHub. [[release-notes-5.12.0-M1-junit-platform-bug-fixes]] ==== Bug Fixes -* Fix support for disabling ANSI colors on the console when the `NO_COLOR` environment - variable is available. +* Enable auto-flushing of output in the `ConsoleLauncher` to fix issues with buffering, + in particular when using the `--details=testfeed` option. [[release-notes-5.12.0-M1-junit-platform-deprecations-and-breaking-changes]] ==== Deprecations and Breaking Changes @@ -38,11 +38,16 @@ JUnit repository on GitHub. `--select-file` and `--select-resource`. * `ConsoleLauncher` now accepts multiple values for all `--select` options. * Add `--select-unique-id` support to ConsoleLauncher. +* Add `getOutputDirectoryProvider()` method to `EngineDiscoveryRequest` and `TestPlan` to + allow test engines to publish/attach files to containers and tests by calling + `EngineExecutionListener.fileEntryPublished(...)`. Registered `TestExecutionListeners` + can then access these files by overriding the `fileEntryPublished(...)` method. * The following improvements have been made to the open-test-reporting XML output: - Information about the Git repository, the current branch, the commit hash, and the current worktree status are now included in the XML report, if applicable. - A section containing JUnit-specific metadata about each test/container to the HTML report is now written by open-test-reporting when added to the classpath/module path + - Information about published files is now included as attachments. [[release-notes-5.12.0-M1-junit-jupiter]] @@ -85,10 +90,13 @@ JUnit repository on GitHub. * Extensions based on `TestTemplateInvocationContextProvider` can now allow returning zero invocation contexts by overriding the new `mayReturnZeroTestTemplateInvocationContexts` method. -* The new `@ParameterizedTest(requireArguments = false)` attribute allows to specify that - the absence of arguments is expected in some cases and should not cause a test failure. +* The new `@ParameterizedTest(allowZeroInvocations = true)` attribute allows to specify that + the absence of invocations is expected in some cases and should not cause a test failure. * Allow determining "shared resources" at runtime via the new `@ResourceLock#providers` attribute that accepts implementations of `ResourceLocksProvider`. +* Allow declaring "shared resources" for _direct_ child nodes via the new + `@ResourceLock(target = CHILDREN)` attribute. This may improve parallelization when + a test class declares a `READ` lock, but only a few methods hold a `READ_WRITE` lock. * Extensions that implement `TestInstancePreConstructCallback`, `TestInstanceFactory`, `TestInstancePostProcessor`, `ParameterResolver`, or `InvocationInterceptor` may override the `getTestInstantiationExtensionContextScope()` method to enable receiving @@ -107,6 +115,8 @@ JUnit repository on GitHub. * When enabled via the `junit.jupiter.execution.timeout.threaddump.enabled` configuration parameter, an implementation of `PreInterruptCallback` is registered that writes a thread dump to `System.out` prior to interrupting a test thread due to a timeout. +* `TestReporter` now allows publishing files for a test method or test class which can be + used to include them in test reports, such as the Open Test Reporting format. [[release-notes-5.12.0-M1-junit-vintage]] diff --git a/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-reporting.adoc b/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-reporting.adoc index 287917904eea..310788bd1812 100644 --- a/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-reporting.adoc +++ b/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-reporting.adoc @@ -3,41 +3,46 @@ The `junit-platform-reporting` artifact contains `{TestExecutionListener}` implementations that generate XML test reports in two flavors: -<> and -<>. +<> and +<>. NOTE: The module also contains other `TestExecutionListener` implementations that can be used to build custom reporting. See <> for details. -[[junit-platform-reporting-legacy-xml]] -==== Legacy XML format +[[junit-platform-reporting-output-directory]] +==== Output Directory -`{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 JUnit Platform provides an `{OutputDirectoryProvider}` via +`{EngineDiscoveryRequest}` and `{TestPlan}` to registered <> +and <>, respectively. Its root directory can be +configured via the following <>: -The `LegacyXmlReportGeneratingListener` is used by the <> -as well. +`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. [[junit-platform-reporting-open-test-reporting]] -==== Open Test Reporting XML format +==== 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 -<>: +<>: `junit.platform.reporting.open.xml.enabled=true|false`:: Enable/disable writing the report. -`junit.platform.reporting.output.dir=`:: - Configure the output directory for the reports. 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. -If enabled, the listener creates an XML report file named -`junit-platform-events-.xml` per test run in the configured output directory. +If enabled, the listener creates an XML report file named `open-test-report.xml` in the +configured <>. TIP: The {OpenTestReportingCliTool} can be used to convert from the event-based format to the hierarchical format which is more human-readable. @@ -145,3 +150,13 @@ via the `--config-resource` option: $ java -jar junit-platform-console-standalone-{platform-version}.jar \ --config-resource=configuration.properties ---- + +[[junit-platform-reporting-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 <> +as well. diff --git a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc index 7b2914a95216..25a442d6c295 100644 --- a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc +++ b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc @@ -1077,8 +1077,9 @@ include::{testDir}/example/TestInfoDemo.java[tags=user_guide] * `{TestReporterParameterResolver}`: 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. The data can be consumed via the `reportingEntryPublished()` method in - a `{TestExecutionListener}`, allowing it to be viewed in IDEs or included in reports. + 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. Using `@RunWith(JUnitPlatform.class)` will output all @@ -2992,7 +2993,7 @@ Note that resources declared statically with `{ResourceLock}` annotation are com 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 +`{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. @@ -3029,6 +3030,28 @@ include::{testDir}/example/sharedresources/StaticSharedResourcesDemo.java[tags=u include::{testDir}/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::{testDir}/example/sharedresources/ChildrenSharedResourcesDemo.java[tags=user_guide] +---- + [[writing-tests-built-in-extensions]] === Built-in Extensions diff --git a/documentation/src/test/java/example/TestReporterDemo.java b/documentation/src/test/java/example/TestReporterDemo.java index 7d9a3f35c206..bafc00f83a3e 100644 --- a/documentation/src/test/java/example/TestReporterDemo.java +++ b/documentation/src/test/java/example/TestReporterDemo.java @@ -10,11 +10,16 @@ package example; +import static java.util.Collections.singletonList; + +import java.nio.file.Files; +import java.nio.file.Path; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestReporter; +import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ExecutionMode; @@ -41,5 +46,18 @@ void reportMultipleKeyValuePairs(TestReporter testReporter) { testReporter.publishEntry(values); } + @Test + void reportFiles(TestReporter testReporter, @TempDir Path tempDir) throws Exception { + + testReporter.publishFile("test1.txt", file -> Files.write(file, singletonList("Test 1"))); + + Path existingFile = Files.write(tempDir.resolve("test2.txt"), singletonList("Test 2")); + testReporter.publishFile(existingFile); + + testReporter.publishFile("test3", dir -> { + Path nestedFile = Files.createDirectory(dir).resolve("nested.txt"); + Files.write(nestedFile, singletonList("Nested content")); + }); + } } // end::user_guide[] diff --git a/documentation/src/test/java/example/sharedresources/ChildrenSharedResourcesDemo.java b/documentation/src/test/java/example/sharedresources/ChildrenSharedResourcesDemo.java new file mode 100644 index 000000000000..5350b9ff02a0 --- /dev/null +++ b/documentation/src/test/java/example/sharedresources/ChildrenSharedResourcesDemo.java @@ -0,0 +1,54 @@ +/* + * Copyright 2015-2024 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms 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[] diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 64783d5e851a..49b3b30278fc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,18 +4,19 @@ apiguardian = "1.1.2" asciidoctorj-pdf = "2.3.19" asciidoctor-plugins = "4.0.3" # Check if workaround in documentation.gradle.kts can be removed when upgrading assertj = "3.26.3" -bnd = "7.0.0" +bnd = "7.1.0" checkstyle = "10.20.1" eclipse = "4.32.0" -jackson = "2.18.1" +jackson = "2.18.2" jacoco = "0.8.12" jmh = "1.37" junit4 = "4.13.2" junit4Min = "4.12" ktlint = "1.4.1" -log4j = "2.24.1" +log4j = "2.24.2" +mockito = "5.14.2" opentest4j = "1.3.0" -openTestReporting = "0.1.0-SNAPSHOT" +openTestReporting = "0.2.0-SNAPSHOT" surefire = "3.5.2" xmlunit = "2.10.0" @@ -29,11 +30,10 @@ apiguardian = { module = "org.apiguardian:apiguardian-api", version.ref = "apigu archunit = { module = "com.tngtech.archunit:archunit-junit5", version = "1.3.0" } assertj = { module = "org.assertj:assertj-core", version.ref = "assertj" } -bartholdy = { module = "de.sormuras:bartholdy", version = "0.2.3" } bndlib = { module = "biz.aQute.bnd:biz.aQute.bndlib", version.ref = "bnd" } checkstyle = { module = "com.puppycrawl.tools:checkstyle", version.ref = "checkstyle" } classgraph = { module = "io.github.classgraph:classgraph", version = "4.8.179" } -commons-io = { module = "commons-io:commons-io", version = "2.17.0" } +commons-io = { module = "commons-io:commons-io", version = "2.18.0" } groovy4 = { module = "org.apache.groovy:groovy", version = "4.0.24" } groovy2-bom = { module = "org.codehaus.groovy:groovy-bom", version = "2.5.23" } hamcrest = { module = "org.hamcrest:hamcrest", version = "3.0" } @@ -45,7 +45,7 @@ jimfs = { module = "com.google.jimfs:jimfs", version = "1.3.0" } 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" } -jte = { module = "gg.jte:jte", version = "3.1.14" } +jte = { module = "gg.jte:jte", version = "3.1.15" } junit4 = { module = "junit:junit", version = { require = "[4.12,)", prefer = "4.13.2" } } kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.9.0" } log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" } @@ -53,7 +53,8 @@ log4j-jul = { module = "org.apache.logging.log4j:log4j-jul", version.ref = "log4 maven = { module = "org.apache.maven:apache-maven", version = "3.9.9" } mavenSurefirePlugin = { module = "org.apache.maven.plugins:maven-surefire-plugin", version.ref = "surefire" } memoryfilesystem = { module = "com.github.marschall:memoryfilesystem", version = "2.8.1" } -mockito = { module = "org.mockito:mockito-junit-jupiter", version = "5.14.2" } +mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito" } +mockito-junit-jupiter = { module = "org.mockito:mockito-junit-jupiter", version.ref = "mockito" } nohttp-checkstyle = { module = "io.spring.nohttp:nohttp-checkstyle", version = "0.0.11" } opentest4j = { module = "org.opentest4j:opentest4j", version.ref = "opentest4j" } openTestReporting-cli = { module = "org.opentest4j.reporting:open-test-reporting-cli", version.ref = "openTestReporting" } diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.testing-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.testing-conventions.gradle.kts index a89dd59b290e..169d5756a10c 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.testing-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.testing-conventions.gradle.kts @@ -1,17 +1,21 @@ import com.gradle.develocity.agent.gradle.internal.test.PredictiveTestSelectionConfigurationInternal import com.gradle.develocity.agent.gradle.test.PredictiveTestSelectionMode -import org.gradle.api.tasks.PathSensitivity.NONE +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.nio.file.Files 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()) @@ -26,7 +30,7 @@ val generateOpenTestHtmlReport by tasks.registering(JavaExec::class) { eventXmlFiles.from(tasks.withType().map { objects.fileTree() .from(it.reports.junitXml.outputLocation) - .include("junit-platform-events-*.xml") + .include("junit-*/open-test-report.xml") }) outputLocation = layout.buildDirectory.file("reports/open-test-report.html") } @@ -36,7 +40,8 @@ val generateOpenTestHtmlReport by tasks.registering(JavaExec::class) { abstract class HtmlReportParameters : CommandLineArgumentProvider { @get:InputFiles - @get:PathSensitive(NONE) + @get:PathSensitive(RELATIVE) + @get:SkipWhenEmpty abstract val eventXmlFiles: ConfigurableFileCollection @get:OutputFile @@ -113,14 +118,20 @@ tasks.withType().configureEach { jvmArgumentProviders += CommandLineArgumentProvider { listOf( "-Djunit.platform.reporting.open.xml.enabled=true", - "-Djunit.platform.reporting.output.dir=${reports.junitXml.outputLocation.get().asFile.absolutePath}" + "-Djunit.platform.reporting.output.dir=${reports.junitXml.outputLocation.get().asFile.absolutePath}/junit-{uniqueNumber}", ) } - val reportFiles = objects.fileTree().from(reports.junitXml.outputLocation).matching { include("junit-platform-events-*.xml") } + jvmArgumentProviders += objects.newInstance(JavaAgentArgumentProvider::class).apply { + classpath.from(javaAgentClasspath) + } + + val reportDirTree = objects.fileTree().from(reports.junitXml.outputLocation) doFirst { - reportFiles.files.forEach { - Files.delete(it.toPath()) + reportDirTree.visit { + if (name.startsWith("junit-")) { + file.deleteRecursively() + } } } @@ -129,7 +140,7 @@ tasks.withType().configureEach { dependencies { testImplementation(dependencyFromLibs("assertj")) - testImplementation(dependencyFromLibs("mockito")) + testImplementation(dependencyFromLibs("mockito-junit-jupiter")) testImplementation(dependencyFromLibs("testingAnnotations")) testImplementation(project(":junit-jupiter")) @@ -147,4 +158,17 @@ dependencies { openTestReportingCli(dependencyFromLibs("openTestReporting-cli")) openTestReportingCli(project(":junit-platform-reporting")) + + javaAgent(dependencyFromLibs("mockito-core")) { + isTransitive = false + } +} + +abstract class JavaAgentArgumentProvider : CommandLineArgumentProvider { + + @get:Classpath + abstract val classpath: ConfigurableFileCollection + + override fun asArguments() = listOf("-javaagent:${classpath.singleFile.absolutePath}") + } diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild/exec/RunConsoleLauncher.kt b/gradle/plugins/common/src/main/kotlin/junitbuild/exec/RunConsoleLauncher.kt index a92f31195ad2..896e76090c6f 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild/exec/RunConsoleLauncher.kt +++ b/gradle/plugins/common/src/main/kotlin/junitbuild/exec/RunConsoleLauncher.kt @@ -6,7 +6,13 @@ 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.* +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 @@ -16,7 +22,6 @@ import org.gradle.process.CommandLineArgumentProvider import org.gradle.process.ExecOperations import trackOperationSystemAsInput import java.io.ByteArrayOutputStream -import java.util.* import javax.inject.Inject @CacheableTask @@ -97,4 +102,13 @@ abstract class RunConsoleLauncher @Inject constructor(private val execOperations debugging.set(enabled) } + @Suppress("unused") + @Option( + option = "show-output", + description = "Show output" + ) + fun setShowOutput(showOutput: Boolean) { + hideOutput.set(!showOutput) + } + } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 82dd18b2043e..eb1a55be0e15 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=57dafb5c2622c6cc08b993c85b7c06956a2f53536432a30ead46166dbca0f1e9 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip +distributionSha256Sum=f397b287023acdba1e9f6fc5ea72d22dd63669d59ed4a289a29b1a76eee151c6 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/junit-jupiter-api/junit-jupiter-api.gradle.kts b/junit-jupiter-api/junit-jupiter-api.gradle.kts index bdf4c359550f..402b5323eb57 100644 --- a/junit-jupiter-api/junit-jupiter-api.gradle.kts +++ b/junit-jupiter-api/junit-jupiter-api.gradle.kts @@ -15,6 +15,8 @@ dependencies { compileOnly(kotlin("stdlib")) + testFixturesImplementation(libs.assertj) + osgiVerification(projects.junitJupiterEngine) osgiVerification(projects.junitPlatformLauncher) } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestReporter.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestReporter.java index ffcf580603ff..3039692b083f 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestReporter.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestReporter.java @@ -10,12 +10,17 @@ package org.junit.jupiter.api; +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Collections; import java.util.Map; import org.apiguardian.api.API; +import org.junit.jupiter.api.function.ThrowingConsumer; /** * Parameters of type {@code TestReporter} can be injected into @@ -77,4 +82,36 @@ default void publishEntry(String value) { this.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 attached; never {@code null} or blank + * @since 5.12 + */ + @API(status = EXPERIMENTAL, since = "5.12") + default void publishFile(Path file) { + publishFile(file.getFileName().toString(), path -> Files.copy(file, path, REPLACE_EXISTING)); + } + + /** + * Publish a file 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, but it's up to the action to write the file or + * directory. + * + * @param fileName the name of the file to be attached; never {@code null} or blank + * and must not contain any path separators + * @param action the action to be executed to write the file; never {@code null} + * @since 5.12 + */ + @API(status = EXPERIMENTAL, since = "5.12") + default void publishFile(String fileName, ThrowingConsumer action) { + throw new UnsupportedOperationException(); + } + } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java index 44a3447a4e7b..36ef064bc17b 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java @@ -10,10 +10,12 @@ package org.junit.jupiter.api.extension; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; 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.Arrays; import java.util.Collections; @@ -25,6 +27,7 @@ import org.apiguardian.api.API; 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; @@ -364,6 +367,22 @@ default void publishReportEntry(String value) { this.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 fileName the name of the file to be attached; never {@code null} or blank + * and must not contain any path separators + * @param action the action to be executed to write the file; never {@code null} + * @since 5.12 + * @see org.junit.platform.engine.EngineExecutionListener#fileEntryPublished + */ + @API(status = EXPERIMENTAL, since = "5.12") + void publishFile(String fileName, ThrowingConsumer action); + /** * Get the {@link Store} for the supplied {@link Namespace}. * diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLock.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLock.java index f32012e6871e..e78abdcb647d 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLock.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLock.java @@ -46,11 +46,14 @@ * *

This annotation can be repeated to declare the use of multiple shared resources. * + *

Uniqueness of a shared resource is identified by both {@link #value()} and + * {@link #mode()}. Duplicated shared resources do not cause errors. + * *

Since JUnit Jupiter 5.4, 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 ResourceLock#providers}. + * dynamically at runtime via {@link #providers}. * *

Resources declared "statically" using {@link #value()} and {@link #mode()} * are combined with "dynamic" resources added via {@link #providers()}. @@ -58,9 +61,29 @@ * 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 the {@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 this 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 the {@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. + * * @see Isolated * @see Resources * @see ResourceAccessMode + * @see ResourceLockTarget * @see ResourceLocks * @see ResourceLocksProvider * @since 5.3 @@ -92,6 +115,20 @@ */ ResourceAccessMode mode() default ResourceAccessMode.READ_WRITE; + /** + * 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 = EXPERIMENTAL, since = "5.12") + ResourceLockTarget target() default ResourceLockTarget.SELF; + /** * An array of one or more classes implementing {@link ResourceLocksProvider}. * diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLockTarget.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLockTarget.java new file mode 100644 index 000000000000..89aa97a67bf1 --- /dev/null +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLockTarget.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015-2024 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public 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.EXPERIMENTAL; + +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 = EXPERIMENTAL, since = "5.12") +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" relationship in the context of + * {@link ResourceLockTarget}: + *

    + *
  • a test class + * - test methods and nested test classes declared in the class.
  • + *
  • a nested test class + * - test methods and nested test classes declared in the nested class. + *
  • + *
  • a test method + * - considered to have no children. Using {@code CHILDREN} for + * a test method results in an exception.
  • + *
+ */ + CHILDREN + +} diff --git a/platform-tests/src/test/java/org/junit/platform/AbstractEqualsAndHashCodeTests.java b/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/EqualsAndHashCodeAssertions.java similarity index 79% rename from platform-tests/src/test/java/org/junit/platform/AbstractEqualsAndHashCodeTests.java rename to junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/EqualsAndHashCodeAssertions.java index d5693baaea2f..735f2425cc84 100644 --- a/platform-tests/src/test/java/org/junit/platform/AbstractEqualsAndHashCodeTests.java +++ b/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/EqualsAndHashCodeAssertions.java @@ -8,19 +8,23 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform; +package org.junit.jupiter.api; import static org.assertj.core.api.Assertions.assertThat; /** - * Abstract base class for unit tests that wish to test + * Assertions for unit tests that wish to test * {@link Object#equals(Object)} and {@link Object#hashCode()}. * * @since 1.3 */ -public abstract class AbstractEqualsAndHashCodeTests { +public class EqualsAndHashCodeAssertions { - protected final void assertEqualsAndHashCode(T equal1, T equal2, T different) { + private EqualsAndHashCodeAssertions() { + } + + @SuppressWarnings("EqualsWithItself") + public static void assertEqualsAndHashCode(T equal1, T equal2, T different) { assertThat(equal1).isNotNull(); assertThat(equal2).isNotNull(); assertThat(different).isNotNull(); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/JupiterTestEngine.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/JupiterTestEngine.java index 94b13afd4548..a4f5ac7fa454 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/JupiterTestEngine.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/JupiterTestEngine.java @@ -63,8 +63,8 @@ public Optional getArtifactId() { @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { - JupiterConfiguration configuration = new CachingJupiterConfiguration( - new DefaultJupiterConfiguration(discoveryRequest.getConfigurationParameters())); + JupiterConfiguration configuration = new CachingJupiterConfiguration(new DefaultJupiterConfiguration( + discoveryRequest.getConfigurationParameters(), discoveryRequest.getOutputDirectoryProvider())); JupiterEngineDescriptor engineDescriptor = new JupiterEngineDescriptor(uniqueId, configuration); new DiscoverySelectorResolver().resolveSelectors(discoveryRequest, engineDescriptor); return engineDescriptor; diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/CachingJupiterConfiguration.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/CachingJupiterConfiguration.java index 9e9ac32e46f8..c0c61b2aeae7 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/CachingJupiterConfiguration.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/CachingJupiterConfiguration.java @@ -31,6 +31,7 @@ import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDirFactory; import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.platform.engine.reporting.OutputDirectoryProvider; /** * Caching implementation of the {@link JupiterConfiguration} API. @@ -144,4 +145,9 @@ public ExtensionContextScope getDefaultTestInstantiationExtensionContextScope() DEFAULT_TEST_INSTANTIATION_EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME, __ -> delegate.getDefaultTestInstantiationExtensionContextScope()); } + + @Override + public OutputDirectoryProvider getOutputDirectoryProvider() { + return delegate.getOutputDirectoryProvider(); + } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java index 667bc522746d..ce18c70b97d4 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java @@ -34,6 +34,7 @@ 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.reporting.OutputDirectoryProvider; /** * Default implementation of the {@link JupiterConfiguration} API. @@ -68,10 +69,13 @@ public class DefaultJupiterConfiguration implements JupiterConfiguration { new EnumConfigurationParameterConverter<>(ExtensionContextScope.class, "extension context scope"); private final ConfigurationParameters configurationParameters; + private final OutputDirectoryProvider outputDirectoryProvider; - public DefaultJupiterConfiguration(ConfigurationParameters configurationParameters) { + public DefaultJupiterConfiguration(ConfigurationParameters configurationParameters, + OutputDirectoryProvider outputDirectoryProvider) { this.configurationParameters = Preconditions.notNull(configurationParameters, "ConfigurationParameters must not be null"); + this.outputDirectoryProvider = outputDirectoryProvider; } @Override @@ -174,4 +178,9 @@ public ExtensionContextScope getDefaultTestInstantiationExtensionContextScope() return extensionContextScopeConverter.get(configurationParameters, DEFAULT_TEST_INSTANTIATION_EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME, ExtensionContextScope.DEFAULT); } + + @Override + public OutputDirectoryProvider getOutputDirectoryProvider() { + return outputDirectoryProvider; + } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/JupiterConfiguration.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/JupiterConfiguration.java index 4e43e1b3dd46..239c3d40bec3 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/JupiterConfiguration.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/JupiterConfiguration.java @@ -30,6 +30,7 @@ import org.junit.jupiter.api.io.TempDirFactory; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.platform.engine.reporting.OutputDirectoryProvider; /** * @since 5.4 @@ -83,4 +84,5 @@ public interface JupiterConfiguration { ExtensionContextScope getDefaultTestInstantiationExtensionContextScope(); + OutputDirectoryProvider getOutputDirectoryProvider(); } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java index 4ff9573a32b6..32fc7339582e 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java @@ -13,6 +13,8 @@ import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.toCollection; +import java.io.IOException; +import java.nio.file.Path; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; @@ -25,6 +27,7 @@ import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource; +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; @@ -33,9 +36,11 @@ import org.junit.jupiter.engine.extension.ExtensionRegistry; 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.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; @@ -109,6 +114,25 @@ public void publishReportEntry(Map values) { this.engineExecutionListener.reportingEntryPublished(this.testDescriptor, ReportEntry.from(values)); } + @Override + public void publishFile(String fileName, ThrowingConsumer action) { + try { + Path dir = configuration.getOutputDirectoryProvider().createOutputDirectory(this.testDescriptor); + try { + Path file = dir.resolve(fileName); + action.accept(file); + this.engineExecutionListener.fileEntryPublished(this.testDescriptor, FileEntry.from(file)); + } + catch (Throwable t) { + UnrecoverableExceptions.rethrowIfUnrecoverable(t); + throw new JUnitException("Failed to publish file", t); + } + } + catch (IOException e) { + throw new JUnitException("Failed to create output directory", e); + } + } + @Override public Optional getParent() { return Optional.ofNullable(this.parent); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExclusiveResourceCollector.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExclusiveResourceCollector.java index 42b1295b2221..53757a4be9e6 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExclusiveResourceCollector.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExclusiveResourceCollector.java @@ -10,6 +10,7 @@ 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 static org.junit.platform.commons.util.CollectionUtils.toUnmodifiableList; @@ -22,6 +23,7 @@ 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.JUnitException; import org.junit.platform.commons.util.ReflectionUtils; @@ -42,7 +44,7 @@ Stream getAllExclusiveResources( } @Override - public Stream getStaticResources() { + Stream getStaticResourcesFor(ResourceLockTarget target) { return Stream.empty(); } @@ -55,10 +57,10 @@ Stream getDynamicResources( Stream getAllExclusiveResources( Function> providerToLocks) { - return Stream.concat(getStaticResources(), getDynamicResources(providerToLocks)); + return Stream.concat(getStaticResourcesFor(SELF), getDynamicResources(providerToLocks)); } - abstract Stream getStaticResources(); + abstract Stream getStaticResourcesFor(ResourceLockTarget target); abstract Stream getDynamicResources( Function> providerToLocks); @@ -78,9 +80,10 @@ private static class DefaultExclusiveResourceCollector extends ExclusiveResource } @Override - public Stream getStaticResources() { + 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()))); } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java index 9c8e4bcb1ce3..95d9ecbf6035 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java @@ -11,6 +11,7 @@ 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.platform.commons.util.CollectionUtils.forEachInReverseOrder; @@ -27,6 +28,7 @@ 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; @@ -82,7 +84,15 @@ public final Set getTags() { @Override public ExclusiveResourceCollector getExclusiveResourceCollector() { // There's no need to cache this as this method should only be called once - return ExclusiveResourceCollector.from(getTestMethod()); + 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ResourceLockAware.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ResourceLockAware.java index 858644d48520..8c50b188d0d2 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ResourceLockAware.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ResourceLockAware.java @@ -10,6 +10,8 @@ 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.Set; @@ -37,11 +39,15 @@ default Stream determineExclusiveResources() { return determineOwnExclusiveResources(); } + Stream parentStaticResourcesForChildren = ancestors.getLast() // + .getExclusiveResourceCollector().getStaticResourcesFor(CHILDREN); + Stream ancestorDynamicResources = ancestors.stream() // .map(ResourceLockAware::getExclusiveResourceCollector) // .flatMap(collector -> collector.getDynamicResources(this::evaluateResourceLocksProvider)); - return Stream.concat(ancestorDynamicResources, determineOwnExclusiveResources()); + return Stream.of(ancestorDynamicResources, parentStaticResourcesForChildren, determineOwnExclusiveResources())// + .flatMap(s -> s); } default Stream determineOwnExclusiveResources() { diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DefaultTestReporter.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DefaultTestReporter.java new file mode 100644 index 000000000000..b776a0fe3b94 --- /dev/null +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DefaultTestReporter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2015-2024 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public 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.TestReporter; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.function.ThrowingConsumer; + +/** + * @since 1.12 + */ +class DefaultTestReporter implements TestReporter { + + private final ExtensionContext extensionContext; + + DefaultTestReporter(ExtensionContext extensionContext) { + this.extensionContext = extensionContext; + } + + @Override + public void publishEntry(Map map) { + extensionContext.publishReportEntry(map); + } + + @Override + public void publishFile(String fileName, ThrowingConsumer action) { + extensionContext.publishFile(fileName, action); + } +} diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestReporterParameterResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestReporterParameterResolver.java index c06536d448dc..5427d8029cf9 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestReporterParameterResolver.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestReporterParameterResolver.java @@ -34,7 +34,7 @@ public boolean supportsParameter(ParameterContext parameterContext, ExtensionCon @Override public TestReporter resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return extensionContext::publishReportEntry; + return new DefaultTestReporter(extensionContext); } } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTest.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTest.java index 03741d19f1fd..5dc2c3a88ad9 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTest.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTest.java @@ -293,18 +293,18 @@ boolean autoCloseArguments() default true; /** - * Configure whether at least one set of arguments is required for this + * Configure whether zero invocations are allowed for this * parameterized test. * - *

Set this attribute to {@code false} if the absence of arguments is + *

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 true}. + *

Defaults to {@code false}. * * @since 5.12 */ @API(status = EXPERIMENTAL, since = "5.12") - boolean requireArguments() default true; + boolean allowZeroInvocations() default false; /** * Configure how the number of arguments provided by an {@link ArgumentsSource} are validated. diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java index 74ff653e15cc..8aed7644f2d8 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java @@ -86,7 +86,7 @@ public Stream provideTestTemplateInvocationContex return createInvocationContext(formatter, methodContext, arguments, invocationCount.intValue()); }) .onClose(() -> - Preconditions.condition(invocationCount.get() > 0 || !methodContext.annotation.requireArguments(), + Preconditions.condition(invocationCount.get() > 0 || methodContext.annotation.allowZeroInvocations(), "Configuration error: You must configure at least one set of arguments for this @ParameterizedTest")); // @formatter:on } @@ -94,7 +94,7 @@ public Stream provideTestTemplateInvocationContex @Override public boolean mayReturnZeroTestTemplateInvocationContexts(ExtensionContext extensionContext) { ParameterizedTestMethodContext methodContext = getMethodContext(extensionContext); - return !methodContext.annotation.requireArguments(); + return methodContext.annotation.allowZeroInvocations(); } private ParameterizedTestMethodContext getMethodContext(ExtensionContext extensionContext) { diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/ConsoleLauncher.java b/junit-platform-console/src/main/java/org/junit/platform/console/ConsoleLauncher.java index 6ffd9ebefa5d..54345f061e2e 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/ConsoleLauncher.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/ConsoleLauncher.java @@ -30,36 +30,17 @@ public class ConsoleLauncher { public static void main(String... args) { - PrintWriter out = new PrintWriter(System.out); - PrintWriter err = new PrintWriter(System.err); - CommandResult result = run(out, err, args); + CommandResult result = newCommandFacade().run(args); System.exit(result.getExitCode()); } @API(status = INTERNAL, since = "1.0") public static CommandResult run(PrintWriter out, PrintWriter err, String... args) { - ConsoleLauncher consoleLauncher = new ConsoleLauncher(ConsoleTestExecutor::new, out, err); - return consoleLauncher.run(args); + return newCommandFacade().run(args, out, err); } - private final ConsoleTestExecutor.Factory consoleTestExecutorFactory; - private final PrintWriter out; - private final PrintWriter err; - - ConsoleLauncher(ConsoleTestExecutor.Factory consoleTestExecutorFactory, PrintWriter out, PrintWriter err) { - this.consoleTestExecutorFactory = consoleTestExecutorFactory; - this.out = out; - this.err = err; - } - - CommandResult run(String... args) { - try { - return new CommandFacade(consoleTestExecutorFactory).run(out, err, args); - } - finally { - out.flush(); - err.flush(); - } + private static CommandFacade newCommandFacade() { + return new CommandFacade(ConsoleTestExecutor::new); } } diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/CommandFacade.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/CommandFacade.java index a591a59abb70..b1690e279c96 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/options/CommandFacade.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/CommandFacade.java @@ -33,10 +33,24 @@ public CommandFacade(ConsoleTestExecutor.Factory consoleTestExecutorFactory) { this.consoleTestExecutorFactory = consoleTestExecutorFactory; } - public CommandResult run(PrintWriter out, PrintWriter err, String[] args) { + 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(out, err, args); + return new MainCommand(consoleTestExecutorFactory).run(args, outputStreamConfig); } } diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/MainCommand.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/MainCommand.java index d4a86e807785..941a4976ea3e 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/options/MainCommand.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/MainCommand.java @@ -103,10 +103,11 @@ private Object runCommand(String subcommand, Optional triggeringOption) List args = new ArrayList<>(commandLine.getParseResult().expandedArgs()); triggeringOption.ifPresent(args::remove); - CommandResult result = runCommand(commandLine.getOut(), // - commandLine.getErr(), // + CommandResult result = runCommand( // + new CommandLine(command), // args.toArray(new String[0]), // - command); + Optional.of(new OutputStreamConfig(commandLine)) // + ); this.commandResult = result; printDeprecationWarning(subcommand, triggeringOption, commandLine); @@ -130,24 +131,19 @@ private static void printDeprecationWarning(String subcommand, Optional err.flush(); } - CommandResult run(PrintWriter out, PrintWriter err, String[] args) { + CommandResult run(String[] args, Optional outputStreamConfig) { CommandLine commandLine = new CommandLine(this) // .addSubcommand(new DiscoverTestsCommand(consoleTestExecutorFactory)) // .addSubcommand(new ExecuteTestsCommand(consoleTestExecutorFactory)) // .addSubcommand(new ListTestEnginesCommand()); - return runCommand(out, err, args, commandLine); + return runCommand(commandLine, args, outputStreamConfig); } - private static CommandResult runCommand(PrintWriter out, PrintWriter err, String[] args, Object command) { - return runCommand(out, err, args, new CommandLine(command)); - } - - private static CommandResult runCommand(PrintWriter out, PrintWriter err, String[] args, - CommandLine commandLine) { - int exitCode = BaseCommand.initialize(commandLine) // - .setOut(out) // - .setErr(err) // - .execute(args); + private static CommandResult runCommand(CommandLine commandLine, String[] args, + Optional outputStreamConfig) { + BaseCommand.initialize(commandLine); + outputStreamConfig.ifPresent(it -> it.applyTo(commandLine)); + int exitCode = commandLine.execute(args); return CommandResult.create(exitCode, getLikelyExecutedCommand(commandLine).getExecutionResult()); } diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/OutputStreamConfig.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/OutputStreamConfig.java new file mode 100644 index 000000000000..dd72ed9e9f0b --- /dev/null +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/OutputStreamConfig.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015-2024 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse 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.PrintWriter; + +import picocli.CommandLine; + +class OutputStreamConfig { + + private final PrintWriter out; + private final PrintWriter err; + + OutputStreamConfig(CommandLine commandLine) { + this(commandLine.getOut(), commandLine.getErr()); + } + + OutputStreamConfig(PrintWriter out, PrintWriter err) { + this.out = out; + this.err = err; + } + + void applyTo(CommandLine commandLine) { + commandLine.setOut(out).setErr(err); + } +} diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ConsoleTestExecutor.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ConsoleTestExecutor.java index 1cdddf814e10..d7dc8f8aacff 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ConsoleTestExecutor.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ConsoleTestExecutor.java @@ -11,6 +11,8 @@ package org.junit.platform.console.tasks; import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.platform.console.tasks.DiscoveryRequestCreator.toDiscoveryRequestBuilder; +import static org.junit.platform.launcher.LauncherConstants.OUTPUT_DIR_PROPERTY_NAME; import java.io.PrintWriter; import java.net.URL; @@ -32,6 +34,7 @@ import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestPlan; +import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; import org.junit.platform.launcher.core.LauncherFactory; import org.junit.platform.launcher.listeners.SummaryGeneratingListener; import org.junit.platform.launcher.listeners.TestExecutionSummary; @@ -75,7 +78,7 @@ private void discoverTests(PrintWriter out) { Launcher launcher = launcherSupplier.get(); Optional commandLineTestPrinter = createDetailsPrintingListener(out); - LauncherDiscoveryRequest discoveryRequest = new DiscoveryRequestCreator().toDiscoveryRequest(discoveryOptions); + LauncherDiscoveryRequest discoveryRequest = toDiscoveryRequestBuilder(discoveryOptions).build(); TestPlan testPlan = launcher.discover(discoveryRequest); commandLineTestPrinter.ifPresent(printer -> printer.listTests(testPlan)); @@ -98,8 +101,10 @@ private TestExecutionSummary executeTests(PrintWriter out, Optional report Launcher launcher = launcherSupplier.get(); SummaryGeneratingListener summaryListener = registerListeners(out, reportsDir, launcher); - LauncherDiscoveryRequest discoveryRequest = new DiscoveryRequestCreator().toDiscoveryRequest(discoveryOptions); - launcher.execute(discoveryRequest); + LauncherDiscoveryRequestBuilder discoveryRequestBuilder = toDiscoveryRequestBuilder(discoveryOptions); + reportsDir.ifPresent(dir -> discoveryRequestBuilder.configurationParameter(OUTPUT_DIR_PROPERTY_NAME, + dir.toAbsolutePath().toString())); + launcher.execute(discoveryRequestBuilder.build()); TestExecutionSummary summary = summaryListener.getSummary(); if (summary.getTotalFailureCount() > 0 || outputOptions.getDetails() != Details.NONE) { diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/DiscoveryRequestCreator.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/DiscoveryRequestCreator.java index 6cbbfa039d6b..25af8a62a0d7 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/DiscoveryRequestCreator.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/DiscoveryRequestCreator.java @@ -42,7 +42,6 @@ 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.LauncherDiscoveryRequest; import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; /** @@ -50,7 +49,7 @@ */ class DiscoveryRequestCreator { - LauncherDiscoveryRequest toDiscoveryRequest(TestDiscoveryOptions options) { + static LauncherDiscoveryRequestBuilder toDiscoveryRequestBuilder(TestDiscoveryOptions options) { LauncherDiscoveryRequestBuilder requestBuilder = request(); List selectors = createDiscoverySelectors(options); requestBuilder.selectors(selectors); @@ -58,10 +57,10 @@ LauncherDiscoveryRequest toDiscoveryRequest(TestDiscoveryOptions options) { requestBuilder.configurationParameters(options.getConfigurationParameters()); requestBuilder.configurationParametersResources( options.getConfigurationParametersResources().toArray(new String[0])); - return requestBuilder.build(); + return requestBuilder; } - private List createDiscoverySelectors(TestDiscoveryOptions options) { + private static List createDiscoverySelectors(TestDiscoveryOptions options) { List explicitSelectors = options.getExplicitSelectors(); if (options.isScanClasspath()) { Preconditions.condition(explicitSelectors.isEmpty(), @@ -77,12 +76,12 @@ private List createDiscoverySelectors(TestDiscovery "Please specify an explicit selector option or use --scan-class-path or --scan-modules"); } - private List createClasspathRootSelectors(TestDiscoveryOptions options) { + private static List createClasspathRootSelectors(TestDiscoveryOptions options) { Set classpathRoots = determineClasspathRoots(options); return selectClasspathRoots(classpathRoots); } - private Set determineClasspathRoots(TestDiscoveryOptions options) { + private static Set determineClasspathRoots(TestDiscoveryOptions options) { if (options.getSelectedClasspathEntries().isEmpty()) { Set rootDirs = new LinkedHashSet<>(ReflectionUtils.getAllClasspathRootDirectories()); rootDirs.addAll(options.getExistingAdditionalClasspathEntries()); @@ -91,7 +90,7 @@ private Set determineClasspathRoots(TestDiscoveryOptions options) { return new LinkedHashSet<>(options.getSelectedClasspathEntries()); } - private void addFilters(LauncherDiscoveryRequestBuilder requestBuilder, TestDiscoveryOptions options, + private static void addFilters(LauncherDiscoveryRequestBuilder requestBuilder, TestDiscoveryOptions options, List selectors) { requestBuilder.filters(includedClassNamePatterns(options, selectors)); @@ -133,7 +132,7 @@ private void addFilters(LauncherDiscoveryRequestBuilder requestBuilder, TestDisc } } - private ClassNameFilter includedClassNamePatterns(TestDiscoveryOptions options, + private static ClassNameFilter includedClassNamePatterns(TestDiscoveryOptions options, List selectors) { Stream patternStreams = Stream.concat( // options.getIncludedClassNamePatterns().stream(), // diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/FlatPrintingListener.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/FlatPrintingListener.java index 0cbe2abd3352..c6a50c6349c2 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/FlatPrintingListener.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/FlatPrintingListener.java @@ -14,6 +14,7 @@ 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; @@ -73,6 +74,12 @@ public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry e 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()); } diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreeNode.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreeNode.java index 8c1217f8cf68..fb119bbfa0f0 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreeNode.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreeNode.java @@ -16,6 +16,7 @@ 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; @@ -31,6 +32,7 @@ class TreeNode { private TestIdentifier identifier; private TestExecutionResult result; final Queue reports = new ConcurrentLinkedQueue<>(); + final Queue files = new ConcurrentLinkedQueue<>(); final Queue children = new ConcurrentLinkedQueue<>(); boolean visible; @@ -61,6 +63,11 @@ TreeNode addReportEntry(ReportEntry reportEntry) { return this; } + TreeNode addFileEntry(FileEntry file) { + files.add(file); + return this; + } + TreeNode setResult(TestExecutionResult result) { this.result = result; this.duration = System.currentTimeMillis() - creation; diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrinter.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrinter.java index 8e171d48d3b2..dceaa952ce50 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrinter.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrinter.java @@ -21,6 +21,7 @@ import org.junit.platform.console.options.Theme; 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; /** @@ -88,6 +89,7 @@ private void printVisible(TreeNode node, String indent, boolean continuous) { 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) { @@ -152,6 +154,14 @@ private void printReportEntry(String indent, Map.Entry mapEntry) out.print("`"); } + private void printFileEntry(String indent, FileEntry fileEntry) { + out.print(indent); + out.print(fileEntry.getTimestamp()); + out.print(" "); + out.print(color(Style.SUCCESSFUL, fileEntry.getFile().toUri().toString())); + out.println(); + } + private void printMessage(Style style, String indent, String message) { String[] lines = message.split("\\R"); out.print(" "); diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrintingListener.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrintingListener.java index 437f7b56733e..34a3b3300b49 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrintingListener.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrintingListener.java @@ -17,6 +17,7 @@ import org.junit.platform.console.options.Theme; 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; @@ -73,6 +74,11 @@ public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry e 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()); diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/VerboseTreePrintingListener.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/VerboseTreePrintingListener.java index a8df758dc114..bf247ff5e1a1 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/VerboseTreePrintingListener.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/VerboseTreePrintingListener.java @@ -19,6 +19,7 @@ import org.junit.platform.console.options.Theme; 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; @@ -130,6 +131,11 @@ public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry e 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. */ diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java index 41f6e3b76f18..3aa9cd5256b5 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java @@ -10,11 +10,14 @@ package org.junit.platform.engine; +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; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.engine.reporting.OutputDirectoryProvider; /** * {@code EngineDiscoveryRequest} provides a {@link TestEngine} access to the @@ -72,10 +75,6 @@ public interface EngineDiscoveryRequest { /** * Get the {@link EngineDiscoveryListener} 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 this API. - * * @return the discovery listener; never {@code null} * @since 1.6 */ @@ -84,4 +83,15 @@ default EngineDiscoveryListener getDiscoveryListener() { return EngineDiscoveryListener.NOOP; } + /** + * Get the {@link OutputDirectoryProvider} for this request. + * + * @return the output directory provider; never {@code null} + * @since 1.12 + */ + @API(status = EXPERIMENTAL, since = "1.12") + default OutputDirectoryProvider getOutputDirectoryProvider() { + throw new JUnitException( + "OutputDirectoryProvider not available; probably due to unaligned versions of the junit-platform-engine and junit-platform-launcher jars on the classpath/module path."); + } } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineExecutionListener.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineExecutionListener.java index 80727af0e3c2..c5fe614fc6b6 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineExecutionListener.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineExecutionListener.java @@ -10,10 +10,12 @@ 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; import org.junit.platform.engine.TestExecutionResult.Status; +import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; /** @@ -137,4 +139,24 @@ default void executionFinished(TestDescriptor testDescriptor, TestExecutionResul default void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry entry) { } + /** + * Can be called for any {@link TestDescriptor} in order to attach a file to + * a test or container — for example: + * + *

    + *
  • Screenshots
  • + *
  • Logs
  • + *
  • Output files written by the code under test
  • + *
+ * + *

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 file a {@code FileEntry} instance to be attached + */ + @API(status = EXPERIMENTAL, since = "1.12") + default void fileEntryPublished(TestDescriptor testDescriptor, FileEntry file) { + } } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/ExecutionRequest.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/ExecutionRequest.java index ce0b9e0c65c7..64bdc62cb37d 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/ExecutionRequest.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/ExecutionRequest.java @@ -31,9 +31,7 @@ public class ExecutionRequest { private final TestDescriptor rootTestDescriptor; - private final EngineExecutionListener engineExecutionListener; - private final ConfigurationParameters configurationParameters; @API(status = INTERNAL, since = "1.0") diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/FileEntry.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/FileEntry.java new file mode 100644 index 000000000000..9dab655e5a1f --- /dev/null +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/FileEntry.java @@ -0,0 +1,74 @@ +/* + * Copyright 2015-2024 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse 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.EXPERIMENTAL; + +import java.nio.file.Path; +import java.time.LocalDateTime; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ToStringBuilder; + +/** + * {@code FileEntry} encapsulates a file to be published to the reporting + * infrastructure. + * + * @since 1.12 + * @see #from(Path) + */ +@API(status = EXPERIMENTAL, since = "1.12") +public final class FileEntry { + + /** + * Factory for creating a new {@code FileEntry} from the supplied file. + * + * @param file the file to publish; never {@code null} + */ + public static FileEntry from(Path file) { + return new FileEntry(file); + } + + private final LocalDateTime timestamp = LocalDateTime.now(); + private final Path file; + + private FileEntry(Path file) { + this.file = Preconditions.notNull(file, "file must not be null"); + } + + /** + * 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 file to be published. + * + * @return the file to publish; never {@code null} + */ + public Path getFile() { + return file; + } + + @Override + public String toString() { + ToStringBuilder builder = new ToStringBuilder(this); + builder.append("timestamp", this.timestamp); + builder.append("file", this.file); + return builder.toString(); + } + +} diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/OutputDirectoryProvider.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/OutputDirectoryProvider.java new file mode 100644 index 000000000000..5bb83fc77dc9 --- /dev/null +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/OutputDirectoryProvider.java @@ -0,0 +1,47 @@ +/* + * Copyright 2015-2024 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse 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.EXPERIMENTAL; + +import java.io.IOException; +import java.nio.file.Path; + +import org.apiguardian.api.API; +import org.junit.platform.engine.EngineDiscoveryRequest; +import org.junit.platform.engine.TestDescriptor; + +/** + * Provider of output directories for test engines to write reports and other + * output files to. + * + * @since 1.12 + * @see EngineDiscoveryRequest#getOutputDirectoryProvider() + */ +@API(status = EXPERIMENTAL, since = "1.12") +public interface OutputDirectoryProvider { + + /** + * {@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; + +} diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/ReportEntry.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/ReportEntry.java index 740df190eaaa..f091e0984ecc 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/ReportEntry.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/ReportEntry.java @@ -83,7 +83,7 @@ private void add(String key, String value) { * * @return a copy of the map of key-value pairs; never {@code null} */ - public final Map getKeyValuePairs() { + public Map getKeyValuePairs() { return Collections.unmodifiableMap(this.keyValuePairs); } @@ -94,7 +94,7 @@ public final Map getKeyValuePairs() { * * @return when this entry was created; never {@code null} */ - public final LocalDateTime getTimestamp() { + public LocalDateTime getTimestamp() { return this.timestamp; } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java index c81c192b8cc7..b8883dd2a941 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java @@ -77,7 +77,7 @@ public ParallelExecutionConfiguration createConfiguration(ConfigurationParameter Preconditions.condition(maxPoolSizeFactor.compareTo(BigDecimal.ONE) >= 0, () -> String.format( "Factor '%s' specified via configuration parameter '%s' must be greater than or equal to 1", - factor, CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME)); + factor, CONFIG_DYNAMIC_MAX_POOL_SIZE_FACTOR_PROPERTY_NAME)); return maxPoolSizeFactor.multiply(BigDecimal.valueOf(parallelism)).intValue(); }).orElseGet(() -> 256 + parallelism); diff --git a/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingExecutionListener.java b/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingExecutionListener.java index cef4753f01b2..04d72df4cb87 100644 --- a/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingExecutionListener.java +++ b/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingExecutionListener.java @@ -26,6 +26,7 @@ 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.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; @@ -97,6 +98,14 @@ public void reportingEntryPublished(TestIdentifier test, ReportEntry reportEntry } } + @Override + public void fileEntryPublished(TestIdentifier testIdentifier, FileEntry file) { + FileEntryEvent event = new FileEntryEvent(); + event.uniqueId = testIdentifier.getUniqueId(); + event.path = file.getFile().toAbsolutePath().toString(); + event.commit(); + } + @Category({ "JUnit", "Execution" }) @StackTrace(false) abstract static class ExecutionEvent extends Event { @@ -159,4 +168,14 @@ static class ReportEntryEvent extends ExecutionEvent { @Label("Value") String value; } + + @Label("File Entry") + @Name("org.junit.FileEntry") + static class FileEntryEvent extends ExecutionEvent { + @UniqueId + @Label("Unique Id") + String uniqueId; + @Label("Path") + String path; + } } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java index 3a7aa079aed1..da3dac7fae74 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java @@ -191,6 +191,34 @@ public class LauncherConstants { @API(status = EXPERIMENTAL, since = "1.10") 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.reporting.OutputDirectoryProvider + */ + @API(status = EXPERIMENTAL, since = "1.12") + 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 = EXPERIMENTAL, since = "1.12") + public static final String OUTPUT_DIR_UNIQUE_NUMBER_PLACEHOLDER = "{uniqueNumber}"; + private LauncherConstants() { /* no-op */ } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestExecutionListener.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestExecutionListener.java index 9179705f3661..ad3c98105bb0 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestExecutionListener.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestExecutionListener.java @@ -10,12 +10,14 @@ package org.junit.platform.launcher; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; 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; /** @@ -184,4 +186,15 @@ default void executionFinished(TestIdentifier testIdentifier, TestExecutionResul default void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry entry) { } + /** + * Called when a file 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} + */ + @API(status = EXPERIMENTAL, since = "1.12") + default void fileEntryPublished(TestIdentifier testIdentifier, FileEntry file) { + } } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestPlan.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestPlan.java index c83f11124913..c236d1980216 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestPlan.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestPlan.java @@ -34,6 +34,7 @@ import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.OutputDirectoryProvider; /** * {@code TestPlan} describes the tree of tests and containers as discovered @@ -70,6 +71,7 @@ public class TestPlan { private final boolean containsTests; private final ConfigurationParameters configurationParameters; + private final OutputDirectoryProvider outputDirectoryProvider; /** * Construct a new {@code TestPlan} from the supplied collection of @@ -82,24 +84,28 @@ public class TestPlan { * plan should be created; never {@code null} * @param configurationParameters the {@code ConfigurationParameters} for * this test plan; never {@code null} + * @param outputDirectoryProvider the {@code OutputDirectoryProvider} for + * this test plan; never {@code null} * @return a new test plan */ - @API(status = INTERNAL, since = "1.0") + @API(status = INTERNAL, since = "1.12") public static TestPlan from(Collection engineDescriptors, - ConfigurationParameters configurationParameters) { + ConfigurationParameters configurationParameters, OutputDirectoryProvider outputDirectoryProvider) { 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(engineDescriptors.stream().anyMatch(TestDescriptor::containsTests), - configurationParameters); + configurationParameters, outputDirectoryProvider); 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) { + protected TestPlan(boolean containsTests, ConfigurationParameters configurationParameters, + OutputDirectoryProvider outputDirectoryProvider) { this.containsTests = containsTests; this.configurationParameters = configurationParameters; + this.outputDirectoryProvider = outputDirectoryProvider; } /** @@ -291,6 +297,17 @@ public ConfigurationParameters getConfigurationParameters() { return this.configurationParameters; } + /** + * Get the {@link OutputDirectoryProvider} for this test plan. + * + * @return the output directory provider; never {@code null} + * @since 1.12 + */ + @API(status = EXPERIMENTAL, since = "1.12") + public OutputDirectoryProvider getOutputDirectoryProvider() { + return this.outputDirectoryProvider; + } + /** * Accept the supplied {@link Visitor} for a depth-first traversal of the * test plan. diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/CompositeEngineExecutionListener.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/CompositeEngineExecutionListener.java index b68e59bee23c..b872c12b0f1a 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/CompositeEngineExecutionListener.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/CompositeEngineExecutionListener.java @@ -21,6 +21,7 @@ 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 { @@ -67,6 +68,13 @@ public void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry e () -> "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 -> { diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/CompositeTestExecutionListener.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/CompositeTestExecutionListener.java index 9107865d8872..1eaeee172a07 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/CompositeTestExecutionListener.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/CompositeTestExecutionListener.java @@ -21,6 +21,7 @@ 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; @@ -95,6 +96,13 @@ public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry e () -> "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 -> { diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultDiscoveryRequest.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultDiscoveryRequest.java index 3ea9c295384a..fd59b0b447a4 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultDiscoveryRequest.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultDiscoveryRequest.java @@ -20,6 +20,7 @@ import org.junit.platform.engine.DiscoveryFilter; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.EngineDiscoveryRequest; +import org.junit.platform.engine.reporting.OutputDirectoryProvider; import org.junit.platform.launcher.EngineFilter; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; @@ -51,15 +52,19 @@ final class DefaultDiscoveryRequest implements LauncherDiscoveryRequest { // Listener for test discovery that may abort on errors. private final LauncherDiscoveryListener discoveryListener; + private final OutputDirectoryProvider outputDirectoryProvider; + DefaultDiscoveryRequest(List selectors, List engineFilters, List> discoveryFilters, List postDiscoveryFilters, - LauncherConfigurationParameters configurationParameters, LauncherDiscoveryListener discoveryListener) { + LauncherConfigurationParameters configurationParameters, LauncherDiscoveryListener discoveryListener, + OutputDirectoryProvider outputDirectoryProvider) { this.selectors = selectors; this.engineFilters = engineFilters; this.discoveryFilters = discoveryFilters; this.postDiscoveryFilters = postDiscoveryFilters; this.configurationParameters = configurationParameters; this.discoveryListener = discoveryListener; + this.outputDirectoryProvider = outputDirectoryProvider; } @Override @@ -91,7 +96,11 @@ public ConfigurationParameters getConfigurationParameters() { @Override public LauncherDiscoveryListener getDiscoveryListener() { - return discoveryListener; + return this.discoveryListener; } + @Override + public OutputDirectoryProvider getOutputDirectoryProvider() { + return this.outputDirectoryProvider; + } } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingEngineExecutionListener.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingEngineExecutionListener.java index 18a95fabdb3b..2eca74628725 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingEngineExecutionListener.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingEngineExecutionListener.java @@ -13,6 +13,7 @@ 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; /** @@ -51,4 +52,8 @@ public void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry e delegate.reportingEntryPublished(testDescriptor, entry); } + @Override + public void fileEntryPublished(TestDescriptor testDescriptor, FileEntry file) { + delegate.fileEntryPublished(testDescriptor, file); + } } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java index 09cb6231b9a7..6e01d443cfb7 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java @@ -75,8 +75,7 @@ public EngineDiscoveryOrchestrator(Iterable testEngines, * {@linkplain TestDescriptor#prune() prunes} the resulting test tree. */ public LauncherDiscoveryResult discover(LauncherDiscoveryRequest request, Phase phase) { - Map result = discover(request, phase, UniqueId::forEngine); - return new LauncherDiscoveryResult(result, request.getConfigurationParameters()); + return discover(request, phase, UniqueId::forEngine); } /** @@ -94,17 +93,18 @@ public LauncherDiscoveryResult discover(LauncherDiscoveryRequest request, Phase * will not emit start or emit events for engines without tests. */ public LauncherDiscoveryResult discover(LauncherDiscoveryRequest request, Phase phase, UniqueId parentId) { - Map testEngines = discover(request, phase, parentId::appendEngine); - LauncherDiscoveryResult result = new LauncherDiscoveryResult(testEngines, request.getConfigurationParameters()); + LauncherDiscoveryResult result = discover(request, phase, parentId::appendEngine); return result.withRetainedEngines(TestDescriptor::containsTests); } - private Map discover(LauncherDiscoveryRequest request, Phase phase, + private LauncherDiscoveryResult discover(LauncherDiscoveryRequest request, Phase phase, Function uniqueIdCreator) { LauncherDiscoveryListener listener = getLauncherDiscoveryListener(request); listener.launcherDiscoveryStarted(request); try { - return discoverSafely(request, phase, listener, uniqueIdCreator); + Map testEngines = discoverSafely(request, phase, listener, uniqueIdCreator); + return new LauncherDiscoveryResult(testEngines, request.getConfigurationParameters(), + request.getOutputDirectoryProvider()); } finally { listener.launcherDiscoveryFinished(request); diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java index c000c09ce6cc..1ef05c02d820 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java @@ -195,7 +195,7 @@ private void execute(TestDescriptor engineDescriptor, EngineExecutionListener li OutcomeDelayingEngineExecutionListener delayingListener = new OutcomeDelayingEngineExecutionListener(listener, engineDescriptor); try { - testEngine.execute(new ExecutionRequest(engineDescriptor, delayingListener, configurationParameters)); + testEngine.execute(ExecutionRequest.create(engineDescriptor, delayingListener, configurationParameters)); delayingListener.reportEngineOutcome(); } catch (Throwable throwable) { diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ExecutionListenerAdapter.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ExecutionListenerAdapter.java index 54e3b7c2ae38..cd85e331dccb 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ExecutionListenerAdapter.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ExecutionListenerAdapter.java @@ -13,6 +13,7 @@ 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; @@ -61,6 +62,11 @@ public void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry e 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()); } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/HierarchicalOutputDirectoryProvider.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/HierarchicalOutputDirectoryProvider.java new file mode 100644 index 000000000000..9296ca634468 --- /dev/null +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/HierarchicalOutputDirectoryProvider.java @@ -0,0 +1,71 @@ +/* + * Copyright 2015-2024 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse 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.nio.file.Paths; +import java.util.List; +import java.util.function.Supplier; +import java.util.regex.Pattern; + +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId.Segment; +import org.junit.platform.engine.reporting.OutputDirectoryProvider; + +/** + * Hierarchical {@link OutputDirectoryProvider} that creates directories based on + * the unique ID segments of a {@link TestDescriptor}. + * + * @since 1.12 + */ +class HierarchicalOutputDirectoryProvider implements OutputDirectoryProvider { + + 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 Path rootDir; + + HierarchicalOutputDirectoryProvider(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(HierarchicalOutputDirectoryProvider::toSanitizedPath) // + .reduce(toSanitizedPath(segments.get(0)), Path::resolve); + return Files.createDirectories(getRootDirectory().resolve(relativePath)); + } + + @Override + public synchronized Path getRootDirectory() { + if (rootDir == null) { + rootDir = rootDirSupplier.get(); + } + return rootDir; + } + + private static Path toSanitizedPath(Segment segment) { + return Paths.get(sanitizeName(segment.getValue())); + } + + private static String sanitizeName(String value) { + return FORBIDDEN_CHARS.matcher(value).replaceAll(REPLACEMENT); + } +} diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InternalTestPlan.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InternalTestPlan.java index 9195eaef2e97..3297909c09ae 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InternalTestPlan.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InternalTestPlan.java @@ -31,12 +31,12 @@ class InternalTestPlan extends TestPlan { static InternalTestPlan from(LauncherDiscoveryResult discoveryResult) { TestPlan delegate = TestPlan.from(discoveryResult.getEngineTestDescriptors(), - discoveryResult.getConfigurationParameters()); + discoveryResult.getConfigurationParameters(), discoveryResult.getOutputDirectoryProvider()); return new InternalTestPlan(discoveryResult, delegate); } private InternalTestPlan(LauncherDiscoveryResult discoveryResult, TestPlan delegate) { - super(delegate.containsTests(), delegate.getConfigurationParameters()); + super(delegate.containsTests(), delegate.getConfigurationParameters(), delegate.getOutputDirectoryProvider()); this.discoveryResult = discoveryResult; this.delegate = delegate; } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java index cb480d2533e3..0d62e9a3b5c2 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java @@ -11,7 +11,9 @@ 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.STABLE; +import static org.junit.platform.launcher.LauncherConstants.OUTPUT_DIR_PROPERTY_NAME; import java.util.ArrayList; import java.util.Arrays; @@ -27,11 +29,14 @@ import org.junit.platform.engine.DiscoveryFilter; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.Filter; +import org.junit.platform.engine.reporting.OutputDirectoryProvider; 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; /** @@ -107,6 +112,7 @@ public final class LauncherDiscoveryRequestBuilder { private final List discoveryListeners = new ArrayList<>(); private boolean implicitConfigurationParametersEnabled = true; private ConfigurationParameters parentConfigurationParameters; + private OutputDirectoryProvider outputDirectoryProvider; /** * Create a new {@code LauncherDiscoveryRequestBuilder}. @@ -283,6 +289,27 @@ public LauncherDiscoveryRequestBuilder listeners(LauncherDiscoveryListener... li return this; } + /** + * Set the {@link 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 OutputDirectoryProvider + * @see LauncherConstants#OUTPUT_DIR_PROPERTY_NAME + */ + @API(status = EXPERIMENTAL, since = "1.12") + public LauncherDiscoveryRequestBuilder outputDirectoryProvider(OutputDirectoryProvider outputDirectoryProvider) { + this.outputDirectoryProvider = Preconditions.notNull(outputDirectoryProvider, + "outputDirectoryProvider must not be null"); + return this; + } + private void storeFilter(Filter filter) { if (filter instanceof EngineFilter) { this.engineFilters.add((EngineFilter) filter); @@ -307,8 +334,18 @@ else if (filter instanceof DiscoveryFilter) { public LauncherDiscoveryRequest build() { LauncherConfigurationParameters launcherConfigurationParameters = buildLauncherConfigurationParameters(); LauncherDiscoveryListener discoveryListener = getLauncherDiscoveryListener(launcherConfigurationParameters); + OutputDirectoryProvider outputDirectoryProvider = getOutputDirectoryProvider(launcherConfigurationParameters); return new DefaultDiscoveryRequest(this.selectors, this.engineFilters, this.discoveryFilters, - this.postDiscoveryFilters, launcherConfigurationParameters, discoveryListener); + this.postDiscoveryFilters, launcherConfigurationParameters, discoveryListener, outputDirectoryProvider); + } + + private OutputDirectoryProvider getOutputDirectoryProvider( + LauncherConfigurationParameters configurationParameters) { + if (this.outputDirectoryProvider != null) { + return this.outputDirectoryProvider; + } + return new HierarchicalOutputDirectoryProvider( + () -> OutputDir.create(configurationParameters.get(OUTPUT_DIR_PROPERTY_NAME)).toPath()); } private LauncherConfigurationParameters buildLauncherConfigurationParameters() { diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryResult.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryResult.java index d65b4c3fe4d1..af4c75db0e67 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryResult.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryResult.java @@ -23,6 +23,7 @@ import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestEngine; +import org.junit.platform.engine.reporting.OutputDirectoryProvider; /** * Represents the result of test discovery of the configured @@ -35,11 +36,13 @@ public class LauncherDiscoveryResult { private final Map testEngineDescriptors; private final ConfigurationParameters configurationParameters; + private final OutputDirectoryProvider outputDirectoryProvider; LauncherDiscoveryResult(Map testEngineDescriptors, - ConfigurationParameters configurationParameters) { + ConfigurationParameters configurationParameters, OutputDirectoryProvider outputDirectoryProvider) { this.testEngineDescriptors = unmodifiableMap(new LinkedHashMap<>(testEngineDescriptors)); this.configurationParameters = configurationParameters; + this.outputDirectoryProvider = outputDirectoryProvider; } public TestDescriptor getEngineTestDescriptor(TestEngine testEngine) { @@ -47,7 +50,11 @@ public TestDescriptor getEngineTestDescriptor(TestEngine testEngine) { } ConfigurationParameters getConfigurationParameters() { - return configurationParameters; + return this.configurationParameters; + } + + OutputDirectoryProvider getOutputDirectoryProvider() { + return this.outputDirectoryProvider; } public Collection getTestEngines() { @@ -60,15 +67,16 @@ Collection getEngineTestDescriptors() { public LauncherDiscoveryResult withRetainedEngines(Predicate predicate) { Map prunedTestEngineDescriptors = retainEngines(predicate); - if (prunedTestEngineDescriptors.size() < testEngineDescriptors.size()) { - return new LauncherDiscoveryResult(prunedTestEngineDescriptors, configurationParameters); + if (prunedTestEngineDescriptors.size() < this.testEngineDescriptors.size()) { + return new LauncherDiscoveryResult(prunedTestEngineDescriptors, this.configurationParameters, + this.outputDirectoryProvider); } return this; } private Map retainEngines(Predicate predicate) { // @formatter:off - return testEngineDescriptors.entrySet() + return this.testEngineDescriptors.entrySet() .stream() .filter(entry -> predicate.test(entry.getValue())) .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/OutputDir.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/OutputDir.java index 31de20e55175..389a83083a7c 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/OutputDir.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/OutputDir.java @@ -11,15 +11,20 @@ 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.Paths; +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; @@ -27,9 +32,16 @@ @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, () -> Paths.get(".")); + } + + static OutputDir create(Optional customDir, Supplier currentWorkingDir) { try { - return createSafely(customDir, () -> Paths.get(".").toAbsolutePath()); + return createSafely(customDir, currentWorkingDir); } catch (IOException e) { throw new UncheckedIOException("Failed to create output dir", e); @@ -40,11 +52,21 @@ public static OutputDir create(Optional customDir) { * Package private for testing purposes. */ static OutputDir createSafely(Optional customDir, Supplier currentWorkingDir) throws IOException { - Path cwd = currentWorkingDir.get(); + 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(customDir.get()); + String customPath = customDir.get(); + while (customPath.contains(OUTPUT_DIR_UNIQUE_NUMBER_PLACEHOLDER)) { + customPath = OUTPUT_DIR_UNIQUE_NUMBER_PLACEHOLDER_PATTERN.matcher(customPath) // + .replaceFirst(String.valueOf(Math.abs(random.nextLong()))); + } + outputDir = cwd.resolve(customPath); } else if (Files.exists(cwd.resolve("pom.xml"))) { outputDir = cwd.resolve("target"); @@ -60,13 +82,15 @@ else if (containsFilesWithExtensions(cwd, ".gradle", ".gradle.kts")) { Files.createDirectories(outputDir); } - return new OutputDir(outputDir); + return new OutputDir(outputDir.normalize(), random); } private final Path path; + private final SecureRandom random; - private OutputDir(Path path) { + private OutputDir(Path path, SecureRandom random) { this.path = path; + this.random = random; } public Path toPath() { @@ -74,7 +98,7 @@ public Path toPath() { } public Path createFile(String prefix, String extension) throws UncheckedIOException { - String filename = String.format("%s-%d.%s", prefix, Math.abs(new SecureRandom().nextLong()), extension); + String filename = String.format("%s-%d.%s", prefix, Math.abs(random.nextLong()), extension); Path outputFile = path.resolve(filename); try { @@ -93,18 +117,18 @@ public Path createFile(String prefix, String extension) throws UncheckedIOExcept * supplied extensions. */ private static boolean containsFilesWithExtensions(Path dir, String... extensions) throws IOException { - return Files.find(dir, 1, // - (path, basicFileAttributes) -> { - if (basicFileAttributes.isRegularFile()) { - for (String extension : extensions) { - if (path.getFileName().toString().endsWith(extension)) { - return true; - } + BiPredicate matcher = (path, basicFileAttributes) -> { + if (basicFileAttributes.isRegularFile()) { + for (String extension : extensions) { + if (path.getFileName().toString().endsWith(extension)) { + return true; } } - return false; - }) // - .findFirst() // - .isPresent(); + } + return false; + }; + try (Stream pathStream = Files.find(dir, 1, matcher)) { + return pathStream.findFirst().isPresent(); + } } } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListener.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListener.java index a7a02b662731..79e49e28c5ee 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListener.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListener.java @@ -17,8 +17,10 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; +import java.util.function.Supplier; import org.apiguardian.api.API; import org.junit.platform.commons.logging.Logger; @@ -125,6 +127,8 @@ public class UniqueIdTrackingListener implements TestExecutionListener { */ 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<>(); @@ -201,7 +205,9 @@ public void testPlanExecutionFinished(TestPlan testPlan) { private Path createOutputFile(ConfigurationParameters configurationParameters) { String prefix = configurationParameters.get(OUTPUT_FILE_PREFIX_PROPERTY_NAME) // .orElse(DEFAULT_OUTPUT_FILE_PREFIX); - return OutputDir.create(configurationParameters.get(OUTPUT_DIR_PROPERTY_NAME)) // + Supplier workingDirSupplier = () -> configurationParameters.get(WORKING_DIR_PROPERTY_NAME).map( + Paths::get).orElseGet(() -> Paths.get(".")); + return OutputDir.create(configurationParameters.get(OUTPUT_DIR_PROPERTY_NAME), workingDirSupplier) // .createFile(prefix, "txt"); } diff --git a/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/OutputDirectoryProviders.java b/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/OutputDirectoryProviders.java new file mode 100644 index 000000000000..408d0740ce6a --- /dev/null +++ b/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/OutputDirectoryProviders.java @@ -0,0 +1,29 @@ +/* + * Copyright 2015-2024 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse 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.reporting.OutputDirectoryProvider; + +public class OutputDirectoryProviders { + + public static OutputDirectoryProvider dummyOutputDirectoryProvider() { + return new HierarchicalOutputDirectoryProvider(() -> { + throw new JUnitException("This should not be called; use a real provider instead"); + }); + } + + public static OutputDirectoryProvider hierarchicalOutputDirectoryProvider(Path rootDir) { + return new HierarchicalOutputDirectoryProvider(() -> rootDir); + } +} diff --git a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListener.java b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListener.java index 9381776a0d40..ba18a68c128b 100644 --- a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListener.java +++ b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListener.java @@ -19,6 +19,7 @@ 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; @@ -57,6 +58,7 @@ import java.net.UnknownHostException; import java.nio.charset.Charset; import java.nio.file.Path; +import java.nio.file.Paths; import java.time.Instant; import java.util.Map; import java.util.Optional; @@ -73,6 +75,7 @@ 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; @@ -85,7 +88,6 @@ import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; -import org.junit.platform.launcher.listeners.OutputDir; import org.opentest4j.reporting.events.api.DocumentWriter; import org.opentest4j.reporting.events.api.NamespaceRegistry; import org.opentest4j.reporting.events.core.Infrastructure; @@ -103,13 +105,20 @@ public class OpenTestReportGeneratingListener implements TestExecutionListener { static final String ENABLED_PROPERTY_NAME = "junit.platform.reporting.open.xml.enabled"; - static final String OUTPUT_DIR_PROPERTY_NAME = "junit.platform.reporting.output.dir"; private final AtomicInteger idCounter = new AtomicInteger(); private final Map inProgressIds = new ConcurrentHashMap<>(); private DocumentWriter eventsFileWriter = DocumentWriter.noop(); + private final Path workingDir; + private Path outputDir; + @SuppressWarnings("unused") // Used via ServiceLoader public OpenTestReportGeneratingListener() { + this(Paths.get(".").toAbsolutePath()); + } + + OpenTestReportGeneratingListener(Path workingDir) { + this.workingDir = workingDir; } @Override @@ -123,8 +132,8 @@ public void testPlanExecutionStarted(TestPlan testPlan) { .add("junit", JUnitFactory.NAMESPACE, "https://junit.org/junit5/schemas/open-test-reporting/junit-1.9.xsd") // .build(); - Path eventsXml = OutputDir.create(config.get(OUTPUT_DIR_PROPERTY_NAME)) // - .createFile("junit-platform-events", "xml"); + outputDir = testPlan.getOutputDirectoryProvider().getRootDirectory(); + Path eventsXml = outputDir.resolve("open-test-report.xml"); try { eventsFileWriter = Events.createDocumentWriter(namespaceRegistry, eventsXml); reportInfrastructure(); @@ -159,7 +168,7 @@ private void reportInfrastructure() { }); } - private static void addGitInfo(Infrastructure infrastructure) { + private void addGitInfo(Infrastructure infrastructure) { boolean gitInstalled = exec("git", "--version").isPresent(); if (gitInstalled) { exec("git", "config", "--get", "remote.origin.url") // @@ -178,7 +187,7 @@ private static void addGitInfo(Infrastructure infrastructure) { } } - static Optional exec(String... args) { + Optional exec(String... args) { Process process = startProcess(args); @@ -210,10 +219,10 @@ private static BufferedReader newBufferedReader(InputStream stream) { return new BufferedReader(new InputStreamReader(stream, Charset.defaultCharset())); } - private static Process startProcess(String[] args) { + private Process startProcess(String[] command) { Process process; try { - process = Runtime.getRuntime().exec(args); + process = new ProcessBuilder().directory(workingDir.toFile()).command(command).start(); } catch (IOException e) { throw new UncheckedIOException("Failed to start process", e); @@ -340,6 +349,14 @@ public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry e data -> entry.getKeyValuePairs().forEach(data::addEntry)))); } + @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(outputDir.relativize(entry.getFile()).toString())))); + } + @Override public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { String id = inProgressIds.remove(testIdentifier.getUniqueIdObject()); diff --git a/junit-platform-reporting/src/testFixtures/java/org/junit/platform/reporting/testutil/FileUtils.java b/junit-platform-reporting/src/testFixtures/java/org/junit/platform/reporting/testutil/FileUtils.java new file mode 100644 index 000000000000..503e792dafa6 --- /dev/null +++ b/junit-platform-reporting/src/testFixtures/java/org/junit/platform/reporting/testutil/FileUtils.java @@ -0,0 +1,31 @@ +/* + * Copyright 2015-2024 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse 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); + } + } +} diff --git a/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatformRunnerListener.java b/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatformRunnerListener.java index e91045ee8422..2a51109e17c2 100644 --- a/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatformRunnerListener.java +++ b/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatformRunnerListener.java @@ -16,6 +16,7 @@ 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.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; @@ -85,6 +86,11 @@ public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry e System.out.println(entry); } + @Override + public void fileEntryPublished(TestIdentifier testIdentifier, FileEntry file) { + System.out.println(file); + } + private Failure toFailure(TestExecutionResult testExecutionResult, Description description) { return new Failure(description, testExecutionResult.getThrowable().orElse(null)); } diff --git a/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilder.java b/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilder.java index 610bc0524e8d..56fd410ce06e 100644 --- a/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilder.java +++ b/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilder.java @@ -41,6 +41,7 @@ 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.engine.reporting.OutputDirectoryProvider; import org.junit.platform.launcher.EngineFilter; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.TagFilter; @@ -261,6 +262,12 @@ public SuiteLauncherDiscoveryRequestBuilder enableImplicitConfigurationParameter return this; } + public SuiteLauncherDiscoveryRequestBuilder outputDirectoryProvider( + OutputDirectoryProvider outputDirectoryProvider) { + delegate.outputDirectoryProvider(outputDirectoryProvider); + return this; + } + /** * Apply a suite's annotation-based configuration, selectors, and filters to * this builder. diff --git a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/ClassSelectorResolver.java b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/ClassSelectorResolver.java index ce9107310ecf..6e7dfffe05a9 100644 --- a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/ClassSelectorResolver.java +++ b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/ClassSelectorResolver.java @@ -25,6 +25,7 @@ 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.reporting.OutputDirectoryProvider; import org.junit.platform.engine.support.discovery.SelectorResolver; /** @@ -39,12 +40,14 @@ final class ClassSelectorResolver implements SelectorResolver { private final Predicate classNameFilter; private final SuiteEngineDescriptor suiteEngineDescriptor; private final ConfigurationParameters configurationParameters; + private final OutputDirectoryProvider outputDirectoryProvider; ClassSelectorResolver(Predicate classNameFilter, SuiteEngineDescriptor suiteEngineDescriptor, - ConfigurationParameters configurationParameters) { + ConfigurationParameters configurationParameters, OutputDirectoryProvider outputDirectoryProvider) { this.classNameFilter = classNameFilter; this.suiteEngineDescriptor = suiteEngineDescriptor; this.configurationParameters = configurationParameters; + this.outputDirectoryProvider = outputDirectoryProvider; } @Override @@ -103,7 +106,7 @@ private Optional newSuiteDescriptor(Class suiteClass, Te return Optional.empty(); } - return Optional.of(new SuiteTestDescriptor(id, suiteClass, configurationParameters)); + return Optional.of(new SuiteTestDescriptor(id, suiteClass, configurationParameters, outputDirectoryProvider)); } private static boolean containsCycle(UniqueId id) { diff --git a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/DiscoverySelectorResolver.java b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/DiscoverySelectorResolver.java index 15d31d65ccc2..8fc2bbeef786 100644 --- a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/DiscoverySelectorResolver.java +++ b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/DiscoverySelectorResolver.java @@ -25,7 +25,8 @@ final class DiscoverySelectorResolver { .addSelectorResolver(context -> new ClassSelectorResolver( context.getClassNameFilter(), context.getEngineDescriptor(), - context.getDiscoveryRequest().getConfigurationParameters())) + context.getDiscoveryRequest().getConfigurationParameters(), + context.getDiscoveryRequest().getOutputDirectoryProvider())) .build(); // @formatter:on diff --git a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestDescriptor.java b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestDescriptor.java index ded5d53aebc2..779c2a03bc3a 100644 --- a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestDescriptor.java +++ b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestDescriptor.java @@ -26,6 +26,7 @@ import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.discovery.DiscoverySelectors; +import org.junit.platform.engine.reporting.OutputDirectoryProvider; import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.platform.engine.support.hierarchical.OpenTest4JAwareThrowableCollector; @@ -53,15 +54,18 @@ final class SuiteTestDescriptor extends AbstractTestDescriptor { private final SuiteLauncherDiscoveryRequestBuilder discoveryRequestBuilder = request(); private final ConfigurationParameters configurationParameters; + private final OutputDirectoryProvider outputDirectoryProvider; private final Boolean failIfNoTests; private final Class suiteClass; private LauncherDiscoveryResult launcherDiscoveryResult; private SuiteLauncher launcher; - SuiteTestDescriptor(UniqueId id, Class suiteClass, ConfigurationParameters configurationParameters) { + SuiteTestDescriptor(UniqueId id, Class suiteClass, ConfigurationParameters configurationParameters, + OutputDirectoryProvider outputDirectoryProvider) { super(id, getSuiteDisplayName(suiteClass), ClassSource.from(suiteClass)); this.configurationParameters = configurationParameters; + this.outputDirectoryProvider = outputDirectoryProvider; this.failIfNoTests = getFailIfNoTests(suiteClass); this.suiteClass = suiteClass; } @@ -99,6 +103,7 @@ void discover() { .enableImplicitConfigurationParameters(false) .parentConfigurationParameters(configurationParameters) .applyConfigurationParametersFromSuite(suiteClass) + .outputDirectoryProvider(outputDirectoryProvider) .build(); // @formatter:on this.launcher = SuiteLauncher.create(); diff --git a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java index 23884df7577b..0d2b634bb01e 100644 --- a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java +++ b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java @@ -13,15 +13,18 @@ import static java.util.Collections.emptySet; import static java.util.Collections.singleton; 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.core.EngineDiscoveryOrchestrator.Phase.EXECUTION; +import java.nio.file.Path; import java.util.Map; import java.util.ServiceLoader; import java.util.stream.Stream; import org.apiguardian.api.API; +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; @@ -34,6 +37,7 @@ import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.OutputDirectoryProvider; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.core.EngineDiscoveryOrchestrator; import org.junit.platform.launcher.core.EngineExecutionOrchestrator; @@ -249,7 +253,7 @@ private static void executeDirectly(TestEngine testEngine, EngineDiscoveryReques EngineExecutionListener listener) { UniqueId engineUniqueId = UniqueId.forEngine(testEngine.getId()); TestDescriptor engineTestDescriptor = testEngine.discover(discoveryRequest, engineUniqueId); - ExecutionRequest request = new ExecutionRequest(engineTestDescriptor, listener, + ExecutionRequest request = ExecutionRequest.create(engineTestDescriptor, listener, discoveryRequest.getConfigurationParameters()); testEngine.execute(request); } @@ -295,7 +299,8 @@ private EngineTestKit() { public static final class Builder { private final LauncherDiscoveryRequestBuilder requestBuilder = LauncherDiscoveryRequestBuilder.request() // - .enableImplicitConfigurationParameters(false); + .enableImplicitConfigurationParameters(false) // + .outputDirectoryProvider(DisabledOutputDirectoryProvider.INSTANCE); private final TestEngine testEngine; private Builder(TestEngine testEngine) { @@ -422,6 +427,25 @@ public Builder enableImplicitConfigurationParameters(boolean enabled) { return this; } + /** + * Set the {@link 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 OutputDirectoryProvider + */ + @API(status = EXPERIMENTAL, since = "1.12") + public Builder outputDirectoryProvider(OutputDirectoryProvider outputDirectoryProvider) { + this.requestBuilder.outputDirectoryProvider(outputDirectoryProvider); + return this; + } + /** * Execute tests for the configured {@link TestEngine}, * {@linkplain DiscoverySelector discovery selectors}, @@ -441,6 +465,27 @@ public EngineExecutionResults execute() { return executionRecorder.getExecutionResults(); } + private static class DisabledOutputDirectoryProvider implements OutputDirectoryProvider { + + public static final OutputDirectoryProvider INSTANCE = new DisabledOutputDirectoryProvider(); + + private static final String FAILURE_MESSAGE = "Writing outputs is disabled by default when using EngineTestKit. " + + "To enable, configure a custom OutputDirectoryProvider via EngineTestKit#outputDirectoryProvider."; + + private DisabledOutputDirectoryProvider() { + } + + @Override + public Path getRootDirectory() { + throw new JUnitException(FAILURE_MESSAGE); + } + + @Override + public Path createOutputDirectory(TestDescriptor testDescriptor) { + throw new JUnitException(FAILURE_MESSAGE); + } + + } } } diff --git a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Event.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Event.java index 190a7aaf5510..08069dfeea17 100644 --- a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Event.java +++ b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Event.java @@ -10,6 +10,7 @@ package org.junit.platform.testkit.engine; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.junit.platform.commons.util.FunctionUtils.where; @@ -22,6 +23,7 @@ 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; /** @@ -51,6 +53,23 @@ public static Event reportingEntryPublished(TestDescriptor testDescriptor, Repor 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 = EXPERIMENTAL, since = "1.12") + 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}. diff --git a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventConditions.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventConditions.java index 3431252cbef2..339321e3c5b5 100644 --- a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventConditions.java +++ b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventConditions.java @@ -12,6 +12,7 @@ import static java.util.function.Predicate.isEqual; import static java.util.stream.Collectors.toList; +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; @@ -27,6 +28,7 @@ import static org.junit.platform.testkit.engine.EventType.SKIPPED; import static org.junit.platform.testkit.engine.EventType.STARTED; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -42,6 +44,7 @@ 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; @@ -470,4 +473,18 @@ public static Condition reportEntry(Map 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 = EXPERIMENTAL, since = "1.12") + public static Condition fileEntry(Predicate filePredicate) { + return new Condition<>(byPayload(FileEntry.class, it -> filePredicate.test(it.getFile())), + "event for file entry with custom predicate"); + } + } diff --git a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventStatistics.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventStatistics.java index bd1cf3e06262..f052d2f2b486 100644 --- a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventStatistics.java +++ b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventStatistics.java @@ -10,6 +10,7 @@ package org.junit.platform.testkit.engine; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.junit.platform.testkit.engine.Assertions.assertEquals; @@ -127,6 +128,20 @@ public EventStatistics reportingEntryPublished(long expected) { 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 = EXPERIMENTAL, since = "1.12") + 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. * diff --git a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventType.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventType.java index 09a6bdb59833..7bb029ad9929 100644 --- a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventType.java +++ b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventType.java @@ -10,11 +10,13 @@ package org.junit.platform.testkit.engine; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; 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; /** @@ -60,6 +62,15 @@ public enum EventType { * * @see org.junit.platform.engine.EngineExecutionListener#reportingEntryPublished(TestDescriptor, ReportEntry) */ - REPORTING_ENTRY_PUBLISHED + 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 = EXPERIMENTAL, since = "1.12") + FILE_ENTRY_PUBLISHED } diff --git a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Events.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Events.java index 256ea9f2d2d3..9f688eb9d763 100644 --- a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Events.java +++ b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Events.java @@ -13,6 +13,7 @@ import static java.util.Collections.sort; import static java.util.function.Predicate.isEqual; import static java.util.stream.Collectors.toList; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; 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; @@ -207,6 +208,18 @@ public Events reportingEntryPublished() { 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 = EXPERIMENTAL, since = "1.12") + 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. diff --git a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/ExecutionRecorder.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/ExecutionRecorder.java index c7f42f5693af..1f375f3a4e33 100644 --- a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/ExecutionRecorder.java +++ b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/ExecutionRecorder.java @@ -10,6 +10,7 @@ package org.junit.platform.testkit.engine; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import java.util.List; @@ -19,6 +20,7 @@ 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; /** @@ -82,6 +84,17 @@ public void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry e this.events.add(Event.reportingEntryPublished(testDescriptor, entry)); } + /** + * Record an {@link Event} for a published {@link FileEntry}. + * + * @since 1.12 + */ + @API(status = EXPERIMENTAL, since = "1.12") + @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}. * diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java index dee38a4fec62..531016e16b4f 100644 --- a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java +++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java @@ -46,7 +46,6 @@ import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; import org.junit.platform.testkit.engine.EngineExecutionResults; @@ -397,14 +396,6 @@ public void executionSkipped(TestDescriptor testDescriptor, String reason) { PlainJUnit4TestCaseWithLifecycleMethods.EVENTS.add( "executionSkipped:" + testDescriptor.getDisplayName()); } - - @Override - public void dynamicTestRegistered(TestDescriptor testDescriptor) { - } - - @Override - public void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry entry) { - } }; execute(testClass, listener); @@ -933,7 +924,7 @@ private static void execute(Class testClass, EngineExecutionListener listener var discoveryRequest = request(testClass); var engineTestDescriptor = testEngine.discover(discoveryRequest, UniqueId.forEngine(testEngine.getId())); testEngine.execute( - new ExecutionRequest(engineTestDescriptor, listener, discoveryRequest.getConfigurationParameters())); + ExecutionRequest.create(engineTestDescriptor, listener, discoveryRequest.getConfigurationParameters())); } private static LauncherDiscoveryRequest request(Class testClass) { diff --git a/jupiter-tests/jupiter-tests.gradle.kts b/jupiter-tests/jupiter-tests.gradle.kts index a6557280218c..061920f4d96d 100644 --- a/jupiter-tests/jupiter-tests.gradle.kts +++ b/jupiter-tests/jupiter-tests.gradle.kts @@ -23,6 +23,7 @@ dependencies { testImplementation(libs.memoryfilesystem) testImplementation(testFixtures(projects.junitJupiterApi)) testImplementation(testFixtures(projects.junitJupiterEngine)) + testImplementation(testFixtures(projects.junitPlatformLauncher)) testImplementation(testFixtures(projects.junitPlatformReporting)) } diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/LockTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/LockTests.java similarity index 86% rename from platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/LockTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/LockTests.java index c0cc1d0c3a7c..3c61edfeecc6 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/LockTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/LockTests.java @@ -8,22 +8,22 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.engine.support.descriptor; +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 static org.junit.jupiter.api.parallel.ResourceLocksProvider.Lock; import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; /** * Unit tests for {@link Lock}. * * @since 5.12 */ -class LockTests extends AbstractEqualsAndHashCodeTests { +class LockTests { @Test void readWriteModeSetByDefault() { diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ResourceLockAnnotationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java similarity index 59% rename from platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ResourceLockAnnotationTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java index e5f3d65fb099..16af5783e41c 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ResourceLockAnnotationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java @@ -8,37 +8,48 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.engine.support.descriptor; +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.engine.support.hierarchical.ExclusiveResource.LockMode; +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.Set; +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.RepeatedTest; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.ExecutionMode; -import org.junit.jupiter.api.parallel.ResourceAccessMode; -import org.junit.jupiter.api.parallel.ResourceLock; -import org.junit.jupiter.api.parallel.ResourceLocksProvider; +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.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.TestExecutionResult; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.hierarchical.ExclusiveResource; +import org.junit.platform.testkit.engine.Event; /** * Integration tests for {@link ResourceLock} and {@link ResourceLocksProvider}. * * @since 5.12 */ -class ResourceLockAnnotationTests { +class ResourceLockAnnotationTests extends AbstractJupiterTestEngineTests { private static final UniqueId uniqueId = UniqueId.root("enigma", "foo"); @@ -64,7 +75,6 @@ void noSharedResources() { assertThat(methodResources).isEmpty(); var nestedClassResources = getNestedClassResources( - NoSharedResourcesTestCase.class, NoSharedResourcesTestCase.NestedClass.class ); assertThat(nestedClassResources).isEmpty(); @@ -86,16 +96,23 @@ void addSharedResourcesViaAnnotationValue() { 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.class, SharedResourcesViaAnnotationValueTestCase.NestedClass.class ); assertThat(nestedClassResources).containsExactlyInAnyOrder( - new ExclusiveResource("c1", LockMode.READ), + 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 @@ -121,7 +138,6 @@ void addSharedResourcesViaAnnotationProviders() { ); var nestedClassResources = getNestedClassResources( - SharedResourcesViaAnnotationProvidersTestCase.class, SharedResourcesViaAnnotationProvidersTestCase.NestedClass.class ); assertThat(nestedClassResources).containsExactlyInAnyOrder( @@ -139,22 +155,23 @@ void addSharedResourcesViaAnnotationValueAndProviders() { ); assertThat(classResources).containsExactlyInAnyOrder( new ExclusiveResource("a1", LockMode.READ_WRITE), - new ExclusiveResource("a2", LockMode.READ) + 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.class, 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) @@ -162,6 +179,52 @@ void addSharedResourcesViaAnnotationValueAndProviders() { // @formatter:on } + @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 @@ -176,7 +239,6 @@ void emptyAnnotation() { assertThat(methodResources).isEmpty(); var nestedClassResources = getNestedClassResources( - EmptyAnnotationTestCase.class, EmptyAnnotationTestCase.NestedClass.class ); assertThat(nestedClassResources).isEmpty(); @@ -192,26 +254,42 @@ private ClassTestDescriptor getClassTestDescriptor(Class testClass) { } private Set getMethodResources(Class testClass) { + var descriptor = new TestMethodTestDescriptor( // + uniqueId, testClass, getDeclaredTestMethod(testClass), configuration // + ); + descriptor.setParent(getClassTestDescriptor(testClass)); + return descriptor.getExclusiveResources(); + } + + private static Method getDeclaredTestMethod(Class testClass) { try { - // @formatter:off - var descriptor = new TestMethodTestDescriptor( - uniqueId, testClass, testClass.getDeclaredMethod("test"), configuration - ); - // @formatter:on - descriptor.setParent(getClassTestDescriptor(testClass)); - return descriptor.getExclusiveResources(); + return testClass.getDeclaredMethod("test"); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } } - private Set getNestedClassResources(Class testClass, Class nestedClass) { - var descriptor = new NestedClassTestDescriptor(uniqueId, nestedClass, configuration); - descriptor.setParent(getClassTestDescriptor(testClass)); + private Set getNestedClassResources(Class testClass) { + var descriptor = new NestedClassTestDescriptor(uniqueId, testClass, 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 + } + // ------------------------------------------------------------------------- @SuppressWarnings("JUnitMalformedDeclaration") @@ -229,18 +307,23 @@ class NestedClass { @SuppressWarnings("JUnitMalformedDeclaration") @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("b2") + @ResourceLock(value = "b2", target = ResourceLockTarget.SELF) void test() { } @Nested @ResourceLock(value = "c1", mode = ResourceAccessMode.READ) - @ResourceLock(value = "c2", mode = ResourceAccessMode.READ) + @ResourceLock(value = "c2", mode = ResourceAccessMode.READ, target = ResourceLockTarget.CHILDREN) class NestedClass { + + @Test + void test() { + } } } @@ -302,8 +385,12 @@ public Set provideForNestedClass(Class testClass) { @SuppressWarnings("JUnitMalformedDeclaration") @ResourceLock( // value = "a1", // - mode = ResourceAccessMode.READ_WRITE, // - providers = SharedResourcesViaAnnotationValueAndProvidersTestCase.ClassLevelProvider.class // + providers = SharedResourcesViaAnnotationValueAndProvidersTestCase.FirstClassLevelProvider.class // + ) + @ResourceLock( // + value = "a2", // + target = ResourceLockTarget.CHILDREN, // + providers = SharedResourcesViaAnnotationValueAndProvidersTestCase.SecondClassLevelProvider.class // ) static class SharedResourcesViaAnnotationValueAndProvidersTestCase { @@ -318,12 +405,15 @@ void test() { class NestedClass { } - static class ClassLevelProvider implements ResourceLocksProvider { + static class FirstClassLevelProvider implements ResourceLocksProvider { @Override public Set provideForClass(Class testClass) { - return Set.of(new Lock("a2", ResourceAccessMode.READ)); + return Set.of(new Lock("a3", ResourceAccessMode.READ)); } + } + + static class SecondClassLevelProvider implements ResourceLocksProvider { @Override public Set provideForMethod(Class testClass, Method testMethod) { @@ -346,51 +436,85 @@ public Set provideForNestedClass(Class testClass) { } @SuppressWarnings("JUnitMalformedDeclaration") - @ResourceLock - static class EmptyAnnotationTestCase { + @ResourceLock( // + value = "a1", // + target = ResourceLockTarget.CHILDREN, // + providers = SharedResourcesHavingTheSameValueAndModeAreDeduplicatedTestCase.Provider.class // + ) + static class SharedResourcesHavingTheSameValueAndModeAreDeduplicatedTestCase { @Test - @ResourceLock + @ResourceLock(value = "a1") void test() { } - @Nested - @ResourceLock - class NestedClass { + static class Provider implements ResourceLocksProvider { + + @Override + public Set provideForMethod(Class testClass, Method testMethod) { + return Set.of(new Lock("a1")); + } } } - static class NestedNestedTestCase { + @SuppressWarnings("JUnitMalformedDeclaration") + @ResourceLock(value = "a1", mode = ResourceAccessMode.READ_WRITE, target = ResourceLockTarget.CHILDREN) + static class SharedResourcesHavingTheSameValueButDifferentModeAreNotDeduplicatedTestCase { - @SuppressWarnings("JUnitMalformedDeclaration") - @Nested - @ResourceLock(providers = NestedNestedTestCase.Provider.class) - static class NestedClass { + @Test + @ResourceLock(value = "a1", mode = ResourceAccessMode.READ) + void test() { + } + } - @Nested - class NestedClassTwo { + @SuppressWarnings("JUnitMalformedDeclaration") + static class TestCanNotDeclareSharedResourcesForChildrenTestCase { - @Test - void test() { - } - } + @Test + @ResourceLock(value = "a1", target = ResourceLockTarget.CHILDREN) + void test() { } + } - static class Provider implements ResourceLocksProvider { - @Override - public Set provideForClass(Class testClass) { - return ResourceLocksProvider.super.provideForClass(testClass); - } + static class ParameterizedTestCanNotDeclareSharedResourcesForChildrenTestCase { - @Override - public Set provideForNestedClass(Class testClass) { - return ResourceLocksProvider.super.provideForNestedClass(testClass); - } + @ParameterizedTest + @ValueSource(ints = { 1, 2, 3 }) + @ResourceLock(value = "a1", target = ResourceLockTarget.CHILDREN) + void test() { + } + } - @Override - public Set provideForMethod(Class testClass, Method testMethod) { - return ResourceLocksProvider.super.provideForMethod(testClass, testMethod); - } + 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", () -> { + })); + } + } + + @SuppressWarnings("JUnitMalformedDeclaration") + @ResourceLock + static class EmptyAnnotationTestCase { + + @Test + @ResourceLock + void test() { + } + + @Nested + @ResourceLock + class NestedClass { } } diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ResourceLocksProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLocksProviderTests.java similarity index 92% rename from platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ResourceLocksProviderTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLocksProviderTests.java index 3b60bc75569a..4fcf6a5012ad 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ResourceLocksProviderTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLocksProviderTests.java @@ -8,14 +8,13 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.engine.support.descriptor; +package org.junit.jupiter.api.parallel; import static java.util.Collections.emptySet; 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.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.test; @@ -27,10 +26,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.ResourceLock; -import org.junit.jupiter.api.parallel.ResourceLocksProvider; -import org.junit.platform.engine.discovery.DiscoverySelectors; -import org.junit.platform.testkit.engine.EngineTestKit; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.platform.testkit.engine.Event; /** @@ -38,7 +34,7 @@ * * @since 5.12 */ -class ResourceLocksProviderTests { +class ResourceLocksProviderTests extends AbstractJupiterTestEngineTests { @Test void classLevelProvider() { @@ -65,14 +61,7 @@ void methodLevelProviderInNestedClass() { } private Stream execute(Class testCase) { - // @formatter:off - var discoveryRequest = request() - .selectors(Stream.of(testCase).map(DiscoverySelectors::selectClass).toList()) - .build(); - return EngineTestKit.execute("junit-jupiter", discoveryRequest) - .allEvents() - .stream(); - // @formatter:on + return executeTestsForClass(testCase).allEvents().stream(); } // ------------------------------------------------------------------------- diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java index 209f07cc4767..b868af74f87f 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java @@ -14,6 +14,7 @@ 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 static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider; import java.util.Set; @@ -39,7 +40,7 @@ protected EngineExecutionResults executeTestsForClass(Class testClass) { } protected EngineExecutionResults executeTests(DiscoverySelector... selectors) { - return executeTests(request().selectors(selectors)); + return executeTests(request().selectors(selectors).outputDirectoryProvider(dummyOutputDirectoryProvider())); } protected EngineExecutionResults executeTests(LauncherDiscoveryRequestBuilder builder) { @@ -51,7 +52,8 @@ protected EngineExecutionResults executeTests(LauncherDiscoveryRequest request) } protected TestDescriptor discoverTests(DiscoverySelector... selectors) { - return discoverTests(request().selectors(selectors).build()); + return discoverTests( + request().selectors(selectors).outputDirectoryProvider(dummyOutputDirectoryProvider()).build()); } protected TestDescriptor discoverTests(LauncherDiscoveryRequest request) { diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ReportingTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ReportingTests.java index 19176dd10003..4cf84f61e430 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ReportingTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ReportingTests.java @@ -10,20 +10,28 @@ package org.junit.jupiter.engine; -import static java.util.Collections.emptyMap; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.engine.Constants.DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME; 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.OutputDirectoryProviders.hierarchicalOutputDirectoryProvider; +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.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.commons.PreconditionViolationException; @@ -35,20 +43,42 @@ class ReportingTests extends AbstractJupiterTestEngineTests { @ParameterizedTest @CsvSource(textBlock = """ - PER_CLASS, 7 - PER_METHOD, 9 + PER_CLASS, 1, 7, 5 + PER_METHOD, 0, 9, 7 """) - void reportEntriesArePublished(Lifecycle lifecycle, int expectedReportEntryCount) { + 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()); - executeTests(request) // + .configurationParameter(DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME, lifecycle.name()) // + .outputDirectoryProvider(hierarchicalOutputDirectoryProvider(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) // - .failed(0) // - .reportingEntryPublished(expectedReportEntryCount)); + .reportingEntryPublished(testReportEntries) // + .fileEntryPublished(testFileEntries)) // + .assertThatEvents() // + .haveExactly(2, reportEntry(Map.of("value", "@BeforeEach"))) // + .haveExactly(2, fileEntry(nameAndContent("beforeEach"))) // + .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"))) // + .haveExactly(2, reportEntry(Map.of("value", "@AfterEach"))) // + .haveExactly(2, fileEntry(nameAndContent("afterEach"))); } @SuppressWarnings("JUnitMalformedDeclaration") @@ -57,23 +87,27 @@ static class MyReportingTestCase { public 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", file -> Files.writeString(file, "constructor")); } @BeforeEach void beforeEach(TestReporter reporter) { reporter.publishEntry("@BeforeEach"); + reporter.publishFile("beforeEach", file -> Files.writeString(file, "beforeEach")); } @AfterEach void afterEach(TestReporter reporter) { reporter.publishEntry("@AfterEach"); + reporter.publishFile("afterEach", file -> Files.writeString(file, "afterEach")); } @Test void succeedingTest(TestReporter reporter) { - reporter.publishEntry(emptyMap()); + reporter.publishEntry(Map.of()); reporter.publishEntry("user name", "dk38"); reporter.publishEntry("message"); + reporter.publishFile("succeedingTest", file -> Files.writeString(file, "succeedingTest")); } @Test @@ -101,4 +135,15 @@ void invalidReportData(TestReporter reporter) { } + private static Predicate nameAndContent(String expectedName) { + return file -> { + try { + return Path.of(expectedName).equals(file.getFileName()) && expectedName.equals(Files.readString(file)); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + }; + } + } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java index a6c1d9919e56..2b329c1704c1 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java @@ -17,6 +17,7 @@ import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.jupiter.api.io.CleanupMode.ALWAYS; import static org.junit.jupiter.engine.Constants.DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME; +import static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -44,20 +45,20 @@ class DefaultJupiterConfigurationTests { @Test void getDefaultTestInstanceLifecyclePreconditions() { PreconditionViolationException exception = assertThrows(PreconditionViolationException.class, - () -> new DefaultJupiterConfiguration(null)); + () -> new DefaultJupiterConfiguration(null, dummyOutputDirectoryProvider())); assertThat(exception).hasMessage("ConfigurationParameters must not be null"); } @Test void getDefaultTestInstanceLifecycleWithNoConfigParamSet() { - JupiterConfiguration configuration = new DefaultJupiterConfiguration(mock()); + JupiterConfiguration configuration = new DefaultJupiterConfiguration(mock(), dummyOutputDirectoryProvider()); Lifecycle lifecycle = configuration.getDefaultTestInstanceLifecycle(); assertThat(lifecycle).isEqualTo(PER_METHOD); } @Test void getDefaultTempDirCleanupModeWithNoConfigParamSet() { - JupiterConfiguration configuration = new DefaultJupiterConfiguration(mock()); + JupiterConfiguration configuration = new DefaultJupiterConfiguration(mock(), dummyOutputDirectoryProvider()); CleanupMode cleanupMode = configuration.getDefaultTempDirCleanupMode(); assertThat(cleanupMode).isEqualTo(ALWAYS); } @@ -82,7 +83,8 @@ void shouldGetDefaultDisplayNameGeneratorWithConfigParamSet() { ConfigurationParameters parameters = mock(); String key = Constants.DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME; when(parameters.get(key)).thenReturn(Optional.of(CustomDisplayNameGenerator.class.getName())); - JupiterConfiguration configuration = new DefaultJupiterConfiguration(parameters); + JupiterConfiguration configuration = new DefaultJupiterConfiguration(parameters, + dummyOutputDirectoryProvider()); DisplayNameGenerator defaultDisplayNameGenerator = configuration.getDefaultDisplayNameGenerator(); @@ -94,7 +96,8 @@ void shouldGetStandardAsDefaultDisplayNameGeneratorWithoutConfigParamSet() { ConfigurationParameters parameters = mock(); String key = Constants.DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME; when(parameters.get(key)).thenReturn(Optional.empty()); - JupiterConfiguration configuration = new DefaultJupiterConfiguration(parameters); + JupiterConfiguration configuration = new DefaultJupiterConfiguration(parameters, + dummyOutputDirectoryProvider()); DisplayNameGenerator defaultDisplayNameGenerator = configuration.getDefaultDisplayNameGenerator(); @@ -106,7 +109,8 @@ void shouldGetNothingAsDefaultTestMethodOrderWithoutConfigParamSet() { ConfigurationParameters parameters = mock(); String key = Constants.DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME; when(parameters.get(key)).thenReturn(Optional.empty()); - JupiterConfiguration configuration = new DefaultJupiterConfiguration(parameters); + JupiterConfiguration configuration = new DefaultJupiterConfiguration(parameters, + dummyOutputDirectoryProvider()); final Optional defaultTestMethodOrder = configuration.getDefaultTestMethodOrderer(); @@ -118,7 +122,8 @@ void shouldGetDefaultTempDirFactorySupplierWithConfigParamSet() { ConfigurationParameters parameters = mock(); String key = Constants.DEFAULT_TEMP_DIR_FACTORY_PROPERTY_NAME; when(parameters.get(key)).thenReturn(Optional.of(CustomFactory.class.getName())); - JupiterConfiguration configuration = new DefaultJupiterConfiguration(parameters); + JupiterConfiguration configuration = new DefaultJupiterConfiguration(parameters, + dummyOutputDirectoryProvider()); Supplier supplier = configuration.getDefaultTempDirFactorySupplier(); @@ -138,7 +143,8 @@ void shouldGetStandardAsDefaultTempDirFactorySupplierWithoutConfigParamSet() { ConfigurationParameters parameters = mock(); String key = Constants.DEFAULT_TEMP_DIR_FACTORY_PROPERTY_NAME; when(parameters.get(key)).thenReturn(Optional.empty()); - JupiterConfiguration configuration = new DefaultJupiterConfiguration(parameters); + JupiterConfiguration configuration = new DefaultJupiterConfiguration(parameters, + dummyOutputDirectoryProvider()); Supplier supplier = configuration.getDefaultTempDirFactorySupplier(); @@ -148,7 +154,8 @@ void shouldGetStandardAsDefaultTempDirFactorySupplierWithoutConfigParamSet() { private void assertDefaultConfigParam(String configValue, Lifecycle expected) { ConfigurationParameters configParams = mock(); when(configParams.get(KEY)).thenReturn(Optional.ofNullable(configValue)); - Lifecycle lifecycle = new DefaultJupiterConfiguration(configParams).getDefaultTestInstanceLifecycle(); + Lifecycle lifecycle = new DefaultJupiterConfiguration(configParams, + dummyOutputDirectoryProvider()).getDefaultTestInstanceLifecycle(); assertThat(lifecycle).isEqualTo(expected); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java index 779ff8620904..66caeeb639f5 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java @@ -16,6 +16,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Named.named; +import static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -280,7 +281,8 @@ void usingStore() { @ParameterizedTest @MethodSource("extensionContextFactories") void configurationParameter(Function extensionContextFactory) { - JupiterConfiguration echo = new DefaultJupiterConfiguration(new EchoParameters()); + JupiterConfiguration echo = new DefaultJupiterConfiguration(new EchoParameters(), + dummyOutputDirectoryProvider()); String key = "123"; Optional expected = Optional.of(key); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtilsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtilsTests.java index 2bbdf1321a95..4a6e6294d80a 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtilsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtilsTests.java @@ -16,6 +16,7 @@ import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.jupiter.engine.Constants.DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME; import static org.junit.jupiter.engine.descriptor.TestInstanceLifecycleUtils.getTestInstanceLifecycle; +import static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -50,7 +51,8 @@ class TestInstanceLifecycleUtilsTests { @Test void getTestInstanceLifecyclePreconditions() { PreconditionViolationException exception = assertThrows(PreconditionViolationException.class, - () -> getTestInstanceLifecycle(null, new DefaultJupiterConfiguration(mock()))); + () -> getTestInstanceLifecycle(null, + new DefaultJupiterConfiguration(mock(), dummyOutputDirectoryProvider()))); assertThat(exception).hasMessage("testClass must not be null"); exception = assertThrows(PreconditionViolationException.class, @@ -60,7 +62,8 @@ void getTestInstanceLifecyclePreconditions() { @Test void getTestInstanceLifecycleWithNoConfigParamSet() { - Lifecycle lifecycle = getTestInstanceLifecycle(getClass(), new DefaultJupiterConfiguration(mock())); + Lifecycle lifecycle = getTestInstanceLifecycle(getClass(), + new DefaultJupiterConfiguration(mock(), dummyOutputDirectoryProvider())); assertThat(lifecycle).isEqualTo(PER_METHOD); } @@ -68,7 +71,8 @@ void getTestInstanceLifecycleWithNoConfigParamSet() { void getTestInstanceLifecycleWithConfigParamSet() { ConfigurationParameters configParams = mock(); when(configParams.get(KEY)).thenReturn(Optional.of(PER_CLASS.name().toLowerCase())); - Lifecycle lifecycle = getTestInstanceLifecycle(getClass(), new DefaultJupiterConfiguration(configParams)); + Lifecycle lifecycle = getTestInstanceLifecycle(getClass(), + new DefaultJupiterConfiguration(configParams, dummyOutputDirectoryProvider())); assertThat(lifecycle).isEqualTo(PER_CLASS); } @@ -76,21 +80,24 @@ void getTestInstanceLifecycleWithConfigParamSet() { void getTestInstanceLifecycleWithLocalConfigThatOverridesCustomDefaultSetViaConfigParam() { ConfigurationParameters configParams = mock(); when(configParams.get(KEY)).thenReturn(Optional.of(PER_CLASS.name().toLowerCase())); - Lifecycle lifecycle = getTestInstanceLifecycle(TestCase.class, new DefaultJupiterConfiguration(configParams)); + Lifecycle lifecycle = getTestInstanceLifecycle(TestCase.class, + new DefaultJupiterConfiguration(configParams, dummyOutputDirectoryProvider())); assertThat(lifecycle).isEqualTo(PER_METHOD); } @Test void getTestInstanceLifecycleFromMetaAnnotationWithNoConfigParamSet() { Class testClass = BaseMetaAnnotatedTestCase.class; - Lifecycle lifecycle = getTestInstanceLifecycle(testClass, new DefaultJupiterConfiguration(mock())); + Lifecycle lifecycle = getTestInstanceLifecycle(testClass, + new DefaultJupiterConfiguration(mock(), dummyOutputDirectoryProvider())); assertThat(lifecycle).isEqualTo(PER_CLASS); } @Test void getTestInstanceLifecycleFromSpecializedClassWithNoConfigParamSet() { Class testClass = SpecializedTestCase.class; - Lifecycle lifecycle = getTestInstanceLifecycle(testClass, new DefaultJupiterConfiguration(mock())); + Lifecycle lifecycle = getTestInstanceLifecycle(testClass, + new DefaultJupiterConfiguration(mock(), dummyOutputDirectoryProvider())); assertThat(lifecycle).isEqualTo(PER_CLASS); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/CloseablePathTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/CloseablePathTests.java index 6a0645975813..11a55e711610 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/CloseablePathTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/CloseablePathTests.java @@ -236,7 +236,7 @@ public Path createTempDirectory(AnnotatedElementContext elementContext, Extensio @Override public void close() throws IOException { - TempDirFactory.super.close(); + fileSystem.close(); } } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/DefaultTestReporterTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/DefaultTestReporterTests.java new file mode 100644 index 000000000000..68fc7dbff8a7 --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/DefaultTestReporterTests.java @@ -0,0 +1,80 @@ +/* + * Copyright 2015-2024 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public 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 java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; + +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 +public class DefaultTestReporterTests { + + @TempDir + Path tempDir; + + @Mock + ExtensionContext extensionContext; + + @Captor + ArgumentCaptor fileNameCaptor; + + @Captor + ArgumentCaptor> actionCaptor; + + @InjectMocks + DefaultTestReporter testReporter; + + @Test + void copiesExistingFileToTarget() throws Throwable { + testReporter.publishFile(Files.writeString(tempDir.resolve("source"), "content")); + + verify(extensionContext).publishFile(fileNameCaptor.capture(), actionCaptor.capture()); + assertThat(fileNameCaptor.getValue()).isEqualTo("source"); + actionCaptor.getValue().accept(tempDir.resolve("target")); + + assertThat(tempDir.resolve("target")).hasContent("content"); + } + + @Test + void executesCustomActionWithTargetFile() throws Throwable { + testReporter.publishFile("target", file -> Files.writeString(file, "content")); + + verify(extensionContext).publishFile(fileNameCaptor.capture(), actionCaptor.capture()); + assertThat(fileNameCaptor.getValue()).isEqualTo("target"); + actionCaptor.getValue().accept(tempDir.resolve("target")); + + assertThat(tempDir.resolve("target")).hasContent("content"); + } + + @Test + void failsWhenPublishingMissingFile() { + testReporter.publishFile(tempDir.resolve("source")); + + verify(extensionContext).publishFile(fileNameCaptor.capture(), actionCaptor.capture()); + assertThat(fileNameCaptor.getValue()).isEqualTo("source"); + assertThatThrownBy(() -> actionCaptor.getValue().accept(tempDir.resolve("target"))) // + .isInstanceOf(NoSuchFileException.class); + } +} diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java index f8d6dee01831..638cdbf95e1f 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java @@ -22,6 +22,7 @@ import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; import java.lang.reflect.Method; +import java.nio.file.Path; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -34,6 +35,7 @@ import org.junit.jupiter.api.extension.ExecutableInvoker; import org.junit.jupiter.api.extension.ExtensionContext; 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; @@ -275,6 +277,10 @@ public Optional getConfigurationParameter(String key, Function public void publishReportEntry(Map map) { } + @Override + public void publishFile(String fileName, ThrowingConsumer action) { + } + @Override public Store getStore(Namespace namespace) { var store = new NamespaceAwareStore(this.store, namespace); @@ -325,7 +331,7 @@ void method() { static class TestCaseAllowNoArgumentsMethod { - @ParameterizedTest(requireArguments = false) + @ParameterizedTest(allowZeroInvocations = true) void method() { } } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java index 50c131c449b3..13739ea4e018 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java @@ -2422,7 +2422,7 @@ void testThatRequiresArguments(String argument) { fail("This test should not be executed, because no arguments are provided."); } - @ParameterizedTest(requireArguments = false) + @ParameterizedTest(allowZeroInvocations = true) @MethodSource("zeroArgumentsProvider") void testThatDoesNotRequireArguments(String argument) { fail("This test should not be executed, because no arguments are provided."); diff --git a/platform-tests/platform-tests.gradle.kts b/platform-tests/platform-tests.gradle.kts index d5e7467c401e..4c1c6960898f 100644 --- a/platform-tests/platform-tests.gradle.kts +++ b/platform-tests/platform-tests.gradle.kts @@ -1,4 +1,5 @@ -import org.gradle.api.tasks.PathSensitivity.NONE + +import junitbuild.extensions.capitalized import org.gradle.api.tasks.PathSensitivity.RELATIVE import org.gradle.internal.os.OperatingSystem @@ -9,6 +10,18 @@ plugins { id("junitbuild.jmh-conventions") } +val processStarter by sourceSets.creating { + java { + srcDir("src/processStarter/java") + } +} + +java { + registerFeature(processStarter.name) { + usingSourceSet(processStarter) + } +} + dependencies { // --- Things we are testing -------------------------------------------------- testImplementation(projects.junitPlatformCommons) @@ -37,6 +50,11 @@ dependencies { testImplementation(libs.bundles.xmlunit) testImplementation(testFixtures(projects.junitJupiterApi)) testImplementation(testFixtures(projects.junitPlatformReporting)) + testImplementation(projects.platformTests) { + capabilities { + requireFeature("process-starter") + } + } // --- Test run-time dependencies --------------------------------------------- testRuntimeOnly(projects.junitVintageEngine) @@ -44,9 +62,20 @@ dependencies { because("`ReflectionUtilsTests.findNestedClassesWithInvalidNestedClassFile` needs it") } - // --- https://openjdk.java.net/projects/code-tools/jmh/ ----------------------- + // --- https://openjdk.java.net/projects/code-tools/jmh/ ---------------------- jmh(projects.junitJupiterApi) jmh(libs.junit4) + + // --- ProcessStarter dependencies -------------------------------------------- + processStarter.implementationConfigurationName(libs.groovy4) { + 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 { @@ -73,13 +102,18 @@ tasks { test { // Additional inputs for remote execution with Test Distribution inputs.dir("src/test/resources").withPathSensitivity(RELATIVE) - inputs.file(buildFile).withPathSensitivity(NONE) // for UniqueIdTrackingListenerIntegrationTests } test_4_12 { useJUnitPlatform { includeTags("junit4") } } + named(processStarter.compileJavaTaskName).configure { + options.release = javaLibrary.testJavaVersion.majorVersion.toInt() + } + named("checkstyle${processStarter.name.capitalized()}").configure { + config = resources.text.fromFile(checkstyle.configDirectory.file("checkstyleMain.xml")) + } } eclipse { diff --git a/platform-tests/src/processStarter/java/org/junit/platform/tests/process/ProcessResult.java b/platform-tests/src/processStarter/java/org/junit/platform/tests/process/ProcessResult.java new file mode 100644 index 000000000000..9c018e7aaf79 --- /dev/null +++ b/platform-tests/src/processStarter/java/org/junit/platform/tests/process/ProcessResult.java @@ -0,0 +1,24 @@ +/* + * Copyright 2015-2024 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse 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(); + } +} diff --git a/platform-tests/src/processStarter/java/org/junit/platform/tests/process/ProcessStarter.java b/platform-tests/src/processStarter/java/org/junit/platform/tests/process/ProcessStarter.java new file mode 100644 index 000000000000..329f1fab138f --- /dev/null +++ b/platform-tests/src/processStarter/java/org/junit/platform/tests/process/ProcessStarter.java @@ -0,0 +1,94 @@ +/* + * Copyright 2015-2024 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse 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.file.Path; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +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 { + + private Path executable; + private Path workingDir; + private final List arguments = new ArrayList<>(); + private final Map environment = new LinkedHashMap<>(); + + 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 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, ProcessGroovyMethods::consumeProcessOutputStream); + var err = forwardAndCaptureOutput(process, System.err, 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, + BiFunction captureAction) { + var capturingStream = new ByteArrayOutputStream(); + var thread = captureAction.apply(process, new TeeOutputStream(delegate, capturingStream)); + return new WatchedOutput(thread, capturingStream); + } + +} diff --git a/platform-tests/src/processStarter/java/org/junit/platform/tests/process/WatchedOutput.java b/platform-tests/src/processStarter/java/org/junit/platform/tests/process/WatchedOutput.java new file mode 100644 index 000000000000..47cf7e5c2c12 --- /dev/null +++ b/platform-tests/src/processStarter/java/org/junit/platform/tests/process/WatchedOutput.java @@ -0,0 +1,23 @@ +/* + * Copyright 2015-2024 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse 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.nio.charset.Charset; + +record WatchedOutput(Thread thread, ByteArrayOutputStream stream) { + + private static final Charset CHARSET = Charset.forName(System.getProperty("native.encoding")); + + String streamAsString() { + return stream.toString(CHARSET); + } +} diff --git a/platform-tests/src/processStarter/java/org/junit/platform/tests/process/WatchedProcess.java b/platform-tests/src/processStarter/java/org/junit/platform/tests/process/WatchedProcess.java new file mode 100644 index 000000000000..4bf675244197 --- /dev/null +++ b/platform-tests/src/processStarter/java/org/junit/platform/tests/process/WatchedProcess.java @@ -0,0 +1,51 @@ +/* + * Copyright 2015-2024 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse 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; + +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(); + } + } +} diff --git a/platform-tests/src/processStarter/java/org/junit/platform/tests/process/package-info.java b/platform-tests/src/processStarter/java/org/junit/platform/tests/process/package-info.java new file mode 100644 index 000000000000..35d962f9c545 --- /dev/null +++ b/platform-tests/src/processStarter/java/org/junit/platform/tests/process/package-info.java @@ -0,0 +1,7 @@ +/** + * Utilities for working with processes. + * + * @since 1.12 + */ + +package org.junit.platform.tests.process; diff --git a/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java b/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java index ef30a6d3db59..f4fb02f4a935 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java @@ -22,7 +22,6 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.EmptySource; import org.junit.jupiter.params.provider.MethodSource; -import org.junit.platform.console.tasks.ConsoleTestExecutor; /** * @since 1.0 @@ -36,8 +35,7 @@ class ConsoleLauncherTests { @EmptySource @MethodSource("commandsWithEmptyOptionExitCodes") void displayHelp(String command) { - var consoleLauncher = new ConsoleLauncher(ConsoleTestExecutor::new, printSink, printSink); - var exitCode = consoleLauncher.run(command, "--help").getExitCode(); + var exitCode = ConsoleLauncher.run(printSink, printSink, command, "--help").getExitCode(); assertEquals(0, exitCode); assertThat(output()).contains("--help"); @@ -47,8 +45,7 @@ void displayHelp(String command) { @EmptySource @MethodSource("commandsWithEmptyOptionExitCodes") void displayVersion(String command) { - var consoleLauncher = new ConsoleLauncher(ConsoleTestExecutor::new, printSink, printSink); - var exitCode = consoleLauncher.run(command, "--version").getExitCode(); + var exitCode = ConsoleLauncher.run(printSink, printSink, command, "--version").getExitCode(); assertEquals(0, exitCode); assertThat(output()).contains("JUnit Platform Console Launcher"); @@ -57,8 +54,7 @@ void displayVersion(String command) { @ParameterizedTest(name = "{0}") @MethodSource("commandsWithEmptyOptionExitCodes") void displayBanner(String command) { - var consoleLauncher = new ConsoleLauncher(ConsoleTestExecutor::new, printSink, printSink); - consoleLauncher.run(command); + ConsoleLauncher.run(printSink, printSink, command); assertThat(output()).contains("Thanks for using JUnit!"); } @@ -66,8 +62,7 @@ void displayBanner(String command) { @ParameterizedTest(name = "{0}") @MethodSource("commandsWithEmptyOptionExitCodes") void disableBanner(String command, int expectedExitCode) { - var consoleLauncher = new ConsoleLauncher(ConsoleTestExecutor::new, printSink, printSink); - var exitCode = consoleLauncher.run(command, "--disable-banner").getExitCode(); + var exitCode = ConsoleLauncher.run(printSink, printSink, command, "--disable-banner").getExitCode(); assertEquals(expectedExitCode, exitCode); assertThat(output()).doesNotContain("Thanks for using JUnit!"); @@ -76,8 +71,7 @@ void disableBanner(String command, int expectedExitCode) { @ParameterizedTest(name = "{0}") @MethodSource("commandsWithEmptyOptionExitCodes") void executeWithUnknownCommandLineOption(String command) { - var consoleLauncher = new ConsoleLauncher(ConsoleTestExecutor::new, printSink, printSink); - var exitCode = consoleLauncher.run(command, "--all").getExitCode(); + var exitCode = ConsoleLauncher.run(printSink, printSink, command, "--all").getExitCode(); assertEquals(-1, exitCode); assertThat(output()).contains("Unknown option: '--all'").contains("Usage:"); @@ -90,8 +84,7 @@ private String output() { @ParameterizedTest(name = "{0}") @MethodSource("commandsWithEmptyOptionExitCodes") void executeWithoutCommandLineOptions(String command, int expectedExitCode) { - var consoleLauncher = new ConsoleLauncher(ConsoleTestExecutor::new, printSink, printSink); - var actualExitCode = consoleLauncher.run(command).getExitCode(); + var actualExitCode = ConsoleLauncher.run(printSink, printSink, command).getExitCode(); assertEquals(expectedExitCode, actualExitCode); } diff --git a/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapper.java b/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapper.java index 7ae6ba8fd921..0d2c240d8ea3 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapper.java +++ b/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapper.java @@ -17,6 +17,7 @@ import java.io.StringWriter; import java.util.Optional; +import org.junit.platform.console.options.CommandFacade; import org.junit.platform.console.tasks.ConsoleTestExecutor; /** @@ -26,16 +27,14 @@ class ConsoleLauncherWrapper { private final StringWriter out = new StringWriter(); private final StringWriter err = new StringWriter(); - private final ConsoleLauncher consoleLauncher; + private final ConsoleTestExecutor.Factory consoleTestExecutorFactory; ConsoleLauncherWrapper() { this(ConsoleTestExecutor::new); } private ConsoleLauncherWrapper(ConsoleTestExecutor.Factory consoleTestExecutorFactory) { - var outWriter = new PrintWriter(out, false); - var errWriter = new PrintWriter(err, false); - this.consoleLauncher = new ConsoleLauncher(consoleTestExecutorFactory, outWriter, errWriter); + this.consoleTestExecutorFactory = consoleTestExecutorFactory; } public ConsoleLauncherWrapperResult execute(String... args) { @@ -47,7 +46,9 @@ public ConsoleLauncherWrapperResult execute(int expectedExitCode, String... args } public ConsoleLauncherWrapperResult execute(Optional expectedCode, String... args) { - var result = consoleLauncher.run(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(); diff --git a/platform-tests/src/test/java/org/junit/platform/console/tasks/ColorPaletteTests.java b/platform-tests/src/test/java/org/junit/platform/console/tasks/ColorPaletteTests.java index fce602b53011..576f2b3ed97b 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/tasks/ColorPaletteTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/tasks/ColorPaletteTests.java @@ -13,6 +13,7 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider; import static org.mockito.Mockito.mock; import java.io.PrintWriter; @@ -186,7 +187,7 @@ void flat_single_color() { private void demoTestRun(TestExecutionListener listener) { TestDescriptor testDescriptor = new TestDescriptorStub(UniqueId.forEngine("demo-engine"), "My Test"); - TestPlan testPlan = TestPlan.from(List.of(testDescriptor), mock()); + TestPlan testPlan = TestPlan.from(List.of(testDescriptor), mock(), dummyOutputDirectoryProvider()); listener.testPlanExecutionStarted(testPlan); listener.executionStarted(TestIdentifier.from(testDescriptor)); listener.executionFinished(TestIdentifier.from(testDescriptor), TestExecutionResult.successful()); diff --git a/platform-tests/src/test/java/org/junit/platform/console/tasks/DiscoveryRequestCreatorTests.java b/platform-tests/src/test/java/org/junit/platform/console/tasks/DiscoveryRequestCreatorTests.java index 95e7123423bc..3e560fa1fabc 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/tasks/DiscoveryRequestCreatorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/tasks/DiscoveryRequestCreatorTests.java @@ -359,8 +359,7 @@ void convertsConfigurationParametersResources() { } private LauncherDiscoveryRequest convert() { - var creator = new DiscoveryRequestCreator(); - return creator.toDiscoveryRequest(options); + return DiscoveryRequestCreator.toDiscoveryRequestBuilder(options).build(); } private void assertIncludes(Filter filter, String included) { diff --git a/platform-tests/src/test/java/org/junit/platform/console/tasks/FlatPrintingListenerTests.java b/platform-tests/src/test/java/org/junit/platform/console/tasks/FlatPrintingListenerTests.java index 8b0c7a5a29b4..afff86ab473a 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/tasks/FlatPrintingListenerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/tasks/FlatPrintingListenerTests.java @@ -18,11 +18,13 @@ 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; @@ -60,6 +62,19 @@ void reportingEntryPublished() { () -> assertTrue(lines[1].endsWith(", foo = 'bar']"))); } + @Test + void fileEntryPublished() { + var stringWriter = new StringWriter(); + listener(stringWriter).fileEntryPublished(newTestIdentifier(), FileEntry.from(Path.of("test.txt"))); + 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(", file = test.txt]"))); + } + @Test void executionFinishedWithFailure() { var stringWriter = new StringWriter(); diff --git a/platform-tests/src/test/java/org/junit/platform/console/tasks/TestFeedPrintingListenerTests.java b/platform-tests/src/test/java/org/junit/platform/console/tasks/TestFeedPrintingListenerTests.java index c6443a624f4e..953cda4a2d58 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/tasks/TestFeedPrintingListenerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/tasks/TestFeedPrintingListenerTests.java @@ -12,6 +12,7 @@ import static org.junit.jupiter.api.Assertions.assertLinesMatch; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; +import static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider; import static org.mockito.Mockito.mock; import java.io.PrintWriter; @@ -44,7 +45,7 @@ void prepareListener() { "%c ool test"); engineDescriptor.addChild(testDescriptor); - testPlan = TestPlan.from(Collections.singleton(engineDescriptor), mock()); + testPlan = TestPlan.from(Collections.singleton(engineDescriptor), mock(), dummyOutputDirectoryProvider()); testIdentifier = testPlan.getTestIdentifier(testDescriptor.getUniqueId()); listener.testPlanExecutionStarted(testPlan); diff --git a/platform-tests/src/test/java/org/junit/platform/console/tasks/TreePrinterTests.java b/platform-tests/src/test/java/org/junit/platform/console/tasks/TreePrinterTests.java index 8316437899d3..7cafb539423f 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/tasks/TreePrinterTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/tasks/TreePrinterTests.java @@ -21,12 +21,14 @@ 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.console.options.Theme; 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; @@ -92,7 +94,7 @@ void reportsAreTabbedCorrectly() { c1.addChild(m1); var m2 = new TreeNode(identifier("m-2", "method two")).setResult(successful()); - m2.addReportEntry(ReportEntry.from("key", "m-2")); + m2.addFileEntry(FileEntry.from(Path.of("test.txt"))); c1.addChild(m2); new TreePrinter(out, Theme.UNICODE, ColorPalette.NONE).print(root); @@ -105,7 +107,7 @@ void reportsAreTabbedCorrectly() { " ├─ method one ✔", // " │ ....-..-..T..:...* key = `m-1`", // " └─ method two ✔", // - " ....-..-..T..:...* key = `m-2`" // + " ....-..-..T..:...* file:.*" // ), // actual()); } diff --git a/platform-tests/src/test/java/org/junit/platform/console/tasks/VerboseTreeListenerTests.java b/platform-tests/src/test/java/org/junit/platform/console/tasks/VerboseTreePrintingListenerTests.java similarity index 87% rename from platform-tests/src/test/java/org/junit/platform/console/tasks/VerboseTreeListenerTests.java rename to platform-tests/src/test/java/org/junit/platform/console/tasks/VerboseTreePrintingListenerTests.java index 52c74188e08a..8da61de46ea8 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/tasks/VerboseTreeListenerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/tasks/VerboseTreePrintingListenerTests.java @@ -15,11 +15,13 @@ 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.console.options.Theme; 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; @@ -27,7 +29,7 @@ /** * @since 1.3.2 */ -class VerboseTreeListenerTests { +class VerboseTreePrintingListenerTests { private static final String EOL = System.lineSeparator(); @@ -56,6 +58,15 @@ void reportingEntryPublished() { 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"))); + var lines = lines(stringWriter); + + assertLinesMatch(List.of(" reports: FileEntry \\[timestamp = .+, file = test.txt\\]"), List.of(lines)); + } + @Test void executionFinishedWithFailure() { var stringWriter = new StringWriter(); diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClassSelectorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClassSelectorTests.java index 8799c8bf5a89..01d39c02d5a8 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClassSelectorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClassSelectorTests.java @@ -12,9 +12,9 @@ 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 org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; import org.junit.platform.commons.PreconditionViolationException; /** @@ -23,7 +23,7 @@ * @since 1.3 * @see DiscoverySelectorsTests */ -class ClassSelectorTests extends AbstractEqualsAndHashCodeTests { +class ClassSelectorTests { @Test void equalsAndHashCode() { diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClasspathResourceSelectorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClasspathResourceSelectorTests.java index ae696da1de20..a3b842048deb 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClasspathResourceSelectorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClasspathResourceSelectorTests.java @@ -10,8 +10,9 @@ package org.junit.platform.engine.discovery; +import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; + import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; /** * Unit tests for {@link ClasspathResourceSelector}. @@ -19,7 +20,7 @@ * @since 1.3 * @see DiscoverySelectorsTests */ -class ClasspathResourceSelectorTests extends AbstractEqualsAndHashCodeTests { +class ClasspathResourceSelectorTests { @Test void equalsAndHashCode() { diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClasspathRootSelectorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClasspathRootSelectorTests.java index bfdb69c072e1..e9fef4739978 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClasspathRootSelectorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClasspathRootSelectorTests.java @@ -10,10 +10,11 @@ package org.junit.platform.engine.discovery; +import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; + import java.net.URI; import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; /** * Unit tests for {@link ClasspathRootSelector}. @@ -21,7 +22,7 @@ * @since 1.3 * @see DiscoverySelectorsTests */ -class ClasspathRootSelectorTests extends AbstractEqualsAndHashCodeTests { +class ClasspathRootSelectorTests { @Test void equalsAndHashCode() throws Exception { diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/DirectorySelectorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/DirectorySelectorTests.java index 1ae830d99289..35d4d630b2ce 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/DirectorySelectorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/DirectorySelectorTests.java @@ -10,8 +10,9 @@ package org.junit.platform.engine.discovery; +import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; + import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; /** * Unit tests for {@link DirectorySelector}. @@ -19,7 +20,7 @@ * @since 1.3 * @see DiscoverySelectorsTests */ -class DirectorySelectorTests extends AbstractEqualsAndHashCodeTests { +class DirectorySelectorTests { @Test void equalsAndHashCode() { diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/FilePositionTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/FilePositionTests.java index e39acbeedf4d..fe080a0eb816 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/FilePositionTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/FilePositionTests.java @@ -13,6 +13,7 @@ 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.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import static org.junit.jupiter.params.provider.Arguments.arguments; import java.util.stream.Stream; @@ -22,7 +23,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import org.junit.platform.AbstractEqualsAndHashCodeTests; import org.junit.platform.commons.PreconditionViolationException; /** @@ -31,7 +31,7 @@ * @since 1.7 */ @DisplayName("FilePosition unit tests") -class FilePositionTests extends AbstractEqualsAndHashCodeTests { +class FilePositionTests { @Test @DisplayName("factory method preconditions") diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/FileSelectorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/FileSelectorTests.java index 7c167b6bb966..a3f1fa718a64 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/FileSelectorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/FileSelectorTests.java @@ -10,8 +10,9 @@ package org.junit.platform.engine.discovery; +import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; + import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; /** * Unit tests for {@link FileSelector}. @@ -19,7 +20,7 @@ * @since 1.3 * @see DiscoverySelectorsTests */ -class FileSelectorTests extends AbstractEqualsAndHashCodeTests { +class FileSelectorTests { @Test void equalsAndHashCode() { diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/MethodSelectorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/MethodSelectorTests.java index 21dafc77214a..699073145f1c 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/MethodSelectorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/MethodSelectorTests.java @@ -12,11 +12,11 @@ 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 java.util.stream.Stream; import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.PreconditionViolationException; @@ -26,7 +26,7 @@ * @since 1.3 * @see DiscoverySelectorsTests */ -class MethodSelectorTests extends AbstractEqualsAndHashCodeTests { +class MethodSelectorTests { private static final String TEST_CASE_NAME = TestCase.class.getName(); diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/ModuleSelectorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/ModuleSelectorTests.java index 9ea129728212..4ffc796b0c66 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/ModuleSelectorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/ModuleSelectorTests.java @@ -10,8 +10,9 @@ package org.junit.platform.engine.discovery; +import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; + import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; /** * Unit tests for {@link ModuleSelector}. @@ -19,7 +20,7 @@ * @since 1.3 * @see DiscoverySelectorsTests */ -class ModuleSelectorTests extends AbstractEqualsAndHashCodeTests { +class ModuleSelectorTests { @Test void equalsAndHashCode() { diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedClassSelectorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedClassSelectorTests.java index 4f73cb36ea5f..42dd2baf1793 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedClassSelectorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedClassSelectorTests.java @@ -12,11 +12,11 @@ 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 java.util.List; import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; import org.junit.platform.commons.PreconditionViolationException; /** @@ -25,7 +25,7 @@ * @since 1.6 * @see DiscoverySelectorsTests */ -class NestedClassSelectorTests extends AbstractEqualsAndHashCodeTests { +class NestedClassSelectorTests { @Test void equalsAndHashCode() { diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedMethodSelectorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedMethodSelectorTests.java index 33235ed769d8..a351b90114fc 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedMethodSelectorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedMethodSelectorTests.java @@ -12,11 +12,11 @@ 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 java.util.List; import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; import org.junit.platform.commons.PreconditionViolationException; /** @@ -25,7 +25,7 @@ * @since 1.6 * @see DiscoverySelectorsTests */ -class NestedMethodSelectorTests extends AbstractEqualsAndHashCodeTests { +class NestedMethodSelectorTests { @Test void equalsAndHashCode() { diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/PackageSelectorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/PackageSelectorTests.java index cff34011791a..72907ae2b931 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/PackageSelectorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/PackageSelectorTests.java @@ -10,8 +10,9 @@ package org.junit.platform.engine.discovery; +import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; + import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; /** * Unit tests for {@link PackageSelector}. @@ -19,7 +20,7 @@ * @since 1.3 * @see DiscoverySelectorsTests */ -class PackageSelectorTests extends AbstractEqualsAndHashCodeTests { +class PackageSelectorTests { @Test void equalsAndHashCode() { diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/UniqueIdSelectorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/UniqueIdSelectorTests.java index 98dedf083850..7266b1833832 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/UniqueIdSelectorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/UniqueIdSelectorTests.java @@ -10,8 +10,9 @@ package org.junit.platform.engine.discovery; +import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; + import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; import org.junit.platform.engine.UniqueId; /** @@ -20,7 +21,7 @@ * @since 1.3 * @see DiscoverySelectorsTests */ -class UniqueIdSelectorTests extends AbstractEqualsAndHashCodeTests { +class UniqueIdSelectorTests { @Test void equalsAndHashCode() { diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/UriSelectorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/UriSelectorTests.java index 76def76ec775..179963809b6b 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/UriSelectorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/UriSelectorTests.java @@ -10,10 +10,11 @@ package org.junit.platform.engine.discovery; +import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; + import java.net.URI; import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; /** * Unit tests for {@link UriSelector}. @@ -21,7 +22,7 @@ * @since 1.3 * @see DiscoverySelectorsTests */ -class UriSelectorTests extends AbstractEqualsAndHashCodeTests { +class UriSelectorTests { @Test void equalsAndHashCode() throws Exception { diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/AbstractTestSourceTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/AbstractTestSourceTests.java index 9ce87b9b47f2..a2af378fadd6 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/AbstractTestSourceTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/AbstractTestSourceTests.java @@ -25,7 +25,6 @@ import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.TestFactory; -import org.junit.platform.AbstractEqualsAndHashCodeTests; import org.junit.platform.engine.TestSource; /** @@ -34,7 +33,7 @@ * * @since 1.0 */ -abstract class AbstractTestSourceTests extends AbstractEqualsAndHashCodeTests { +abstract class AbstractTestSourceTests { abstract Stream createSerializableInstances() throws Exception; diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClassSourceTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClassSourceTests.java index ef8b2b8d9a91..2915946c54b0 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClassSourceTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClassSourceTests.java @@ -12,6 +12,7 @@ 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 java.io.Serializable; import java.net.URI; diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSourceTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSourceTests.java index 1b861208a4ea..90335c81dbef 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSourceTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSourceTests.java @@ -12,6 +12,7 @@ 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.engine.support.descriptor.ClasspathResourceSource.CLASSPATH_SCHEME; import java.net.URI; diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/CompositeTestSourceTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/CompositeTestSourceTests.java index 121000e5c961..2b32f485df8e 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/CompositeTestSourceTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/CompositeTestSourceTests.java @@ -12,6 +12,7 @@ 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 java.io.File; import java.util.ArrayList; diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DefaultUriSourceTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DefaultUriSourceTests.java index 1fe34b233a90..e67431ac93bd 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DefaultUriSourceTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DefaultUriSourceTests.java @@ -13,6 +13,7 @@ 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.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import java.net.URI; import java.util.stream.Stream; diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FilePositionTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FilePositionTests.java index af5562bc8f57..b50fd31d7e4b 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FilePositionTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FilePositionTests.java @@ -13,6 +13,7 @@ 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.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import static org.junit.jupiter.params.provider.Arguments.arguments; import java.util.stream.Stream; diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FileSystemSourceTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FileSystemSourceTests.java index fd205bc2c6dd..43a1bd9aae85 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FileSystemSourceTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FileSystemSourceTests.java @@ -12,6 +12,7 @@ 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 java.io.File; import java.util.stream.Stream; diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/MethodSourceTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/MethodSourceTests.java index 3b8984829a61..37df1266425f 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/MethodSourceTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/MethodSourceTests.java @@ -14,6 +14,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import java.io.Serializable; import java.lang.reflect.Method; diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/PackageSourceTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/PackageSourceTests.java index c9a752ca5a68..5bf3f8fd2a0b 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/PackageSourceTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/PackageSourceTests.java @@ -12,6 +12,7 @@ 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 java.io.Serializable; import java.util.stream.Stream; diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java index 11d1842ce4a5..057fb6a66826 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java @@ -77,7 +77,7 @@ void init() { private HierarchicalTestExecutor createExecutor( HierarchicalTestExecutorService executorService) { - var request = new ExecutionRequest(root, listener, null); + var request = ExecutionRequest.create(root, listener, null); return new HierarchicalTestExecutor<>(request, rootContext, executorService, OpenTest4JAwareThrowableCollector::new); } diff --git a/platform-tests/src/test/java/org/junit/platform/jfr/FlightRecordingExecutionListenerIntegrationTests.java b/platform-tests/src/test/java/org/junit/platform/jfr/FlightRecordingExecutionListenerIntegrationTests.java index 7d3fe88736a4..cef08d696d9d 100644 --- a/platform-tests/src/test/java/org/junit/platform/jfr/FlightRecordingExecutionListenerIntegrationTests.java +++ b/platform-tests/src/test/java/org/junit/platform/jfr/FlightRecordingExecutionListenerIntegrationTests.java @@ -12,13 +12,19 @@ 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.OutputDirectoryProviders.hierarchicalOutputDirectoryProvider; +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.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; @@ -33,15 +39,18 @@ public class FlightRecordingExecutionListenerIntegrationTests { @Test @EnableEvent("org.junit.*") - void reportsEvents() { + void reportsEvents(@TempDir Path tempDir) { var launcher = LauncherFactoryForTestingPurposesOnly.createLauncher(new JupiterTestEngine()); var request = request() // .selectors(selectClass(TestCase.class)) // + .outputDirectoryProvider(hierarchicalOutputDirectoryProvider(tempDir)) // .build(); launcher.execute(request, new FlightRecordingExecutionListener()); jfrEvents.awaitEvents(); + var testFile = findPath(tempDir, "glob:**/test.txt"); + assertThat(jfrEvents) // .contains(event("org.junit.TestPlanExecution") // .with("engineNames", "JUnit Jupiter")) // @@ -59,6 +68,8 @@ void reportsEvents() { .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") // @@ -70,6 +81,7 @@ static class TestCase { @Test void test(TestReporter reporter) { reporter.publishEntry("message", "Hello JFR!"); + reporter.publishFile("test.txt", file -> Files.writeString(file, "test")); } @Test diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/TestPlanTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/TestPlanTests.java index e62df473bb38..80c09b0bea04 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/TestPlanTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/TestPlanTests.java @@ -11,6 +11,7 @@ package org.junit.platform.launcher; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; @@ -40,7 +41,7 @@ public Type getType() { } }); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams, dummyOutputDirectoryProvider()); assertThat(testPlan.containsTests()).as("contains tests").isFalse(); } @@ -55,7 +56,7 @@ public Type getType() { } }); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams, dummyOutputDirectoryProvider()); assertThat(testPlan.containsTests()).as("contains tests").isTrue(); } @@ -75,7 +76,7 @@ public boolean mayRegisterTests() { } }); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams, dummyOutputDirectoryProvider()); assertThat(testPlan.containsTests()).as("contains tests").isTrue(); } @@ -93,7 +94,8 @@ void acceptsVisitorsInDepthFirstOrder() { engineDescriptor2.addChild(test2); engineDescriptor2.addChild(test3); - var testPlan = TestPlan.from(List.of(engineDescriptor, engineDescriptor2), configParams); + var testPlan = TestPlan.from(List.of(engineDescriptor, engineDescriptor2), configParams, + dummyOutputDirectoryProvider()); var visitor = mock(TestPlan.Visitor.class); testPlan.accept(visitor); diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeEngineExecutionListenerTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeEngineExecutionListenerTests.java index d56669e6eb80..46f26f12d7cf 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeEngineExecutionListenerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeEngineExecutionListenerTests.java @@ -28,6 +28,7 @@ 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; @@ -171,6 +172,11 @@ public void executionFinished(TestDescriptor testDescriptor, TestExecutionResult 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"); + } } } diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeTestExecutionListenerTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeTestExecutionListenerTests.java index cf3004e23966..cb2973165fff 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeTestExecutionListenerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeTestExecutionListenerTests.java @@ -12,6 +12,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; @@ -206,7 +207,7 @@ private void assertThatTestListenerErrorLogged(LogRecordListener logRecordListen } private static TestPlan anyTestPlan() { - return TestPlan.from(Set.of(anyTestDescriptor()), mock()); + return TestPlan.from(Set.of(anyTestDescriptor()), mock(), dummyOutputDirectoryProvider()); } private static DemoMethodTestDescriptor anyTestDescriptor() { diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/ExecutionListenerAdapterTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/ExecutionListenerAdapterTests.java index 0d2d5786687d..d60b569abb98 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/ExecutionListenerAdapterTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/ExecutionListenerAdapterTests.java @@ -11,6 +11,7 @@ package org.junit.platform.launcher.core; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider; import static org.mockito.Mockito.mock; import java.util.Map; @@ -34,7 +35,8 @@ class ExecutionListenerAdapterTests { void testReportingEntryPublished() { var testDescriptor = getSampleMethodTestDescriptor(); - var discoveryResult = new LauncherDiscoveryResult(Map.of(mock(), testDescriptor), mock()); + var discoveryResult = new LauncherDiscoveryResult(Map.of(mock(), testDescriptor), mock(), + dummyOutputDirectoryProvider()); var testPlan = InternalTestPlan.from(discoveryResult); var testIdentifier = testPlan.getTestIdentifier(testDescriptor.getUniqueId()); diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/HierarchicalOutputDirectoryProviderTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/HierarchicalOutputDirectoryProviderTests.java new file mode 100644 index 000000000000..c5c23e3e6fa2 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/HierarchicalOutputDirectoryProviderTests.java @@ -0,0 +1,83 @@ +/* + * Copyright 2015-2024 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse 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 +public class HierarchicalOutputDirectoryProviderTests { + + @TempDir + Path tempDir; + + @Mock + Supplier rootDirSupplier; + + @Mock + TestDescriptor testDescriptor; + + @InjectMocks + HierarchicalOutputDirectoryProvider 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(); + } +} diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/OutputDirTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/OutputDirTests.java index ecab5c1b90c5..d3ac3bea016b 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/OutputDirTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/OutputDirTests.java @@ -10,77 +10,92 @@ 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.nio.file.Paths; 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 { - String customDir = "build/UniqueIdTrackingListenerIntegrationTests"; - Path outputDir = OutputDir.create(Optional.of(customDir)).toPath(); - assertThat(Files.isSameFile(Paths.get(customDir), outputDir)).isTrue(); + 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 { - String cwd = "src/test/resources/listeners/uidtracking"; - String expected = cwd; + var expected = cwd; - assertOutputDirIsDetected(cwd, expected); + assertOutputDirIsDetected(expected); } @Test void getOutputDirDetectsMavenPom() throws Exception { - String cwd = "src/test/resources/listeners/uidtracking/maven"; - String expected = cwd + "/target"; + Files.createFile(cwd.resolve("pom.xml")); + var expected = cwd.resolve("target"); - assertOutputDirIsDetected(cwd, expected); + assertOutputDirIsDetected(expected); } @Test void getOutputDirDetectsGradleGroovyDefaultBuildScript() throws Exception { - String cwd = "src/test/resources/listeners/uidtracking/gradle/groovy"; - String expected = cwd + "/build"; + Files.createFile(cwd.resolve("build.gradle")); + var expected = cwd.resolve("build"); - assertOutputDirIsDetected(cwd, expected); + assertOutputDirIsDetected(expected); } @Test void getOutputDirDetectsGradleGroovyCustomBuildScript() throws Exception { - String cwd = "src/test/resources/listeners/uidtracking/gradle/groovy/sub-project"; - String expected = cwd + "/build"; + Files.createFile(cwd.resolve("sub-project.gradle")); + var expected = cwd.resolve("build"); - assertOutputDirIsDetected(cwd, expected); + assertOutputDirIsDetected(expected); } @Test void getOutputDirDetectsGradleKotlinDefaultBuildScript() throws Exception { - String cwd = "src/test/resources/listeners/uidtracking/gradle/kotlin"; - String expected = cwd + "/build"; + Files.createFile(cwd.resolve("build.gradle.kts")); + var expected = cwd.resolve("build"); - assertOutputDirIsDetected(cwd, expected); + assertOutputDirIsDetected(expected); } @Test void getOutputDirDetectsGradleKotlinCustomBuildScript() throws Exception { - String cwd = "src/test/resources/listeners/uidtracking/gradle/kotlin/sub-project"; - String expected = cwd + "/build"; + Files.createFile(cwd.resolve("sub-project.gradle.kts")); + var expected = cwd.resolve("build"); - assertOutputDirIsDetected(cwd, expected); + assertOutputDirIsDetected(expected); } - private void assertOutputDirIsDetected(String cwd, String expected) throws IOException { - Path outputDir = OutputDir.createSafely(Optional.empty(), () -> Paths.get(cwd)).toPath(); - assertThat(Files.isSameFile(Paths.get(expected), outputDir)).isTrue(); + private void assertOutputDirIsDetected(Path expected) throws IOException { + var outputDir = OutputDir.createSafely(Optional.empty(), () -> cwd).toPath(); + assertThat(Files.isSameFile(expected, outputDir)).isTrue(); assertThat(outputDir).exists(); } diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/SummaryGenerationTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/SummaryGenerationTests.java index 3eeb506f44f1..70c519e08167 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/SummaryGenerationTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/SummaryGenerationTests.java @@ -16,6 +16,7 @@ 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.OutputDirectoryProviders.dummyOutputDirectoryProvider; import static org.mockito.Mockito.mock; import java.io.PrintWriter; @@ -39,7 +40,7 @@ class SummaryGenerationTests { private final SummaryGeneratingListener listener = new SummaryGeneratingListener(); - private final TestPlan testPlan = TestPlan.from(List.of(), mock()); + private final TestPlan testPlan = TestPlan.from(List.of(), mock(), dummyOutputDirectoryProvider()); @Test void emptyReport() { diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListenerIntegrationTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListenerIntegrationTests.java index ada4c823a6ad..feb645cefaa8 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListenerIntegrationTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListenerIntegrationTests.java @@ -23,6 +23,7 @@ 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; @@ -35,7 +36,6 @@ import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -44,15 +44,16 @@ import java.util.stream.Stream; import org.assertj.core.api.Condition; +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.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; @@ -88,6 +89,15 @@ class UniqueIdTrackingListenerIntegrationTests { 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 @@ -116,28 +126,18 @@ private Condition uniqueId(String uniqueId) { @Test void listenerIsRegisteredButDisabledByDefault() throws Exception { - long numListenersRegistered = ServiceLoader.load(TestExecutionListener.class).stream()// + var numListenersRegistered = ServiceLoader.load(TestExecutionListener.class).stream()// .filter(provider -> UniqueIdTrackingListener.class.equals(provider.type()))// .count(); assertThat(numListenersRegistered).isEqualTo(1); - String outputDir = "build"; - String prefix = DEFAULT_OUTPUT_FILE_PREFIX; + var actualUniqueIds = executeTests(Map.of()); - deleteFiles(outputDir, prefix); + // Sanity check using the results of our local TestExecutionListener + assertThat(actualUniqueIds).containsExactlyInAnyOrder(expectedUniqueIds); - try { - List 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(outputDir, prefix)).isEmpty(); - } - finally { - deleteFiles(outputDir, prefix); - } + // Check that files were not generated by the UniqueIdTrackingListener + assertThat(findFiles(workingDir, DEFAULT_OUTPUT_FILE_PREFIX)).isEmpty(); } @Test @@ -147,20 +147,20 @@ void verifyUniqueIdsAreTrackedWithDefaults() throws Exception { @Test void verifyUniqueIdsAreTrackedWithCustomOutputFile() throws Exception { - String customPrefix = "test_ids"; + var customPrefix = "test_ids"; verifyUniqueIdsAreTracked("build", customPrefix, Map.of(OUTPUT_FILE_PREFIX_PROPERTY_NAME, customPrefix)); } @Test void verifyUniqueIdsAreTrackedWithCustomOutputDir() throws Exception { - String customDir = "build/UniqueIdTrackingListenerIntegrationTests"; + var customDir = "build/UniqueIdTrackingListenerIntegrationTests"; verifyUniqueIdsAreTracked(customDir, DEFAULT_OUTPUT_FILE_PREFIX, Map.of(OUTPUT_DIR_PROPERTY_NAME, customDir)); } @Test void verifyUniqueIdsAreTrackedWithCustomOutputFileAndCustomOutputDir() throws Exception { - String customPrefix = "test_ids"; - String customDir = "build/UniqueIdTrackingListenerIntegrationTests"; + var customPrefix = "test_ids"; + var customDir = "build/UniqueIdTrackingListenerIntegrationTests"; verifyUniqueIdsAreTracked(customDir, customPrefix, Map.of(OUTPUT_DIR_PROPERTY_NAME, customDir, OUTPUT_FILE_PREFIX_PROPERTY_NAME, customPrefix)); @@ -172,59 +172,45 @@ private void verifyUniqueIdsAreTracked(String outputDir, String prefix, Map(configurationParameters); configurationParameters.put(LISTENER_ENABLED_PROPERTY_NAME, "true"); - deleteFiles(outputDir, prefix); - - try { - List actualUniqueIds = executeTests(configurationParameters); + var actualUniqueIds = executeTests(configurationParameters); - // Sanity check using the results of our local TestExecutionListener - assertThat(actualUniqueIds).containsExactlyInAnyOrder(expectedUniqueIds); + // 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(outputDir, prefix)).containsExactlyInAnyOrder(expectedUniqueIds); - } - finally { - deleteFiles(outputDir, prefix); - } + // Check contents of the file (or files) generated by the UniqueIdTrackingListener + assertThat(readAllFiles(workingDir.resolve(outputDir), prefix)).containsExactlyInAnyOrder(expectedUniqueIds); } @Test void verifyUniqueIdsAreTrackedWithConcurrentlyExecutingTestPlans() throws Exception { - String customDir = "build/UniqueIdTrackingListenerIntegrationTests"; - String prefix = DEFAULT_OUTPUT_FILE_PREFIX; + 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); - - deleteFiles(customDir, prefix); + configurationParameters.put(OUTPUT_DIR_PROPERTY_NAME, customDir.toAbsolutePath().toString()); - try { - Stream.of(TestCase2.class, TestCase3.class, TestCase4.class).parallel()// - .forEach(clazz -> executeTests(configurationParameters, selectClass(clazz))); + 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); + // 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); - } - finally { - deleteFiles(customDir, prefix); - } + // Check contents of the file (or files) generated by the UniqueIdTrackingListener + assertThat(readAllFiles(customDir, prefix)).containsExactlyInAnyOrder(expectedConcurrentUniqueIds); } - private static List executeTests(Map configurationParameters) { + private List executeTests(Map configurationParameters) { return executeTests(configurationParameters, selectClasses()); } - private static List executeTests(Map configurationParameters, - ClassSelector... classSelectors) { + private List executeTests(Map configurationParameters, ClassSelector... classSelectors) { List uniqueIds = new ArrayList<>(); - LauncherDiscoveryRequest request = request()// + var request = request()// .selectors(classSelectors)// .filters(includeEngines("junit-jupiter"))// .configurationParameters(configurationParameters)// + .configurationParameter(WORKING_DIR_PROPERTY_NAME, workingDir.toAbsolutePath().toString())// .build(); LauncherFactory.create().execute(request, new TestExecutionListener() { @@ -260,8 +246,7 @@ private static ClassSelector[] selectClasses() { selectClass(DisabledTestCase.class) }; } - private static Stream findFiles(String dir, String prefix) throws IOException { - Path outputDir = Paths.get(dir); + private static Stream findFiles(Path outputDir, String prefix) throws IOException { if (!Files.exists(outputDir)) { return Stream.empty(); } @@ -270,18 +255,7 @@ private static Stream findFiles(String dir, String prefix) throws IOExcept && path.getFileName().toString().startsWith(prefix))); } - private void deleteFiles(String outputDir, String prefix) throws IOException { - findFiles(outputDir, prefix).forEach(file -> { - try { - Files.deleteIfExists(file); - } - catch (IOException ex) { - throw new UncheckedIOException(ex); - } - }); - } - - private Stream readAllFiles(String outputDir, String prefix) throws IOException { + private Stream readAllFiles(Path outputDir, String prefix) throws IOException { return findFiles(outputDir, prefix).map(outputFile -> { try { return Files.readAllLines(outputFile); @@ -306,6 +280,7 @@ void passingTest() { void skippedTest() { } + @SuppressWarnings("DataFlowIssue") @Test void abortedTest() { assumeTrue(false); diff --git a/platform-tests/src/test/java/org/junit/platform/reporting/legacy/LegacyReportingUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/reporting/legacy/LegacyReportingUtilsTests.java index 408a85ef9bc9..a9a11684c5b1 100644 --- a/platform-tests/src/test/java/org/junit/platform/reporting/legacy/LegacyReportingUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/reporting/legacy/LegacyReportingUtilsTests.java @@ -11,6 +11,7 @@ package org.junit.platform.reporting.legacy; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider; import static org.mockito.Mockito.mock; import java.util.Set; @@ -71,13 +72,13 @@ void legacyReportingClassNameForDescendantOfTestIdentifierWithClassSourceIsClass } private String getClassName(UniqueId uniqueId) { - var testPlan = TestPlan.from(Set.of(engineDescriptor), mock()); + var testPlan = TestPlan.from(Set.of(engineDescriptor), mock(), dummyOutputDirectoryProvider()); return LegacyReportingUtils.getClassName(testPlan, testPlan.getTestIdentifier(uniqueId)); } @SuppressWarnings("deprecation") private String getClassNameFromOldLocation(UniqueId uniqueId) { - var testPlan = TestPlan.from(Set.of(engineDescriptor), mock()); + var testPlan = TestPlan.from(Set.of(engineDescriptor), mock(), dummyOutputDirectoryProvider()); return org.junit.platform.launcher.listeners.LegacyReportingUtils.getClassName(testPlan, testPlan.getTestIdentifier(uniqueId)); } diff --git a/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListenerTests.java b/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListenerTests.java index de3b42a79f2f..1f6eeb70dc63 100644 --- a/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListenerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListenerTests.java @@ -19,6 +19,7 @@ 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.OutputDirectoryProviders.dummyOutputDirectoryProvider; import static org.junit.platform.reporting.legacy.xml.XmlReportAssertions.assertValidAccordingToJenkinsSchema; import static org.mockito.Mockito.mock; @@ -370,7 +371,7 @@ void printsExceptionWhenReportsDirCannotBeCreated() throws Exception { var out = new StringWriter(); var listener = new LegacyXmlReportGeneratingListener(reportsDir, new PrintWriter(out)); - listener.testPlanExecutionStarted(TestPlan.from(Set.of(), mock())); + listener.testPlanExecutionStarted(TestPlan.from(Set.of(), mock(), dummyOutputDirectoryProvider())); assertThat(out.toString()).containsSubsequence("Could not create reports directory", "FileAlreadyExistsException", "at "); @@ -386,7 +387,8 @@ void printsExceptionWhenReportCouldNotBeWritten() throws Exception { var out = new StringWriter(); var listener = new LegacyXmlReportGeneratingListener(tempDirectory, new PrintWriter(out)); - listener.testPlanExecutionStarted(TestPlan.from(Set.of(engineDescriptor), mock())); + listener.testPlanExecutionStarted( + TestPlan.from(Set.of(engineDescriptor), mock(), dummyOutputDirectoryProvider())); listener.executionFinished(TestIdentifier.from(engineDescriptor), successful()); assertThat(out.toString()).containsSubsequence("Could not write XML report", "Exception", "at "); @@ -397,7 +399,7 @@ 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(Set.of(engineDescriptor), mock()); + var testPlan = TestPlan.from(Set.of(engineDescriptor), mock(), dummyOutputDirectoryProvider()); var out = new StringWriter(); var listener = new LegacyXmlReportGeneratingListener(tempDirectory, new PrintWriter(out)); diff --git a/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportDataTests.java b/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportDataTests.java index 8d685654c1fa..f2890b183884 100644 --- a/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportDataTests.java +++ b/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportDataTests.java @@ -13,6 +13,7 @@ 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.OutputDirectoryProviders.dummyOutputDirectoryProvider; import static org.mockito.Mockito.mock; import java.time.Clock; @@ -37,7 +38,7 @@ 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(Set.of(engineDescriptor), configParams); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams, dummyOutputDirectoryProvider()); var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); var results = reportData.getResults(testPlan.getTestIdentifier(childUniqueId)); @@ -50,7 +51,7 @@ 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(Set.of(engineDescriptor), configParams); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams, dummyOutputDirectoryProvider()); var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); var failureOfAncestor = failed(new RuntimeException("failed!")); @@ -66,7 +67,7 @@ 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(Set.of(engineDescriptor), configParams); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams, dummyOutputDirectoryProvider()); var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); reportData.markFinished(testPlan.getTestIdentifier(engineDescriptor.getUniqueId()), successful()); diff --git a/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportWriterTests.java b/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportWriterTests.java index ef81af430d6e..7f8a324c50de 100644 --- a/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportWriterTests.java +++ b/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportWriterTests.java @@ -19,6 +19,7 @@ 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.OutputDirectoryProviders.dummyOutputDirectoryProvider; import static org.junit.platform.reporting.legacy.xml.XmlReportAssertions.assertValidAccordingToJenkinsSchema; import static org.mockito.Mockito.mock; @@ -54,7 +55,7 @@ class XmlReportWriterTests { @Test void writesTestsuiteElementsWithoutTestcaseElementsWithoutAnyTests() throws Exception { - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams, dummyOutputDirectoryProvider()); var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); @@ -72,7 +73,7 @@ void writesReportEntry() throws Exception { var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); var testDescriptor = new TestDescriptorStub(uniqueId, "successfulTest"); engineDescriptor.addChild(testDescriptor); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams, dummyOutputDirectoryProvider()); var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); reportData.addReportEntry(TestIdentifier.from(testDescriptor), ReportEntry.from("myKey", "myValue")); @@ -90,7 +91,7 @@ void writesCapturedOutput() throws Exception { var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); var testDescriptor = new TestDescriptorStub(uniqueId, "successfulTest"); engineDescriptor.addChild(testDescriptor); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams, dummyOutputDirectoryProvider()); var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); var reportEntry = ReportEntry.from(Map.of( // @@ -119,7 +120,7 @@ void writesCapturedOutput() throws Exception { void writesEmptySkippedElementForSkippedTestWithoutReason() throws Exception { var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); engineDescriptor.addChild(new TestDescriptorStub(uniqueId, "skippedTest")); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams, dummyOutputDirectoryProvider()); var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); reportData.markSkipped(testPlan.getTestIdentifier(uniqueId), null); @@ -149,7 +150,7 @@ public String getLegacyReportingName() { return "failedTest"; } }); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams, dummyOutputDirectoryProvider()); var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); reportData.markFinished(testPlan.getTestIdentifier(uniqueId), failed(null)); @@ -169,7 +170,7 @@ public String getLegacyReportingName() { void omitsMessageAttributeForFailedTestWithThrowableWithoutMessage() throws Exception { var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); engineDescriptor.addChild(new TestDescriptorStub(uniqueId, "failedTest")); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams, dummyOutputDirectoryProvider()); var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); reportData.markFinished(testPlan.getTestIdentifier(uniqueId), failed(new NullPointerException())); @@ -186,7 +187,7 @@ void omitsMessageAttributeForFailedTestWithThrowableWithoutMessage() throws Exce void writesValidXmlEvenIfExceptionMessageContainsCData() throws Exception { var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); engineDescriptor.addChild(new TestDescriptorStub(uniqueId, "test")); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams, dummyOutputDirectoryProvider()); var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); var assertionError = new AssertionError(""); @@ -202,7 +203,7 @@ void writesValidXmlEvenIfExceptionMessageContainsCData() throws Exception { void escapesInvalidCharactersInSystemPropertiesAndExceptionMessages() throws Exception { var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); engineDescriptor.addChild(new TestDescriptorStub(uniqueId, "test")); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams, dummyOutputDirectoryProvider()); var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); var assertionError = new AssertionError("expected: but was: "); @@ -231,7 +232,7 @@ void escapesInvalidCharactersInSystemPropertiesAndExceptionMessages() throws Exc void doesNotReopenCDataWithinCDataContent() throws Exception { var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); engineDescriptor.addChild(new TestDescriptorStub(uniqueId, "test")); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams, dummyOutputDirectoryProvider()); var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); var assertionError = new AssertionError(""); diff --git a/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/JUnitContributorTests.java b/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/JUnitContributorTests.java index 33fe331527a4..1bf1ee6fb0cc 100644 --- a/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/JUnitContributorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/JUnitContributorTests.java @@ -26,7 +26,7 @@ public class JUnitContributorTests { void contributesJUnitSpecificMetadata(@TempDir Path tempDir) throws Exception { var xmlFile = Files.writeString(tempDir.resolve("report.xml"), """ - diff --git a/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListenerTests.java b/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListenerTests.java index b1bf59ebffb1..4d1f9ebda5c5 100644 --- a/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListenerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListenerTests.java @@ -12,25 +12,33 @@ 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.io.CleanupMode.ON_SUCCESS; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; +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.OUTPUT_DIR_PROPERTY_NAME; +import static org.junit.platform.reporting.testutil.FileUtils.findPath; -import java.io.IOException; import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Map; 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.engine.TestEngine; import org.junit.platform.engine.UniqueId; 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; @@ -43,11 +51,8 @@ */ public class OpenTestReportGeneratingListenerTests { - @TempDir(cleanup = ON_SUCCESS) - Path tempDirectory; - @Test - void writesValidXmlReport() throws Exception { + void writesValidXmlReport(@TempDir Path tempDirectory) throws Exception { var engine = new DemoHierarchicalTestEngine("dummy"); engine.addTest("failingTest", "display<-->Name 😎", (context, descriptor) -> { var listener = context.request.getEngineExecutionListener(); @@ -55,15 +60,18 @@ void writesValidXmlReport() throws Exception { fail("failure message"); }); - executeTests(engine); + executeTests(tempDirectory, engine, tempDirectory.resolve("junit-" + OUTPUT_DIR_UNIQUE_NUMBER_PLACEHOLDER)); - var xmlFile = findXmlReport(); + 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 = """ - @@ -75,10 +83,6 @@ void writesValidXmlReport() throws Exception { ${xmlunit.ignore} ${xmlunit.ignore} - - ${xmlunit.ignore} - ${xmlunit.matchesRegex([0-9a-f]{40})} - ${xmlunit.ignore} @@ -120,24 +124,85 @@ void writesValidXmlReport() throws Exception { .areIdentical(); } + @ParameterizedTest + @ValueSource(strings = { "https://github.com/junit-team/junit5.git", "git@github.com:junit-team/junit5.git" }) + void includesGitInfo(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", "-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(); + + var namespaceContext = Map.of("core", Namespace.REPORTING_CORE.getUri(), "e", + Namespace.REPORTING_EVENTS.getUri(), "git", Namespace.REPORTING_GIT.getUri()); + + XmlAssert.assertThat(xmlFile) // + .withNamespaceContext(namespaceContext) // + .valueByXPath("/e:events/core:infrastructure/git:repository/@originUrl") // + .isEqualTo(originUrl); + + XmlAssert.assertThat(xmlFile) // + .withNamespaceContext(namespaceContext) // + .valueByXPath("/e:events/core:infrastructure/git:branch") // + .isEqualTo("my_branch"); + + var commitHash = execGit(tempDirectory, "rev-parse", "--verify", "HEAD").stdOut().trim(); + XmlAssert.assertThat(xmlFile) // + .withNamespaceContext(namespaceContext) // + .valueByXPath("/e:events/core:infrastructure/git:commit") // + .isEqualTo(commitHash); + + XmlAssert.assertThat(xmlFile) // + .withNamespaceContext(namespaceContext) // + .valueByXPath("/e:events/core:infrastructure/git:status/@clean") // + .isEqualTo(false); + + XmlAssert.assertThat(xmlFile) // + .withNamespaceContext(namespaceContext) // + .valueByXPath("/e:events/core:infrastructure/git:status") // + .startsWith("?? junit-reports"); + } + + 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")) // + .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 void executeTests(TestEngine engine) { + private void executeTests(Path tempDirectory, TestEngine engine, Path outputDir) { var build = request() // .selectors(selectUniqueId(UniqueId.forEngine(engine.getId()))) // .configurationParameter(ENABLED_PROPERTY_NAME, String.valueOf(true)) // - .configurationParameter(OUTPUT_DIR_PROPERTY_NAME, tempDirectory.toString()) // + .configurationParameter(OUTPUT_DIR_PROPERTY_NAME, outputDir.toString()) // .build(); - createLauncher(engine).execute(build, new OpenTestReportGeneratingListener()); - } - - private Path findXmlReport() throws IOException { - try (var stream = Files.list(tempDirectory)) { - return stream.findAny().orElseThrow(AssertionError::new); - } + createLauncher(engine).execute(build, new OpenTestReportGeneratingListener(tempDirectory)); } } diff --git a/platform-tests/src/test/java/org/junit/platform/runner/JUnitPlatformRunnerTests.java b/platform-tests/src/test/java/org/junit/platform/runner/JUnitPlatformRunnerTests.java index b9f54ef9d147..45064f00a2b1 100644 --- a/platform-tests/src/test/java/org/junit/platform/runner/JUnitPlatformRunnerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/runner/JUnitPlatformRunnerTests.java @@ -22,6 +22,7 @@ import static org.junit.platform.engine.TestExecutionResult.successful; import static org.junit.platform.engine.discovery.ClassNameFilter.STANDARD_INCLUDE_PATTERN; import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncher; +import static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider; import static org.junit.runner.Description.createSuiteDescription; import static org.junit.runner.Description.createTestDescription; import static org.junit.runner.manipulation.Filter.matchMethodDescription; @@ -465,7 +466,7 @@ void convertsTestIdentifiersIntoDescriptions() { TestDescriptor container2 = new TestDescriptorStub(UniqueId.root("root", "container2"), "container2"); container2.addChild(new TestDescriptorStub(UniqueId.root("root", "test2a"), "test2a")); container2.addChild(new TestDescriptorStub(UniqueId.root("root", "test2b"), "test2b")); - var testPlan = TestPlan.from(List.of(container1, container2), mock()); + var testPlan = TestPlan.from(List.of(container1, container2), mock(), dummyOutputDirectoryProvider()); var launcher = mock(Launcher.class); when(launcher.discover(any())).thenReturn(testPlan); @@ -512,11 +513,12 @@ void appliesFilter() throws Exception { TestDescriptor originalParent2 = new TestDescriptorStub(UniqueId.root("root", "parent2"), "parent2"); originalParent2.addChild(new TestDescriptorStub(UniqueId.root("root", "leaf2a"), "leaf2a")); originalParent2.addChild(new TestDescriptorStub(UniqueId.root("root", "leaf2b"), "leaf2b")); - var fullTestPlan = TestPlan.from(List.of(originalParent1, originalParent2), configParams); + var fullTestPlan = TestPlan.from(List.of(originalParent1, originalParent2), configParams, + dummyOutputDirectoryProvider()); TestDescriptor filteredParent = new TestDescriptorStub(UniqueId.root("root", "parent2"), "parent2"); filteredParent.addChild(new TestDescriptorStub(UniqueId.root("root", "leaf2b"), "leaf2b")); - var filteredTestPlan = TestPlan.from(Set.of(filteredParent), configParams); + var filteredTestPlan = TestPlan.from(Set.of(filteredParent), configParams, dummyOutputDirectoryProvider()); var launcher = mock(Launcher.class); var captor = ArgumentCaptor.forClass(LauncherDiscoveryRequest.class); @@ -539,7 +541,7 @@ void appliesFilter() throws Exception { @Test void throwsNoTestsRemainExceptionWhenNoTestIdentifierMatchesFilter() { var testPlan = TestPlan.from(Set.of(new TestDescriptorStub(UniqueId.root("root", "test"), "test")), - configParams); + configParams, dummyOutputDirectoryProvider()); var launcher = mock(Launcher.class); when(launcher.discover(any())).thenReturn(testPlan); @@ -777,7 +779,8 @@ private TestDescriptor testDescriptorWithTags(String... tag) { private LauncherDiscoveryRequest instantiateRunnerAndCaptureGeneratedRequest(Class testClass) { var launcher = mock(Launcher.class); var captor = ArgumentCaptor.forClass(LauncherDiscoveryRequest.class); - when(launcher.discover(captor.capture())).thenReturn(TestPlan.from(Set.of(), mock())); + when(launcher.discover(captor.capture())).thenReturn( + TestPlan.from(Set.of(), mock(), dummyOutputDirectoryProvider())); new JUnitPlatform(testClass, launcher); diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteEngineTests.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteEngineTests.java index 24d683c3397d..9675f662c5b8 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteEngineTests.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteEngineTests.java @@ -10,9 +10,11 @@ package org.junit.platform.suite.engine; +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.launcher.TagFilter.excludeTags; +import static org.junit.platform.launcher.core.OutputDirectoryProviders.hierarchicalOutputDirectoryProvider; 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; @@ -24,7 +26,10 @@ import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; +import java.nio.file.Path; + 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; @@ -65,11 +70,15 @@ */ class SuiteEngineTests { + @TempDir + private Path outputDir; + @Test void selectClasses() { // @formatter:off EngineTestKit.engine(ENGINE_ID) .selectors(selectClass(SelectClassesSuite.class)) + .outputDirectoryProvider(hierarchicalOutputDirectoryProvider(outputDir)) .execute() .testEvents() .assertThatEvents() @@ -168,6 +177,7 @@ void suiteSuite() { // @formatter:off EngineTestKit.engine(ENGINE_ID) .selectors(selectClass(SuiteSuite.class)) + .outputDirectoryProvider(hierarchicalOutputDirectoryProvider(outputDir)) .execute() .testEvents() .assertThatEvents() @@ -184,6 +194,7 @@ void selectClassesByUniqueId() { .append(SuiteTestDescriptor.SEGMENT_TYPE, SelectClassesSuite.class.getName()); EngineTestKit.engine(ENGINE_ID) .selectors(selectUniqueId(uniqId)) + .outputDirectoryProvider(hierarchicalOutputDirectoryProvider(outputDir)) .execute() .testEvents() .assertThatEvents() @@ -239,6 +250,7 @@ void selectMethodAndSuiteInTestPlanByUniqueId() { EngineTestKit.engine(ENGINE_ID) .selectors(selectUniqueId(uniqueId)) .selectors(selectClass(SelectClassesSuite.class)) + .outputDirectoryProvider(hierarchicalOutputDirectoryProvider(outputDir)) .execute() .testEvents() .assertThatEvents() @@ -391,6 +403,7 @@ void cyclicSuite() { // @formatter:off EngineTestKit.engine(ENGINE_ID) .selectors(selectClass(CyclicSuite.class)) + .outputDirectoryProvider(hierarchicalOutputDirectoryProvider(outputDir)) .execute() .allEvents() .assertThatEvents() @@ -417,6 +430,7 @@ void threePartCyclicSuite() { // @formatter:off EngineTestKit.engine(ENGINE_ID) .selectors(selectClass(ThreePartCyclicSuite.PartA.class)) + .outputDirectoryProvider(hierarchicalOutputDirectoryProvider(outputDir)) .execute() .allEvents() .assertThatEvents() @@ -429,6 +443,7 @@ void selectByIdentifier() { // @formatter:off EngineTestKit.engine(ENGINE_ID) .selectors(selectClass(SelectByIdentifierSuite.class)) + .outputDirectoryProvider(hierarchicalOutputDirectoryProvider(outputDir)) .execute() .testEvents() .assertThatEvents() @@ -437,6 +452,21 @@ void selectByIdentifier() { // @formatter:on } + @Test + void passesOutputDirectoryProviderToEnginesInSuite() { + // @formatter:off + EngineTestKit.engine(ENGINE_ID) + .selectors(selectClass(SelectClassesSuite.class)) + .outputDirectoryProvider(hierarchicalOutputDirectoryProvider(outputDir)) + .execute() + .testEvents() + .assertThatEvents() + .haveExactly(1, event(test(SingleTestTestCase.class.getName()), finishedSuccessfully())); + // @formatter:on + + assertThat(outputDir).isDirectoryRecursivelyContaining("glob:**/test.txt"); + } + @Suite @SelectClasses(SingleTestTestCase.class) private static class PrivateSuite { diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteTestDescriptorTests.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteTestDescriptorTests.java index 02ad36b35029..2f9507a01dc1 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteTestDescriptorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteTestDescriptorTests.java @@ -19,6 +19,7 @@ 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; @@ -26,6 +27,8 @@ import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.OutputDirectoryProvider; +import org.junit.platform.launcher.core.OutputDirectoryProviders; import org.junit.platform.suite.api.Suite; import org.junit.platform.suite.engine.testcases.SingleTestTestCase; import org.junit.platform.suite.engine.testsuites.SelectClassesSuite; @@ -40,10 +43,13 @@ class SuiteTestDescriptorTests { 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()"); + final UniqueId methodId = testClassId.append(TestMethodTestDescriptor.SEGMENT_TYPE, + "test(%s)".formatted(TestReporter.class.getName())); final ConfigurationParameters configurationParameters = new EmptyConfigurationParameters(); - final SuiteTestDescriptor suite = new SuiteTestDescriptor(suiteId, TestSuite.class, configurationParameters); + final OutputDirectoryProvider outputDirectoryProvider = OutputDirectoryProviders.dummyOutputDirectoryProvider(); + final SuiteTestDescriptor suite = new SuiteTestDescriptor(suiteId, TestSuite.class, configurationParameters, + outputDirectoryProvider); @Test void suiteIsEmptyBeforeDiscovery() { diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/SingleTestTestCase.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/SingleTestTestCase.java index fb0c0230e18d..eb6dc4734034 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/SingleTestTestCase.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/SingleTestTestCase.java @@ -10,7 +10,10 @@ package org.junit.platform.suite.engine.testcases; +import java.nio.file.Files; + import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestReporter; /** * @since 1.8 @@ -18,6 +21,7 @@ public class SingleTestTestCase { @Test - void test() { + void test(TestReporter testReporter) { + testReporter.publishFile("test.txt", file -> Files.writeString(file, "test")); } } diff --git a/platform-tests/src/test/resources/listeners/uidtracking/gradle/groovy/build.gradle b/platform-tests/src/test/resources/listeners/uidtracking/gradle/groovy/build.gradle deleted file mode 100644 index a4185e83967b..000000000000 --- a/platform-tests/src/test/resources/listeners/uidtracking/gradle/groovy/build.gradle +++ /dev/null @@ -1 +0,0 @@ -// This file is used in tests for the UniqueIdTrackingListener diff --git a/platform-tests/src/test/resources/listeners/uidtracking/gradle/groovy/sub-project/sub-project.gradle b/platform-tests/src/test/resources/listeners/uidtracking/gradle/groovy/sub-project/sub-project.gradle deleted file mode 100644 index a4185e83967b..000000000000 --- a/platform-tests/src/test/resources/listeners/uidtracking/gradle/groovy/sub-project/sub-project.gradle +++ /dev/null @@ -1 +0,0 @@ -// This file is used in tests for the UniqueIdTrackingListener diff --git a/platform-tests/src/test/resources/listeners/uidtracking/gradle/kotlin/build.gradle.kts b/platform-tests/src/test/resources/listeners/uidtracking/gradle/kotlin/build.gradle.kts deleted file mode 100644 index a4185e83967b..000000000000 --- a/platform-tests/src/test/resources/listeners/uidtracking/gradle/kotlin/build.gradle.kts +++ /dev/null @@ -1 +0,0 @@ -// This file is used in tests for the UniqueIdTrackingListener diff --git a/platform-tests/src/test/resources/listeners/uidtracking/gradle/kotlin/sub-project/sub-project.gradle.kts b/platform-tests/src/test/resources/listeners/uidtracking/gradle/kotlin/sub-project/sub-project.gradle.kts deleted file mode 100644 index a4185e83967b..000000000000 --- a/platform-tests/src/test/resources/listeners/uidtracking/gradle/kotlin/sub-project/sub-project.gradle.kts +++ /dev/null @@ -1 +0,0 @@ -// This file is used in tests for the UniqueIdTrackingListener diff --git a/platform-tests/src/test/resources/listeners/uidtracking/maven/pom.xml b/platform-tests/src/test/resources/listeners/uidtracking/maven/pom.xml deleted file mode 100644 index 1a462ec9525d..000000000000 --- a/platform-tests/src/test/resources/listeners/uidtracking/maven/pom.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts b/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts index 605428191449..88f991589508 100644 --- a/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts +++ b/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts @@ -13,7 +13,7 @@ plugins { } javaLibrary { - mainJavaVersion = JavaVersion.VERSION_11 + mainJavaVersion = JavaVersion.VERSION_21 } spotless { @@ -44,12 +44,17 @@ val mavenDistributionClasspath = configurations.resolvable("mavenDistributionCla } dependencies { - implementation(libs.bartholdy) { - because("manage external tool installations") - } 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") + } testImplementation(libs.archunit) { because("checking the architecture of JUnit 5") @@ -57,9 +62,6 @@ dependencies { testImplementation(libs.apiguardian) { because("we validate that public classes are annotated") } - testImplementation(libs.groovy4) { - because("it provides convenience methods to handle process output") - } testImplementation(libs.bndlib) { because("parsing OSGi metadata") } @@ -71,6 +73,7 @@ dependencies { } testImplementation(libs.bundles.xmlunit) testImplementation(testFixtures(projects.junitJupiterApi)) + testImplementation(testFixtures(projects.junitPlatformReporting)) thirdPartyJars(libs.junit4) thirdPartyJars(libs.assertj) @@ -139,6 +142,7 @@ tasks.test { 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") @@ -169,6 +173,10 @@ tasks.test { } } jvmArgumentProviders += JavaHomeDir(project, 8, 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 { diff --git a/platform-tooling-support-tests/projects/graalvm-starter/build.gradle.kts b/platform-tooling-support-tests/projects/graalvm-starter/build.gradle.kts index c83d5d840ada..77a7a5053c65 100644 --- a/platform-tooling-support-tests/projects/graalvm-starter/build.gradle.kts +++ b/platform-tooling-support-tests/projects/graalvm-starter/build.gradle.kts @@ -3,8 +3,8 @@ plugins { id("org.graalvm.buildtools.native") } -val jupiterVersion: String = System.getenv("JUNIT_JUPITER_VERSION") -val platformVersion: String = System.getenv("JUNIT_PLATFORM_VERSION") +val jupiterVersion: String by project +val platformVersion: String by project repositories { maven { url = uri(file(System.getProperty("maven.repo"))) } @@ -31,6 +31,8 @@ tasks.test { graalvmNative { binaries { named("test") { + // TODO #3040 Add to native-image.properties + buildArgs.add("--initialize-at-build-time=org.junit.platform.launcher.core.HierarchicalOutputDirectoryProvider") buildArgs.add("-H:+ReportExceptionStackTraces") } } diff --git a/platform-tooling-support-tests/projects/graalvm-starter/settings.gradle.kts b/platform-tooling-support-tests/projects/graalvm-starter/settings.gradle.kts index ef491d025954..6b9636ea6529 100644 --- a/platform-tooling-support-tests/projects/graalvm-starter/settings.gradle.kts +++ b/platform-tooling-support-tests/projects/graalvm-starter/settings.gradle.kts @@ -8,4 +8,8 @@ pluginManagement { } } +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" +} + rootProject.name = "graalvm-starter" diff --git a/platform-tooling-support-tests/projects/gradle-kotlin-extensions/build.gradle.kts b/platform-tooling-support-tests/projects/gradle-kotlin-extensions/build.gradle.kts index ce55382514be..5f343d010524 100644 --- a/platform-tooling-support-tests/projects/gradle-kotlin-extensions/build.gradle.kts +++ b/platform-tooling-support-tests/projects/gradle-kotlin-extensions/build.gradle.kts @@ -1,7 +1,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - kotlin("jvm") version "2.0.21" + kotlin("jvm") version "2.1.0" } repositories { @@ -9,12 +9,19 @@ repositories { mavenCentral() } -// grab jupiter version from system environment -val jupiterVersion = System.getenv("JUNIT_JUPITER_VERSION") +val jupiterVersion: String by project +val platformVersion: String by project dependencies { testImplementation(kotlin("stdlib-jdk8")) testImplementation("org.junit.jupiter:junit-jupiter:$jupiterVersion") + testRuntimeOnly("org.junit.platform:junit-platform-launcher:$platformVersion") +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(8) + } } tasks.withType().configureEach { diff --git a/platform-tooling-support-tests/projects/gradle-kotlin-extensions/gradle.properties b/platform-tooling-support-tests/projects/gradle-kotlin-extensions/gradle.properties new file mode 100644 index 000000000000..79b428ebc70c --- /dev/null +++ b/platform-tooling-support-tests/projects/gradle-kotlin-extensions/gradle.properties @@ -0,0 +1 @@ +org.gradle.java.installations.fromEnv=JDK8 diff --git a/platform-tooling-support-tests/projects/gradle-kotlin-extensions/settings.gradle.kts b/platform-tooling-support-tests/projects/gradle-kotlin-extensions/settings.gradle.kts index 7a052453781c..7836e6f5ba89 100644 --- a/platform-tooling-support-tests/projects/gradle-kotlin-extensions/settings.gradle.kts +++ b/platform-tooling-support-tests/projects/gradle-kotlin-extensions/settings.gradle.kts @@ -1 +1,5 @@ +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" +} + rootProject.name = "gradle-kotlin-extensions" diff --git a/platform-tooling-support-tests/projects/gradle-missing-engine/build.gradle.kts b/platform-tooling-support-tests/projects/gradle-missing-engine/build.gradle.kts index 022b1d3a71eb..50b4e7b2924b 100644 --- a/platform-tooling-support-tests/projects/gradle-missing-engine/build.gradle.kts +++ b/platform-tooling-support-tests/projects/gradle-missing-engine/build.gradle.kts @@ -2,25 +2,8 @@ plugins { java } -// grab jupiter version from system environment -val jupiterVersion: String = System.getenv("JUNIT_JUPITER_VERSION") -val vintageVersion: String = System.getenv("JUNIT_VINTAGE_VERSION") -val platformVersion: String = System.getenv("JUNIT_PLATFORM_VERSION") - -// emit default file encoding to a file -file("file.encoding.txt").writeText(System.getProperty("file.encoding")) - -// emit more Java runtime information -file("java.runtime.txt").writeText(""" -java.version=${System.getProperty("java.version")} -""") - -// emit versions of JUnit groups -file("junit.versions.txt").writeText(""" -jupiterVersion=$jupiterVersion -vintageVersion=$vintageVersion -platformVersion=$platformVersion -""") +val jupiterVersion: String by project +val platformVersion: String by project repositories { maven { url = uri(file(System.getProperty("maven.repo"))) } @@ -31,6 +14,12 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter-api:$jupiterVersion") { exclude(group = "org.junit.jupiter", module = "junit-jupiter-engine") } + testRuntimeOnly("org.junit.platform:junit-platform-launcher:$platformVersion")} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(8) + } } tasks.test { diff --git a/platform-tooling-support-tests/projects/gradle-missing-engine/gradle.properties b/platform-tooling-support-tests/projects/gradle-missing-engine/gradle.properties new file mode 100644 index 000000000000..79b428ebc70c --- /dev/null +++ b/platform-tooling-support-tests/projects/gradle-missing-engine/gradle.properties @@ -0,0 +1 @@ +org.gradle.java.installations.fromEnv=JDK8 diff --git a/platform-tooling-support-tests/projects/gradle-missing-engine/settings.gradle.kts b/platform-tooling-support-tests/projects/gradle-missing-engine/settings.gradle.kts index c68450bb3bf5..366bca2588d1 100644 --- a/platform-tooling-support-tests/projects/gradle-missing-engine/settings.gradle.kts +++ b/platform-tooling-support-tests/projects/gradle-missing-engine/settings.gradle.kts @@ -1 +1,5 @@ +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" +} + rootProject.name = "gradle-missing-engine" diff --git a/platform-tooling-support-tests/projects/gradle-starter/build.gradle.kts b/platform-tooling-support-tests/projects/gradle-starter/build.gradle.kts index e1053da535b7..751889da23d0 100644 --- a/platform-tooling-support-tests/projects/gradle-starter/build.gradle.kts +++ b/platform-tooling-support-tests/projects/gradle-starter/build.gradle.kts @@ -2,10 +2,8 @@ plugins { java } -// grab jupiter version from system environment -val jupiterVersion: String = System.getenv("JUNIT_JUPITER_VERSION") -val vintageVersion: String = System.getenv("JUNIT_VINTAGE_VERSION") -val platformVersion: String = System.getenv("JUNIT_PLATFORM_VERSION") +val jupiterVersion: String by project +val platformVersion: String by project repositories { maven { url = uri(file(System.getProperty("maven.repo"))) } @@ -17,11 +15,17 @@ dependencies { testRuntimeOnly("org.junit.platform:junit-platform-reporting:$platformVersion") } +java { + toolchain { + languageVersion = JavaLanguageVersion.of(8) + } +} + tasks.test { useJUnitPlatform() testLogging { - events("passed", "skipped", "failed") + events("passed", "skipped", "failed", "standardOut") } reports { @@ -35,8 +39,4 @@ tasks.test { "-Djunit.platform.reporting.output.dir=${outputDir.get().asFile.absolutePath}" ) } - - doFirst { - println("Using Java version: ${JavaVersion.current()}") - } } diff --git a/platform-tooling-support-tests/projects/gradle-starter/gradle.properties b/platform-tooling-support-tests/projects/gradle-starter/gradle.properties new file mode 100644 index 000000000000..79b428ebc70c --- /dev/null +++ b/platform-tooling-support-tests/projects/gradle-starter/gradle.properties @@ -0,0 +1 @@ +org.gradle.java.installations.fromEnv=JDK8 diff --git a/platform-tooling-support-tests/projects/gradle-starter/settings.gradle.kts b/platform-tooling-support-tests/projects/gradle-starter/settings.gradle.kts index 481940859cf9..e818a8e04116 100644 --- a/platform-tooling-support-tests/projects/gradle-starter/settings.gradle.kts +++ b/platform-tooling-support-tests/projects/gradle-starter/settings.gradle.kts @@ -1 +1,5 @@ +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" +} + rootProject.name = "gradle-starter" diff --git a/platform-tooling-support-tests/projects/gradle-starter/src/test/java/com/example/project/CalculatorTests.java b/platform-tooling-support-tests/projects/gradle-starter/src/test/java/com/example/project/CalculatorTests.java index 0b0a25ab7fdc..06e73789b1af 100644 --- a/platform-tooling-support-tests/projects/gradle-starter/src/test/java/com/example/project/CalculatorTests.java +++ b/platform-tooling-support-tests/projects/gradle-starter/src/test/java/com/example/project/CalculatorTests.java @@ -12,6 +12,7 @@ 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; @@ -19,6 +20,11 @@ class CalculatorTests { + @BeforeAll + static void printJavaVersion() { + System.out.println("Using Java version: " + System.getProperty("java.specification.version")); + } + @Test @DisplayName("1 + 1 = 2") void addsTwoNumbers() { diff --git a/platform-tooling-support-tests/projects/java-versions/pom.xml b/platform-tooling-support-tests/projects/java-versions/pom.xml index b71339842409..d9d22bbec6b6 100644 --- a/platform-tooling-support-tests/projects/java-versions/pom.xml +++ b/platform-tooling-support-tests/projects/java-versions/pom.xml @@ -9,8 +9,6 @@ UTF-8 - ${env.JUNIT_JUPITER_VERSION} - ${env.JUNIT_PLATFORM_VERSION} diff --git a/platform-tooling-support-tests/projects/maven-starter/pom.xml b/platform-tooling-support-tests/projects/maven-starter/pom.xml index 68525c5f3c37..4f1552a66cab 100644 --- a/platform-tooling-support-tests/projects/maven-starter/pom.xml +++ b/platform-tooling-support-tests/projects/maven-starter/pom.xml @@ -11,7 +11,6 @@ UTF-8 1.8 ${maven.compiler.source} - ${env.JUNIT_JUPITER_VERSION} diff --git a/platform-tooling-support-tests/projects/maven-surefire-compatibility/pom.xml b/platform-tooling-support-tests/projects/maven-surefire-compatibility/pom.xml index 087c319f5c44..3ac0876b2d8b 100644 --- a/platform-tooling-support-tests/projects/maven-surefire-compatibility/pom.xml +++ b/platform-tooling-support-tests/projects/maven-surefire-compatibility/pom.xml @@ -11,7 +11,6 @@ UTF-8 1.8 ${maven.compiler.source} - ${env.JUNIT_JUPITER_VERSION} diff --git a/platform-tooling-support-tests/projects/multi-release-jar/default/pom.xml b/platform-tooling-support-tests/projects/multi-release-jar/pom.xml similarity index 90% rename from platform-tooling-support-tests/projects/multi-release-jar/default/pom.xml rename to platform-tooling-support-tests/projects/multi-release-jar/pom.xml index 599ad6302bef..9c96f86949bf 100644 --- a/platform-tooling-support-tests/projects/multi-release-jar/default/pom.xml +++ b/platform-tooling-support-tests/projects/multi-release-jar/pom.xml @@ -9,9 +9,6 @@ UTF-8 - ${env.JUNIT_JUPITER_VERSION} - ${env.JUNIT_VINTAGE_VERSION} - ${env.JUNIT_PLATFORM_VERSION} @@ -45,7 +42,7 @@ de.sormuras.junit junit-platform-maven-plugin - 1.1.7 + 1.1.8 true JAVA diff --git a/platform-tooling-support-tests/projects/multi-release-jar/default/src/test/java/integration/integration/JupiterIntegrationTests.java b/platform-tooling-support-tests/projects/multi-release-jar/src/test/java/integration/integration/JupiterIntegrationTests.java similarity index 100% rename from platform-tooling-support-tests/projects/multi-release-jar/default/src/test/java/integration/integration/JupiterIntegrationTests.java rename to platform-tooling-support-tests/projects/multi-release-jar/src/test/java/integration/integration/JupiterIntegrationTests.java diff --git a/platform-tooling-support-tests/projects/multi-release-jar/default/src/test/java/integration/integration/ModuleUtilsTests.java b/platform-tooling-support-tests/projects/multi-release-jar/src/test/java/integration/integration/ModuleUtilsTests.java similarity index 100% rename from platform-tooling-support-tests/projects/multi-release-jar/default/src/test/java/integration/integration/ModuleUtilsTests.java rename to platform-tooling-support-tests/projects/multi-release-jar/src/test/java/integration/integration/ModuleUtilsTests.java diff --git a/platform-tooling-support-tests/projects/reflection-tests/build.gradle.kts b/platform-tooling-support-tests/projects/reflection-tests/build.gradle.kts index 653257ae1042..0dea5aab80a1 100644 --- a/platform-tooling-support-tests/projects/reflection-tests/build.gradle.kts +++ b/platform-tooling-support-tests/projects/reflection-tests/build.gradle.kts @@ -2,10 +2,8 @@ plugins { java } -// grab jupiter version from system environment -val jupiterVersion: String = System.getenv("JUNIT_JUPITER_VERSION") -val vintageVersion: String = System.getenv("JUNIT_VINTAGE_VERSION") -val platformVersion: String = System.getenv("JUNIT_PLATFORM_VERSION") +val jupiterVersion: String by project +val platformVersion: String by project repositories { maven { url = uri(file(System.getProperty("maven.repo"))) } @@ -18,11 +16,17 @@ dependencies { testRuntimeOnly("org.junit.platform:junit-platform-reporting:$platformVersion") } +java { + toolchain { + languageVersion = JavaLanguageVersion.of(8) + } +} + tasks.test { useJUnitPlatform() testLogging { - events("failed") + events("failed", "standardOut") } reports { @@ -36,8 +40,4 @@ tasks.test { "-Djunit.platform.reporting.output.dir=${outputDir.get().asFile.absolutePath}" ) } - - doFirst { - println("Using Java version: ${JavaVersion.current()}") - } } diff --git a/platform-tooling-support-tests/projects/reflection-tests/gradle.properties b/platform-tooling-support-tests/projects/reflection-tests/gradle.properties new file mode 100644 index 000000000000..79b428ebc70c --- /dev/null +++ b/platform-tooling-support-tests/projects/reflection-tests/gradle.properties @@ -0,0 +1 @@ +org.gradle.java.installations.fromEnv=JDK8 diff --git a/platform-tooling-support-tests/projects/reflection-tests/settings.gradle.kts b/platform-tooling-support-tests/projects/reflection-tests/settings.gradle.kts index af17e8f41649..38c95bf52810 100644 --- a/platform-tooling-support-tests/projects/reflection-tests/settings.gradle.kts +++ b/platform-tooling-support-tests/projects/reflection-tests/settings.gradle.kts @@ -1 +1,5 @@ +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" +} + rootProject.name = "reflection-tests" diff --git a/platform-tooling-support-tests/projects/reflection-tests/src/test/java/ReflectionTestCase.java b/platform-tooling-support-tests/projects/reflection-tests/src/test/java/ReflectionTestCase.java index 0692db43426b..a6df7c7dcfd7 100644 --- a/platform-tooling-support-tests/projects/reflection-tests/src/test/java/ReflectionTestCase.java +++ b/platform-tooling-support-tests/projects/reflection-tests/src/test/java/ReflectionTestCase.java @@ -17,6 +17,7 @@ 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; @@ -31,6 +32,11 @@ 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, diff --git a/platform-tooling-support-tests/projects/vintage/build.gradle.kts b/platform-tooling-support-tests/projects/vintage/build.gradle.kts index b06aca00e221..37bab7448d5b 100644 --- a/platform-tooling-support-tests/projects/vintage/build.gradle.kts +++ b/platform-tooling-support-tests/projects/vintage/build.gradle.kts @@ -9,15 +9,25 @@ repositories { mavenCentral() } +val platformVersion: String by project +val vintageVersion: String by project + dependencies { val junit4Version = System.getProperty("junit4Version", "4.12") testImplementation("junit:junit:$junit4Version") - val vintageVersion = System.getenv("JUNIT_VINTAGE_VERSION") ?: "5.3.2" testImplementation("org.junit.vintage:junit-vintage-engine:$vintageVersion") { exclude(group = "junit") because("we want to override it to test against different versions") } + + testRuntimeOnly("org.junit.platform:junit-platform-launcher:$platformVersion") +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(8) + } } tasks.test { diff --git a/platform-tooling-support-tests/projects/vintage/gradle.properties b/platform-tooling-support-tests/projects/vintage/gradle.properties new file mode 100644 index 000000000000..79b428ebc70c --- /dev/null +++ b/platform-tooling-support-tests/projects/vintage/gradle.properties @@ -0,0 +1 @@ +org.gradle.java.installations.fromEnv=JDK8 diff --git a/platform-tooling-support-tests/projects/vintage/pom.xml b/platform-tooling-support-tests/projects/vintage/pom.xml index e87d42d8d728..164570994740 100644 --- a/platform-tooling-support-tests/projects/vintage/pom.xml +++ b/platform-tooling-support-tests/projects/vintage/pom.xml @@ -11,14 +11,13 @@ UTF-8 1.8 ${maven.compiler.source} - ${env.JUNIT_VINTAGE_VERSION} org.junit.vintage junit-vintage-engine - ${vintageVersion} + ${junit.vintage.version} test diff --git a/platform-tooling-support-tests/projects/vintage/settings.gradle.kts b/platform-tooling-support-tests/projects/vintage/settings.gradle.kts index f073959d096e..3674c8832dca 100644 --- a/platform-tooling-support-tests/projects/vintage/settings.gradle.kts +++ b/platform-tooling-support-tests/projects/vintage/settings.gradle.kts @@ -1 +1,5 @@ +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" +} + rootProject.name = "vintage" diff --git a/platform-tooling-support-tests/src/main/java/platform/tooling/support/Helper.java b/platform-tooling-support-tests/src/main/java/platform/tooling/support/Helper.java index 4bb2c6c73ad6..9f5d3f08d1ee 100644 --- a/platform-tooling-support-tests/src/main/java/platform/tooling/support/Helper.java +++ b/platform-tooling-support-tests/src/main/java/platform/tooling/support/Helper.java @@ -10,23 +10,15 @@ package platform.tooling.support; -import java.io.IOException; -import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.time.Duration; -import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Properties; -import java.util.function.Consumer; -import java.util.function.Predicate; -import java.util.jar.JarFile; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; import java.util.stream.Stream; /** @@ -34,8 +26,6 @@ */ public class Helper { - public static final Duration TOOL_TIMEOUT = Duration.ofMinutes(3); - private static final Path ROOT = Paths.get(".."); private static final Path GRADLE_PROPERTIES = ROOT.resolve("gradle.properties"); private static final Path SETTINGS_GRADLE = ROOT.resolve("settings.gradle.kts"); @@ -64,56 +54,21 @@ public static String version(String module) { throw new AssertionError("Unknown module: " + module); } - 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"; - } - - public static String replaceVersionPlaceholders(String line) { - line = line.replace("${jupiterVersion}", version("junit-jupiter")); - line = line.replace("${vintageVersion}", version("junit-vintage")); - line = line.replace("${platformVersion}", version("junit-platform")); - return line; - } - public static List loadModuleDirectoryNames() { var moduleLinePattern = Pattern.compile("include\\(\"(.+)\"\\)"); - try (var stream = Files.lines(SETTINGS_GRADLE) // - .map(moduleLinePattern::matcher) // - .filter(Matcher::matches) // - .map(matcher -> matcher.group(1)) // - .filter(name -> name.startsWith("junit-")) // - .filter(name -> !name.equals("junit-bom")) // - .filter(name -> !name.equals("junit-platform-console-standalone"))) { - return stream.collect(Collectors.toList()); + 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 -> !name.equals("junit-bom")) // + .filter(name -> !name.equals("junit-platform-console-standalone")).toList(); } catch (Exception e) { throw new AssertionError("loading module directory names failed: " + SETTINGS_GRADLE); } } - static JarFile createJarFile(String module) { - var path = MavenRepo.jar(module); - try { - return new JarFile(path.toFile()); - } - catch (IOException e) { - throw new UncheckedIOException("Creating JarFile for '" + path + "' failed.", e); - } - } - - public static List loadJarFiles() { - return loadModuleDirectoryNames().stream().map(Helper::createJarFile).collect(Collectors.toList()); - } - public static Optional getJavaHome(String version) { // First, try various system sources... var sources = Stream.of( // @@ -127,32 +82,4 @@ public static Optional getJavaHome(String version) { ); return sources.filter(Objects::nonNull).findFirst().map(Path::of); } - - /** Load, here copy, modular jar files to the given target directory. */ - public static void loadAllJUnitModules(Path target) throws Exception { - for (var module : loadModuleDirectoryNames()) { - var jar = MavenRepo.jar(module); - Files.copy(jar, target.resolve(jar.getFileName())); - } - } - - /** Walk directory tree structure. */ - public static List treeWalk(Path root) { - var lines = new ArrayList(); - treeWalk(root, lines::add); - return lines; - } - - /** Walk directory tree structure. */ - public 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); - } - } } diff --git a/platform-tooling-support-tests/src/main/java/platform/tooling/support/MavenRepo.java b/platform-tooling-support-tests/src/main/java/platform/tooling/support/MavenRepo.java index 2ba22ebeb8cc..84e47bfd98b8 100644 --- a/platform-tooling-support-tests/src/main/java/platform/tooling/support/MavenRepo.java +++ b/platform-tooling-support-tests/src/main/java/platform/tooling/support/MavenRepo.java @@ -45,7 +45,7 @@ public static Path pom(String artifactId) { private static Path artifact(String artifactId, Predicate fileNamePredicate) { var parentDir = dir() // - .resolve(Helper.groupId(artifactId).replace('.', File.separatorChar)) // + .resolve(groupId(artifactId).replace('.', File.separatorChar)) // .resolve(artifactId) // .resolve(Helper.version(artifactId)); try (var files = Files.list(parentDir)) { @@ -57,4 +57,16 @@ private static Path artifact(String artifactId, Predicate fileNamePredic } } + 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"; + } } diff --git a/platform-tooling-support-tests/src/main/java/platform/tooling/support/ProcessStarters.java b/platform-tooling-support-tests/src/main/java/platform/tooling/support/ProcessStarters.java new file mode 100644 index 000000000000..2fa7e4c94e70 --- /dev/null +++ b/platform-tooling-support-tests/src/main/java/platform/tooling/support/ProcessStarters.java @@ -0,0 +1,72 @@ +/* + * Copyright 2015-2024 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms 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("-PjupiterVersion=" + Helper.version("junit-jupiter")) // + .addArguments("-PvintageVersion=" + Helper.version("junit-vintage")) // + .addArguments("-PplatformVersion=" + Helper.version("junit-platform")); + } + + public static ProcessStarter maven() { + return new ProcessStarter() // + .executable(Path.of(System.getProperty("mavenDistribution")).resolve("bin").resolve( + windowsOrOtherExecutable("mvn.cmd", "mvn")).toAbsolutePath()) // + .addArguments("-Djunit.jupiter.version=" + Helper.version("junit-jupiter")) // + .addArguments("-Djunit.bom.version=" + Helper.version("junit-jupiter")) // + .addArguments("-Djunit.vintage.version=" + Helper.version("junit-vintage")) // + .addArguments("-Djunit.platform.version=" + Helper.version("junit-platform")); + } + + private static String windowsOrOtherExecutable(String cmdOrExe, String other) { + return OS.current() == OS.WINDOWS ? cmdOrExe : other; + } + + public static Optional getGradleJavaHome() { + return Helper.getJavaHome(System.getProperty("gradle.java.version")); + } +} diff --git a/platform-tooling-support-tests/src/main/java/platform/tooling/support/Request.java b/platform-tooling-support-tests/src/main/java/platform/tooling/support/Request.java deleted file mode 100644 index 3ccaac91edee..000000000000 --- a/platform-tooling-support-tests/src/main/java/platform/tooling/support/Request.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright 2015-2024 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms 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.FileFilter; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.time.Duration; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Stream; - -import de.sormuras.bartholdy.Configuration; -import de.sormuras.bartholdy.Result; -import de.sormuras.bartholdy.Tool; -import de.sormuras.bartholdy.tool.Maven; - -import org.apache.commons.io.FileUtils; - -/** - * @since 1.3 - */ -public class Request { - - public static final Path PROJECTS = Paths.get("projects"); - private static final Path TOOLS = Paths.get("build", "test-tools"); - public static final Path WORKSPACE = Paths.get("build", "test-workspace"); - - public static Builder builder() { - return new Builder(); - } - - public static Maven maven() { - return new Maven(Path.of(System.getProperty("mavenDistribution"))); - } - - private Tool tool; - private String project; - private String workspace; - private List arguments = new ArrayList<>(); - private Map environment = new HashMap<>(); - private FileFilter copyProjectToWorkspaceFileFilter; - private Duration timeout = Duration.ofMinutes(1); - - public String getProject() { - return project; - } - - public FileFilter getCopyProjectToWorkspaceFileFilter() { - return copyProjectToWorkspaceFileFilter; - } - - public String getWorkspace() { - return workspace; - } - - public Map getEnvironment() { - return environment; - } - - public List getArguments() { - return arguments; - } - - public Duration getTimeout() { - return timeout; - } - - public Result run() { - return run(true); - } - - public Result run(boolean cleanWorkspace) { - try { - // sanity check - if (!Files.isDirectory(PROJECTS)) { - var cwd = Paths.get(".").normalize().toAbsolutePath(); - throw new IllegalStateException("Directory " + PROJECTS + " not found in: " + cwd); - } - - Files.createDirectories(TOOLS); - Files.createDirectories(WORKSPACE); - - var workspace = WORKSPACE.resolve(getWorkspace()); - if (cleanWorkspace) { - FileUtils.deleteQuietly(workspace.toFile()); - var project = PROJECTS.resolve(getProject()); - if (Files.isDirectory(project)) { - var filter = getCopyProjectToWorkspaceFileFilter(); - FileUtils.copyDirectory(project.toFile(), workspace.toFile(), filter); - } - } - - var configuration = Configuration.builder(); - configuration.setArguments(getArguments()); - configuration.setWorkingDirectory(workspace); - configuration.setTimeout(getTimeout()); - configuration.getEnvironment().putAll(getEnvironment()); - - var result = tool.run(configuration.build()); - System.out.println(result.getOutput("out")); - System.err.println(result.getOutput("err")); - return result; - } - catch (Exception e) { - throw new IllegalStateException("run failed", e); - } - } - - public static class Builder { - - private final Request request = new Request(); - - public Request build() { - if (request.project == null) { - throw new IllegalStateException("project must not be null"); - } - if (request.workspace == null) { - request.workspace = request.project; - } - buildEnvironment(request.environment); - request.arguments = List.copyOf(request.arguments); - request.environment = Map.copyOf(request.environment); - return request; - } - - private void buildEnvironment(Map environment) { - environment.computeIfAbsent("JUNIT_JUPITER_VERSION", key -> Helper.version("junit-jupiter")); - environment.computeIfAbsent("JUNIT_VINTAGE_VERSION", key -> Helper.version("junit-vintage")); - environment.computeIfAbsent("JUNIT_PLATFORM_VERSION", key -> Helper.version("junit-platform")); - } - - public Builder setTool(Tool tool) { - request.tool = tool; - return this; - } - - public Builder setJavaHome(Path javaHome) { - return putEnvironment("JAVA_HOME", javaHome.normalize().toAbsolutePath().toString()); - } - - public Builder setProject(String project) { - request.project = project; - return this; - } - - public Builder setProjectToWorkspaceCopyFileFilter(FileFilter copyProjectToWorkspaceFileFilter) { - request.copyProjectToWorkspaceFileFilter = copyProjectToWorkspaceFileFilter; - return this; - } - - public Builder setWorkspace(String workspace) { - request.workspace = workspace; - return this; - } - - public Builder addArguments(Object... arguments) { - Stream.of(arguments).map(Object::toString).forEach(request.arguments::add); - return this; - } - - public Builder putEnvironment(String key, String value) { - request.environment.put(key, value); - return this; - } - - public Builder setTimeout(Duration timeout) { - request.timeout = timeout; - return this; - } - } -} diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/AntStarterTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/AntStarterTests.java index cfcda9130c05..b57d24f4f272 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/AntStarterTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/AntStarterTests.java @@ -11,41 +11,35 @@ package platform.tooling.support.tests; 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 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.sormuras.bartholdy.tool.Java; - import org.apache.tools.ant.Main; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.io.TempDir; -import platform.tooling.support.Request; +import platform.tooling.support.ProcessStarters; /** * @since 1.3 */ class AntStarterTests { - @ResourceLock(Projects.ANT_STARTER) @Test - void ant_starter() { - var request = Request.builder() // - .setTool(new Java()) // - .setProject(Projects.ANT_STARTER) // + @Timeout(60) + void ant_starter(@TempDir Path workspace) throws Exception { + var result = ProcessStarters.java() // + .workingDir(copyToWorkspace(Projects.ANT_STARTER, workspace)) // .addArguments("-cp", System.getProperty("antJars"), Main.class.getName()) // - .addArguments("-verbose") // - .build(); - - var result = request.run(); + .startAndWait(); - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); - - assertEquals(0, result.getExitCode()); - assertEquals("", result.getOutput("err"), "error log isn't empty"); + assertEquals(0, result.exitCode()); + assertEquals("", result.stdErr(), "error log isn't empty"); assertLinesMatch(List.of(">> HEAD >>", // "test.junit.launcher:", // ">>>>", // @@ -58,9 +52,10 @@ void ant_starter() { " \\[java\\] \\[ 5 tests successful \\]", // " \\[java\\] \\[ 0 tests failed \\]", // ">> TAIL >>"), // - result.getOutputLines("out")); + result.stdOutLines()); - var testResultsDir = Request.WORKSPACE.resolve(request.getWorkspace()).resolve("build/test-report"); + var testResultsDir = workspace.resolve("build/test-report"); verifyContainsExpectedStartedOpenTestReport(testResultsDir); } + } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ArchUnitTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ArchUnitTests.java index 21c7e2076f30..54c297c863c8 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ArchUnitTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ArchUnitTests.java @@ -26,9 +26,11 @@ import static com.tngtech.archunit.lang.conditions.ArchPredicates.have; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices; +import static java.util.stream.Collectors.toSet; import static org.junit.jupiter.api.Assertions.assertTrue; -import static platform.tooling.support.Helper.loadJarFiles; +import java.io.IOException; +import java.io.UncheckedIOException; import java.lang.annotation.Annotation; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; @@ -36,7 +38,8 @@ import java.util.Arrays; import java.util.Set; import java.util.function.BiPredicate; -import java.util.stream.Collectors; +import java.util.jar.JarFile; +import java.util.stream.Stream; import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.core.domain.JavaClass; @@ -51,6 +54,9 @@ import org.apiguardian.api.API; +import platform.tooling.support.Helper; +import platform.tooling.support.MavenRepo; + @AnalyzeClasses(locations = ArchUnitTests.AllJars.class) class ArchUnitTests { @@ -133,9 +139,22 @@ static class AllJars implements LocationProvider { @Override public Set get(Class testClass) { - return loadJarFiles().stream().map(Location::of).collect(Collectors.toSet()); + return loadJarFiles().map(Location::of).collect(toSet()); + } + + private static Stream loadJarFiles() { + return Helper.loadModuleDirectoryNames().stream().map(AllJars::createJarFile); } + private static JarFile createJarFile(String module) { + var path = MavenRepo.jar(module); + try { + return new JarFile(path.toFile()); + } + catch (IOException e) { + throw new UncheckedIOException("Creating JarFile for '" + path + "' failed.", e); + } + } } private static class RepeatableAnnotationPredicate extends DescribedPredicate { diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java index 349d5d520b4d..b9f76e059010 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java @@ -10,23 +10,22 @@ 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 org.junit.jupiter.api.Assertions.assertFalse; +import static platform.tooling.support.tests.Projects.copyToWorkspace; -import java.nio.file.Paths; -import java.time.Duration; - -import de.sormuras.bartholdy.tool.GradleWrapper; +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.parallel.ResourceLock; +import org.junit.jupiter.api.io.TempDir; import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; +import platform.tooling.support.ProcessStarters; /** * @since 1.9.1 @@ -36,23 +35,18 @@ @EnabledIfEnvironmentVariable(named = "GRAALVM_HOME", matches = ".+") class GraalVmStarterTests { - @ResourceLock(Projects.GRAALVM_STARTER) @Test - void runsTestsInNativeImage() { - var request = Request.builder() // - .setTool(new GradleWrapper(Paths.get(".."))) // - .setProject(Projects.GRAALVM_STARTER) // + @Timeout(value = 10, unit = MINUTES) + void runsTestsInNativeImage(@TempDir Path workspace) 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") // - .setTimeout(Duration.ofMinutes(10)) // - .build(); - - var result = request.run(); - - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); + .addArguments("javaToolchains", "nativeTest", "--no-daemon", "--stacktrace", "--no-build-cache", + "--warning-mode=fail") // + .startAndWait(); - assertEquals(0, result.getExitCode()); - assertThat(result.getOutputLines("out")) // + 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 > test() SUCCESSFUL")) // diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleKotlinExtensionsTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleKotlinExtensionsTests.java index fee5e16082d0..042a06b066cc 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleKotlinExtensionsTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleKotlinExtensionsTests.java @@ -11,43 +11,34 @@ package platform.tooling.support.tests; 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 platform.tooling.support.Helper.TOOL_TIMEOUT; +import static platform.tooling.support.tests.Projects.copyToWorkspace; -import java.nio.file.Paths; - -import de.sormuras.bartholdy.tool.GradleWrapper; +import java.nio.file.Path; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.io.TempDir; import org.opentest4j.TestAbortedException; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; +import platform.tooling.support.ProcessStarters; /** * @since 1.3 */ class GradleKotlinExtensionsTests { - @ResourceLock(Projects.GRADLE_KOTLIN_EXTENSIONS) @Test - void gradle_wrapper() { - var result = Request.builder() // - .setTool(new GradleWrapper(Paths.get(".."))) // - .setProject(Projects.GRADLE_KOTLIN_EXTENSIONS) // + void gradle_wrapper(@TempDir Path workspace) 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") // - .setTimeout(TOOL_TIMEOUT) // - .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // - .build() // - .run(); - - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); + .addArguments("build", "--no-daemon", "--stacktrace", "--no-build-cache", "--warning-mode=fail") // + .putEnvironment("JDK8", Helper.getJavaHome("8").orElseThrow(TestAbortedException::new).toString()) // + .startAndWait(); - assertEquals(0, result.getExitCode(), "result=" + result); - assertTrue(result.getOutputLines("out").stream().anyMatch(line -> line.contains("BUILD SUCCESSFUL"))); + assertEquals(0, result.exitCode(), "result=" + result); + assertTrue(result.stdOut().lines().anyMatch(line -> line.contains("BUILD SUCCESSFUL"))); } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleMissingEngineTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleMissingEngineTests.java index 69204b5a2a46..6c51f84c5f71 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleMissingEngineTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleMissingEngineTests.java @@ -10,58 +10,41 @@ 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.assertFalse; -import static org.junit.jupiter.api.Assertions.assertLinesMatch; -import static platform.tooling.support.Helper.TOOL_TIMEOUT; +import static platform.tooling.support.tests.Projects.copyToWorkspace; -import java.nio.file.Paths; -import java.util.List; - -import de.sormuras.bartholdy.Tool; -import de.sormuras.bartholdy.tool.GradleWrapper; +import java.nio.file.Path; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.io.TempDir; +import org.junit.platform.reporting.testutil.FileUtils; import org.opentest4j.TestAbortedException; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; +import platform.tooling.support.ProcessStarters; /** * @since 1.3 */ class GradleMissingEngineTests { - @ResourceLock(Projects.GRADLE_MISSING_ENGINE) @Test - void gradle_wrapper() { - test(new GradleWrapper(Paths.get(".."))); - } - - private void test(Tool gradle) { - var result = Request.builder() // - .setProject(Projects.GRADLE_MISSING_ENGINE) // - .setTool(gradle) // + void gradle_wrapper(@TempDir Path workspace) throws Exception { + var result = ProcessStarters.gradlew() // + .workingDir(copyToWorkspace(Projects.GRADLE_MISSING_ENGINE, workspace)) // .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // - .addArguments("build", "--no-daemon", "--debug", "--stacktrace", "--no-build-cache") // - .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // - .setTimeout(TOOL_TIMEOUT).build() // - .run(); + .addArguments("build", "--no-daemon", "--stacktrace", "--no-build-cache", "--warning-mode=fail") // + .putEnvironment("JDK8", Helper.getJavaHome("8").orElseThrow(TestAbortedException::new).toString()) // + .startAndWait(); - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); + assertEquals(1, result.exitCode()); + assertThat(result.stdErrLines()) // + .contains("FAILURE: Build failed with an exception."); - assertEquals(1, result.getExitCode()); - assertLinesMatch(List.of( // - ">> HEAD >>", // - ".+DEBUG.+Cannot create Launcher without at least one TestEngine.+", // - ">> TAIL >>"), // - result.getOutputLines("out")); - assertLinesMatch(List.of( // - ">> HEAD >>", // - ".+ERROR.+FAILURE: Build failed with an exception.", // - ">> TAIL >>"), // - result.getOutputLines("err")); + var htmlFile = FileUtils.findPath(workspace, "glob:**/build/reports/tests/test/classes/*.html"); + assertThat(htmlFile).content() // + .contains("Cannot create Launcher without at least one TestEngine"); } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleStarterTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleStarterTests.java index 38c400ed79e8..e2d9297dbd2a 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleStarterTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleStarterTests.java @@ -12,49 +12,39 @@ 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 platform.tooling.support.Helper.TOOL_TIMEOUT; +import static platform.tooling.support.tests.Projects.copyToWorkspace; import static platform.tooling.support.tests.XmlAssertions.verifyContainsExpectedStartedOpenTestReport; -import java.nio.file.Paths; - -import de.sormuras.bartholdy.tool.GradleWrapper; +import java.nio.file.Path; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.io.TempDir; import org.opentest4j.TestAbortedException; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; +import platform.tooling.support.ProcessStarters; /** * @since 1.3 */ class GradleStarterTests { - @ResourceLock(Projects.GRADLE_STARTER) @Test - void gradle_wrapper() { - var request = Request.builder() // - .setTool(new GradleWrapper(Paths.get(".."))) // - .setProject(Projects.GRADLE_STARTER) // + void gradle_wrapper(@TempDir Path workspace) throws Exception { + var result = ProcessStarters.gradlew() // + .workingDir(copyToWorkspace(Projects.GRADLE_STARTER, workspace)) // .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // - .addArguments("build", "--no-daemon", "--stacktrace", "--no-build-cache") // - .setTimeout(TOOL_TIMEOUT) // - .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // - .build(); - - var result = request.run(); - - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); + .addArguments("build", "--no-daemon", "--stacktrace", "--no-build-cache", "--warning-mode=fail") // + .putEnvironment("JDK8", Helper.getJavaHome("8").orElseThrow(TestAbortedException::new).toString()) // + .startAndWait(); - assertEquals(0, result.getExitCode()); - assertTrue(result.getOutputLines("out").stream().anyMatch(line -> line.contains("BUILD SUCCESSFUL"))); - assertThat(result.getOutput("out")).contains("Using Java version: 1.8"); + assertEquals(0, result.exitCode()); + assertTrue(result.stdOut().lines().anyMatch(line -> line.contains("BUILD SUCCESSFUL"))); + assertThat(result.stdOut()).contains("Using Java version: 1.8"); - var testResultsDir = Request.WORKSPACE.resolve(request.getWorkspace()).resolve("build/test-results/test"); + var testResultsDir = workspace.resolve("build/test-results/test"); verifyContainsExpectedStartedOpenTestReport(testResultsDir); } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarDescribeModuleTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarDescribeModuleTests.java index afb87c75534c..3bb8ca941ade 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarDescribeModuleTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarDescribeModuleTests.java @@ -11,26 +11,20 @@ package platform.tooling.support.tests; 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.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; +import static platform.tooling.support.tests.Projects.getSourceDirectory; import java.lang.module.ModuleFinder; import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; - -import de.sormuras.bartholdy.jdk.Jar; import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.parallel.ResourceLock; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; +import platform.tooling.support.ProcessStarters; /** * @since 1.3 @@ -38,32 +32,23 @@ @Order(Integer.MAX_VALUE) class JarDescribeModuleTests { - @ResourceLock(Projects.JAR_DESCRIBE_MODULE) @ParameterizedTest @MethodSource("platform.tooling.support.Helper#loadModuleDirectoryNames") void describeModule(String module) throws Exception { + var sourceDirectory = getSourceDirectory(Projects.JAR_DESCRIBE_MODULE); var modulePath = MavenRepo.jar(module); - var result = Request.builder() // - .setTool(new Jar()) // - .setProject(Projects.JAR_DESCRIBE_MODULE) // - .setProjectToWorkspaceCopyFileFilter(file -> file.getName().startsWith(module)) // - .setWorkspace("jar-describe-module/" + module) // - .addArguments("--describe-module", "--file", modulePath) // - .build() // - .run(); - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); + var result = ProcessStarters.javaCommand("jar") // + .workingDir(sourceDirectory) // + .addArguments("--describe-module", "--file", modulePath.toAbsolutePath().toString()) // + .startAndWait(); - assertEquals(0, result.getExitCode()); - assertEquals("", result.getOutput("err"), "error log isn't empty"); - var expected = Paths.get("build", "test-workspace", "jar-describe-module", module, module + ".expected.txt"); - if (Files.notExists(expected)) { - result.getOutputLines("out").forEach(System.err::println); - fail("No such file: " + expected); - } - var expectedLines = Files.lines(expected).map(Helper::replaceVersionPlaceholders).toList(); - var origin = Path.of("projects", "jar-describe-module", module + ".expected.txt").toUri(); - assertLinesMatch(expectedLines, result.getOutputLines("out"), () -> String.format("%s\nError", origin)); + assertEquals(0, result.exitCode()); + assertEquals("", result.stdErr(), "error log isn't empty"); + + var expectedLines = replaceVersionPlaceholders( + Files.readString(sourceDirectory.resolve(module + ".expected.txt")).trim()); + assertLinesMatch(expectedLines.lines().toList(), result.stdOut().trim().lines().toList()); } @ParameterizedTest @@ -77,4 +62,11 @@ void packageNamesStartWithNameOfTheModule(String module) { } } + private static String replaceVersionPlaceholders(String line) { + line = line.replace("${jupiterVersion}", Helper.version("junit-jupiter")); + line = line.replace("${vintageVersion}", Helper.version("junit-vintage")); + line = line.replace("${platformVersion}", Helper.version("junit-platform")); + return line; + } + } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JavaVersionsTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JavaVersionsTests.java index 12528bddb994..bd487d2289aa 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JavaVersionsTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JavaVersionsTests.java @@ -11,22 +11,21 @@ package platform.tooling.support.tests; 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.Assumptions.assumeTrue; -import static platform.tooling.support.Helper.TOOL_TIMEOUT; +import static platform.tooling.support.ProcessStarters.currentJdkHome; +import static platform.tooling.support.tests.Projects.copyToWorkspace; import java.nio.file.Path; import java.util.List; import java.util.Map; -import de.sormuras.bartholdy.tool.Java; - import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; +import platform.tooling.support.ProcessStarters; /** * @since 1.4 @@ -36,39 +35,37 @@ class JavaVersionsTests { @GlobalResource LocalMavenRepo localMavenRepo; + @TempDir + Path workspace; + @Test - void java_8() { + void java_8() throws Exception { var java8Home = Helper.getJavaHome("8"); assumeTrue(java8Home.isPresent(), "Java 8 installation directory not found!"); - var actualLines = execute("8", java8Home.get(), Map.of()); + var actualLines = execute(java8Home.get(), Map.of()); assertTrue(actualLines.contains("[WARNING] Tests run: 2, Failures: 0, Errors: 0, Skipped: 1")); } @Test - void java_default() { - var actualLines = execute("default", new Java().getHome(), MavenEnvVars.FOR_JDK24_AND_LATER); + void java_default() throws Exception { + var actualLines = execute(currentJdkHome(), MavenEnvVars.FOR_JDK24_AND_LATER); assertTrue(actualLines.contains("[WARNING] Tests run: 2, Failures: 0, Errors: 0, Skipped: 1")); } - List execute(String version, Path javaHome, Map environmentVars) { - var builder = Request.builder() // - .setTool(Request.maven()) // - .setProject(Projects.JAVA_VERSIONS) // - .setWorkspace("java-versions-" + version) // + List execute(Path javaHome, Map environmentVars) throws Exception { + var result = ProcessStarters.maven() // + .workingDir(copyToWorkspace(Projects.JAVA_VERSIONS, workspace)) // + .putEnvironment("JAVA_HOME", javaHome) // + .putEnvironment(environmentVars) // .addArguments(localMavenRepo.toCliArgument(), "-Dmaven.repo=" + MavenRepo.dir()) // .addArguments("--update-snapshots", "--batch-mode", "verify") // - .setTimeout(TOOL_TIMEOUT) // - .setJavaHome(javaHome); - environmentVars.forEach(builder::putEnvironment); - - var result = builder.build().run(); + .startAndWait(); - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); - assertEquals(0, result.getExitCode()); - assertEquals("", result.getOutput("err")); - return result.getOutputLines("out"); + assertEquals(0, result.exitCode()); + assertEquals("", result.stdErr()); + return result.stdOutLines(); } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java index d69df0020425..88022bd095fd 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java @@ -12,18 +12,19 @@ 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 platform.tooling.support.Helper.TOOL_TIMEOUT; +import static platform.tooling.support.tests.Projects.copyToWorkspace; import static platform.tooling.support.tests.XmlAssertions.verifyContainsExpectedStartedOpenTestReport; +import java.nio.file.Path; + import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.io.TempDir; import org.opentest4j.TestAbortedException; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; +import platform.tooling.support.ProcessStarters; /** * @since 1.3 @@ -36,30 +37,23 @@ class MavenStarterTests { @GlobalResource MavenRepoProxy mavenRepoProxy; - @ResourceLock(Projects.MAVEN_STARTER) @Test - void verifyMavenStarterProject() { - var request = Request.builder() // - .setTool(Request.maven()) // - .setProject(Projects.MAVEN_STARTER) // + void verifyMavenStarterProject(@TempDir Path workspace) throws Exception { + var result = ProcessStarters.maven() // + .workingDir(copyToWorkspace(Projects.MAVEN_STARTER, workspace)) // .addArguments(localMavenRepo.toCliArgument(), "-Dmaven.repo=" + MavenRepo.dir()) // .addArguments("-Dsnapshot.repo.url=" + mavenRepoProxy.getBaseUri()) // .addArguments("--update-snapshots", "--batch-mode", "verify") // - .setTimeout(TOOL_TIMEOUT) // - .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // - .build(); - - var result = request.run(); - - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); + .putEnvironment("JAVA_HOME", Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // + .startAndWait(); - assertEquals(0, result.getExitCode()); - assertEquals("", result.getOutput("err")); - assertTrue(result.getOutputLines("out").contains("[INFO] BUILD SUCCESS")); - assertTrue(result.getOutputLines("out").contains("[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0")); - assertThat(result.getOutput("out")).contains("Using Java version: 1.8"); + assertEquals(0, result.exitCode()); + assertEquals("", result.stdErr()); + assertTrue(result.stdOutLines().contains("[INFO] BUILD SUCCESS")); + assertTrue(result.stdOutLines().contains("[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0")); + assertThat(result.stdOut()).contains("Using Java version: 1.8"); - var testResultsDir = Request.WORKSPACE.resolve(request.getWorkspace()).resolve("target/surefire-reports"); + var testResultsDir = workspace.resolve("target/surefire-reports"); verifyContainsExpectedStartedOpenTestReport(testResultsDir); } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenSurefireCompatibilityTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenSurefireCompatibilityTests.java index 4188637a2744..59baf7e3d9e8 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenSurefireCompatibilityTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenSurefireCompatibilityTests.java @@ -12,21 +12,20 @@ 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 platform.tooling.support.Helper.TOOL_TIMEOUT; +import static platform.tooling.support.tests.Projects.copyToWorkspace; -import java.io.IOException; import java.nio.file.Files; +import java.nio.file.Path; -import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.opentest4j.TestAbortedException; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; +import platform.tooling.support.ProcessStarters; /** * @since 1.9.2 @@ -36,37 +35,31 @@ class MavenSurefireCompatibilityTests { @GlobalResource LocalMavenRepo localMavenRepo; - @ResourceLock(Projects.MAVEN_SUREFIRE_COMPATIBILITY) @ParameterizedTest @CsvSource(delimiter = '|', nullValues = "", textBlock = """ 2.22.2 | --activate-profiles=manual-platform-dependency 3.0.0-M4 | """) - void testMavenSurefireCompatibilityProject(String surefireVersion, String extraArg) throws IOException { - var extraArgs = extraArg == null ? new Object[0] : new Object[] { extraArg }; - var request = Request.builder() // - .setTool(Request.maven()) // - .setProject(Projects.MAVEN_SUREFIRE_COMPATIBILITY) // + void testMavenSurefireCompatibilityProject(String surefireVersion, String extraArg, @TempDir Path workspace) + throws Exception { + var extraArgs = extraArg == null ? new String[0] : new String[] { extraArg }; + var result = ProcessStarters.maven() // + .workingDir(copyToWorkspace(Projects.MAVEN_SUREFIRE_COMPATIBILITY, workspace)) // .addArguments(localMavenRepo.toCliArgument(), "-Dmaven.repo=" + MavenRepo.dir()) // .addArguments("-Dsurefire.version=" + surefireVersion) // .addArguments("--update-snapshots", "--batch-mode", "test") // .addArguments(extraArgs) // - .setTimeout(TOOL_TIMEOUT) // - .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // - .build(); + .putEnvironment("JAVA_HOME", Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // + .startAndWait(); - var result = request.run(); + assertEquals(0, result.exitCode()); + assertEquals("", result.stdErr()); - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); - - assertEquals(0, result.getExitCode()); - assertEquals("", result.getOutput("err")); - - var output = result.getOutputLines("out"); + 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 = Request.WORKSPACE.resolve(request.getWorkspace()).resolve("target"); + 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); diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularUserGuideTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularUserGuideTests.java index 5f3f6190d069..7aa591963757 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularUserGuideTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularUserGuideTests.java @@ -13,7 +13,7 @@ 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.Assertions.fail; +import static platform.tooling.support.Helper.loadModuleDirectoryNames; import java.io.File; import java.io.PrintWriter; @@ -23,14 +23,17 @@ 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.codehaus.groovy.runtime.ProcessGroovyMethods; 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 platform.tooling.support.Helper; +import platform.tooling.support.MavenRepo; +import platform.tooling.support.ProcessStarters; import platform.tooling.support.ThirdPartyJars; /** @@ -89,7 +92,7 @@ private static List compile(Path temp, Writer out, Writer err) throws Ex ThirdPartyJars.copy(lib, "org.opentest4j.reporting", "open-test-reporting-tooling-spi"); ThirdPartyJars.copy(lib, "com.google.jimfs", "jimfs"); ThirdPartyJars.copy(lib, "com.google.guava", "guava"); - Helper.loadAllJUnitModules(lib); + loadAllJUnitModules(lib); args.add("--module-path"); args.add(lib.toString()); @@ -118,61 +121,32 @@ private static List compile(Path temp, Writer out, Writer err) throws Ex return args; } - private static void junit(Path temp, Writer out, Writer err) throws Exception { - var command = new ArrayList(); - var projectDir = Path.of("../documentation"); - command.add(Path.of(System.getProperty("java.home"), "bin", "java").toString()); - - command.add("-XX:StartFlightRecording:filename=" + temp.resolve("user-guide.jfr")); - - command.add("--show-version"); - command.add("--show-module-resolution"); - - command.add("--module-path"); - command.add(String.join(File.pathSeparator, // - temp.resolve("destination").toString(), // - temp.resolve("lib").toString() // - )); - - command.add("--add-modules"); - command.add("documentation"); - - // TODO This `patch-module` should work! Why doesn't it? - // command.add("--patch-module"); - // command.add("documentation=../documentation/src/test/resources/"); - Files.copy(projectDir.resolve("src/test/resources/two-column.csv"), - temp.resolve("destination/documentation/two-column.csv")); - - command.add("--module"); - command.add("org.junit.platform.console"); - - command.add("execute"); - command.add("--scan-modules"); - - command.add("--config"); - command.add("enableHttpServer=true"); - - command.add("--fail-if-no-tests"); - command.add("--include-classname"); - command.add(".*Tests"); - command.add("--include-classname"); - command.add(".*Demo"); - command.add("--exclude-tag"); - command.add("exclude"); - - // System.out.println("______________"); - // command.forEach(System.out::println); - - var builder = new ProcessBuilder(command).directory(projectDir.toFile()); - var java = builder.start(); - ProcessGroovyMethods.waitForProcessOutput(java, out, err); - var code = java.exitValue(); - - if (code != 0) { - System.out.println(out); - System.err.println(err); - fail("Unexpected exit code: " + code); - } + private static void junit(Path temp) 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("--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") // + .startAndWait(); + + assertEquals(0, result.exitCode()); } @Test @@ -184,7 +158,7 @@ void runTestsFromUserGuideWithinModularBoundaries(@TempDir Path temp) throws Exc // args.forEach(System.out::println); assertTrue(err.toString().isBlank(), () -> err + "\n\n" + String.join("\n", args)); - var listing = Helper.treeWalk(temp); + var listing = treeWalk(temp); assertLinesMatch(List.of( // "destination", // ">> CLASSES >>", // @@ -205,7 +179,32 @@ void runTestsFromUserGuideWithinModularBoundaries(@TempDir Path temp) throws Exc // System.out.println("______________"); // listing.forEach(System.out::println); - junit(temp, out, err); + junit(temp); + } + + 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); + } } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MultiReleaseJarTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MultiReleaseJarTests.java index 434c4f6bfe53..6f6ea1112d8c 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MultiReleaseJarTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MultiReleaseJarTests.java @@ -14,20 +14,17 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertLinesMatch; import static org.junit.jupiter.api.Assertions.assertTrue; -import static platform.tooling.support.Helper.TOOL_TIMEOUT; +import static platform.tooling.support.tests.Projects.copyToWorkspace; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; -import java.util.Map; - -import de.sormuras.bartholdy.Result; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.io.TempDir; import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; +import platform.tooling.support.ProcessStarters; /** * @since 1.4 @@ -40,10 +37,8 @@ class MultiReleaseJarTests { @GlobalResource MavenRepoProxy mavenRepoProxy; - @ResourceLock(Projects.MULTI_RELEASE_JAR) @Test - void checkDefault() throws Exception { - var variant = "default"; + void checkDefault(@TempDir Path workspace) throws Exception { var expectedLines = List.of( // ">> BANNER >>", // ".", // @@ -74,38 +69,25 @@ void checkDefault() throws Exception { "" // ); - var result = mvn(variant); - - result.getOutputLines("out").forEach(System.out::println); - result.getOutputLines("err").forEach(System.err::println); - - assertEquals(0, result.getExitCode()); - assertEquals("", result.getOutput("err")); - assertTrue(result.getOutputLines("out").contains("[INFO] BUILD SUCCESS")); - - var workspace = Path.of("build/test-workspace/multi-release-jar", variant); - var actualLines = Files.readAllLines(workspace.resolve("target/junit-platform/console-launcher.out.log")); - assertLinesMatch(expectedLines, actualLines); - } - - private Result mvn(String variant) { - Map environmentVars = MavenEnvVars.FOR_JDK24_AND_LATER; - - var builder = Request.builder() // - .setTool(Request.maven()) // - .setProject(Projects.MULTI_RELEASE_JAR) // + var result = ProcessStarters.maven() // + .workingDir(copyToWorkspace(Projects.MULTI_RELEASE_JAR, workspace)) // .addArguments(localMavenRepo.toCliArgument(), "-Dmaven.repo=" + MavenRepo.dir()) // .addArguments("-Dsnapshot.repo.url=" + mavenRepoProxy.getBaseUri()) // - .addArguments("--update-snapshots", "--show-version", "--errors", "--batch-mode", "--file", variant, - "test") // - .setTimeout(TOOL_TIMEOUT); - environmentVars.forEach(builder::putEnvironment); + .addArguments("--update-snapshots", "--show-version", "--errors", "--batch-mode") // + .addArguments("test") // + .putEnvironment(MavenEnvVars.FOR_JDK24_AND_LATER) // + .startAndWait(); - var result = builder.build().run(); + assertEquals(0, result.exitCode()); + assertEquals("", result.stdErr()); - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); + var outputLines = result.stdOutLines(); + assertTrue(outputLines.contains("[INFO] BUILD SUCCESS")); + assertFalse(outputLines.contains("[WARNING] "), "Warning marker detected"); + assertFalse(outputLines.contains("[ERROR] "), "Error marker detected"); - return result; + var actualLines = Files.readAllLines(workspace.resolve("target/junit-platform/console-launcher.out.log")); + assertLinesMatch(expectedLines, actualLines); } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/PackageCyclesDetectionTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/PackageCyclesDetectionTests.java deleted file mode 100644 index 9b2126bec212..000000000000 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/PackageCyclesDetectionTests.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2015-2024 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms 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 de.sormuras.bartholdy.Configuration; -import de.sormuras.bartholdy.tool.CyclesDetector; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; - -import platform.tooling.support.MavenRepo; - -/** - * @since 1.3 - */ -class PackageCyclesDetectionTests { - - @ParameterizedTest - @MethodSource("platform.tooling.support.Helper#loadModuleDirectoryNames") - @Disabled("Need to pass --module-path...") - void moduleDoesNotContainCyclicPackageReferences(String module) { - var jar = MavenRepo.jar(module); - var result = new CyclesDetector(jar, this::ignore).run(Configuration.of()); - assertEquals(0, result.getExitCode(), "result=" + result); - } - - private boolean ignore(String source, String target) { - if (source.equals(target)) { - return true; - } - if (source.startsWith("org.junit.jupiter.params.shadow.com.univocity.parsers.")) { - return true; - } - //noinspection RedundantIfStatement - if (!target.startsWith("org.junit.")) { - return true; - } - return false; - } - -} diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/Projects.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/Projects.java index 87f8920d16a6..b2e64d2848df 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/Projects.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/Projects.java @@ -10,6 +10,11 @@ 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 ANT_STARTER = "ant-starter"; @@ -28,4 +33,13 @@ public class Projects { 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); + } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ReflectionCompatibilityTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ReflectionCompatibilityTests.java index 273a9a107111..993e8e6012eb 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ReflectionCompatibilityTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ReflectionCompatibilityTests.java @@ -12,45 +12,35 @@ 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 platform.tooling.support.Helper.TOOL_TIMEOUT; +import static platform.tooling.support.tests.Projects.copyToWorkspace; -import java.nio.file.Paths; - -import de.sormuras.bartholdy.tool.GradleWrapper; +import java.nio.file.Path; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.io.TempDir; import org.opentest4j.TestAbortedException; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; +import platform.tooling.support.ProcessStarters; /** * @since 1.11 */ class ReflectionCompatibilityTests { - @ResourceLock(Projects.REFLECTION_TESTS) @Test - void gradle_wrapper() { - var request = Request.builder() // - .setTool(new GradleWrapper(Paths.get(".."))) // - .setProject(Projects.REFLECTION_TESTS) // + void gradle_wrapper(@TempDir Path workspace) 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") // - .setTimeout(TOOL_TIMEOUT) // - .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // - .build(); - - var result = request.run(); - - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); + .addArguments("build", "--no-daemon", "--stacktrace", "--no-build-cache", "--warning-mode=fail") // + .putEnvironment("JDK8", Helper.getJavaHome("8").orElseThrow(TestAbortedException::new).toString()) // + .startAndWait(); - assertEquals(0, result.getExitCode()); - assertTrue(result.getOutputLines("out").stream().anyMatch(line -> line.contains("BUILD SUCCESSFUL"))); - assertThat(result.getOutput("out")).contains("Using Java version: 1.8"); + assertEquals(0, result.exitCode()); + assertTrue(result.stdOut().lines().anyMatch(line -> line.contains("BUILD SUCCESSFUL"))); + assertThat(result.stdOut()).contains("Using Java version: 1.8"); } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java index fcca116f5b83..20596d164e35 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java @@ -15,6 +15,10 @@ 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; @@ -25,31 +29,37 @@ import java.util.List; import java.util.stream.Stream; -import de.sormuras.bartholdy.Result; -import de.sormuras.bartholdy.jdk.Jar; -import de.sormuras.bartholdy.jdk.Javac; -import de.sormuras.bartholdy.tool.Java; - +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; 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.parallel.ResourceLock; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.api.parallel.Execution; +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.Request; +import platform.tooling.support.ProcessStarters; import platform.tooling.support.ThirdPartyJars; /** * @since 1.4 */ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@Execution(CONCURRENT) class StandaloneTests { - @ResourceLock(Projects.STANDALONE) + @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"); @@ -66,17 +76,15 @@ void jarFileWithoutCompiledModuleDescriptorClass() throws Exception { assertTrue(found.isEmpty(), jar + " must not contain any " + name + " files: " + found); } - @ResourceLock(Projects.STANDALONE) @Test - void listAllObservableEngines() { - var result = Request.builder() // - .setTool(new Java()) // - .setProject(Projects.STANDALONE) // - .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone")) // - .addArguments("engines", "--disable-ansi-colors", "--disable-banner").build() // - .run(false); + void listAllObservableEngines() 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") // + .startAndWait(); - assertEquals(0, result.getExitCode(), () -> getExitCodeMessage(result)); + assertEquals(0, result.exitCode()); var jupiterVersion = Helper.version("junit-jupiter-engine"); var suiteVersion = Helper.version("junit-platform-suite-engine"); @@ -86,22 +94,19 @@ void listAllObservableEngines() { junit-platform-suite (org.junit.platform:junit-platform-suite-engine:%s) junit-vintage (org.junit.vintage:junit-vintage-engine:%s) """.formatted(jupiterVersion, suiteVersion, vintageVersion).lines(), // - result.getOutput("out").lines()); + result.stdOut().lines()); } - @ResourceLock(Projects.STANDALONE) @Test - void printVersionViaJar() { - var result = Request.builder() // - .setTool(new Java()) // - .setProject(Projects.STANDALONE) // - .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone")) // + void printVersionViaJar() 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) - .build() // - .run(); + .startAndWait(); - assertEquals(0, result.getExitCode(), () -> getExitCodeMessage(result)); + assertEquals(0, result.exitCode()); var version = Helper.version("junit-platform-console"); assertLinesMatch(""" @@ -109,12 +114,11 @@ void printVersionViaJar() { JVM: .* OS: .* """.formatted(version).lines(), // - result.getOutputLines("out").stream()); + result.stdOut().lines()); } - @ResourceLock(Projects.STANDALONE) @Test - void printVersionViaModule() { + void printVersionViaModule() throws Exception { var junitJars = Stream.of("junit-platform-console", "junit-platform-reporting", "junit-platform-engine", "junit-platform-launcher", "junit-platform-commons") // .map(MavenRepo::jar); @@ -125,17 +129,15 @@ void printVersionViaModule() { var modulePath = Stream.concat(junitJars, thirdPartyJars) // .map(String::valueOf) // .collect(joining(File.pathSeparator)); - var result = Request.builder() // - .setTool(new Java()) // - .setProject(Projects.STANDALONE) // + 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) - .build() // - .run(); + .startAndWait(); - assertEquals(0, result.getExitCode(), () -> getExitCodeMessage(result)); + assertEquals(0, result.exitCode()); var version = Helper.version("junit-platform-console"); assertLinesMatch(""" @@ -143,49 +145,46 @@ void printVersionViaModule() { JVM: .* OS: .* """.formatted(version).lines(), // - result.getOutputLines("out").stream()); + result.stdOut().lines()); } - @ResourceLock(Projects.STANDALONE) @Test @Order(1) + @Execution(SAME_THREAD) void compile() throws Exception { - var workspace = Request.WORKSPACE.resolve(Projects.STANDALONE); - var result = Request.builder() // - .setTool(new Javac()) // - .setProject(Projects.STANDALONE) // + var result = ProcessStarters.javaCommand("javac") // + .workingDir(workspace) // .addArguments("-Xlint:-options") // .addArguments("--release", "8") // .addArguments("-proc:none") // - .addArguments("-d", workspace.resolve("bin")) // - .addArguments("--class-path", MavenRepo.jar("junit-platform-console-standalone")) // - .addArguments(workspace.resolve("src/standalone/JupiterIntegration.java")) // - .addArguments(workspace.resolve("src/standalone/JupiterParamsIntegration.java")) // - .addArguments(workspace.resolve("src/standalone/SuiteIntegration.java")) // - .addArguments(workspace.resolve("src/standalone/VintageIntegration.java")).build() // - .run(); - - assertEquals(0, result.getExitCode(), () -> getExitCodeMessage(result)); - assertTrue(result.getOutput("out").isEmpty()); - assertTrue(result.getOutput("err").isEmpty()); + .addArguments("-d", workspace.resolve("bin").toString()) // + .addArguments("--class-path", MavenRepo.jar("junit-platform-console-standalone").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()) // + .startAndWait(); + + assertEquals(0, result.exitCode()); + assertTrue(result.stdOut().isEmpty()); + assertTrue(result.stdErr().isEmpty()); // create "tests.jar" that'll be picked-up by "testWithJarredTestClasses()" later var jarFolder = Files.createDirectories(workspace.resolve("jar")); - var jarResult = Request.builder() // - .setTool(new Jar()) // - .setProject(Projects.STANDALONE) // + var jarResult = ProcessStarters.javaCommand("jar") // + .workingDir(workspace) // .addArguments("--create") // - .addArguments("--file", jarFolder.resolve("tests.jar")) // - .addArguments("-C", workspace.resolve("bin"), ".") // - .build().run(false); - assertEquals(0, jarResult.getExitCode(), String.join("\n", jarResult.getOutputLines("out"))); + .addArguments("--file", jarFolder.resolve("tests.jar").toString()) // + .addArguments("-C", workspace.resolve("bin").toString(), ".") // + .startAndWait(); + assertEquals(0, jarResult.exitCode()); } - @ResourceLock(Projects.STANDALONE) @Test @Order(2) - void discoverTree() { - Result result = discover("--details-theme=ascii"); + @Execution(SAME_THREAD) + void discoverTree() throws Exception { + var result = discover("--details-theme=ascii"); var expected = """ . @@ -214,14 +213,14 @@ void discoverTree() { [ 9 tests found ] """.stripIndent(); - assertLinesMatch(expected.lines(), result.getOutputLines("out").stream()); + assertLinesMatch(expected.lines(), result.stdOut().lines()); } - @ResourceLock(Projects.STANDALONE) @Test @Order(2) - void discoverFlat() { - Result result = discover("--details=flat"); + @Execution(SAME_THREAD) + void discoverFlat() throws Exception { + var result = discover("--details=flat"); var expected = """ JUnit Platform Suite ([engine:junit-platform-suite]) @@ -249,13 +248,14 @@ JUnit Vintage ([engine:junit-vintage]) [ 9 tests found ] """.stripIndent(); - assertLinesMatch(expected.lines(), result.getOutputLines("out").stream()); + assertLinesMatch(expected.lines(), result.stdOut().lines()); } @Test @Order(2) - void discoverVerbose() { - Result result = discover("--details=verbose", "--details-theme=ascii"); + @Execution(SAME_THREAD) + void discoverVerbose() throws Exception { + var result = discover("--details=verbose", "--details-theme=ascii"); var expected = """ +-- JUnit Platform Suite @@ -333,23 +333,23 @@ void discoverVerbose() { [ 9 tests found ] """.stripIndent(); - assertLinesMatch(expected.lines(), result.getOutputLines("out").stream()); + assertLinesMatch(expected.lines(), result.stdOut().lines()); } - @ResourceLock(Projects.STANDALONE) @Test @Order(2) - void discoverNone() { - Result result = discover("--details=none"); + @Execution(SAME_THREAD) + void discoverNone() throws Exception { + var result = discover("--details=none"); - assertThat(result.getOutputLines("out")).isEmpty(); + assertThat(result.stdOut()).isEmpty(); } - @ResourceLock(Projects.STANDALONE) @Test @Order(2) - void discoverSummary() { - Result result = discover("--details=summary"); + @Execution(SAME_THREAD) + void discoverSummary() throws Exception { + var result = discover("--details=summary"); var expected = """ @@ -357,14 +357,14 @@ void discoverSummary() { [ 9 tests found ] """.stripIndent(); - assertLinesMatch(expected.lines(), result.getOutputLines("out").stream()); + assertLinesMatch(expected.lines(), result.stdOut().lines()); } - @ResourceLock(Projects.STANDALONE) @Test @Order(2) - void discoverTestFeed() { - Result result = discover("--details=testfeed"); + @Execution(SAME_THREAD) + void discoverTestFeed() throws Exception { + var result = discover("--details=testfeed"); var expected = """ JUnit Platform Suite > SuiteIntegration > JUnit Jupiter > SuiteIntegration$SingleTestContainer > successful() JUnit Jupiter > JupiterIntegration > successful() @@ -381,55 +381,51 @@ JUnit Jupiter > JupiterIntegration > disabled() """.stripIndent(); - assertLinesMatch(expected.lines(), result.getOutputLines("out").stream()); + assertLinesMatch(expected.lines(), result.stdOut().lines()); } - private static Result discover(String... args) { - var result = Request.builder() // + private static ProcessResult discover(String... args) throws Exception { + var result = ProcessStarters.java() // + .workingDir(workspace) // .putEnvironment("NO_COLOR", "1") // --disable-ansi-colors - .setTool(new Java()) // - .setProject(Projects.STANDALONE) // - .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone")) // + .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((Object[]) args) // - .build() // - .run(false); + .addArguments(args) // + .startAndWait(); - assertEquals(0, result.getExitCode(), () -> getExitCodeMessage(result)); + assertEquals(0, result.exitCode()); return result; } - @ResourceLock(Projects.STANDALONE) @Test @Order(3) - void execute() throws IOException { - var result = Request.builder() // + @Execution(SAME_THREAD) + void execute() throws Exception { + var result = ProcessStarters.java() // + .workingDir(workspace) // .putEnvironment("NO_COLOR", "1") // --disable-ansi-colors - .setTool(new Java()) // - .setProject(Projects.STANDALONE) // .addArguments("--show-version") // .addArguments("-enableassertions") // .addArguments("-Djava.util.logging.config.file=logging.properties") // .addArguments("-Djunit.platform.launcher.interceptors.enabled=true") // - .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone")) // + .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").build() // - .run(false); + .addArguments("--classpath", "bin") // + .startAndWait(); - assertEquals(1, result.getExitCode(), () -> getExitCodeMessage(result)); + assertEquals(1, result.exitCode()); - var workspace = Request.WORKSPACE.resolve(Projects.STANDALONE); var expectedOutLines = Files.readAllLines(workspace.resolve("expected-out.txt")); var expectedErrLines = Files.readAllLines(workspace.resolve("expected-err.txt")); - assertLinesMatch(expectedOutLines, result.getOutputLines("out")); - List actualErrLines = result.getOutputLines("err"); + 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); @@ -439,84 +435,77 @@ void execute() throws IOException { var jupiterVersion = Helper.version("junit-jupiter-engine"); var vintageVersion = Helper.version("junit-vintage-engine"); - assertTrue(result.getOutput("err").contains("junit-jupiter" + assertTrue(result.stdErr().contains("junit-jupiter" + " (group ID: org.junit.jupiter, artifact ID: junit-jupiter-engine, version: " + jupiterVersion)); - assertTrue(result.getOutput("err").contains("junit-vintage" + assertTrue(result.stdErr().contains("junit-vintage" + " (group ID: org.junit.vintage, artifact ID: junit-vintage-engine, version: " + vintageVersion)); } - @ResourceLock(Projects.STANDALONE) @Test @Order(4) - void executeOnJava8() throws IOException { - Java java8 = getJava8(); - var result = Request.builder() // - .setTool(java8) // - .setJavaHome(java8.getHome()) // - .setProject(Projects.STANDALONE) // + @Execution(SAME_THREAD) + void executeOnJava8() throws Exception { + var java8Home = Helper.getJavaHome("8").orElseThrow(TestAbortedException::new); + var result = ProcessStarters.java(java8Home) // + .workingDir(workspace) // .addArguments("-showversion") // .addArguments("-enableassertions") // .addArguments("-Djava.util.logging.config.file=logging.properties") // .addArguments("-Djunit.platform.launcher.interceptors.enabled=true") // - .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone")) // + .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").build() // - .run(false); + .addArguments("--classpath", "bin") // + .startAndWait(); - assertEquals(1, result.getExitCode(), () -> getExitCodeMessage(result)); + assertEquals(1, result.exitCode()); - var workspace = Request.WORKSPACE.resolve(Projects.STANDALONE); var expectedOutLines = Files.readAllLines(workspace.resolve("expected-out.txt")); var expectedErrLines = getExpectedErrLinesOnJava8(workspace); - assertLinesMatch(expectedOutLines, result.getOutputLines("out")); - assertLinesMatch(expectedErrLines, result.getOutputLines("err")); + assertLinesMatch(expectedOutLines, result.stdOutLines()); + assertLinesMatch(expectedErrLines, result.stdErrLines()); var jupiterVersion = Helper.version("junit-jupiter-engine"); var vintageVersion = Helper.version("junit-vintage-engine"); - assertTrue(result.getOutput("err").contains("junit-jupiter" + assertTrue(result.stdErr().contains("junit-jupiter" + " (group ID: org.junit.jupiter, artifact ID: junit-jupiter-engine, version: " + jupiterVersion)); - assertTrue(result.getOutput("err").contains("junit-vintage" + assertTrue(result.stdErr().contains("junit-vintage" + " (group ID: org.junit.vintage, artifact ID: junit-vintage-engine, version: " + vintageVersion)); } - @ResourceLock(Projects.STANDALONE) @Test @Order(5) + @Execution(SAME_THREAD) // https://github.com/junit-team/junit5/issues/2600 - void executeOnJava8SelectPackage() throws IOException { - Java java8 = getJava8(); - var result = Request.builder() // - .setTool(java8) // - .setJavaHome(java8.getHome()) // - .setProject(Projects.STANDALONE) // - .addArguments("-showversion") // + void executeOnJava8SelectPackage() throws Exception { + var java8Home = Helper.getJavaHome("8").orElseThrow(TestAbortedException::new); + var result = ProcessStarters.java(java8Home) // + .workingDir(workspace).addArguments("-showversion") // .addArguments("-enableassertions") // .addArguments("-Djava.util.logging.config.file=logging.properties") // .addArguments("-Djunit.platform.launcher.interceptors.enabled=true") // - .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone")) // + .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").build() // - .run(false); + .addArguments("--classpath", "bin") // + .startAndWait(); - assertEquals(1, result.getExitCode(), () -> getExitCodeMessage(result)); + assertEquals(1, result.exitCode()); - var workspace = Request.WORKSPACE.resolve(Projects.STANDALONE); var expectedOutLines = Files.readAllLines(workspace.resolve("expected-out.txt")); var expectedErrLines = getExpectedErrLinesOnJava8(workspace); - assertLinesMatch(expectedOutLines, result.getOutputLines("out")); - assertLinesMatch(expectedErrLines, result.getOutputLines("err")); + assertLinesMatch(expectedOutLines, result.stdOutLines()); + assertLinesMatch(expectedErrLines, result.stdErrLines()); var jupiterVersion = Helper.version("junit-jupiter-engine"); var vintageVersion = Helper.version("junit-vintage-engine"); - assertTrue(result.getOutput("err").contains("junit-jupiter" + assertTrue(result.stdErr().contains("junit-jupiter" + " (group ID: org.junit.jupiter, artifact ID: junit-jupiter-engine, version: " + jupiterVersion)); - assertTrue(result.getOutput("err").contains("junit-vintage" + assertTrue(result.stdErr().contains("junit-vintage" + " (group ID: org.junit.vintage, artifact ID: junit-vintage-engine, version: " + vintageVersion)); } @@ -527,19 +516,17 @@ private static List getExpectedErrLinesOnJava8(Path workspace) throws IO return expectedErrLines; } - @ResourceLock(Projects.STANDALONE) @Test @Order(6) + @Execution(SAME_THREAD) @Disabled("https://github.com/junit-team/junit5/issues/1724") - void executeWithJarredTestClasses() { + void executeWithJarredTestClasses() throws Exception { var jar = MavenRepo.jar("junit-platform-console-standalone"); var path = new ArrayList(); // path.add("bin"); // "exploded" test classes are found, see also test() above - path.add(Request.WORKSPACE.resolve("standalone/jar/tests.jar").toAbsolutePath().toString()); + path.add(workspace.resolve("standalone/jar/tests.jar").toAbsolutePath().toString()); path.add(jar.toString()); - var result = Request.builder() // - .setTool(new Java()) // - .setProject(Projects.STANDALONE) // + var result = ProcessStarters.java() // .addArguments("--show-version") // .addArguments("-enableassertions") // .addArguments("-Djava.util.logging.config.file=logging.properties") // @@ -550,32 +537,8 @@ void executeWithJarredTestClasses() { .addArguments("--disable-banner") // .addArguments("--include-classname", "standalone.*") // .addArguments("--fail-if-no-tests") // - .build() // - .run(false); + .startAndWait(); - assertEquals(1, result.getExitCode(), () -> getExitCodeMessage(result)); - } - - private static String getExitCodeMessage(Result result) { - return "Exit codes don't match. Stdout:\n" + result.getOutput("out") + // - "\n\nStderr:\n" + result.getOutput("err") + "\n"; - } - - /** - * Special override of class {@link Java} to resolve against a different {@code JAVA_HOME}. - */ - private static Java getJava8() { - Path java8Home = Helper.getJavaHome("8").orElseThrow(TestAbortedException::new); - return new Java() { - @Override - public Path getHome() { - return java8Home; - } - - @Override - public String getVersion() { - return "8"; - } - }; + assertEquals(1, result.exitCode()); } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ToolProviderTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ToolProviderTests.java index 0b979d1dff44..792e2aa49667 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ToolProviderTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ToolProviderTests.java @@ -33,14 +33,16 @@ 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.Request; import platform.tooling.support.ThirdPartyJars; /** @@ -49,12 +51,13 @@ @Order(Integer.MAX_VALUE) class ToolProviderTests { - private static final Path LIB = Request.WORKSPACE.resolve("tool-provider-tests/lib"); + @TempDir + static Path lib; @BeforeAll static void prepareLocalLibraryDirectoryWithJUnitPlatformModules() { try { - var lib = Files.createDirectories(LIB); + Files.createDirectories(lib); try (var directoryStream = Files.newDirectoryStream(lib, "*.jar")) { for (Path jarFile : directoryStream) { Files.delete(jarFile); @@ -75,13 +78,21 @@ static void prepareLocalLibraryDirectoryWithJUnitPlatformModules() { } } + @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())) { + 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); + assertTrue(junit.isPresent(), "Tool 'junit' not found in: " + lib); assertJUnitPrintsHelpMessage(junit.get()); } catch (IOException e) { @@ -92,7 +103,7 @@ void findAndRunJUnitOnTheClassPath() { @Test @DisabledOnOpenJ9 void findAndRunJUnitOnTheModulePath() { - var finder = ModuleFinder.of(LIB); + var finder = ModuleFinder.of(lib); var modules = finder.findAll().stream() // .map(ModuleReference::descriptor) // .map(ModuleDescriptor::toNameAndVersion) // diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageGradleIntegrationTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageGradleIntegrationTests.java index b655753eaa10..e6088c074900 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageGradleIntegrationTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageGradleIntegrationTests.java @@ -11,63 +11,58 @@ package platform.tooling.support.tests; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static platform.tooling.support.Helper.TOOL_TIMEOUT; +import static platform.tooling.support.tests.Projects.copyToWorkspace; -import java.nio.file.Paths; - -import de.sormuras.bartholdy.Result; -import de.sormuras.bartholdy.tool.GradleWrapper; +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.ProcessResult; import org.opentest4j.TestAbortedException; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; +import platform.tooling.support.ProcessStarters; class VintageGradleIntegrationTests { + @TempDir + Path workspace; + @Test - void unsupportedVersion() { + void unsupportedVersion() throws Exception { var result = run("4.11"); - assertThat(result.getExitCode()).isGreaterThan(0); - assertThat(result.getOutput("out")) // + assertThat(result.exitCode()).isGreaterThan(0); + assertThat(result.stdOut()) // .doesNotContain("STARTED") // .contains("Unsupported version of junit:junit: 4.11"); } @ParameterizedTest(name = "{0}") @ValueSource(strings = { "4.12", "4.13.2" }) - void supportedVersions(String version) { + void supportedVersions(String version) throws Exception { var result = run(version); - assertThat(result.getExitCode()).isGreaterThan(0); - assertThat(result.getOutput("out")) // + assertThat(result.exitCode()).isGreaterThan(0); + assertThat(result.stdOut()) // .contains("VintageTest > success PASSED") // .contains("VintageTest > failure FAILED"); - var testResultsDir = Request.WORKSPACE.resolve("vintage-gradle-" + version).resolve("build/test-results/test"); + var testResultsDir = workspace.resolve("build/test-results/test"); assertThat(testResultsDir.resolve("TEST-com.example.vintage.VintageTest.xml")).isRegularFile(); } - private Result run(String version) { - var result = Request.builder() // - .setTool(new GradleWrapper(Paths.get(".."))) // - .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // - .setProject(Projects.VINTAGE) // - .setWorkspace("vintage-gradle-" + version) // - .addArguments("build", "--no-daemon", "--stacktrace", "--no-build-cache") // + private ProcessResult run(String version) throws Exception { + return ProcessStarters.gradlew() // + .workingDir(copyToWorkspace(Projects.VINTAGE, workspace)) // + .putEnvironment("JDK8", Helper.getJavaHome("8").orElseThrow(TestAbortedException::new).toString()) // + .addArguments("build", "--no-daemon", "--stacktrace", "--no-build-cache", "--warning-mode=fail") // .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // .addArguments("-Djunit4Version=" + version) // - .setTimeout(TOOL_TIMEOUT) // - .build() // - .run(); - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); - return result; + .startAndWait(); } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageMavenIntegrationTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageMavenIntegrationTests.java index 217d40e1bbd0..79a7968131aa 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageMavenIntegrationTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageMavenIntegrationTests.java @@ -11,66 +11,63 @@ package platform.tooling.support.tests; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static platform.tooling.support.Helper.TOOL_TIMEOUT; +import static platform.tooling.support.tests.Projects.copyToWorkspace; -import de.sormuras.bartholdy.Result; +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.ProcessResult; import org.opentest4j.TestAbortedException; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; +import platform.tooling.support.ProcessStarters; class VintageMavenIntegrationTests { @GlobalResource LocalMavenRepo localMavenRepo; + @TempDir + Path workspace; + @Test - void unsupportedVersion() { + void unsupportedVersion() throws Exception { var result = run("4.11"); - assertThat(result.getExitCode()).isEqualTo(1); - assertThat(result.getOutput("out")) // + 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}") @ValueSource(strings = { "4.12", "4.13.2" }) - void supportedVersions(String version) { + void supportedVersions(String version) throws Exception { var result = run(version); - assertThat(result.getExitCode()).isGreaterThan(0); - assertThat(result.getOutput("out")) // + 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 = Request.WORKSPACE.resolve("vintage-maven-" + version).resolve( - "target/surefire-reports"); + 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 Result run(String version) { - var result = Request.builder() // - .setTool(Request.maven()) // - .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // - .setProject(Projects.VINTAGE) // - .setWorkspace("vintage-maven-" + version) // + private ProcessResult run(String version) throws Exception { + return ProcessStarters.maven() // + .workingDir(copyToWorkspace(Projects.VINTAGE, workspace)) // + .putEnvironment("JAVA_HOME", Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // .addArguments("clean", "test", "--update-snapshots", "--batch-mode") // .addArguments(localMavenRepo.toCliArgument(), "-Dmaven.repo=" + MavenRepo.dir()) // .addArguments("-Djunit4Version=" + version) // - .setTimeout(TOOL_TIMEOUT) // - .build() // - .run(); - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); - return result; + .startAndWait(); } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/XmlAssertions.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/XmlAssertions.java index 7e0987860245..052b3208e681 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/XmlAssertions.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/XmlAssertions.java @@ -10,10 +10,10 @@ package platform.tooling.support.tests; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.Files; +import static org.junit.platform.reporting.testutil.FileUtils.findPath; + import java.nio.file.Path; +import java.util.regex.Pattern; import org.xmlunit.assertj3.XmlAssert; import org.xmlunit.placeholder.PlaceholderDifferenceEvaluator; @@ -21,22 +21,18 @@ class XmlAssertions { static void verifyContainsExpectedStartedOpenTestReport(Path testResultsDir) { - try (var files = Files.list(testResultsDir)) { - Path xmlFile = files.filter(it -> it.getFileName().toString().startsWith("junit-platform-events-")) // - .findAny() // - .orElseThrow(() -> new AssertionError("Missing open-test-reporting XML file in " + testResultsDir)); - verifyContent(xmlFile); - } - catch (IOException e) { - throw new UncheckedIOException(e); - } + var xmlFile = findPath(testResultsDir, "glob:**/open-test-report.xml"); + verifyContent(xmlFile); } private static void verifyContent(Path xmlFile) { var expected = """ - ${xmlunit.ignore} @@ -46,10 +42,6 @@ private static void verifyContent(Path xmlFile) { ${xmlunit.ignore} ${xmlunit.ignore} - - ${xmlunit.ignore} - ${xmlunit.matchesRegex([0-9a-f]{40})} - ${xmlunit.ignore} @@ -156,7 +148,8 @@ private static void verifyContent(Path xmlFile) { """; XmlAssert.assertThat(xmlFile).and(expected) // - .withDifferenceEvaluator(new PlaceholderDifferenceEvaluator()) // + .withDifferenceEvaluator(new PlaceholderDifferenceEvaluator(Pattern.quote("${"), Pattern.quote("}"), + Pattern.quote("#"), Pattern.quote("#"), ",")) // .ignoreWhitespace() // .areIdentical(); } diff --git a/platform-tooling-support-tests/src/test/resources/junit-platform.properties b/platform-tooling-support-tests/src/test/resources/junit-platform.properties index 1ddeedc6a798..d24bbed7d3d9 100644 --- a/platform-tooling-support-tests/src/test/resources/junit-platform.properties +++ b/platform-tooling-support-tests/src/test/resources/junit-platform.properties @@ -2,6 +2,9 @@ junit.jupiter.execution.parallel.enabled=true junit.jupiter.execution.parallel.mode.default=concurrent 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