From f18a70eb68ee710058b70a207de889b21c28b280 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 30 May 2023 21:19:48 +0200 Subject: [PATCH] Spike API for attaching files to test executions --- .../test/java/example/TestReporterDemo.java | 18 +++++ .../org/junit/jupiter/api/TestReporter.java | 35 +++++++++ .../api/extension/ExtensionContext.java | 18 +++++ .../jupiter/engine/JupiterTestEngine.java | 4 +- .../descriptor/AbstractExtensionContext.java | 45 +++++++++++ .../descriptor/JupiterEngineDescriptor.java | 2 +- .../JupiterEngineExtensionContext.java | 5 +- .../JupiterEngineExecutionContext.java | 16 +++- .../TestReporterParameterResolver.java | 16 +++- .../descriptor/ExtensionContextTests.java | 10 ++- .../TestFactoryTestDescriptorTests.java | 3 +- .../JupiterEngineExecutionContextTests.java | 3 +- .../ParameterizedTestExtensionTests.java | 6 ++ .../console/tasks/FlatPrintingListener.java | 7 ++ .../platform/console/tasks/TreeNode.java | 7 ++ .../platform/console/tasks/TreePrinter.java | 10 +++ .../console/tasks/TreePrintingListener.java | 6 ++ .../tasks/VerboseTreePrintingListener.java | 6 ++ .../engine/EngineExecutionListener.java | 5 ++ .../platform/engine/ExecutionRequest.java | 45 ++++++++++- .../platform/engine/reporting/FileEntry.java | 67 ++++++++++++++++ .../engine/reporting/OutputDirProvider.java | 25 ++++++ .../engine/reporting/ReportEntry.java | 4 +- .../jfr/FlightRecordingExecutionListener.java | 19 +++++ .../platform/launcher/LauncherConstants.java | 1 + .../launcher/TestExecutionListener.java | 11 +++ .../CompositeEngineExecutionListener.java | 8 ++ .../core/CompositeTestExecutionListener.java | 8 ++ .../DelegatingEngineExecutionListener.java | 5 ++ .../core/EngineExecutionOrchestrator.java | 21 ++++- .../core/ExecutionListenerAdapter.java | 6 ++ .../core/HierarchicalOutputDirProvider.java | 78 +++++++++++++++++++ .../launcher/listeners/OutputDir.java | 18 ++++- .../platform/reporting/open/xml/File.java | 53 +++++++++++++ .../reporting/open/xml/JUnitFactory.java | 5 ++ .../xml/OpenTestReportGeneratingListener.java | 18 ++++- .../runner/JUnitPlatformRunnerListener.java | 6 ++ .../testkit/engine/EngineTestKit.java | 5 +- .../junit/platform/testkit/engine/Event.java | 16 ++++ .../platform/testkit/engine/EventType.java | 10 ++- .../testkit/engine/ExecutionRecorder.java | 9 +++ .../VintageTestEngineExecutionTests.java | 14 +--- .../HierarchicalTestExecutorTests.java | 3 +- ...CompositeEngineExecutionListenerTests.java | 6 ++ ...OpenTestReportGeneratingListenerTests.java | 2 +- 45 files changed, 640 insertions(+), 45 deletions(-) create mode 100644 junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/FileEntry.java create mode 100644 junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/OutputDirProvider.java create mode 100644 junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/HierarchicalOutputDirProvider.java create mode 100644 junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/File.java diff --git a/documentation/src/test/java/example/TestReporterDemo.java b/documentation/src/test/java/example/TestReporterDemo.java index dbd78d94aa94..038def7d7524 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 = tempDir.resolve("test2.txt"); + testReporter.publishFile(Files.write(existingFile, singletonList("Test 2"))); + + testReporter.publishFile("test3", dir -> { + Path nestedFile = Files.createDirectory(dir).resolve("nested.txt"); + Files.write(nestedFile, singletonList("Nested content")); + }); + } } // end::user_guide[] 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 6b5b349b62de..0f4cc07dfdb5 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,34 @@ 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. + * + * @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 file will be created 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 + */ + @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 3182b91035db..491d51f97a97 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 dbd799b7f5b2..a41608241587 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 @@ -82,8 +82,8 @@ protected HierarchicalTestExecutorService createExecutorService(ExecutionRequest @Override protected JupiterEngineExecutionContext createExecutionContext(ExecutionRequest request) { - return new JupiterEngineExecutionContext(request.getEngineExecutionListener(), - getJupiterConfiguration(request)); + return new JupiterEngineExecutionContext(request.getEngineExecutionListener(), getJupiterConfiguration(request), + request.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 58ff7c73936b..5b1e679ce8ba 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,18 @@ 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.OutputDirProvider; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.engine.support.hierarchical.Node; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; @@ -51,11 +57,18 @@ abstract class AbstractExtensionContext implements Ext private final T testDescriptor; private final Set tags; private final JupiterConfiguration configuration; + private final OutputDirProvider outputDirProvider; private final NamespacedHierarchicalStore valuesStore; private final ExecutableInvoker executableInvoker; AbstractExtensionContext(ExtensionContext parent, EngineExecutionListener engineExecutionListener, T testDescriptor, JupiterConfiguration configuration, ExecutableInvoker executableInvoker) { + this(parent, engineExecutionListener, testDescriptor, configuration, null, executableInvoker); + } + + AbstractExtensionContext(ExtensionContext parent, EngineExecutionListener engineExecutionListener, T testDescriptor, + JupiterConfiguration configuration, OutputDirProvider outputDirProvider, + ExecutableInvoker executableInvoker) { this.executableInvoker = executableInvoker; Preconditions.notNull(testDescriptor, "TestDescriptor must not be null"); @@ -65,6 +78,7 @@ abstract class AbstractExtensionContext implements Ext this.engineExecutionListener = engineExecutionListener; this.testDescriptor = testDescriptor; this.configuration = configuration; + this.outputDirProvider = outputDirProvider; this.valuesStore = createStore(parent); // @formatter:off @@ -102,6 +116,37 @@ public void publishReportEntry(Map values) { this.engineExecutionListener.reportingEntryPublished(this.testDescriptor, ReportEntry.from(values)); } + @Override + public void publishFile(String fileName, ThrowingConsumer action) { + try { + 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); + } + } + + private OutputDirProvider getOutputDirProvider() { + if (outputDirProvider == null) { + return getParent() // + .filter(it -> it instanceof AbstractExtensionContext) // + .map(it -> (AbstractExtensionContext) it) // + .map(AbstractExtensionContext::getOutputDirProvider).orElseThrow( + () -> new JUnitException("Missing OutputDirProvider")); + } + return outputDirProvider; + } + @Override public Optional getParent() { return Optional.ofNullable(this.parent); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineDescriptor.java index 3a87753ca530..03da66756e86 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineDescriptor.java @@ -55,7 +55,7 @@ public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext conte EngineExecutionListener executionListener = context.getExecutionListener(); ExecutableInvoker executableInvoker = new DefaultExecutableInvoker(context); ExtensionContext extensionContext = new JupiterEngineExtensionContext(executionListener, this, - context.getConfiguration(), executableInvoker); + context.getConfiguration(), context.getOutputDirProvider(), executableInvoker); // @formatter:off return context.extend() diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineExtensionContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineExtensionContext.java index 26eb83f7fa50..72fb7eda061d 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineExtensionContext.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineExtensionContext.java @@ -19,6 +19,7 @@ import org.junit.jupiter.api.extension.TestInstances; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.platform.engine.EngineExecutionListener; +import org.junit.platform.engine.reporting.OutputDirProvider; import org.junit.platform.engine.support.hierarchical.Node; /** @@ -28,9 +29,9 @@ final class JupiterEngineExtensionContext extends AbstractExtensionContext 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/descriptor/ExtensionContextTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java index af140f29f877..c5a2c8bcd6bc 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 { @@ -74,7 +75,7 @@ void fromJupiterEngineDescriptor() { UniqueId.root("engine", "junit-jupiter"), configuration); JupiterEngineExtensionContext engineContext = new JupiterEngineExtensionContext(null, engineTestDescriptor, - configuration, null); + configuration, OutputDirProvider.NOOP, null); // @formatter:off assertAll("engineContext", @@ -161,7 +162,7 @@ void fromMethodTestDescriptor() { Method testMethod = methodTestDescriptor.getTestMethod(); JupiterEngineExtensionContext engineExtensionContext = new JupiterEngineExtensionContext(null, engineDescriptor, - configuration, null); + configuration, OutputDirProvider.NOOP, null); ClassExtensionContext classExtensionContext = new ClassExtensionContext(engineExtensionContext, null, classTestDescriptor, configuration, null, null); MethodExtensionContext methodExtensionContext = new MethodExtensionContext(classExtensionContext, null, @@ -274,7 +275,8 @@ Stream configurationParameter() throws Exception { configuration); return Stream.of( // - (ExtensionContext) new JupiterEngineExtensionContext(null, engineDescriptor, echo, null), // + (ExtensionContext) new JupiterEngineExtensionContext(null, engineDescriptor, echo, OutputDirProvider.NOOP, + null), // new ClassExtensionContext(null, null, classTestDescriptor, echo, null, null), // new MethodExtensionContext(null, null, methodTestDescriptor, echo, null, null) // ).map(context -> dynamicTest(context.getClass().getSimpleName(), diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java index 7718e771ae2f..3e9f52bdb6c5 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java @@ -32,6 +32,7 @@ import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; import org.junit.platform.engine.TestSource; import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.OutputDirProvider; import org.junit.platform.engine.support.descriptor.ClasspathResourceSource; import org.junit.platform.engine.support.descriptor.DirectorySource; import org.junit.platform.engine.support.descriptor.FilePosition; @@ -138,7 +139,7 @@ void before() throws Exception { extensionContext = mock(); isClosed = false; - context = new JupiterEngineExecutionContext(null, null) // + context = new JupiterEngineExecutionContext(null, null, OutputDirProvider.NOOP) // .extend() // .withThrowableCollector(new OpenTest4JAwareThrowableCollector()) // .withExtensionContext(extensionContext) // diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContextTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContextTests.java index d6e619d886bd..e20207007a83 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContextTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContextTests.java @@ -27,6 +27,7 @@ import org.junit.jupiter.engine.extension.MutableExtensionRegistry; import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.engine.EngineExecutionListener; +import org.junit.platform.engine.reporting.OutputDirProvider; /** * Unit tests for {@link JupiterEngineExecutionContext}. @@ -40,7 +41,7 @@ class JupiterEngineExecutionContextTests { private final EngineExecutionListener engineExecutionListener = mock(); private final JupiterEngineExecutionContext originalContext = new JupiterEngineExecutionContext( - engineExecutionListener, configuration); + engineExecutionListener, configuration, OutputDirProvider.NOOP); @Test void executionListenerIsHandedOnWhenContextIsExtended() { 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 40c1eeeec28a..4f68857dfe63 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 304cedc27a2f..fbfebb4ce72b 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 40fdbf9345ef..6bc1c35d7009 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 34de4f217854..cb2b99bb0d9b 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 39d8126595fa..2775db0b4cfe 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 2dbd7e01d77a..78e73ba3c32f 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/EngineExecutionListener.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineExecutionListener.java index 315548ff3b98..fbcf4f5bb0d7 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 2c77f1fd5d66..b288652fd634 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 @@ -10,10 +10,12 @@ package org.junit.platform.engine; -import static org.apiguardian.api.API.Status.INTERNAL; +import static org.apiguardian.api.API.Status.DEPRECATED; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; +import org.junit.platform.engine.reporting.OutputDirProvider; /** * Provides a single {@link TestEngine} access to the information necessary to @@ -35,13 +37,21 @@ public class ExecutionRequest { private final EngineExecutionListener engineExecutionListener; private final ConfigurationParameters configurationParameters; + private final OutputDirProvider outputDirProvider; - @API(status = INTERNAL, since = "1.0") + @API(status = DEPRECATED, since = "1.11") + @Deprecated public ExecutionRequest(TestDescriptor rootTestDescriptor, EngineExecutionListener engineExecutionListener, ConfigurationParameters configurationParameters) { + this(rootTestDescriptor, engineExecutionListener, configurationParameters, OutputDirProvider.NOOP); + } + + private ExecutionRequest(TestDescriptor rootTestDescriptor, EngineExecutionListener engineExecutionListener, + ConfigurationParameters configurationParameters, OutputDirProvider outputDirProvider) { this.rootTestDescriptor = rootTestDescriptor; this.engineExecutionListener = engineExecutionListener; this.configurationParameters = configurationParameters; + this.outputDirProvider = outputDirProvider; } /** @@ -55,10 +65,33 @@ public ExecutionRequest(TestDescriptor rootTestDescriptor, EngineExecutionListen * @return a new {@code ExecutionRequest}; never {@code null} * @since 1.9 */ - @API(status = STABLE, since = "1.9") + @Deprecated + @API(status = DEPRECATED, since = "1.11") public static ExecutionRequest create(TestDescriptor rootTestDescriptor, EngineExecutionListener engineExecutionListener, ConfigurationParameters configurationParameters) { - return new ExecutionRequest(rootTestDescriptor, engineExecutionListener, configurationParameters); + return create(rootTestDescriptor, engineExecutionListener, configurationParameters, OutputDirProvider.NOOP); + } + + /** + * Factory for creating an execution request. + * + * @param rootTestDescriptor the engine's root {@link TestDescriptor}; must + * not be {@code null} + * @param engineExecutionListener the {@link EngineExecutionListener} to be + * notified of test execution events; must not be {@code null} + * @param configurationParameters {@link ConfigurationParameters} that the + * engine may use to influence test execution; must not be {@code null} + * @param outputDirProvider the provider for test output directories; must + * not be {@code null} + * @return a new {@code ExecutionRequest}; never {@code null} + * @since 1.11 + */ + @API(status = EXPERIMENTAL, since = "1.11") + public static ExecutionRequest create(TestDescriptor rootTestDescriptor, + EngineExecutionListener engineExecutionListener, ConfigurationParameters configurationParameters, + OutputDirProvider outputDirProvider) { + return new ExecutionRequest(rootTestDescriptor, engineExecutionListener, configurationParameters, + outputDirProvider); } /** @@ -89,4 +122,8 @@ public ConfigurationParameters getConfigurationParameters() { return this.configurationParameters; } + public OutputDirProvider getOutputDirProvider() { + return outputDirProvider; + } + } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/FileEntry.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/FileEntry.java new file mode 100644 index 000000000000..dc040ca4f7ba --- /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 000000000000..a68e507593e8 --- /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 6e114191d110..ee5cd84aae2f 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 be66660299cc..6502fd42384d 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 bf16677f933c..d102bf93ab48 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 @@ -217,6 +217,7 @@ public class LauncherConstants { */ @API(status = EXPERIMENTAL, since = "1.10") public static final String STACKTRACE_PRUNING_DEFAULT_PATTERN = "org.junit.*,java.*,jdk.*"; + 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 548395c43cfa..a9559bb3d316 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 29311c9b0773..b6ad5b50903b 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 977bc4a30739..1fa895d13f82 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/DelegatingEngineExecutionListener.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingEngineExecutionListener.java index e2f410533602..b2fc69eafa1d 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 ab1a8e94b503..10907c812f39 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 @@ -12,6 +12,7 @@ import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.launcher.LauncherConstants.DRY_RUN_PROPERTY_NAME; +import static org.junit.platform.launcher.LauncherConstants.OUTPUT_DIR_PROPERTY_NAME; import static org.junit.platform.launcher.LauncherConstants.STACKTRACE_PRUNING_DEFAULT_PATTERN; import static org.junit.platform.launcher.LauncherConstants.STACKTRACE_PRUNING_ENABLED_PROPERTY_NAME; import static org.junit.platform.launcher.LauncherConstants.STACKTRACE_PRUNING_PATTERN_PROPERTY_NAME; @@ -30,9 +31,11 @@ import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.reporting.OutputDirProvider; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; +import org.junit.platform.launcher.listeners.OutputDir; /** * Orchestrates test execution using the configured test engines. @@ -159,6 +162,7 @@ public void execute(LauncherDiscoveryResult discoveryResult, EngineExecutionList Preconditions.notNull(engineExecutionListener, "engineExecutionListener must not be null"); ConfigurationParameters configurationParameters = discoveryResult.getConfigurationParameters(); + OutputDirProvider outputDirProvider = createOutputDirProvider(configurationParameters); EngineExecutionListener listener = selectExecutionListener(engineExecutionListener, configurationParameters); for (TestEngine testEngine : discoveryResult.getTestEngines()) { @@ -169,11 +173,20 @@ public void execute(LauncherDiscoveryResult discoveryResult, EngineExecutionList TestExecutionResult.failed(((EngineDiscoveryErrorDescriptor) engineDescriptor).getCause())); } else { - execute(engineDescriptor, listener, configurationParameters, testEngine); + execute(engineDescriptor, listener, configurationParameters, outputDirProvider, testEngine); } } } + private static OutputDirProvider createOutputDirProvider(ConfigurationParameters 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 static EngineExecutionListener selectExecutionListener(EngineExecutionListener engineExecutionListener, ConfigurationParameters configurationParameters) { boolean stackTracePruningEnabled = configurationParameters.getBoolean(STACKTRACE_PRUNING_ENABLED_PROPERTY_NAME) // @@ -195,12 +208,14 @@ private ListenerRegistry buildListenerRegistryForExecutio } private void execute(TestDescriptor engineDescriptor, EngineExecutionListener listener, - ConfigurationParameters configurationParameters, TestEngine testEngine) { + ConfigurationParameters configurationParameters, OutputDirProvider outputDirProvider, + TestEngine testEngine) { OutcomeDelayingEngineExecutionListener delayingListener = new OutcomeDelayingEngineExecutionListener(listener, engineDescriptor); try { - testEngine.execute(new ExecutionRequest(engineDescriptor, delayingListener, configurationParameters)); + testEngine.execute(ExecutionRequest.create(engineDescriptor, delayingListener, configurationParameters, + outputDirProvider)); 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 f2d87e4bfea8..eb1f0976fa07 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 000000000000..8c76f346fe72 --- /dev/null +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/HierarchicalOutputDirProvider.java @@ -0,0 +1,78 @@ +/* + * 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(); + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + if (!FORBIDDEN_CHARS.contains(c) || Character.isISOControl(c)) { + result.append(c); + } + else { + result.append(REPLACEMENT); + } + } + return result.toString(); + } +} 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 14080b1f7e27..8f6822a2489f 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 000000000000..8f44ea4e6d0c --- /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 382404bdd7a1..72bd75feef09 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 05c452880a90..c379b1661548 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 039117187dd4..e5c8e4dbc2e9 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 ee4ab403e27f..2c7fc4004d04 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 @@ -34,6 +34,7 @@ import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.OutputDirProvider; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.core.EngineDiscoveryOrchestrator; import org.junit.platform.launcher.core.EngineExecutionOrchestrator; @@ -249,8 +250,8 @@ 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, - discoveryRequest.getConfigurationParameters()); + ExecutionRequest request = ExecutionRequest.create(engineTestDescriptor, listener, + discoveryRequest.getConfigurationParameters(), OutputDirProvider.NOOP); 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 63c69409b381..9e32405ed652 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 1fed5b9b67ba..3d79ae7fb7d6 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 15db74b2b7d6..64507dfae7b5 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/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java index 66af815aff99..65916fdda781 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,7 @@ 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.engine.reporting.OutputDirProvider; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; import org.junit.platform.testkit.engine.EngineExecutionResults; @@ -394,14 +394,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); @@ -908,8 +900,8 @@ private static void execute(Class testClass, EngineExecutionListener listener TestEngine testEngine = new VintageTestEngine(); var discoveryRequest = request(testClass); var engineTestDescriptor = testEngine.discover(discoveryRequest, UniqueId.forEngine(testEngine.getId())); - testEngine.execute( - new ExecutionRequest(engineTestDescriptor, listener, discoveryRequest.getConfigurationParameters())); + testEngine.execute(ExecutionRequest.create(engineTestDescriptor, listener, + discoveryRequest.getConfigurationParameters(), OutputDirProvider.NOOP)); } private static LauncherDiscoveryRequest request(Class testClass) { 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 80d46102f08e..e9a1e94fe31e 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 @@ -42,6 +42,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.OutputDirProvider; import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; import org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode; import org.junit.platform.engine.support.hierarchical.Node.DynamicTestExecutor; @@ -77,7 +78,7 @@ void init() { private HierarchicalTestExecutor createExecutor( HierarchicalTestExecutorService executorService) { - var request = new ExecutionRequest(root, listener, null); + var request = ExecutionRequest.create(root, listener, null, OutputDirProvider.NOOP); 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 4935d038d107..4e7c4917f908 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 99398dbc31e8..117905635e1c 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;