Skip to content

Commit

Permalink
Capture process output as file attachments
Browse files Browse the repository at this point in the history
  • Loading branch information
marcphilipp committed Dec 19, 2024
1 parent f1b5d7a commit aa922c7
Show file tree
Hide file tree
Showing 21 changed files with 286 additions and 62 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Copyright 2015-2024 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.tests.process;

import java.nio.file.Path;

public record OutputFiles(Path stdOut, Path stdErr) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.UncheckedIOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.stream.Stream;

Expand All @@ -28,10 +31,13 @@

public class ProcessStarter {

public static final Charset OUTPUT_ENCODING = Charset.forName(System.getProperty("native.encoding"));

private Path executable;
private Path workingDir;
private final List<String> arguments = new ArrayList<>();
private final Map<String, String> environment = new LinkedHashMap<>();
private Optional<OutputFiles> outputFiles = Optional.empty();

public ProcessStarter executable(Path executable) {
this.executable = executable;
Expand Down Expand Up @@ -62,6 +68,11 @@ public ProcessStarter putEnvironment(Map<String, String> values) {
return this;
}

public ProcessStarter redirectOutput(OutputFiles outputFiles) {
this.outputFiles = Optional.of(outputFiles);
return this;
}

public ProcessResult startAndWait() throws InterruptedException {
return start().waitFor();
}
Expand All @@ -75,8 +86,10 @@ public WatchedProcess start() {
}
builder.environment().putAll(environment);
var process = builder.start();
var out = forwardAndCaptureOutput(process, System.out, ProcessGroovyMethods::consumeProcessOutputStream);
var err = forwardAndCaptureOutput(process, System.err, ProcessGroovyMethods::consumeProcessErrorStream);
var out = forwardAndCaptureOutput(process, System.out, outputFiles.map(OutputFiles::stdOut),
ProcessGroovyMethods::consumeProcessOutputStream);
var err = forwardAndCaptureOutput(process, System.err, outputFiles.map(OutputFiles::stdErr),
ProcessGroovyMethods::consumeProcessErrorStream);
return new WatchedProcess(process, out, err);
}
catch (IOException e) {
Expand All @@ -85,10 +98,23 @@ public WatchedProcess start() {
}

private static WatchedOutput forwardAndCaptureOutput(Process process, PrintStream delegate,
BiFunction<Process, OutputStream, Thread> captureAction) {
Optional<Path> outputFile, BiFunction<Process, OutputStream, Thread> captureAction) {
var capturingStream = new ByteArrayOutputStream();
var thread = captureAction.apply(process, new TeeOutputStream(delegate, capturingStream));
return new WatchedOutput(thread, capturingStream);
Optional<OutputStream> fileStream = outputFile.map(path -> {
try {
return Files.newOutputStream(path);
}
catch (IOException e) {
throw new UncheckedIOException("Failed to open output file: " + path, e);
}
});
var attachedStream = tee(delegate, fileStream.map(it -> tee(capturingStream, it)).orElse(capturingStream));
var thread = captureAction.apply(process, attachedStream);
return new WatchedOutput(thread, capturingStream, fileStream);
}

private static OutputStream tee(OutputStream out, OutputStream branch) {
return new TeeOutputStream(out, branch);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@

package org.junit.platform.tests.process;

import java.io.ByteArrayOutputStream;
import java.nio.charset.Charset;
import static org.junit.platform.tests.process.ProcessStarter.OUTPUT_ENCODING;

record WatchedOutput(Thread thread, ByteArrayOutputStream stream) {
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.util.Optional;

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

String streamAsString() {
return stream.toString(CHARSET);
return stream.toString(OUTPUT_ENCODING);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@

package org.junit.platform.tests.process;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Optional;

public class WatchedProcess {

private final Process process;
Expand Down Expand Up @@ -46,6 +50,19 @@ ProcessResult waitFor() throws InterruptedException {
}
finally {
process.destroyForcibly();
closeQuietly(out.fileStream());
closeQuietly(err.fileStream());
}
}

private static void closeQuietly(Optional<OutputStream> fileStream) {
if (fileStream.isEmpty()) {
return;
}
try {
fileStream.get().close();
}
catch (IOException ignore) {
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.api.io.TempDir;
import org.junit.platform.tests.process.OutputFiles;

import platform.tooling.support.ProcessStarters;

Expand All @@ -32,10 +33,11 @@ class AntStarterTests {

@Test
@Timeout(60)
void ant_starter(@TempDir Path workspace) throws Exception {
void ant_starter(@TempDir Path workspace, @FilePrefix("ant") OutputFiles outputFiles) throws Exception {
var result = ProcessStarters.java() //
.workingDir(copyToWorkspace(Projects.ANT_STARTER, workspace)) //
.addArguments("-cp", System.getProperty("antJars"), Main.class.getName()) //
.redirectOutput(outputFiles) //
.startAndWait();

assertEquals(0, result.exitCode());
Expand All @@ -57,5 +59,4 @@ void ant_starter(@TempDir Path workspace) throws Exception {
var testResultsDir = workspace.resolve("build/test-report");
verifyContainsExpectedStartedOpenTestReport(testResultsDir);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2015-2024 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 platform.tooling.support.tests;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.junit.jupiter.api.extension.ExtendWith;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(OutputAttachingExtension.class)
@interface FilePrefix {
String value();
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
import org.junit.jupiter.api.extension.DisabledOnOpenJ9;
import org.junit.jupiter.api.io.TempDir;
import org.junit.platform.tests.process.OutputFiles;

import platform.tooling.support.MavenRepo;
import platform.tooling.support.ProcessStarters;
Expand All @@ -37,12 +38,14 @@ class GraalVmStarterTests {

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

assertEquals(0, result.exitCode());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.platform.tests.process.OutputFiles;
import org.opentest4j.TestAbortedException;

import platform.tooling.support.Helper;
Expand All @@ -30,12 +31,13 @@
class GradleKotlinExtensionsTests {

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

assertEquals(0, result.exitCode(), "result=" + result);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.platform.reporting.testutil.FileUtils;
import org.junit.platform.tests.process.OutputFiles;
import org.opentest4j.TestAbortedException;

import platform.tooling.support.Helper;
Expand All @@ -31,13 +32,13 @@
class GradleMissingEngineTests {

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

assertEquals(1, result.exitCode());
assertThat(result.stdErrLines()) //
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.platform.tests.process.OutputFiles;
import org.opentest4j.TestAbortedException;

import platform.tooling.support.Helper;
Expand All @@ -32,12 +33,13 @@
class GradleStarterTests {

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

assertEquals(0, result.exitCode());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.junit.jupiter.api.Order;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.platform.tests.process.OutputFiles;

import platform.tooling.support.Helper;
import platform.tooling.support.MavenRepo;
Expand All @@ -34,13 +35,14 @@ class JarDescribeModuleTests {

@ParameterizedTest
@MethodSource("platform.tooling.support.Helper#loadModuleDirectoryNames")
void describeModule(String module) throws Exception {
void describeModule(String module, @FilePrefix("jar") OutputFiles outputFiles) throws Exception {
var sourceDirectory = getSourceDirectory(Projects.JAR_DESCRIBE_MODULE);
var modulePath = MavenRepo.jar(module);

var result = ProcessStarters.javaCommand("jar") //
.workingDir(sourceDirectory) //
.addArguments("--describe-module", "--file", modulePath.toAbsolutePath().toString()) //
.redirectOutput(outputFiles) //
.startAndWait();

assertEquals(0, result.exitCode());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.platform.tests.process.OutputFiles;

import platform.tooling.support.Helper;
import platform.tooling.support.MavenRepo;
Expand All @@ -39,22 +40,22 @@ class JavaVersionsTests {
Path workspace;

@Test
void java_8() throws Exception {
void java_8(@FilePrefix("maven") OutputFiles outputFiles) throws Exception {
var java8Home = Helper.getJavaHome("8");
assumeTrue(java8Home.isPresent(), "Java 8 installation directory not found!");
var actualLines = execute(java8Home.get(), Map.of());
var actualLines = execute(java8Home.get(), outputFiles, Map.of());

assertTrue(actualLines.contains("[WARNING] Tests run: 2, Failures: 0, Errors: 0, Skipped: 1"));
}

@Test
void java_default() throws Exception {
var actualLines = execute(currentJdkHome(), MavenEnvVars.FOR_JDK24_AND_LATER);
void java_default(@FilePrefix("maven") OutputFiles outputFiles) throws Exception {
var actualLines = execute(currentJdkHome(), outputFiles, MavenEnvVars.FOR_JDK24_AND_LATER);

assertTrue(actualLines.contains("[WARNING] Tests run: 2, Failures: 0, Errors: 0, Skipped: 1"));
}

List<String> execute(Path javaHome, Map<String, String> environmentVars) throws Exception {
List<String> execute(Path javaHome, OutputFiles outputFiles, Map<String, String> environmentVars) throws Exception {
var result = ProcessStarters.maven(javaHome) //
.workingDir(copyToWorkspace(Projects.JAVA_VERSIONS, workspace)) //
.putEnvironment(environmentVars) //
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.platform.tests.process.OutputFiles;
import org.opentest4j.TestAbortedException;

import platform.tooling.support.Helper;
Expand All @@ -38,12 +39,14 @@ class MavenStarterTests {
MavenRepoProxy mavenRepoProxy;

@Test
void verifyMavenStarterProject(@TempDir Path workspace) throws Exception {
void verifyMavenStarterProject(@TempDir Path workspace, @FilePrefix("maven") OutputFiles outputFiles)
throws Exception {
var result = ProcessStarters.maven(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) //
.workingDir(copyToWorkspace(Projects.MAVEN_STARTER, workspace)) //
.addArguments(localMavenRepo.toCliArgument(), "-Dmaven.repo=" + MavenRepo.dir()) //
.addArguments("-Dsnapshot.repo.url=" + mavenRepoProxy.getBaseUri()) //
.addArguments("--update-snapshots", "--batch-mode", "verify") //
.redirectOutput(outputFiles) //
.startAndWait();

assertEquals(0, result.exitCode());
Expand Down
Loading

0 comments on commit aa922c7

Please sign in to comment.