Skip to content

Commit 216bfdb

Browse files
committed
Issue: #3166: Introduce --redirect-stdout and --redirect-stderr CLI options to redirect STDOUT and STDERR
1 parent 35d1d98 commit 216bfdb

File tree

6 files changed

+127
-1
lines changed

6 files changed

+127
-1
lines changed

documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-RC2.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ repository on GitHub.
4848
* The `@TempDir` now warns when deleting symlinks that target locations outside the
4949
temporary directory to signal that the target file or directory is _not_ deleted, only
5050
the link to it.
51-
51+
* New optional CLI options `--redirect-stdout` and `--redirect-stderr` to redirect stdout and stderr outputs to a file.
5252

5353
[[release-notes-5.12.0-RC2-junit-vintage]]
5454
=== JUnit Vintage

documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-reporting.adoc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,15 @@ $ java -jar junit-platform-console-standalone-{platform-version}.jar <OPTIONS> \
154154
--config-resource=configuration.properties
155155
----
156156

157+
You can redirect standard output and standard error using the --redirect-stdout and --redirect-stderr options:
158+
159+
[source,console,subs=attributes+]
160+
----
161+
$ java -jar junit-platform-console-standalone-{platform-version}.jar <OPTIONS> \
162+
--redirect-stdout=foo.txt
163+
--redirect-stderr=bar.txt
164+
----
165+
157166
[[junit-platform-reporting-legacy-xml]]
158167
==== Legacy XML format
159168

