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")