diff --git a/build.gradle.kts b/build.gradle.kts index 048d466a454..23eabf4c88f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -41,7 +41,8 @@ val jupiterProjects by extra(listOf( ).map { it.dependencyProject }) val vintageProjects by extra(listOf( - projects.junitVintageEngine.dependencyProject + projects.junitVintageEngine.dependencyProject, + projects.junitVintageReporting.dependencyProject )) val mavenizedProjects by extra(platformProjects + jupiterProjects + vintageProjects) diff --git a/documentation/documentation.gradle.kts b/documentation/documentation.gradle.kts index fd4877cdd6f..f805279aeca 100644 --- a/documentation/documentation.gradle.kts +++ b/documentation/documentation.gradle.kts @@ -46,6 +46,7 @@ dependencies { testImplementation(kotlin("stdlib")) testImplementation(projects.junitVintageEngine) + testImplementation(projects.junitVintageReporting) testRuntimeOnly(libs.apiguardian) { because("it's required to generate API tables") } diff --git a/documentation/src/test/java/example/TestReporterDemo.java b/documentation/src/test/java/example/TestReporterDemo.java index dbd78d94aa9..827d1b31383 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/vintage/VintageTestReportingDemo.java b/documentation/src/test/java/example/vintage/VintageTestReportingDemo.java new file mode 100644 index 00000000000..2d2cf91102a --- /dev/null +++ b/documentation/src/test/java/example/vintage/VintageTestReportingDemo.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms 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.vintage; + +import static java.util.Collections.singletonList; + +import java.nio.file.Files; +import java.nio.file.Path; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.vintage.reporting.TestReporting; + +public class VintageTestReportingDemo { + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Rule + public TestReporting testReporting = new TestReporting(); + + @Test + public void reportFiles() throws Exception { + Path existingFile = Files.write(temporaryFolder.getRoot().toPath().resolve("test.txt"), singletonList("Test")); + testReporting.publishFile(existingFile); + } +} 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 6b5b349b62d..063353567bd 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.11 + */ + @API(status = EXPERIMENTAL, since = "5.11") + 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.11 + */ + @API(status = EXPERIMENTAL, since = "5.11") + 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 3182b91035d..491d51f97a9 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 @@ -15,6 +15,7 @@ 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; @@ -26,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; @@ -365,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 located 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.11 + * @see org.junit.platform.engine.EngineExecutionListener#fileEntryPublished + */ + @API(status = EXPERIMENTAL, since = "5.11") + void publishFile(String fileName, ThrowingConsumer action); + /** * Get the {@link Store} for the supplied {@link Namespace}. * 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 dbd799b7f5b..4e2c969e946 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.getOutputDirProvider())); 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 2d61b58c1c3..e8be71d4b58 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 @@ -29,6 +29,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.OutputDirProvider; /** * Caching implementation of the {@link JupiterConfiguration} API. @@ -125,4 +126,8 @@ public Supplier getDefaultTempDirFactorySupplier() { key -> delegate.getDefaultTempDirFactorySupplier()); } + @Override + public OutputDirProvider getOutputDirProvider() { + return delegate.getOutputDirProvider(); + } } 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 d64c4ceee31..e4c4862a7cb 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 @@ -32,6 +32,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.OutputDirProvider; /** * Default implementation of the {@link JupiterConfiguration} API. @@ -63,10 +64,13 @@ public class DefaultJupiterConfiguration implements JupiterConfiguration { new InstantiatingConfigurationParameterConverter<>(TempDirFactory.class, "temp dir factory"); private final ConfigurationParameters configurationParameters; + private final OutputDirProvider outputDirProvider; - public DefaultJupiterConfiguration(ConfigurationParameters configurationParameters) { + public DefaultJupiterConfiguration(ConfigurationParameters configurationParameters, + OutputDirProvider outputDirProvider) { this.configurationParameters = Preconditions.notNull(configurationParameters, "ConfigurationParameters must not be null"); + this.outputDirProvider = outputDirProvider; } @Override @@ -141,4 +145,8 @@ public Supplier getDefaultTempDirFactorySupplier() { return () -> supplier.get().orElse(TempDirFactory.Standard.INSTANCE); } + @Override + public OutputDirProvider getOutputDirProvider() { + return outputDirProvider; + } } 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 559b4d7d571..62a219e9787 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 @@ -27,6 +27,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.OutputDirProvider; /** * @since 5.4 @@ -70,4 +71,5 @@ public interface JupiterConfiguration { Supplier getDefaultTempDirFactorySupplier(); + OutputDirProvider getOutputDirProvider(); } 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 58ff7c73936..7b93a9ef7c8 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.Map; @@ -23,14 +25,17 @@ import org.junit.jupiter.api.extension.ExecutableInvoker; 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.NamespaceAwareStore; 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; @@ -102,6 +107,26 @@ public void publishReportEntry(Map values) { this.engineExecutionListener.reportingEntryPublished(this.testDescriptor, ReportEntry.from(values)); } + @Override + public void publishFile(String fileName, ThrowingConsumer action) { + try { + configuration.getOutputDirProvider().createOutputDirectory(this.testDescriptor).ifPresent(dir -> { + 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/extension/TestReporterParameterResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestReporterParameterResolver.java index ffa5cf066e2..68b68075c19 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 @@ -10,10 +10,14 @@ 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.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.jupiter.api.function.ThrowingConsumer; /** * {@link ParameterResolver} that injects a {@link TestReporter}. @@ -29,7 +33,17 @@ public boolean supportsParameter(ParameterContext parameterContext, ExtensionCon @Override public TestReporter resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return extensionContext::publishReportEntry; + return new TestReporter() { + @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/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java index 44d05998be0..be2e2052dc7 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java @@ -36,6 +36,7 @@ import org.junit.jupiter.engine.descriptor.CustomDisplayNameGenerator; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.engine.ConfigurationParameters; +import org.junit.platform.engine.reporting.OutputDirProvider; class DefaultJupiterConfigurationTests { @@ -44,20 +45,20 @@ class DefaultJupiterConfigurationTests { @Test void getDefaultTestInstanceLifecyclePreconditions() { PreconditionViolationException exception = assertThrows(PreconditionViolationException.class, - () -> new DefaultJupiterConfiguration(null)); + () -> new DefaultJupiterConfiguration(null, OutputDirProvider.NOOP)); assertThat(exception).hasMessage("ConfigurationParameters must not be null"); } @Test void getDefaultTestInstanceLifecycleWithNoConfigParamSet() { - JupiterConfiguration configuration = new DefaultJupiterConfiguration(mock()); + JupiterConfiguration configuration = new DefaultJupiterConfiguration(mock(), OutputDirProvider.NOOP); Lifecycle lifecycle = configuration.getDefaultTestInstanceLifecycle(); assertThat(lifecycle).isEqualTo(PER_METHOD); } @Test void getDefaultTempDirCleanupModeWithNoConfigParamSet() { - JupiterConfiguration configuration = new DefaultJupiterConfiguration(mock()); + JupiterConfiguration configuration = new DefaultJupiterConfiguration(mock(), OutputDirProvider.NOOP); CleanupMode cleanupMode = configuration.getDefaultTempDirCleanupMode(); assertThat(cleanupMode).isEqualTo(ALWAYS); } @@ -82,7 +83,7 @@ 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, OutputDirProvider.NOOP); DisplayNameGenerator defaultDisplayNameGenerator = configuration.getDefaultDisplayNameGenerator(); @@ -94,7 +95,7 @@ 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, OutputDirProvider.NOOP); DisplayNameGenerator defaultDisplayNameGenerator = configuration.getDefaultDisplayNameGenerator(); @@ -106,7 +107,7 @@ 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, OutputDirProvider.NOOP); final Optional defaultTestMethodOrder = configuration.getDefaultTestMethodOrderer(); @@ -118,7 +119,7 @@ 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, OutputDirProvider.NOOP); Supplier supplier = configuration.getDefaultTempDirFactorySupplier(); @@ -138,7 +139,7 @@ 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, OutputDirProvider.NOOP); Supplier supplier = configuration.getDefaultTempDirFactorySupplier(); @@ -148,7 +149,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, + OutputDirProvider.NOOP).getDefaultTestInstanceLifecycle(); assertThat(lifecycle).isEqualTo(expected); } diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java index af140f29f87..6f2da45ede5 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java @@ -43,6 +43,7 @@ import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.OutputDirProvider; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.engine.support.hierarchical.OpenTest4JAwareThrowableCollector; import org.mockito.ArgumentCaptor; @@ -54,8 +55,8 @@ * {@link JupiterEngineExtensionContext}, {@link ClassExtensionContext}, and * {@link MethodExtensionContext}. * - * @since 5.0 * @see org.junit.jupiter.engine.execution.ExtensionValuesStoreTests + * @since 5.0 */ public class ExtensionContextTests { @@ -258,7 +259,7 @@ void usingStore() { @TestFactory Stream configurationParameter() throws Exception { - JupiterConfiguration echo = new DefaultJupiterConfiguration(new EchoParameters()); + JupiterConfiguration echo = new DefaultJupiterConfiguration(new EchoParameters(), OutputDirProvider.NOOP); String key = "123"; Optional expected = Optional.of(key); diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtilsTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtilsTests.java index 46fab2deece..c04a56a93bc 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtilsTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtilsTests.java @@ -32,6 +32,7 @@ import org.junit.jupiter.engine.config.DefaultJupiterConfiguration; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.engine.ConfigurationParameters; +import org.junit.platform.engine.reporting.OutputDirProvider; /** * Unit tests for {@link TestInstanceLifecycleUtils}. @@ -50,7 +51,7 @@ class TestInstanceLifecycleUtilsTests { @Test void getTestInstanceLifecyclePreconditions() { PreconditionViolationException exception = assertThrows(PreconditionViolationException.class, - () -> getTestInstanceLifecycle(null, new DefaultJupiterConfiguration(mock()))); + () -> getTestInstanceLifecycle(null, new DefaultJupiterConfiguration(mock(), OutputDirProvider.NOOP))); assertThat(exception).hasMessage("testClass must not be null"); exception = assertThrows(PreconditionViolationException.class, @@ -60,7 +61,8 @@ void getTestInstanceLifecyclePreconditions() { @Test void getTestInstanceLifecycleWithNoConfigParamSet() { - Lifecycle lifecycle = getTestInstanceLifecycle(getClass(), new DefaultJupiterConfiguration(mock())); + Lifecycle lifecycle = getTestInstanceLifecycle(getClass(), + new DefaultJupiterConfiguration(mock(), OutputDirProvider.NOOP)); assertThat(lifecycle).isEqualTo(PER_METHOD); } @@ -68,7 +70,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, OutputDirProvider.NOOP)); assertThat(lifecycle).isEqualTo(PER_CLASS); } @@ -76,21 +79,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, OutputDirProvider.NOOP)); 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(), OutputDirProvider.NOOP)); 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(), OutputDirProvider.NOOP)); assertThat(lifecycle).isEqualTo(PER_CLASS); } diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java index 40c1eeeec28..4f68857dfe6 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java +++ b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java @@ -20,6 +20,7 @@ import java.io.FileNotFoundException; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; +import java.nio.file.Path; import java.util.Arrays; import java.util.Map; import java.util.Optional; @@ -33,6 +34,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; @@ -265,6 +267,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) { return new NamespaceAwareStore(store, namespace); 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 304cedc27a2..fbfebb4ce72 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 40fdbf9345e..6bc1c35d700 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 34de4f21785..cb2b99bb0d9 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 39d8126595f..2775db0b4cf 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 2dbd7e01d77..78e73ba3c32 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 0ea3b70ce8d..0c5c3691077 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 @@ -15,6 +15,7 @@ import java.util.List; import org.apiguardian.api.API; +import org.junit.platform.engine.reporting.OutputDirProvider; /** * {@code EngineDiscoveryRequest} provides a {@link TestEngine} access to the @@ -84,4 +85,7 @@ default EngineDiscoveryListener getDiscoveryListener() { return EngineDiscoveryListener.NOOP; } + default OutputDirProvider getOutputDirProvider() { + return OutputDirProvider.NOOP; + } } 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 315548ff3b9..fbcf4f5bb0d 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,7 @@ default void executionFinished(TestDescriptor testDescriptor, TestExecutionResul default void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry entry) { } + @API(status = EXPERIMENTAL, since = "1.11") + 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 2c77f1fd5d6..f4f503f3b75 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 00000000000..dc040ca4f7b --- /dev/null +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/FileEntry.java @@ -0,0 +1,67 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse 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.11 + * @see #from(Path) + */ +@API(status = EXPERIMENTAL, since = "1.11") +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; + } + + public Path getFile() { + return file; + } + + @Override + public String toString() { + ToStringBuilder builder = new ToStringBuilder(this); + builder.append("file", this.file); + return builder.toString(); + } + +} diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/OutputDirProvider.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/OutputDirProvider.java new file mode 100644 index 00000000000..a68e507593e --- /dev/null +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/OutputDirProvider.java @@ -0,0 +1,25 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.reporting; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import org.junit.platform.engine.TestDescriptor; + +public interface OutputDirProvider { + + OutputDirProvider NOOP = __ -> Optional.empty(); + + Optional 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 6e114191d11..ee5cd84aae2 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-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingExecutionListener.java b/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingExecutionListener.java index be66660299c..6502fd42384 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 7e85b35d1c5..0a35dde1bcc 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 @@ -174,6 +174,8 @@ public class LauncherConstants { @API(status = EXPERIMENTAL, since = "1.10") public static final String STACKTRACE_PRUNING_ENABLED_PROPERTY_NAME = "junit.platform.stacktrace.pruning.enabled"; + public static final String OUTPUT_DIR_PROPERTY_NAME = "junit.platform.reporting.output.dir"; + 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 548395c43cf..a9559bb3d31 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 @@ -15,6 +15,7 @@ import org.apiguardian.api.API; 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; /** @@ -177,4 +178,14 @@ 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} + */ + default void fileEntryPublished(TestIdentifier testIdentifier, FileEntry file) { + } } 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 29311c9b077..b6ad5b50903 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 977bc4a3073..1fa895d13f8 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 422f9592374..82244965f39 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.OutputDirProvider; 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 OutputDirProvider outputDirProvider; + DefaultDiscoveryRequest(List selectors, List engineFilters, List> discoveryFilters, List postDiscoveryFilters, - LauncherConfigurationParameters configurationParameters, LauncherDiscoveryListener discoveryListener) { + LauncherConfigurationParameters configurationParameters, LauncherDiscoveryListener discoveryListener, + OutputDirProvider outputDirProvider) { this.selectors = selectors; this.engineFilters = engineFilters; this.discoveryFilters = discoveryFilters; this.postDiscoveryFilters = postDiscoveryFilters; this.configurationParameters = configurationParameters; this.discoveryListener = discoveryListener; + this.outputDirProvider = outputDirProvider; } @Override @@ -94,4 +99,8 @@ public LauncherDiscoveryListener getDiscoveryListener() { return discoveryListener; } + @Override + public OutputDirProvider getOutputDirProvider() { + return outputDirProvider; + } } 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 e2f41053360..b2fc69eafa1 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/EngineExecutionOrchestrator.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java index bf0f78a7bde..c13f744fcdf 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 f2d87e4bfea..eb1f0976fa0 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/HierarchicalOutputDirProvider.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/HierarchicalOutputDirProvider.java new file mode 100644 index 00000000000..e0d82258c89 --- /dev/null +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/HierarchicalOutputDirProvider.java @@ -0,0 +1,77 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static java.util.Collections.unmodifiableSet; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Supplier; + +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId.Segment; +import org.junit.platform.engine.reporting.OutputDirProvider; + +class HierarchicalOutputDirProvider implements OutputDirProvider { + + private static final Set FORBIDDEN_CHARS = unmodifiableSet( + new HashSet<>(Arrays.asList('\0', '/', '\\', ':', '*', '?', '"', '<', '>', '|'))); + private static final char REPLACEMENT = '_'; + + private final Supplier rootDirSupplier; + private Path rootDir; + + HierarchicalOutputDirProvider(Supplier rootDirSupplier) { + this.rootDirSupplier = rootDirSupplier; + } + + @Override + public Optional createOutputDirectory(TestDescriptor testDescriptor) throws IOException { + List segments = testDescriptor.getUniqueId().getSegments(); + if (segments.isEmpty()) { + return Optional.empty(); + } + Segment firstSegment = segments.get(0); + Path relativePath = segments.stream() // + .skip(1) // + .map(Segment::getValue) // + .map(HierarchicalOutputDirProvider::sanitizeName).map(Paths::get) // + .reduce(Paths.get(firstSegment.getValue()), Path::resolve); + return Optional.of(Files.createDirectories(resolveRootDir().resolve(relativePath))); + } + + private synchronized Path resolveRootDir() { + if (rootDir == null) { + rootDir = rootDirSupplier.get(); + } + return rootDir; + } + + private static String sanitizeName(String value) { + StringBuilder result = new StringBuilder(value.length()); + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + result.append(isForbiddenCharacter(c) ? REPLACEMENT : c); + } + return result.toString(); + } + + private static boolean isForbiddenCharacter(char c) { + return FORBIDDEN_CHARS.contains(c) || Character.isISOControl(c); + } +} 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 1d2f22d7a55..3d6117fc437 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 @@ -12,6 +12,7 @@ import static org.apiguardian.api.API.Status.DEPRECATED; 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; @@ -26,11 +27,13 @@ import org.junit.platform.engine.DiscoveryFilter; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.Filter; +import org.junit.platform.engine.reporting.OutputDirProvider; import org.junit.platform.launcher.EngineFilter; 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; /** @@ -289,8 +292,19 @@ else if (filter instanceof DiscoveryFilter) { public LauncherDiscoveryRequest build() { LauncherConfigurationParameters launcherConfigurationParameters = buildLauncherConfigurationParameters(); LauncherDiscoveryListener discoveryListener = getLauncherDiscoveryListener(launcherConfigurationParameters); + OutputDirProvider outputDirProvider = createOutputDirProvider(launcherConfigurationParameters); return new DefaultDiscoveryRequest(this.selectors, this.engineFilters, this.discoveryFilters, - this.postDiscoveryFilters, launcherConfigurationParameters, discoveryListener); + this.postDiscoveryFilters, launcherConfigurationParameters, discoveryListener, outputDirProvider); + } + + private OutputDirProvider createOutputDirProvider(LauncherConfigurationParameters configurationParameters) { + // TODO Provider another configuration parameter to disable writing outputs? + // TODO OutputDirProvider could be made configurable via another configuration parameter + return new HierarchicalOutputDirProvider(() -> { + OutputDir outputDir = OutputDir.create(configurationParameters.get(OUTPUT_DIR_PROPERTY_NAME)); + return outputDir.createDir("junit"); + }); + } private LauncherConfigurationParameters buildLauncherConfigurationParameters() { 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 14080b1f7e2..8f6822a2489 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 @@ -27,6 +27,8 @@ @API(status = INTERNAL, since = "1.9") public class OutputDir { + private SecureRandom random = new SecureRandom(); + public static OutputDir create(Optional customDir) { try { return createSafely(customDir, () -> Paths.get(".").toAbsolutePath()); @@ -60,7 +62,7 @@ else if (containsFilesWithExtensions(cwd, ".gradle", ".gradle.kts")) { Files.createDirectories(outputDir); } - return new OutputDir(outputDir); + return new OutputDir(outputDir.normalize()); } private final Path path; @@ -73,8 +75,20 @@ public Path toPath() { return path; } + public Path createDir(String prefix) throws UncheckedIOException { + String filename = String.format("%s-%d", prefix, Math.abs(random.nextLong())); + Path outputFile = path.resolve(filename); + + try { + return Files.createDirectory(outputFile); + } + catch (IOException e) { + throw new UncheckedIOException("Failed to create output directory: " + outputFile, e); + } + } + 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 { diff --git a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/File.java b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/File.java new file mode 100644 index 00000000000..8f44ea4e6d0 --- /dev/null +++ b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/File.java @@ -0,0 +1,53 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.reporting.open.xml; + +import java.time.LocalDateTime; + +import org.opentest4j.reporting.events.api.ChildElement; +import org.opentest4j.reporting.events.api.Context; +import org.opentest4j.reporting.events.core.Attachments; +import org.opentest4j.reporting.schema.QualifiedName; + +class File extends ChildElement { + + // TODO Move this element to the core namespace in the open-test-reporting project + + static final QualifiedName ELEMENT = QualifiedName.of(JUnitFactory.NAMESPACE, "file"); + private static final QualifiedName TIME = QualifiedName.of(JUnitFactory.NAMESPACE, "time"); + private static final QualifiedName PATH = QualifiedName.of(JUnitFactory.NAMESPACE, "path"); + + File(Context context) { + super(context, ELEMENT); + } + + /** + * Set the {@code time} attribute of this element. + * + * @param timestamp the timestamp to set + * @return this element + */ + public File withTime(LocalDateTime timestamp) { + withAttribute(TIME, timestamp.toString()); + return this; + } + + /** + * Set the {@code path} attribute of this element. + * + * @param path the path to set + * @return this element + */ + public File withPath(String path) { + withAttribute(PATH, path); + return this; + } +} diff --git a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/JUnitFactory.java b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/JUnitFactory.java index 382404bdd7a..72bd75feef0 100644 --- a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/JUnitFactory.java +++ b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/JUnitFactory.java @@ -32,4 +32,9 @@ static Factory legacyReportingName(String legacyReportingNa static Factory type(TestDescriptor.Type type) { return context -> new Type(context, type); } + + static Factory file() { + return File::new; + } + } 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 05c452880a9..c379b166154 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 @@ -11,6 +11,7 @@ package org.junit.platform.reporting.open.xml; import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.junit.platform.reporting.open.xml.JUnitFactory.file; import static org.junit.platform.reporting.open.xml.JUnitFactory.legacyReportingName; import static org.junit.platform.reporting.open.xml.JUnitFactory.type; import static org.junit.platform.reporting.open.xml.JUnitFactory.uniqueId; @@ -59,6 +60,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; @@ -68,6 +70,7 @@ import org.junit.platform.engine.support.descriptor.MethodSource; import org.junit.platform.engine.support.descriptor.PackageSource; import org.junit.platform.engine.support.descriptor.UriSource; +import org.junit.platform.launcher.LauncherConstants; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; @@ -88,11 +91,11 @@ 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 OutputDir outputDir; public OpenTestReportGeneratingListener() { } @@ -107,7 +110,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)) // + outputDir = OutputDir.create(config.get(LauncherConstants.OUTPUT_DIR_PROPERTY_NAME)); + Path eventsXml = outputDir // .createFile("junit-platform-events", "xml"); try { eventsFileWriter = Events.createDocumentWriter(namespaceRegistry, eventsXml); @@ -242,6 +246,16 @@ public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry e }))); } + @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(), file -> { + file.withTime(entry.getTimestamp()); + file.withPath(outputDir.toPath().relativize(entry.getFile()).toString()); + }))); + } + @Override public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { String id = inProgressIds.remove(testIdentifier.getUniqueIdObject()); 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 039117187dd..e5c8e4dbc2e 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-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 ee4ab403e27..c8415e3ac1f 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 @@ -249,7 +249,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); } 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 63c69409b38..9e32405ed65 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 @@ -22,6 +22,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 +52,21 @@ 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} + * @see EventType#FILE_ENTRY_PUBLISHED + */ + 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/EventType.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventType.java index 1fed5b9b67b..3d79ae7fb7d 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 @@ -15,6 +15,7 @@ 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 +61,13 @@ 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. + * + * @see org.junit.platform.engine.EngineExecutionListener#fileEntryPublished(TestDescriptor, FileEntry) + */ + FILE_ENTRY_PUBLISHED } 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 15db74b2b7d..64507dfae7b 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 @@ -19,6 +19,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 +83,14 @@ public void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry e this.events.add(Event.reportingEntryPublished(testDescriptor, entry)); } + /** + * Record an {@link Event} for a published {@link FileEntry}. + */ + @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/junit-vintage-engine.gradle.kts b/junit-vintage-engine/junit-vintage-engine.gradle.kts index 288b3b18407..437be2d5ea9 100644 --- a/junit-vintage-engine/junit-vintage-engine.gradle.kts +++ b/junit-vintage-engine/junit-vintage-engine.gradle.kts @@ -15,6 +15,8 @@ dependencies { compileOnlyApi(libs.apiguardian) + compileOnly(projects.junitVintageReporting) + testFixturesApi(platform(libs.groovy2.bom)) testFixturesApi(libs.spock1) testFixturesImplementation(projects.junitPlatformRunner) diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/VintageTestEngine.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/VintageTestEngine.java index fb52f79bf06..90678ae21e6 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/VintageTestEngine.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/VintageTestEngine.java @@ -75,7 +75,8 @@ public void execute(ExecutionRequest request) { private void executeAllChildren(VintageEngineDescriptor engineDescriptor, EngineExecutionListener engineExecutionListener) { - RunnerExecutor runnerExecutor = new RunnerExecutor(engineExecutionListener); + RunnerExecutor runnerExecutor = new RunnerExecutor(engineExecutionListener, + engineDescriptor.getOutputDirProvider()); for (Iterator iterator = engineDescriptor.getModifiableChildren().iterator(); iterator.hasNext();) { runnerExecutor.execute((RunnerTestDescriptor) iterator.next()); iterator.remove(); diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageEngineDescriptor.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageEngineDescriptor.java index 9e0450fa9a0..0d050a5c51a 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageEngineDescriptor.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageEngineDescriptor.java @@ -17,6 +17,7 @@ import org.apiguardian.api.API; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.OutputDirProvider; import org.junit.platform.engine.support.descriptor.EngineDescriptor; /** @@ -25,12 +26,19 @@ @API(status = INTERNAL, since = "5.6") public class VintageEngineDescriptor extends EngineDescriptor { - public VintageEngineDescriptor(UniqueId uniqueId) { + private final OutputDirProvider outputDirProvider; + + public VintageEngineDescriptor(UniqueId uniqueId, OutputDirProvider outputDirProvider) { super(uniqueId, "JUnit Vintage"); + this.outputDirProvider = outputDirProvider; } public Set getModifiableChildren() { return children; } + public OutputDirProvider getOutputDirProvider() { + return outputDirProvider; + } + } diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/VintageDiscoverer.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/VintageDiscoverer.java index 427526bfeb1..bbd8e5ec126 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/VintageDiscoverer.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/VintageDiscoverer.java @@ -38,7 +38,8 @@ public class VintageDiscoverer { // @formatter:on public VintageEngineDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { - VintageEngineDescriptor engineDescriptor = new VintageEngineDescriptor(uniqueId); + VintageEngineDescriptor engineDescriptor = new VintageEngineDescriptor(uniqueId, + discoveryRequest.getOutputDirProvider()); resolver.resolve(discoveryRequest, engineDescriptor); RunnerTestDescriptorPostProcessor postProcessor = new RunnerTestDescriptorPostProcessor(); for (TestDescriptor testDescriptor : engineDescriptor.getChildren()) { diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/JUnitPlatformVintageReportingService.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/JUnitPlatformVintageReportingService.java new file mode 100644 index 00000000000..c741463d005 --- /dev/null +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/JUnitPlatformVintageReportingService.java @@ -0,0 +1,23 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.execution; + +import java.nio.file.Path; + +import org.junit.runner.Description; +import org.junit.vintage.reporting.VintageReportingService; + +public class JUnitPlatformVintageReportingService implements VintageReportingService { + @Override + public void publishFile(Description description, Path file) { + RunListenerAdapter.CURRENT.get().publishFile(description, file); + } +} diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunListenerAdapter.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunListenerAdapter.java index 9cefb8faee3..2591a103551 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunListenerAdapter.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunListenerAdapter.java @@ -12,14 +12,21 @@ import static org.junit.vintage.engine.descriptor.VintageTestDescriptor.SEGMENT_TYPE_DYNAMIC; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.util.Optional; import java.util.function.Function; import org.junit.Ignore; +import org.junit.platform.commons.JUnitException; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.FileEntry; +import org.junit.platform.engine.reporting.OutputDirProvider; import org.junit.runner.Description; import org.junit.runner.Result; import org.junit.runner.notification.Failure; @@ -29,22 +36,29 @@ import org.junit.vintage.engine.descriptor.VintageTestDescriptor; import org.junit.vintage.engine.support.UniqueIdReader; import org.junit.vintage.engine.support.UniqueIdStringifier; +import org.junit.vintage.reporting.VintageReportingService; /** * @since 4.12 */ -class RunListenerAdapter extends RunListener { +class RunListenerAdapter extends RunListener implements VintageReportingService { + + static final InheritableThreadLocal CURRENT = new InheritableThreadLocal<>(); private final TestRun testRun; private final EngineExecutionListener listener; private final TestSourceProvider testSourceProvider; + private final OutputDirProvider outputDirProvider; private final Function uniqueIdExtractor; - RunListenerAdapter(TestRun testRun, EngineExecutionListener listener, TestSourceProvider testSourceProvider) { + RunListenerAdapter(TestRun testRun, EngineExecutionListener listener, TestSourceProvider testSourceProvider, + OutputDirProvider outputDirProvider) { this.testRun = testRun; this.listener = listener; this.testSourceProvider = testSourceProvider; + this.outputDirProvider = outputDirProvider; this.uniqueIdExtractor = new UniqueIdReader().andThen(new UniqueIdStringifier()); + CURRENT.set(this); } @Override @@ -248,4 +262,23 @@ private void fireExecutionFinished(TestDescriptor testDescriptor) { listener.executionFinished(testDescriptor, testRun.getStoredResultOrSuccessful(testDescriptor)); } + @Override + public void publishFile(Description description, Path file) { + try { + TestDescriptor testDescriptor = lookupOrRegisterCurrentTestDescriptor(description); + outputDirProvider.createOutputDirectory(testDescriptor).ifPresent(outputDir -> { + try { + Path targetFile = outputDir.resolve(file.getFileName()); + Files.move(file, targetFile, StandardCopyOption.REPLACE_EXISTING); + listener.fileEntryPublished(testDescriptor, FileEntry.from(targetFile)); + } + catch (IOException e) { + throw new JUnitException("Failed to publish file", e); + } + }); + } + catch (IOException e) { + throw new JUnitException("Failed to create output directory", e); + } + } } diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunnerExecutor.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunnerExecutor.java index 23fad4dea5c..52b758026c4 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunnerExecutor.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunnerExecutor.java @@ -17,6 +17,7 @@ import org.junit.platform.commons.util.UnrecoverableExceptions; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.reporting.OutputDirProvider; import org.junit.runner.JUnitCore; import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; import org.junit.vintage.engine.descriptor.TestSourceProvider; @@ -28,16 +29,19 @@ public class RunnerExecutor { private final EngineExecutionListener engineExecutionListener; + private final OutputDirProvider outputDirProvider; private final TestSourceProvider testSourceProvider = new TestSourceProvider(); - public RunnerExecutor(EngineExecutionListener engineExecutionListener) { + public RunnerExecutor(EngineExecutionListener engineExecutionListener, OutputDirProvider outputDirProvider) { this.engineExecutionListener = engineExecutionListener; + this.outputDirProvider = outputDirProvider; } public void execute(RunnerTestDescriptor runnerTestDescriptor) { TestRun testRun = new TestRun(runnerTestDescriptor); JUnitCore core = new JUnitCore(); - core.addListener(new RunListenerAdapter(testRun, engineExecutionListener, testSourceProvider)); + core.addListener( + new RunListenerAdapter(testRun, engineExecutionListener, testSourceProvider, outputDirProvider)); try { core.run(runnerTestDescriptor.toRequest()); } diff --git a/junit-vintage-engine/src/main/resources/META-INF/services/org.junit.vintage.reporting.VintageReportingService b/junit-vintage-engine/src/main/resources/META-INF/services/org.junit.vintage.reporting.VintageReportingService new file mode 100644 index 00000000000..9096f9b79bc --- /dev/null +++ b/junit-vintage-engine/src/main/resources/META-INF/services/org.junit.vintage.reporting.VintageReportingService @@ -0,0 +1 @@ +org.junit.vintage.engine.execution.JUnitPlatformVintageReportingService diff --git a/junit-vintage-engine/src/module/org.junit.vintage.engine/module-info.java b/junit-vintage-engine/src/module/org.junit.vintage.engine/module-info.java index 859744fcec1..2dfa1944c53 100644 --- a/junit-vintage-engine/src/module/org.junit.vintage.engine/module-info.java +++ b/junit-vintage-engine/src/module/org.junit.vintage.engine/module-info.java @@ -20,7 +20,10 @@ requires junit; // 4 requires static org.apiguardian.api; requires org.junit.platform.engine; + requires static org.junit.vintage.reporting; provides org.junit.platform.engine.TestEngine with org.junit.vintage.engine.VintageTestEngine; + provides org.junit.vintage.reporting.VintageReportingService + with org.junit.vintage.engine.execution.JUnitPlatformVintageReportingService; } 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 66af815aff9..c396402d3b9 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 @@ -45,7 +45,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; @@ -394,14 +393,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); @@ -909,7 +900,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/junit-vintage-reporting/junit-vintage-reporting.gradle.kts b/junit-vintage-reporting/junit-vintage-reporting.gradle.kts new file mode 100644 index 00000000000..c1322422d76 --- /dev/null +++ b/junit-vintage-reporting/junit-vintage-reporting.gradle.kts @@ -0,0 +1,12 @@ +plugins { + id("junitbuild.java-library-conventions") +} + +description = "JUnit Vintage Reporting Extensions" + +dependencies { + api(platform(projects.junitBom)) + api(libs.junit4) + compileOnlyApi(libs.apiguardian) + osgiVerification(projects.junitPlatformLauncher) +} diff --git a/junit-vintage-reporting/src/main/java/org/junit/vintage/reporting/TestReporting.java b/junit-vintage-reporting/src/main/java/org/junit/vintage/reporting/TestReporting.java new file mode 100644 index 00000000000..fd76271f7c3 --- /dev/null +++ b/junit-vintage-reporting/src/main/java/org/junit/vintage/reporting/TestReporting.java @@ -0,0 +1,52 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.reporting; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.nio.file.Path; +import java.util.ServiceLoader; + +import org.apiguardian.api.API; +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; + +@API(status = EXPERIMENTAL, since = "5.11") +public class TestReporting extends TestWatcher { + + private final ServiceLoader services; + private Description description; + + public TestReporting() { + services = ServiceLoader.load(VintageReportingService.class); + } + + @Override + protected void starting(Description description) { + this.description = description; + } + + /** + * 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.11 + */ + public void publishFile(Path file) { + for (VintageReportingService service : services) { + service.publishFile(description, file); + } + } + +} diff --git a/junit-vintage-reporting/src/main/java/org/junit/vintage/reporting/VintageReportingService.java b/junit-vintage-reporting/src/main/java/org/junit/vintage/reporting/VintageReportingService.java new file mode 100644 index 00000000000..46c9b3ecd49 --- /dev/null +++ b/junit-vintage-reporting/src/main/java/org/junit/vintage/reporting/VintageReportingService.java @@ -0,0 +1,21 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.reporting; + +import java.nio.file.Path; + +import org.junit.runner.Description; + +public interface VintageReportingService { + + void publishFile(Description description, Path file); + +} diff --git a/junit-vintage-reporting/src/main/java/org/junit/vintage/reporting/package-info.java b/junit-vintage-reporting/src/main/java/org/junit/vintage/reporting/package-info.java new file mode 100644 index 00000000000..21f77ba090d --- /dev/null +++ b/junit-vintage-reporting/src/main/java/org/junit/vintage/reporting/package-info.java @@ -0,0 +1,6 @@ +/** + * Reporting extensions for running JUnit 4 based tests using the JUnit Vintage + * engine. + */ + +package org.junit.vintage.reporting; diff --git a/junit-vintage-reporting/src/module/org.junit.vintage.reporting/module-info.java b/junit-vintage-reporting/src/module/org.junit.vintage.reporting/module-info.java new file mode 100644 index 00000000000..66ea50b83fd --- /dev/null +++ b/junit-vintage-reporting/src/module/org.junit.vintage.reporting/module-info.java @@ -0,0 +1,16 @@ +/* + * Copyright 2015-2021 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +module org.junit.vintage.reporting { + requires transitive junit; + requires static transitive org.apiguardian.api; + exports org.junit.vintage.reporting; + uses org.junit.vintage.reporting.VintageReportingService; +} 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 80d46102f08..230b566694e 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/launcher/core/CompositeEngineExecutionListenerTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeEngineExecutionListenerTests.java index 4935d038d10..4e7c4917f90 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/reporting/open/xml/OpenTestReportGeneratingListenerTests.java b/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListenerTests.java index 99398dbc31e..117905635e1 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 @@ -15,10 +15,10 @@ import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS; 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.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 java.io.IOException; import java.net.URISyntaxException; diff --git a/settings.gradle.kts b/settings.gradle.kts index 8ae205fbcbe..e5e72d41638 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -94,6 +94,7 @@ include("junit-platform-suite-commons") include("junit-platform-suite-engine") include("junit-platform-testkit") include("junit-vintage-engine") +include("junit-vintage-reporting") include("platform-tests") include("platform-tooling-support-tests") include("junit-bom")