junit-platform-console/src/main/java/org/junit/platform/console/options/TestConsoleOutputOptions.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ public class TestConsoleOutputOptions {
3232
private boolean isSingleColorPalette;
3333
private Details details = DEFAULT_DETAILS;
3434
private Theme theme = DEFAULT_THEME;
35+
private Path stdoutPath;
36+
private Path stderrPath;
3537

3638
public boolean isAnsiColorOutputDisabled() {
3739
return this.ansiColorOutputDisabled;
@@ -73,4 +75,24 @@ public void setTheme(Theme theme) {
7375
this.theme = theme;
7476
}
7577

78+
@API(status = INTERNAL, since = "5.12")
79+
public Path getStdoutPath() {
80+
return this.stdoutPath;
81+
}
82+
83+
@API(status = INTERNAL, since = "5.12")
84+
public void setStdoutPath(Path stdoutPath) {
85+
this.stdoutPath = stdoutPath;
86+
}
87+
88+
@API(status = INTERNAL, since = "5.12")
89+
public Path getStderrPath() {
90+
return this.stderrPath;
91+
}
92+
93+
@API(status = INTERNAL, since = "5.12")
94+
public void setStderrPath(Path stderrPath) {
95+
this.stderrPath = stderrPath;
96+
}
97+
7698
}

junit-platform-console/src/main/java/org/junit/platform/console/options/TestConsoleOutputOptionsMixin.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,19 @@ static class ConsoleOutputOptions {
5151
@Option(names = "-details-theme", hidden = true)
5252
private final Theme theme2 = DEFAULT_THEME;
5353

54+
@Option(names = "--redirect-stdout", paramLabel = "FILE", description = "Redirect tests stdout to a file.")
55+
private Path stdout;
56+
57+
@Option(names = "--redirect-stderr", paramLabel = "FILE", description = "Redirect tests stderr to a file.")
58+
private Path stderr;
59+
5460
private void applyTo(TestConsoleOutputOptions result) {
5561
result.setColorPalettePath(choose(colorPalette, colorPalette2, null));
5662
result.setSingleColorPalette(singleColorPalette || singleColorPalette2);
5763
result.setDetails(choose(details, details2, DEFAULT_DETAILS));
5864
result.setTheme(choose(theme, theme2, DEFAULT_THEME));
65+
result.setStdoutPath(stdout);
66+
result.setStderrPath(stderr);
5967
}
6068
}
6169

junit-platform-console/src/main/java/org/junit/platform/console/tasks/ConsoleTestExecutor.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@
1414
import static org.junit.platform.console.tasks.DiscoveryRequestCreator.toDiscoveryRequestBuilder;
1515
import static org.junit.platform.launcher.LauncherConstants.OUTPUT_DIR_PROPERTY_NAME;
1616

17+
import java.io.IOException;
18+
import java.io.PrintStream;
1719
import java.io.PrintWriter;
1820
import java.net.URL;
1921
import java.net.URLClassLoader;
22+
import java.nio.file.Files;
2023
import java.nio.file.Path;
2124
import java.util.EnumSet;
2225
import java.util.List;
@@ -101,6 +104,8 @@ private TestExecutionSummary executeTests(PrintWriter out, Optional<Path> report
101104
Launcher launcher = launcherSupplier.get();
102105
SummaryGeneratingListener summaryListener = registerListeners(out, reportsDir, launcher);
103106

107+
redirectStdStreams(outputOptions.getStdoutPath(), outputOptions.getStderrPath());
108+
104109
LauncherDiscoveryRequestBuilder discoveryRequestBuilder = toDiscoveryRequestBuilder(discoveryOptions);
105110
reportsDir.ifPresent(dir -> discoveryRequestBuilder.configurationParameter(OUTPUT_DIR_PROPERTY_NAME,
106111
dir.toAbsolutePath().toString()));
@@ -190,6 +195,47 @@ private void printSummary(TestExecutionSummary summary, PrintWriter out) {
190195
summary.printTo(out);
191196
}
192197

198+
@API(status = INTERNAL, since = "5.12")
199+
private boolean isSameFile(Path path1, Path path2) {
200+
if (path1 == null || path2 == null)
201+
return false;
202+
return (path1.normalize().toAbsolutePath().equals(path2.normalize().toAbsolutePath()));
203+
}
204+
205+
private void redirectStdStreams(Path stdoutPath, Path stderrPath) {
206+
if (isSameFile(stdoutPath, stderrPath)) {
207+
try {
208+
PrintStream commonStream = new PrintStream(Files.newOutputStream(stdoutPath), true);
209+
System.setOut(commonStream);
210+
System.setErr(commonStream);
211+
}
212+
catch (IOException e) {
213+
throw new JUnitException("Error setting up stream for Stdout and Stderr at path: " + stdoutPath, e);
214+
}
215+
}
216+
else {
217+
if (stdoutPath != null) {
218+
try {
219+
PrintStream outStream = new PrintStream(Files.newOutputStream(stdoutPath), true);
220+
System.setOut(outStream);
221+
}
222+
catch (IOException e) {
223+
throw new JUnitException("Error setting up stream for Stdout at path: " + stdoutPath, e);
224+
}
225+
}
226+
227+
if (stderrPath != null) {
228+
try {
229+
PrintStream errStream = new PrintStream(Files.newOutputStream(stderrPath), true);
230+
System.setErr(errStream);
231+
}
232+
catch (IOException e) {
233+
throw new JUnitException("Error setting up stream for Stderr at path: " + stderrPath, e);
234+
}
235+
}
236+
}
237+
}
238+
193239
@FunctionalInterface
194240
public interface Factory {
195241
ConsoleTestExecutor create(TestDiscoveryOptions discoveryOptions, TestConsoleOutputOptions outputOptions);

platform-tests/src/test/java/org/junit/platform/console/options/CommandLineOptionsParsingTests.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import static org.junit.jupiter.api.Assertions.assertAll;
1616
import static org.junit.jupiter.api.Assertions.assertEquals;
1717
import static org.junit.jupiter.api.Assertions.assertFalse;
18+
import static org.junit.jupiter.api.Assertions.assertNull;
1819
import static org.junit.jupiter.api.Assertions.assertThrows;
1920
import static org.junit.jupiter.api.Assertions.assertTrue;
2021
import static org.junit.platform.engine.discovery.ClassNameFilter.STANDARD_INCLUDE_PATTERN;
@@ -60,6 +61,8 @@ void parseNoArguments() {
6061
// @formatter:off
6162
assertAll(
6263
() -> assertFalse(options.output.isAnsiColorOutputDisabled()),
64+
() -> assertNull(options.output.getStdoutPath()),
65+
() -> assertNull(options.output.getStderrPath()),
6366
() -> assertEquals(TestConsoleOutputOptions.DEFAULT_DETAILS, options.output.getDetails()),
6467
() -> assertFalse(options.discovery.isScanClasspath()),
6568
() -> assertEquals(List.of(STANDARD_INCLUDE_PATTERN), options.discovery.getIncludedClassNamePatterns()),
@@ -632,6 +635,44 @@ void parseInvalidConfigurationParameters() {
632635
assertOptionWithMissingRequiredArgumentThrowsException("-config", "--config");
633636
}
634637

638+
@ParameterizedTest
639+
@EnumSource
640+
void parseValidStdoutRedirectionFile(ArgsType type) {
641+
var file = Paths.get("foo.txt");
642+
// @formatter:off
643+
assertAll(
644+
() -> assertNull(type.parseArgLine("").output.getStdoutPath()),
645+
() -> assertEquals(file, type.parseArgLine("--redirect-stdout=foo.txt").output.getStdoutPath()),
646+
() -> assertEquals(file, type.parseArgLine("--redirect-stdout foo.txt").output.getStdoutPath()),
647+
() -> assertEquals(file, type.parseArgLine("--redirect-stdout bar.txt --redirect-stdout foo.txt").output.getStdoutPath())
648+
);
649+
// @formatter:on
650+
}
651+
652+
@Test
653+
void parseInvalidStdoutRedirectionFile() {
654+
assertOptionWithMissingRequiredArgumentThrowsException("--redirect-stdout");
655+
}
656+
657+
@ParameterizedTest
658+
@EnumSource
659+
void parseValidStderrRedirectionFile(ArgsType type) {
660+
var file = Paths.get("foo.txt");
661+
// @formatter:off
662+
assertAll(
663+
() -> assertNull(type.parseArgLine("").output.getStderrPath()),
664+
() -> assertEquals(file, type.parseArgLine("--redirect-stderr=foo.txt").output.getStderrPath()),
665+
() -> assertEquals(file, type.parseArgLine("--redirect-stderr foo.txt").output.getStderrPath()),
666+
() -> assertEquals(file, type.parseArgLine("--redirect-stderr bar.txt --redirect-stderr foo.txt").output.getStderrPath())
667+
);
668+
// @formatter:on
669+
}
670+
671+
@Test
672+
void parseInvalidStderrRedirectionFile() {
673+
assertOptionWithMissingRequiredArgumentThrowsException("--redirect-stderr");
674+
}
675+
635676
@Test
636677
void parseInvalidConfigurationParametersResource() {
637678
assertOptionWithMissingRequiredArgumentThrowsException("--config-resource");

0 commit comments

Comments
 (0)