Skip to content

Commit aa922c7

Browse files
committed
Capture process output as file attachments
1 parent f1b5d7a commit aa922c7

21 files changed

+286
-62
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* Copyright 2015-2024 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v2.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* https://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package org.junit.platform.tests.process;
12+
13+
import java.nio.file.Path;
14+
15+
public record OutputFiles(Path stdOut, Path stdErr) {
16+
}

platform-tests/src/processStarter/java/org/junit/platform/tests/process/ProcessStarter.java

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,14 @@
1515
import java.io.OutputStream;
1616
import java.io.PrintStream;
1717
import java.io.UncheckedIOException;
18+
import java.nio.charset.Charset;
19+
import java.nio.file.Files;
1820
import java.nio.file.Path;
1921
import java.util.ArrayList;
2022
import java.util.LinkedHashMap;
2123
import java.util.List;
2224
import java.util.Map;
25+
import java.util.Optional;
2326
import java.util.function.BiFunction;
2427
import java.util.stream.Stream;
2528

@@ -28,10 +31,13 @@
2831

2932
public class ProcessStarter {
3033

34+
public static final Charset OUTPUT_ENCODING = Charset.forName(System.getProperty("native.encoding"));
35+
3136
private Path executable;
3237
private Path workingDir;
3338
private final List<String> arguments = new ArrayList<>();
3439
private final Map<String, String> environment = new LinkedHashMap<>();
40+
private Optional<OutputFiles> outputFiles = Optional.empty();
3541

3642
public ProcessStarter executable(Path executable) {
3743
this.executable = executable;
@@ -62,6 +68,11 @@ public ProcessStarter putEnvironment(Map<String, String> values) {
6268
return this;
6369
}
6470

71+
public ProcessStarter redirectOutput(OutputFiles outputFiles) {
72+
this.outputFiles = Optional.of(outputFiles);
73+
return this;
74+
}
75+
6576
public ProcessResult startAndWait() throws InterruptedException {
6677
return start().waitFor();
6778
}
@@ -75,8 +86,10 @@ public WatchedProcess start() {
7586
}
7687
builder.environment().putAll(environment);
7788
var process = builder.start();
78-
var out = forwardAndCaptureOutput(process, System.out, ProcessGroovyMethods::consumeProcessOutputStream);
79-
var err = forwardAndCaptureOutput(process, System.err, ProcessGroovyMethods::consumeProcessErrorStream);
89+
var out = forwardAndCaptureOutput(process, System.out, outputFiles.map(OutputFiles::stdOut),
90+
ProcessGroovyMethods::consumeProcessOutputStream);
91+
var err = forwardAndCaptureOutput(process, System.err, outputFiles.map(OutputFiles::stdErr),
92+
ProcessGroovyMethods::consumeProcessErrorStream);
8093
return new WatchedProcess(process, out, err);
8194
}
8295
catch (IOException e) {
@@ -85,10 +98,23 @@ public WatchedProcess start() {
8598
}
8699

87100
private static WatchedOutput forwardAndCaptureOutput(Process process, PrintStream delegate,
88-
BiFunction<Process, OutputStream, Thread> captureAction) {
101+
Optional<Path> outputFile, BiFunction<Process, OutputStream, Thread> captureAction) {
89102
var capturingStream = new ByteArrayOutputStream();
90-
var thread = captureAction.apply(process, new TeeOutputStream(delegate, capturingStream));
91-
return new WatchedOutput(thread, capturingStream);
103+
Optional<OutputStream> fileStream = outputFile.map(path -> {
104+
try {
105+
return Files.newOutputStream(path);
106+
}
107+
catch (IOException e) {
108+
throw new UncheckedIOException("Failed to open output file: " + path, e);
109+
}
110+
});
111+
var attachedStream = tee(delegate, fileStream.map(it -> tee(capturingStream, it)).orElse(capturingStream));
112+
var thread = captureAction.apply(process, attachedStream);
113+
return new WatchedOutput(thread, capturingStream, fileStream);
114+
}
115+
116+
private static OutputStream tee(OutputStream out, OutputStream branch) {
117+
return new TeeOutputStream(out, branch);
92118
}
93119

94120
}

platform-tests/src/processStarter/java/org/junit/platform/tests/process/WatchedOutput.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,15 @@
1010

1111
package org.junit.platform.tests.process;
1212

13-
import java.io.ByteArrayOutputStream;
14-
import java.nio.charset.Charset;
13+
import static org.junit.platform.tests.process.ProcessStarter.OUTPUT_ENCODING;
1514

16-
record WatchedOutput(Thread thread, ByteArrayOutputStream stream) {
15+
import java.io.ByteArrayOutputStream;
16+
import java.io.OutputStream;
17+
import java.util.Optional;
1718

18-
private static final Charset CHARSET = Charset.forName(System.getProperty("native.encoding"));
19+
record WatchedOutput(Thread thread, ByteArrayOutputStream stream, Optional<OutputStream> fileStream) {
1920

2021
String streamAsString() {
21-
return stream.toString(CHARSET);
22+
return stream.toString(OUTPUT_ENCODING);
2223
}
2324
}

platform-tests/src/processStarter/java/org/junit/platform/tests/process/WatchedProcess.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010

1111
package org.junit.platform.tests.process;
1212

13+
import java.io.IOException;
14+
import java.io.OutputStream;
15+
import java.util.Optional;
16+
1317
public class WatchedProcess {
1418

1519
private final Process process;
@@ -46,6 +50,19 @@ ProcessResult waitFor() throws InterruptedException {
4650
}
4751
finally {
4852
process.destroyForcibly();
53+
closeQuietly(out.fileStream());
54+
closeQuietly(err.fileStream());
55+
}
56+
}
57+
58+
private static void closeQuietly(Optional<OutputStream> fileStream) {
59+
if (fileStream.isEmpty()) {
60+
return;
61+
}
62+
try {
63+
fileStream.get().close();
64+
}
65+
catch (IOException ignore) {
4966
}
5067
}
5168
}

platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/AntStarterTests.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.junit.jupiter.api.Test;
2323
import org.junit.jupiter.api.Timeout;
2424
import org.junit.jupiter.api.io.TempDir;
25+
import org.junit.platform.tests.process.OutputFiles;
2526

2627
import platform.tooling.support.ProcessStarters;
2728

@@ -32,10 +33,11 @@ class AntStarterTests {
3233

3334
@Test
3435
@Timeout(60)
35-
void ant_starter(@TempDir Path workspace) throws Exception {
36+
void ant_starter(@TempDir Path workspace, @FilePrefix("ant") OutputFiles outputFiles) throws Exception {
3637
var result = ProcessStarters.java() //
3738
.workingDir(copyToWorkspace(Projects.ANT_STARTER, workspace)) //
3839
.addArguments("-cp", System.getProperty("antJars"), Main.class.getName()) //
40+
.redirectOutput(outputFiles) //
3941
.startAndWait();
4042

4143
assertEquals(0, result.exitCode());
@@ -57,5 +59,4 @@ void ant_starter(@TempDir Path workspace) throws Exception {
5759
var testResultsDir = workspace.resolve("build/test-report");
5860
verifyContainsExpectedStartedOpenTestReport(testResultsDir);
5961
}
60-
6162
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright 2015-2024 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v2.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* https://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package platform.tooling.support.tests;
12+
13+
import java.lang.annotation.ElementType;
14+
import java.lang.annotation.Retention;
15+
import java.lang.annotation.RetentionPolicy;
16+
import java.lang.annotation.Target;
17+
18+
import org.junit.jupiter.api.extension.ExtendWith;
19+
20+
@Target(ElementType.PARAMETER)
21+
@Retention(RetentionPolicy.RUNTIME)
22+
@ExtendWith(OutputAttachingExtension.class)
23+
@interface FilePrefix {
24+
String value();
25+
}

platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
2424
import org.junit.jupiter.api.extension.DisabledOnOpenJ9;
2525
import org.junit.jupiter.api.io.TempDir;
26+
import org.junit.platform.tests.process.OutputFiles;
2627

2728
import platform.tooling.support.MavenRepo;
2829
import platform.tooling.support.ProcessStarters;
@@ -37,12 +38,14 @@ class GraalVmStarterTests {
3738

3839
@Test
3940
@Timeout(value = 10, unit = MINUTES)
40-
void runsTestsInNativeImage(@TempDir Path workspace) throws Exception {
41+
void runsTestsInNativeImage(@TempDir Path workspace, @FilePrefix("gradle") OutputFiles outputFiles)
42+
throws Exception {
4143
var result = ProcessStarters.gradlew() //
4244
.workingDir(copyToWorkspace(Projects.GRAALVM_STARTER, workspace)) //
4345
.addArguments("-Dmaven.repo=" + MavenRepo.dir()) //
4446
.addArguments("javaToolchains", "nativeTest", "--no-daemon", "--stacktrace", "--no-build-cache",
4547
"--warning-mode=fail") //
48+
.redirectOutput(outputFiles) //
4649
.startAndWait();
4750

4851
assertEquals(0, result.exitCode());

platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleKotlinExtensionsTests.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import org.junit.jupiter.api.Test;
2020
import org.junit.jupiter.api.io.TempDir;
21+
import org.junit.platform.tests.process.OutputFiles;
2122
import org.opentest4j.TestAbortedException;
2223

2324
import platform.tooling.support.Helper;
@@ -30,12 +31,13 @@
3031
class GradleKotlinExtensionsTests {
3132

3233
@Test
33-
void gradle_wrapper(@TempDir Path workspace) throws Exception {
34+
void gradle_wrapper(@TempDir Path workspace, @FilePrefix("gradle") OutputFiles outputFiles) throws Exception {
3435
var result = ProcessStarters.gradlew() //
3536
.workingDir(copyToWorkspace(Projects.GRADLE_KOTLIN_EXTENSIONS, workspace)) //
3637
.addArguments("-Dmaven.repo=" + MavenRepo.dir()) //
3738
.addArguments("build", "--no-daemon", "--stacktrace", "--no-build-cache", "--warning-mode=fail") //
3839
.putEnvironment("JDK8", Helper.getJavaHome("8").orElseThrow(TestAbortedException::new).toString()) //
40+
.redirectOutput(outputFiles) //
3941
.startAndWait();
4042

4143
assertEquals(0, result.exitCode(), "result=" + result);

platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleMissingEngineTests.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.junit.jupiter.api.Test;
2020
import org.junit.jupiter.api.io.TempDir;
2121
import org.junit.platform.reporting.testutil.FileUtils;
22+
import org.junit.platform.tests.process.OutputFiles;
2223
import org.opentest4j.TestAbortedException;
2324

2425
import platform.tooling.support.Helper;
@@ -31,13 +32,13 @@
3132
class GradleMissingEngineTests {
3233

3334
@Test
34-
void gradle_wrapper(@TempDir Path workspace) throws Exception {
35+
void gradle_wrapper(@TempDir Path workspace, @FilePrefix("gradle") OutputFiles outputFiles) throws Exception {
3536
var result = ProcessStarters.gradlew() //
3637
.workingDir(copyToWorkspace(Projects.GRADLE_MISSING_ENGINE, workspace)) //
3738
.addArguments("-Dmaven.repo=" + MavenRepo.dir()) //
3839
.addArguments("build", "--no-daemon", "--stacktrace", "--no-build-cache", "--warning-mode=fail") //
3940
.putEnvironment("JDK8", Helper.getJavaHome("8").orElseThrow(TestAbortedException::new).toString()) //
40-
.startAndWait();
41+
.redirectOutput(outputFiles).startAndWait();
4142

4243
assertEquals(1, result.exitCode());
4344
assertThat(result.stdErrLines()) //

platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleStarterTests.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import org.junit.jupiter.api.Test;
2222
import org.junit.jupiter.api.io.TempDir;
23+
import org.junit.platform.tests.process.OutputFiles;
2324
import org.opentest4j.TestAbortedException;
2425

2526
import platform.tooling.support.Helper;
@@ -32,12 +33,13 @@
3233
class GradleStarterTests {
3334

3435
@Test
35-
void gradle_wrapper(@TempDir Path workspace) throws Exception {
36+
void gradle_wrapper(@TempDir Path workspace, @FilePrefix("gradle") OutputFiles outputFiles) throws Exception {
3637
var result = ProcessStarters.gradlew() //
3738
.workingDir(copyToWorkspace(Projects.GRADLE_STARTER, workspace)) //
3839
.addArguments("-Dmaven.repo=" + MavenRepo.dir()) //
3940
.addArguments("build", "--no-daemon", "--stacktrace", "--no-build-cache", "--warning-mode=fail") //
4041
.putEnvironment("JDK8", Helper.getJavaHome("8").orElseThrow(TestAbortedException::new).toString()) //
42+
.redirectOutput(outputFiles) //
4143
.startAndWait();
4244

4345
assertEquals(0, result.exitCode());

0 commit comments

Comments
 (0)